← voidwest    research notes
سياق الملاحظة

الإعدادات والتقديرات والخطوات التالية هنا تخص وقت كتابة الملاحظة. نحتفظ بنتائج التشغيل كما هي؛ تحديث الصياغة ما يعني إننا أعدنا التجربة أو إن حالة التنفيذ القديمة لسه هي الحالية.

قبل ما نربط ذاكرات KV بين النماذج، لازم نخليها قابلة للقياس

حد قابل للتحقق قبل تجربة ridge بين نموذجين Qwen
محمد الثبيتي · 2026-08-09
Ember KV cache reproducibility causal diagnostics Qwen LLaMA

الملخص

Ember صار يحفظ KV cache متوافق ويقارنه ويعيد تشغيله، قبل ما نجرب نقله بين نموذجين. صيغة الحفظ ember.kv-snapshot.v1، وفحص الإعادة يطلب logits بصيغة f32 مطابقة بالضبط. اللي اختبرناه هو واجهة القياس؛ النقل بين النماذج لسه ما اختبرناه.

بس ما بنيت الـmapper.

التنفيذ يضيف snapshot من ثلاثة ملفات وإعادة تشغيل مطابقة داخل نفس النموذج. ويقيس فروق K/V لكل طبقة ورأس، وattention outputs، والـlogits، وتغييرات مضبوطة داخل الذاكرة.

الشغل هنا يجهز طريقة نفهم بها أي نتيجة نقل لاحقة، سواء نجحت أو فشلت.

الحالة

يقدر Ember الآن يقيس حالة KV المتوافقة ويطبق عليها تغييراً سببياً مضبوطاً. هذي مو نتيجة نقل بين نموذجين، ومو دليل إن ridge mapper راح ينجح، ومو ادعاء تسريع.

ليش كان لازم نأجل الـmapper؟

الـcache المحوّل ممكن يفشل لأسباب كثيرة ما لها علاقة ببعض. ممكن يكون الـsnapshot ناقص، أو تسلسل الـtokens مختلف، أو مؤشر الـcache متقدم أو متأخر بموقع واحد.

ممكن نفسر RoPE أو Q/K normalization في فضاء غلط، أو يتغير بت واحد عند الاستيراد، أو يأخذ النموذج مسار تنفيذ مختلف. وممكن ببساطة يكون الـmap المتعلم ضعيفاً.

إذا جمعنا كل هالأخطاء في نص مولّد واحد، فالنتيجة السلبية ما تقول لنا كثير. وحتى النص اللي شكله صحيح ممكن يخفي انحرافاً عددياً، أو تعادلاً محظوظاً في top-1، أو مسار إعادة تشغيل ما استخدم الحالة اللي نقصدها أصلاً.

عشان كذا صار السؤال الأول أضيق: هل يقدر Ember يجمّد حالة KV الأصلية ويثبت إن استيرادها ما يغير شيئاً تحت عقد صارم داخل النموذج نفسه؟

وبعدها يجي السؤال الثاني: لما نغير الحالة عمداً، وين يظهر الفرق أول مرة؟ في إعادة بناء K/V؟ أو في attention output؟ أو في الـlogits؟ أو في مسار التوليد الجشع؟

إيش صار جزءاً أساسياً؟

الأثر الجديد له إصدار مستقل باسم ember.kv-snapshot.v1. وما غيّر عقد خطة التنفيذ المجمّد، ولا صيغة حزم التجارب في Ember.

snapshot/
├── manifest.json
├── keys.f16le
└── values.f16le

تنحفظ K وV كبتات f16 مضغوطة بالترتيب [layer][kv_head][position][dimension]. ما نحفظ المساحة غير المستخدمة من الـcache. ويسجل الـmanifest بصمات النموذج والـtokenizer، وهندسة الـcache، وتخطيط RoPE وtheta، ودلالة Q/K normalization، وحالة V، ووضع التنفيذ وبصمته، وبصمة prefix tokens، وresume token، وبصمات الملفات.

