لم يعد تشغيل النماذج اللغوية الكبيرة على الهاتف مجرد تجربة بحثية، بل أصبح واقعاً ملموساً في المنتجات التجارية. ومع ذلك، في اللحظة التي تنتقل فيها من نموذج تجريبي بسيط إلى منتج فعلي، يبرز زمن الاستجابة (latency) من جديد. لقد قمت بتقليص النموذج، وكممت الأوزان (quantized the weights)، ومع ذلك لا تزال مرحلة التعبئة المسبقة (prefill phase) بطيئة. تتوقف الرموز (tokens)، وتتجمد واجهة المستخدم. ونادراً ما يقع اللوم في المكان الصحيح.

في أجهزة Android، نادراً ما تكون القدرة الحسابية هي عنق الزجاجة أثناء مرحلة الـ LLM prefill. بل هي عرض نطاق الذاكرة (Memory bandwidth). تأتي أنظمة SoC الرائدة الحديثة مع نوى GPU و NPU قوية يمكنها معالجة العمليات الحسابية بسرعة أكبر بكثير مما يمكن للنظام الفرعي للذاكرة توفيره لها. عندما تقوم بتحليل أداء (profile) تنفيذ بسيط لآلية الانتباه (attention implementation)، ستجد أن وحدات التنفيذ ليست مشبعة، بل هي في حالة انتظار. تنتظر الـ DRAM.

لماذا لا يزال نموذجك المكمم (Quantized) يبدو بطيئاً

أصبح التكميم (Quantization) هو الخطوة الأولى الافتراضية للاستدلال على الجهاز (on-device inference). إن تقليص الأوزان من FP16 إلى INT8 يقلل حجم النموذج إلى النصف ويقلل مساحة التخزين. هذا يساعد، لكنه لا يعالج زمن استجابة طبقة الانتباه (attention layer latency). السبب بسيط: يقلل التكميم من كمية البيانات التي تخزنها، لكنه لا يقلل من عدد عمليات نقل الذاكرة (memory transactions) التي تقوم بها آلية الانتباه.

طبقة الانتباه متعددة الرؤوس (multi-head attention layer) القياسية، إذا تم تنفيذها بالطريقة التقليدية، تقوم بثلاث رحلات ذهاب وإياب كاملة إلى الـ DRAM لكل طبقة. تُقرأ مصفوفات Query و Key و Value من الذاكرة الرئيسية، وتُحسب النتائج (scores)، ثم تُكتب النتائج الوسيطة مرة أخرى. العمليات الحسابية بسيطة، لكن حركة البيانات عنيفة. في Android، حيث تكون ميزانيات الطاقة والحرارة محدودة، يؤدي هذا النمط إلى إرهاق ناقل الذاكرة (memory bus). فالمعالج يدفع "رسوم العبور" ثلاث مرات لقطع الجسر نفسه.

إذا كنت تقوم بإطلاق نماذج INT8 وتتساءل لماذا لا تزال خطوة الـ prefill تتوسع بشكل تربيعي (quadratically) مع طول المطالبة (prompt length)، فهذا هو الجواب. الأوزان أصغر، لكن حركة بيانات التنشيط (activation traffic) تظل هائلة.

عنق الزجاجة هو الذاكرة، وليس الحسابات

لفهم الحل، انظر إلى مخطط الـ roofline. تمتلك وحدات GPU و NPUs المحمولة في رقائق مثل Snapdragon 8 Gen 3 و Dimensity 9300 قدرة حسابية نظرية تتجاوز بكثير ما يمكن لواجهات LPDDR5X الخاصة بها تحمله. في نواة انتباه (attention kernel) بسيطة، تحسب كل رأس softmax عبر حاصل ضرب Q و K، ثم تضرب الناتج في V. يتم تجسيد كل مصفوفة نتائج (score matrix) وسيطة في الذاكرة العامة (global memory). وهذا يعني أن ذروة قراءات الـ DRAM تتوسع مع مربع طول التسلسل، أو O(n²). بالنسبة لمطالبة (prompt) مكونة من 1024 رمزاً (token)، فإن حركة بيانات الذاكرة تكون كبيرة بما يكفي لتسيطر على وقت التنفيذ.

لا يتم استغلال النوى بالكامل لأنها لا تستطيع إخفاء زمن الاستجابة. تعتمد المعالجات الحديثة على الذاكرة المخبئية (caches) لإبقاء خطوط المعالجة (pipelines) ممتلئة. عندما يفشل الخوارزم في العثور على البيانات في الـ cache باستمرار ويقوم بجلبها من الـ DRAM، تظل وحدات التنفيذ خاملة. لا يمكن لأي قدر من التكميم أن يعالج هذا عدم التوافق الهيكلي بين القدرة الحسابية وإمدادات الذاكرة.

كيف تستعيد تقنية التجزئة (Tiling) عرض النطاق الترددي

الحل هو استراتيجية التجزئة (tiling strategy) التي تبقي النتائج الوسيطة في ذاكرة SRAM الموجودة على الشريحة بدلاً من نقلها إلى الـ DRAM. هذه هي نفس الرؤية التي تحرك Flash Attention، ولكنها مكيفة لتناسب بنية الحوسبة في Android. بدلاً من تجسيد مصفوفة نتائج كاملة بمقاس n × n في الذاكرة، تقوم بتقسيم الحساب إلى أجزاء صغيرة (tiles) تناسب الـ L1 cache. تقوم بحساب إحصائيات softmax المحلية، وتجميع قيم الـ max الجارية ومجموعات التطبيع (normalization sums)، ولا تكتب إلا المخرجات الموزونة النهائية مرة أخرى إلى الذاكرة.

يغير هذا من تعقيد عرض النطاق الترددي (bandwidth complexity). تنخفض ذروة قراءات الـ DRAM من O(n²) إلى O(n)، لأنك لم تعد بحاجة إلى نقل مصفوفات نتائج كاملة عبر الذاكرة الرئيسية. تتم المهمة الشاقة داخل الـ SRAM، بجوار وحدات التنفيذ مباشرة.

كمثال ملموس، لنفترض أن حجم الجزء (tile size) هو 64 وأبعاد الرأس (head dimension) هي 128. سيشغل جزء النتائج (score tile) مساحة 16 كيلوبايت. هذا الحجم يتناسب تماماً مع الـ L1 cache في أنظمة SoC الرائدة الحالية مثل Snapdragon 8 Gen 3 و Dimensity 9300. تظل العمليات الحسابية محلية، ويخف الضغط عن ناقل الذاكرة (memory bus).

تنفيذ ذلك على Android

المخطط الخوارزمي مباشر، رغم أن ضبط التفاصيل بدقة أمر بالغ الأهمية.

قم بتقسيم الـ Query، الـ Key