← voidwest    ember    الطريق إلى Ember 1.0

v0.3، كمّي على القرص، كمّي في الذاكرة

مسودة · الجزء 3 من سلسلة الطريق إلى Ember 1.0 · غير منشورة بعد
محمد الثبيتي · 2026-08-04
Ember كمّية GGUF ذاكرة AVX2 LLaMA Qwen
حالة المسودة

مسودة مكتملة باستشهادات صريحة لـ artifacts المستودع. الأشكال مضمّنة كـ SVG (نسختا الوضع الداكن والفاتح) بتعليقات تستشهد بالأدلة. أرقام الأداء والذاكرة هنا أُعيد اشتقاقها من بناءات موسومة في 2026-08-04 ومسجّلة في artifacts/benchmark-v03/part3-binary-memory.md؛ قيم زمن الإصدار مستشهد بها من artifacts/benchmark-v03/bench-summary.json و docs/validation.md.

الملف قال Q4 والمحرك قال f32. كان النموذج أصغر على القرص وبنفس الحجم في الذاكرة، حتى أوقف v0.3 المحرك من فك ضغط النموذج.

المشكلة: الانقلاب

الملف الكمّي وعد بالحجم. Q4_K_M تعني نحو أربع بتات لكل وزن على القرص، والملف يفي: Llama-3.2-1B بصيغة Q4_K_M أصغر نحو 39% من النموذج نفسه بصيغة Q8_0. ينكسر الوعد لما يُحمَّل النموذج. محمّل Ember قبل v0.3 كان يفك كل tensor K إلى f32 عند التحميل ويُبقي المخازن الموسّعة مقيمة، ثم يشغّل كل شيء عبر kernels f32 عامة. بقي الملف Q4 على القرص؛ النموذج الجاري كان f32 في الذاكرة.

العواقب قِيست قبل v0.3 في ملاحظة خط الأساس المجمّدة (docs/research-notes/the-file-said-q4-runtime-said-f32.html) على الجهاز نفسه اللي أنتج قياسات الإصدار:

النموذجالملفdecode جشع tok/sذروة RSSالتمثيل في زمن التشغيل
Qwen2.5-1.5BQ4_K_M 1066 MB2.68712 MBتوسعة f32 دائمة
Qwen2.5-1.5BQ6_K 1396 MB2.58713 MBتوسعة f32 دائمة
Llama-3.2-1BQ4_K_M 770 MB3.26965 MBتوسعة f32 دائمة
Llama-3.2-1BQ6_K 974 MB2.96965 MBتوسعة f32 دائمة
Llama-3.2-1BQ8_0 1260 MB149.62685 MBمضغوط مقيم

عبر الصفوف الأربعة Q4/Q6، كانت ذروة RSS أعلى 2.59×–4.49× من خط أساس Q8_0، والـ decode أبطأ 15.6×–51.6×. المحور الوحيد اللي كانت فيه الملفات منخفضة البت أصغر هو الملف نفسه.

هذا هو السؤال اللي يجيب عنه الإصدار دا: ما فائدة نموذج Q4 إذا كان المحرك يفكّه إلى f32؟

الحل الساذج: فك كل شيء، بأمانة

محمّل dequant-to-f32 ما كان كسلًا. كان الاختيار الهندسي الصحيح لمحرك بحثي وظيفته الأولى الصدق الرقمي: تمثيل واحد، مسار kernels عام واحد، لا تنفيذ لكل dtype ليُتحقق. يذكر عقد v0.3 (docs/v03-execution-contracts.md) الكلفة بصراحة في القسم 3: ذاكرة الأوزان المقيمة 4× الحجم المضغوط، Llama-3.2-1B Q6_K بحجم 1.02 GB على القرص ونحو 4.1 GB كـ f32 مقيم، وكل matmul gemm كثيف f32.