خلّيت الاستيراد صارم عن قصد. ما فيه خيار اسمه «قريب بما يكفي». أي اختلاف في هوية النموذج، أو الـtokenizer، أو الهندسة، أو دلالة المواقع والتمثيل، أو مصدر التنفيذ العددي ينرفض قبل decode.

ember kv export --model MODEL.gguf --tokenizer TOKENIZER.json   --arch qwen3 --prompt 'prefix' --output runs/kv/prefix
ember kv verify runs/kv/prefix
ember kv replay --snapshot runs/kv/prefix   --model MODEL.gguf --tokenizer TOKENIZER.json --arch qwen3

فرق الموقع الواحد اللي لازم يظل واضح

الـsnapshot يغطي الـprefix المكتمل، بس الـtoken المختار من prefix-boundary logits ما دخل الـcache إلى الآن. إذا كان طول الـprefix هو P، فالـresume token مكانه المطلق P.

أول forward في إعادة التشغيل يقيّم هذا الـtoken، ويضيف صف K/V الخاص به، وينتج logits للموقع P+1. لو أدخلنا آخر token من الـprompt مرة ثانية، نكون كررناه. ولو قلنا إن الـsnapshot يحتوي boundary logits الأصلية، نكون وصفناه بشكل غلط.

على الورق هذا مجرد حساب بسيط للمواقع. بس بالضبط نوع الخطأ اللي ممكن يخلي تجربة transfer تبدو فاشلة بينما المشكلة الحقيقية هي محاذاة التسلسل.

طيب كيف تأكدنا من إعادة التشغيل المطابقة؟

التحقق بين العمليات ما قارن النص المفكوك، وما استخدم تسامح allclose. حفظ full f32 logits من التوليد المتصل، وحد التصدير، وإعادة التشغيل:

native[N,V] == concatenate(export_boundary[1,V], replay[N-1,V])

وهنا التطابق يعني نفس بتات f32. المصفوفة المكتملة شملت Llama-3.2-1B وQwen2.5-1.5B، وثلاثة مسارات Q8_0/Q6_K/Q4_K_M، وprompts ثابتة بالإنجليزي والعربي.

نتيجة إعادة التشغيل داخل النموذج نفسه

نجحت 12/12 خلية و24/24 ملاحظة زمنية. من أصل 13,449,216 قيمة f32 تمت مقارنتها، ما ظهر أي اختلاف في البتات. ونجحت العمليات الفرعية الـ72 كلها.

وهذا يثبت حد الـsnapshot/replay في الحالات المختبرة. ما يثبت صحة المرجع لكل نموذج، وأرقام التوقيت المسجلة ملاحظات فقط، مو ادعاء تسريع.

القياس قبل الـmapping

إعادة التشغيل المطابقة هي حالة الضبط. بعدها أضفت ember kv compare، وهو ما يقارن حاللين إلا إذا ثبت إن إحداثيات النموذج والـprefix متطابقة.

لكل طبقة ورأس KV، التقرير يعطي لـK وV:

الحدود الاختيارية تعطينا قائمة فشل ثابتة وأول تجاوز. وتقرير JSON ما يحتوي توقيت، أو اسم جهاز، أو process ID، أو حقول غير مرتبة.

ember kv compare LEFT RIGHT --json --r2   --max-abs 0.001 --min-cosine 0.999

ولما نحمّل النموذج الهدف نفسه، يقدر الأمر يدخل مسار reference-greedy مشترك إلى الـcaches. بعدها يقارن ناتج attention O-projection الدلالي في كل طبقة، والـfull logits، واتفاق top-1، وأول اختلاف في التوقع.

مرحلة منفصلة تعيد استيراد حاللين نظيفتين وتترك كل فرع يتبع argmax الخاص به. كذا نحصل على اتفاق التسلسل وأول اختلاف من دون ما نفسر تنشيطات ما بعد الاختلاف كأنها ناتجة عن نفس الإدخال.

ضابط سببي من دون mapper provenance وهمي

كان لازم أتأكد إن مسار التشخيص يكتشف تغييراً حقيقياً في الـcache قبل ما أعطيه ناتج mapper. الضابط الضيق يصفّر أو يضرب بقيمة ثابتة رأس K/V واحد عبر الـprefix داخل الذاكرة.

