بدأت هذا العمل من خطوة مغرية: آخذ KV cache من نموذج، وأتعلم linear map صغير، ثم أختبر إذا كان نموذج ثاني يقدر يكمل منه من دون ما يعيد حساب الـprefix كامل.
لكن ما بنيت الـmapper.
بدل ذلك، قضيت اليوم أخلي حالة الـKV شيئاً يقدر Ember يصدّره، ويتحقق منه، ويستورده، ويعيد تشغيله، ويقارنه، ويغيره تحت عقد واضح. النتيجة هي snapshot من ثلاثة ملفات، وإعادة تشغيل مطابقة داخل النموذج نفسه، ومقاييس على مستوى كل طبقة ورأس، وتشخيصات لـattention output والـlogits، ومسار تغيير مضبوط داخل الذاكرة.
هذا أقل إثارة من عرض transfer. لكنه الشغل اللي يخلي أي نتيجة نقل لاحقة قابلة للفهم.
يقدر Ember الآن يقيس حالة KV المتوافقة ويطبق عليها تغييراً سببياً مضبوطاً. هذه مو نتيجة نقل بين نموذجين، ومو دليل إن ridge 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 في الحالات المختبرة. ما يثبت صحة المرجع لكل نموذج، وأرقام التوقيت المسجلة ملاحظات فقط، مو ادعاء تسريع.
إعادة التشغيل المطابقة هي حالة الضبط. بعدها أضفت
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 الخاص به. بهذا نحصل على اتفاق التسلسل وأول اختلاف من دون ما نفسر تنشيطات ما بعد الاختلاف كأنها ناتجة عن نفس الإدخال.
احتجت أتأكد إن مسار التشخيص يكتشف تغييراً حقيقياً في الـ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 متفقاً في الأفق القصير جداً.
التشغيل المعدّل يثبت إن الأداة تقدر تحدد تغيير 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 على نفس الإدخال. وصار للانحراف السلوكي مسار جشع مستقل. وصار للحالة المعدلة مصدر واضح من دون ما ندّعي إنها أثر متعلم.
هذا هو الحد الممل اللي أبيه قبل ما أسأل إذا كان KV cache من Qwen يقدر يقوم مقام cache من Qwen ثاني.