المشكلة ما كانت أبدًا أن المسار المرجعي موجود. المشكلة أنه كان المسار الوحيد. ملف Q4 لازم يُوسَّع إلى f32 قبل ما يشتغل مو نشرًا منخفض الذاكرة؛ إنه محمّل بحثي يصادف أنه يقرأ ملفات Q4.

ليش كان لازم يتغير

ثلاثة قياسات جعلت التغيير لا مفر منه. أولًا، ذروة RSS للمسار الـ eager تتبّعت الأوزان الموسّعة لا الملف: نحو 6.99 GB لـ Llama Q4_K_M/Q6_K ونحو 8.83 GB لـ Qwen2.5-1.5B على هذا الجهاز (أُعيد في 2026-08-04؛ القسم 2 من artifacts/benchmark-v03/part3-binary-memory.md). ثانيًا، مرجع llama.cpp المثبّت شغّل الملفات نفسها عند ذروة RSS من 1.08–1.77 GB (artifacts/benchmark-v03/bench-summary.json). ثالثًا، كان decode الـ K-quant الـ eager أبطأ من جلسة Q8_0 للنموذج نفسه على الجهاز نفسه بمرتبة إلى مرتبتين. لا شيء من هذا ادعاء أن Q4 "أسوأ" من Q8، إنه بيان عما يفعله المحرك بملف.

قيد التصميم

القيد، المجمّد قبل التنفيذ (docs/v03-execution-contracts.md)، سطر الحالة: تبقى الأوزان مضغوطة ومقيمة عبر mmap، ولا يتم فك الكمّية إلا على دقة كتلة أو بلاطة داخل kernels الـ matmul. لا توسعة f32 كاملة دائمة لـ tensors المسار الأصلي. جمد العقد كمان حدود التغيير:

التوسعة الـ eager مقابل الإقامة المضغوطة: المسار المرجعي يوسّع مصفوفة الأوزان كلها إلى f32؛ مسار v0.3 يُبقي الكتل مضغوطة عبر mmap ويشغّل matvec K-quant أصليًا التوسعة الـ eager مقابل الإقامة المضغوطة (الوضع الفاتح)
الشكل 1، التوسعة الـ eager مقابل الإقامة المضغوطة. ذروة RSS على بناء tag v0.3.0: 6,989,724–8,837,276 KB (eager) مقابل 844,216–1,266,684 KB (مضغوط) عبر Llama-3.2-1B وQwen2.5-1.5B عند Q4_K_M/Q6_K (artifacts/benchmark-v03/part3-binary-memory.md).

ما تفرضه ترتيبات GGUF

الـ K-quants مو صيغة واحدة. tensor Q4_K يخزن 8 كتل لكل super-block مع مقاييس وإزاحات لكل كتلة؛ Q6_K يخزن 16 كتلة لكل super-block بمقاييس int8؛ ونموذج "Q4_K_M" مزيج لكل tensor، ملف Llama-3.2-1B Q4_K_M يحتوي 96 tensor من Q4_K و17 من Q6_K (قيمة attention + هبوط FFN في طبقات مختارة، إضافة إلى الـ embedding المرتبط) و34 tensor من F32 للـ norms والتحيزات (جرد في docs/v03-execution-contracts.md القسم 5). وعشان كدا لازم على المسار الأصلي أن يوزّع لكل tensor لا لكل ملف، وQ4_K_M تفرض وجود kernels Q4_K وQ6_K معًا.

وولازم المحمّل كمان يحترم تحوّل ترقيم الـ dtype في 2024 (Q2_K=10 … Q6_K=14 في GGUF)، فقراءة نوع tensor K خطأً تُفسد كل وزن من غير خطأ (نطاق الـ dtype موثق في القسم 3 من العقد). ومعنى "لكل tensor" أن "Q4" لا تُدّعى أبدًا من اسم الملف؛ الجرد يُقرأ من رؤوس GGUF.