العملية معها إيصال مكتوب يحدد الـsnapshot الأصلي، والطبقة، والرأس، واختيار K/V، وبتات معامل الضرب، وعدد العناصر المتأثرة. ما نقدر نحفظها كـember.kv-snapshot.v1، وما نحط قيمة وهمية في حقل بصمة الـmapper. وإعادة التشغيل العادية ما زالت تقبل native snapshots فقط.

في smoke على Qwen2.5-1.5B Q6_K planned، مقارنة الـsnapshot بنفسه أعطت فروقاً صفرية، واتفاق top-1، ونفس التسلسل الجشع القصير. تصفير K وV في الطبقة 0 والرأس 0 أعطى attention-output cosine يقارب 0.886 في الطبقة الأولى، وfinal-logit cosine يقارب 0.9953.

بقي top-1 متفقاً في الأفق القصير جداً.

كيف نقرأ هذا الـsmoke؟

التشغيل المعدّل يورّي إن الأداة تقدر تحدد تغيير KV مضبوط وتتابع انتقال أثره. عدم انقلاب الـtoken خلال توقع واحد مو دليل متانة، وقيم الـcosine مو دليل على جودة نقل بين نموذجين.

إيش يثبته الدليل الحالي؟

هذي نتائج عن صلاحية أداة القياس، تخلي تجربة mapper لاحقة أسهل في التشخيص؛ بسا مو دليل على نجاح الـmapper.

إيش ما يثبته هذا العمل؟

attention-output cosine مفيد، بس مو احتمال انتباه، ومو تفسير سببي لوحده. وK/V R² إحصاء لإعادة البناء، مو دليل إن النموذج الهدف يستخدم الـcache المعاد بناؤه. واتفاق التوليد لفترة قصيرة سلوك فعلي، بس يظل مقياساً محدود الأفق ويعتمد على الـprompt.

التجربة الصغيرة المقصودة لاحقاً

تجربة ridge ممكن تجي لاحقاً، ولازم تظل أصغر من البنية اللي صارت تقيسها.

النطاق المقترح حالياً زوج واحد ثابت من Qwen إلى Qwen، بنفس الـtokenizer وهندسة KV متطابقة. نحوّل المفاتيح في فضاء المحتوى المعلن بعد التطبيع وقبل RoPE، ونحوّل القيم في حالتها المعلنة.

لكل طبقة ورأس هدف ridge map مغلق وثابت، مع تقسيم معايرة مجمّد، وقيمة lambda واحدة، ومن دون بحث عن الطبقات.

التجربة بتقارن identity baseline مع إعادة بناء ridge أولاً، ثم تستخدم تشخيصات الانتباه والـlogits على نفس الإدخال، وبعدها المسار الجشع المستقل. ما راح نعمم الهندسة، وما نحسن مسار التشغيل، وما نضبط kernels، وما ننشئ صيغة إنتاج لـtransformed snapshots.

وإذا بقيت الملفات المحلية الحالية هي الزوج الوحيد، فالمسار من Q4_K_M إلى Q8_0 بيكون تجربة على حد التكميم في نفس النموذج الأساسي، مو دليل على نقل بين حجمين مختلفين. لازم هذا الفرق يظل واضحاً في العنوان، والـmetadata، والخلاصة.

الخلاصة

اليوم ما أنتج mapper. أنتج الشروط اللي تخلي فشله صريحاً وقابلاً للقياس.

صار للحالة الأصلية هوية صارمة. وصار لإعادة التشغيل ضابط مطابق على مستوى البتات.

وصار لإعادة البناء مقاييس على مستوى الطبقة والرأس. وصار للأثر اللاحق قياس انتباه وlogits على نفس الإدخال.

وصار للانحراف السلوكي مسار جشع مستقل. وصار للحالة المعدلة مصدر واضح من دون ما ندّعي إنها أثر متعلم.

هذا الحد اللي أبغى أثبته قبل ما أسأل إذا cache من Qwen يقدر يقوم مقام cache من Qwen ثاني.