تشريح super-block لـ Q4_K وQ6_K: 8 كتل من 32 وزنًا بعوامل مقاييس 2-بت لـ Q4_K؛ 16 كتلة من 16 وزنًا بمقاييس int8 لـ Q6_K تشريح super-block لـ Q4_K وQ6_K (الوضع الفاتح)
الشكل 4، تشريح super-block لـ Q4_K وQ6_K (مفاهيمي). التوزيع لكل tensor لا لكل ملف: ملف llama-3.2-1b q4_k_m يخلط 96 Q4_K + 17 Q6_K + 34 F32 (docs/v03-execution-contracts.md §5).

التنفيذ

التمثيل الجديد (KQuantWeight في src/quant_k.rs) يطابق قالب Q8_0 المضغوط الموجود: تخزين كتل خام مدعوم بـ mmap، مع بناء متحقق (محاذاة كتل، طول بايت، نطاق معيّن). فك الكمّية يعيد استخدام dequant_q4_k/dequant_q6_k المتحققَين مرجعيًا على دقة الكتلة. ووفوق كدا:

بطارية أشكال kernels في اختبارات الوحدات عريضة عمدًا، صفوف {1,2,8,32} × in {256,512,1536,2048,8960} × out {128,512,2048}، إضافة إلى حواف مقياس صفري وسالب أدنى ومشبع وغير محاذاة (القسم 9 من العقد، Gate A).

تشريح matvec K-quant: فك كتلة واحدة في مخزن صغير لكل خيط، ضربها بشريحة التنشيط، تراكمها؛ المصفوفة كلها لا تصبح f32 مقيمة أبدًا تشريح matvec K-quant (الوضع الفاتح)
الشكل 2، تشريح matvec K-quant. تُفك كتلة واحدة في مخزن [f32; 256] لكل خيط في كل مرة؛ مساحة عمل المسار المضغوط لكل خيط ≤ 9 KiB ولا يُنشأ مخزن f32 بمقياس النموذج أبدًا (docs/v03-execution-contracts.md §7).

بوابات التحقق

سُجّلت البوابات مسبقًا في العقد قبل التنفيذ ومو ممكن إلا تشديدها لا تخفيفها لملاءمة النتائج. أربع بوابات عددية والخامسة سير الشغل السببي:

سلم التحقق: kernel عددية مضغوطة، kernel AVX2 مضغوطة، تنفيذ النموذج المخطط، توازي الـ hidden states لكل طبقة، logits نهائية، tokens جشعة، مرجع llama.cpp المثبّت؛ hooks الالتقاط والتدخل تلتصق بالمواقع الست نفسها سلم التحقق (الوضع الفاتح)
الشكل 3، سلم التحقق. توازي kernels (Gate A) → توازي النموذج (Gates B/D) → golden خارجي مقابل llama.cpp المثبّت (Gate C)، مع التصاق مواقع الـ hooks الدلالية الست بمواقع النداء نفسها في المسارين (docs/v03-execution-contracts.md §8–9؛ docs/validation.md).

الجزء الصادق من Gate C تاريخ تعديلاته، اللي يسجّله العقد حرفيًا. أول حدّ مسود (max abs ≤ 1e-2 على logits النهائية) أخطأ قراءة المعيار golden القائم، فتقرير Q8_0 للـ pilot نفسه أظهر max abs 0.364 مقابل kernels تراكم الأعداد الصحيحة في llama.cpp، فلا تغيير ترتيب تراكم ممكن أن يبلغ 1e-2 على هذي العائلة. أصبح المعيار المبني على الأدلة: top-1 100%، cosine ≥ 1−1e-3، mean ≤ 0.1، max ≤ 1.0؛ وتعديل ثانٍ نقل حد العنصر الأقصى الفردي من 0.5 إلى 1.0 بعد ما أثبت عنصر متطرف واحد من 128k لكل عينة حساسيته للمُكمِّم وللترتيب؛ وتعديل ثالث حدد المغلفات النهائية لكل عائلة من تشغيل السلم الست الكامل. لم يغيّر أي منها البوابة الأولية، ظل توافق top-1 100% طوال الوقت.

النتيجة

أُعيد اشتقاقها في 2026-08-04 من بناء tag v0.3.0 (الأوامر الكاملة والقيم الخام في artifacts/benchmark-v03/part3-binary-memory.md)، بالبروتوكول نفسه لمصفوفة الإصدار (scripts/bench_v03.sh، 64 token، إحماء 1، 3 تكرارات، 8 خيوط، ذروة RSS للعملية كلها):

النموذجالاستراتيجيةmedian tok/sذروة RSS
Llama-3.2-1B Q4_K_Meager-f320.986,989,724 KB (6.67 GiB)
Llama-3.2-1B Q4_K_Mمضغوط (x86)2.01844,216 KB (0.81 GiB)
Llama-3.2-1B Q6_Keager-f320.986,989,252 KB (6.67 GiB)
Llama-3.2-1B Q6_Kمضغوط (x86)2.211,053,524 KB (1.00 GiB)
Qwen2.5-1.5B Q4_K_Meager-f320.878,828,632 KB (8.42 GiB)
Qwen2.5-1.5B Q4_K_Mمضغوط (x86)1.66987,360 KB (0.94 GiB)
Qwen2.5-1.5B Q6_Keager-f320.858,837,276 KB (8.43 GiB)
Qwen2.5-1.5B Q6_Kمضغوط (x86)1.671,266,684 KB (1.21 GiB)

خفّض المسار المضغوط ذروة RSS 6.6×–8.9× مقابل الـ eager على الثنائي نفسه، وتحسّن الـ decode 1.9×–2.3× في هذي الجلسة. وسجّلت مصفوفة زمن الإصدار تسريعات مماثلة (1.94×–2.75×) وبصمة الأوزان المقيمة بحجم الملف: 799.6 MB مضغوطًا مقابل نحو 3.2 GB موسّعًا لـ Llama Q4_K_M، و1457.6 MB مقابل نحو 5.83 GB لـ Qwen Q6_K (artifacts/benchmark-v03/bench-summary.json، docs/v03-execution-contracts.md القسم 5). مرجع llama.cpp المثبّت شغّل الملفات نفسها عند 1.08–1.77 GB ذروة RSS، مسار Ember المضغوط أصبح في الحي نفسه، وهو حيث لازم يعيش النموذج الكمّي.

وأذرع الـ eager لهذه الإعادة تطابق خط الأساس المجمّد قبل v0.3 ضمن 0.4% (6,989,724 KB مقابل 6,965 MB المسجّلة لـ Llama Q4_K_M)، وهو فحص الصحة اللي يؤكد أن المقارنة تقيس تغيير التمثيل لا انجراف الجهاز (artifacts/benchmark-v03/part3-binary-memory.md القسم 2).

كلفة الثنائي: النمو المقاس من v0.2.0 إلى v0.3.0 هو 820,480 بايت (نحو 801 KiB) على الأداة الحالية (rustc 1.95.0). تقدير داخلي بنحو "190 KB" لا يعيد إنتاجه تحت هذي الأداة؛ القيمة المقاسة مسجّلة في ملف الأدلة. أضاف الإصدار نحو 0.8 MiB من kernels وتوزيع ومصدر إلى القابل للتنفيذ، مقابل 6–8 GiB من الذاكرة أزالها.

إيش اللي لا يشتغل بعد

إيش اللي أطلق هذا

إبقاء الأوزان مضغوطة أصلح مشكلة الذاكرة، بس الـ decode لسه يشغّل المسار المربوط العام مع إعادة اكتشاف توزيع لكل token ومساحة عمل لكل token. الإصدار التالي، v0.4، يخطط داك التنفيذ مرة واحدة لكل نموذج، ويشتغل مشغّل الخطة على هذي الركيزة المضغوطة بالضبط. الذاكرة اللي وفّرها المسار الأصلي هي الذاكرة اللي يرفض المحرك المُخطَّط إعادة تخصيصها وبعدها.