REPLAYSSM: إعادة التفكير في تخزين الحالة لتسريع فك تشفير النماذج الهجينة
يقترح ReplaySSM نهجًا مختلفًا لتخزين الحالة المتكررة في النماذج الهجينة، بهدف تسريع عملية فك التشفير دون التضحية بالجودة. استنادًا إلى مدونة مطوري NVIDIA، يبحث هذا المقال في الفكرة، ولماذا يصبح تخزين الحالة عائقًا في الهياكل الهجينة، وما هي الآثار العملية لذلك على هندسة التنبؤ.
ملخص سريع
يقترح ReplaySSM نهجًا مختلفًا لتخزين الحالة المتكررة في النماذج الهجينة، بهدف تسريع عملية فك التشفير دون التضحية بالجودة. استنادًا إلى مدونة مطوري NVIDIA، يبحث هذا المقال في الفكرة، ولماذا يصبح تخزين الحالة عائقًا في الهياكل الهجينة، وما هي الآثار العملية لذلك على هندسة التنبؤ.
ReplaySSM: إعادة التفكير في تخزين الحالة لتسريع فك تشفير النماذج الهجينة
وعدت نماذج اللغة الهجينة — وهي بنى تدمج طبقات الانتباه مع طبقات فضاء الحالة أو الطبقات التكرارية الخطية — بأفضل ما في العالمين: الاسترجاع الدقيق للانتباه وسلوك التدفق ذو الذاكرة الثابتة للطبقات التكرارية. في الممارسة العملية، يكتشف العديد من الفرق أن هذا الوعد يصمد أثناء مرحلة الملء المسبق ثم يتلاشى تدريجيًا أثناء عملية الفك التشفير. ينخفض معدل المعالجة لكل رمز ناتج، ويصبح من الصعب فهم حسابات الذاكرة، كما تصبح الميزات التي تُعتبر شائعة في نماذج الترانسفورمر النقية — مثل بحث الشعاع، والفك التشفير التخميني، وإنتاج عدة عينات لكل طلب — غير مناسبة.
تستعرض مقالة مدونة NVIDIA بعنوان "ReplaySSM: إعادة التفكير في تخزين الحالة لتسريع فك تشفير النماذج الهجينة" (المؤرخة في 12 أكتوبر 2026) هذا التوتر وتقترح اعتبار تخزين الحالة مسألة تصميم أساسية بدلاً من تفصيل تنفيذي. يشرح هذا المقال مجال المشكلة، ويوضح كيفية إعداد بيئة عملية لتجربة فك التشفير الهجين على الأجهزة الخاصة بك، ويحدد ما يجب قياسه. التفسير المفاهيمي هنا هو تأويل؛ يجب الرجوع إلى المقال الأصلي لمعرفة تفاصيل الطريقة المقترحة.
لماذا النماذج الهجينة جذابة — وصعبة التشفير
تحتفظ طبقة الترانسفورمر بذاكرة مؤقتة للمفتاح/القيمة تزداد بشكل خطي مع طول التسلسل. أما طبقة SSM فتحتفظ بحالة تكرارية ذات حجم ثابت يتم إعادة كتابتها مع كل رمز. عند دمجهما معًا، نحصل على نموذج يمكنه التركيز بدقة على المناطق المهمة والتدفق بكفاءة في كل المناطق الأخرى.
المشكلة تكمن في أن نظامي الذاكرة هذين لا يشتركان في أي شيء تقريبًا:
- ذاكرة KV هي ذاكرة إضافية فقط، لكل رمز على حدة، ويتم الوصول إلى محتوياتها حسب الموقع. يمكن تقسيمها، أو مشاركة بادئاتها، أو حذف لاحقاتها دون التأثير على الباقي.
- حالة SSM هي ملخص مستمر. لا يوجد فيها فهرس للمواقع. ينتج الرمز t حالة جديدة تغطي الحالة القديمة، وتكون تلك الحالة دالة مضغوطة لكل ما سبقها.
هذا عدم التماثل هو جذر مشكلة الفك التشفير. ذاكرة KV هي بنية بيانات يمكن التعامل معها، أما الحالة التكرارية فهي قيمة يجب إما الحفاظ عليها أو إعادة حسابها.
لماذا الفك التشفير، وليس الملء المسبق، هو الجزء الصعب
يقوم برنامج Prefill بمعالجة الطلب بأكمله في مرة واحدة. يكون كل رمز متاحًا في نفس الوقت، ويكون وحدة المعالجة الرسومية مشغولة بعمليات ضرب مصفوفي، ويتم توزيع تكلفة تخزين الحالة على تسلسل طويل. أما عملية الترجمة فهي عكس ذلك: رمز واحد في كل خطوة، عمليات ضرب مصفوفي صغيرة، ونواة برمجية مقيدة بالذاكرة حيث يتحدد وقت تشغيلها بقراءة الحالة وتحديثها.
بالنسبة لنموذج الانتباه البحت، فإن أداء عملية الترجمة يعتمد إلى حد كبير على حجم ذاكرة التخزين KV ومدى كفاءة قراءتها. تؤثر عمليات التكميم والتصفح ومشاركة البادئات على ذلك. أما بالنسبة للنموذج المختلط، فإنك تواجه كل هذه المشاكل بالإضافة إلى مجموعة ثانية من الحالات ذات التركيب المختلف الذي لا يمكن تصفحه أو مشاركة بادئاته بنفس الطريقة.
عنق الزجاجة في تخزين الحالة في الأعباء العملية المتفرعة
تصبح المشكلة حادة كلما أنتج بادئ واحد عدة استمراريات:
- البحث بالشعاع يحتفظ بـ k فرضية نشطة، كل منها لها حالتها الخاصة.
- الترجمة التخمينية تقترح عدة رموز مسودة، وتتحقق منها بشكل متوازٍ، ويجب إلغاء الرموز المرفوضة.
- أخذ العينات من أفضل النتائج يولد n استكمالًا مستقلاً من طلب مشترك.
- الاستدلال ذو الهيكل الشجري يستكشف الفروع التي تنقسم وتعود للاندماج مجددًا.
بالنسبة لطبقات الانتباه، فإن كل هذه الحالات قابلة للتعامل معها. إلغاء رمز مسودة مرفوض يتم عن طريق تقليص ذاكرة التخزين KV. أما مشاركة بادئ الطلب فتعني مشاركة المفاتيح والقيم المخزنة فيها. أما بالنسبة لطبقات SSM، فإن أيًا من هاتين العمليتين ليست مجانية. نقطة الفرع تعني أن الحالة يجب أن تكون موجودة بشكل مستقل لكل فرع، وإلغاء التغييرات يعني استعادة الحالة إلى قيمة سابقة لم تعد موجودة ما لم يتم حفظها.
هناك ثلاث طرق فقط، ولكل منها تكلفة:
- أخذ لقطات سريعة. احتفظ بنسخة من الحالة المتكررة في كل نقطة قد تحتاج إلى العودة إليها. تزداد الذاكرة مع مضاعف عامل التفرع مضروبًا في تكرار نقاط التحقق.
- إعادة الحساب من نقطة تحقق. خزن الحالة نادرًا، وأعد تشغيل الطبقات من آخر نقطة تحقق عند الحاجة إلى حالة وسيطة. الذاكرة منخفضة؛ التأخير مرتفع.
- إعادة هيكلة التخزين نفسه. غيّر ما يتم تخزينه ومتى وبأي شكل، بحيث يكون إعادة البناء رخيصًا بما يكفي ليكون الخيار الافتراضي.
ما الذي يعنيه تصميم يعتمد على إعادة التشغيل أولاً
يشير اسم هذا النهج إلى الخيار الثالث. كلمة "إعادة التشغيل" توحي بأن إعادة البناء لم تعد حلًا طارئًا، بل هي الطريقة الطبيعية، وأن طبقة التخزين مصممة لجعل عملية إعادة التشغيل رخيصة.
إذا فُهم الأمر بهذه الطريقة، فإن سؤال التصميم يتحول من "كم من الحالة يجب أن نحتفظ بها؟" إلى "ما هو الحد الأدنى الذي يجب أن نحتفظ به لكي يمكن إعادة بناء أي حالة مطلوبة بسرعة؟" لهذا التصور عدة عواقب تستحق التفكير فيها:
- يصبح التخزين محورًا قابلًا للتعديل بدلاً من تكلفة ثابتة. يمكن المقايضة بين الذاكرة وإعادة الحساب على مقياس مستمر، واختيار نقطة معينة حسب حجم العمل.
- يحصل حلقة التفسير على أداة جديدة: استعادة الحالة إلى الموضع p. سواء تم ذلك عن طريق تحميل لقطة لحظية أو إعادة تشغيل جزء قصير، فإن ذلك يغير الزمن المستغرق وليس المعنى.
- يمكن لأنظمة التقديم تخصيص ميزانيات الحالة بنفس الطريقة التي تخصص بها ميزانيات ذاكرة KV — كمورد لكل طلب مع سياسات إزالة، بدلاً من تخصيص حتمي لكل تسلسل.
- يصبح من الأسهل الجدال حول الدقة، لأن الحالة المعاد تشغيلها يجب أن تكون مطابقة رقميًا (أو قريبة جدًا) من الحالة المخزنة. هذه خاصية قابلة للاختبار، على عكس التضحيات المتعلقة بالجودة في عملية التكميم.
ما إذا كانت الطريقة المنشورة تتوافق مع هذا الوصف بالتفصيل هو أمر يجيب عليه المصدر مباشرةً؛ الاستدلال أعلاه هو إطار لفهمه، وليس بديلاً عنه.
المتطلبات
قبل التجربة، تأكد من أن بيئتك قادرة على دعم التفسير الهجين على الإطلاق. المتطلبات الواقعية هي:
- كارت شاشة يدعم CUDA ذو ذاكرة كافية للأوزان بالإضافة إلى أكبر حجم للحالة التي تنوي السماح بها. ابدأ باستخدام نموذج يتناسب بشكل مريح، ثم قلل الميزانية.
- مفسر Python حديث من نوع 3.x مدعوم من إصدار الإطار الذي اخترته. يُعد دمج نسخة حديثة جدًا من Python مع برامج CUDA الثابتة أكثر أسباب فشل عملية الإعداد شيوعًا.
- PyTorch مُصمم لمحرك CUDA الخاص بك. يؤدي عدم تطابق الإصدارات بين المحرك والبرنامج إلى أخطاء تشغيلية مربكة بدلاً من فشل واضح.
- إطار عمل يفهم الأركيتектورات الهجينة. تدعم الإصدارات الحديثة من
transformersكائنات ذاكرة هجينة؛ وتتعامل محركات التقديم مثل vLLM وTensorRT-LLM مع النماذج الهجينة بدرجات متفاوتة من النضج. تحقق من دعم الإطار لنموذجك المحدد قبل إجراء اختبارات الأداء. - ذاكرة وصول عشوائي كافية للجهاز وقرص تخزين إذا كنت تخطط لحفظ نقاط التحقق أو نقل الحالة. يمكن أن يصبح متجه الحالة الصغير على كارت الشاشة كبيرًا عند ضربه في تسلسلات ونقاط تحقق مختلفة.
تتعلق المتطلبات الدقيقة للإصدارات الخاصة بتنفيذ ReplaySSM، إن تم إصدارها، بالمنشور الأصلي والملفات المرفقة به.
التثبيت خطوة بخطوة
تقوم الأوامر أدناه بإنشاء بيئة عامة لاستنتاج النماذج الهجينة. هي لا تقوم بتثبيت حزمة ReplaySSM المحددة — راجع المصدر للحصول على الطريق الرسمي.
أولاً، تحقق من أن كارت الشاشة مرئي وأن المحرك سليم.
nvidia-smiأنشئ بيئة منعزلة حتى لا تتعارض إصدارات الإطار مع أي شيء آخر على الجهاز.
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip wheelقم بتثبيت PyTorch. اختر عنوان URL المتوافق مع بيئة تشغيل CUDA الخاصة بك؛ قد يستهدف الإصدار الافتراضي من PyPI إصدارًا مختلفًا من CUDA عن المحرك الخاص بك.
pip install torch --index-url https://download.pytorch.org/whl/cu124تأكد من أن PyTorch يرى كارت الشاشة قبل تثبيت أي شيء آخر، لأن كل فشل لاحق يسهل تشخيصه انطلاقًا من نقطة أساس سليمة معروفة.
python -c "import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))"قم بتثبيت مكتبات النمذجة والتقديم التي تنوي مقارنتها.
pip install transformers accelerate
pip install vllmإذا كان نموذجك هجينًا يستخدم طبقات من نوع Mamba، فإن مكتبات النوى التي تسرّع طبقات التكرار الخطي تُستخدم عادةً إلى جانب الإطار الرئيسي. قم بتثبيتها فقط إذا وصف النموذج الذي تشغله هذه المكتبات كاعتمادات له.
pip install flash-linear-attention causal-conv1dأخيرًا، سجّل الإصدارات الدقيقة في ملف قفل حتى يمكن إعادة إنتاج نتيجة المقارنة لاحقًا.
pip freeze > requirements-lock.txtالتكوين
ينقسم تكوين التفسير الهجين إلى فئتين: الإعدادات التي يوفرها الإطار الخاص بك بالفعل، وأي خيارات تضيفها تنفيذية ReplaySSM. يمكن استخدام الفئة الأولى حاليًا.
قم بتعيين المخصص لتقليل التفتت عند تخصيص وإطلاق متجهات الحالة ذات الأحجام المختلفة عبر الطلبات.
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:Trueثبّت التشغيل على GPU واحد فقط حتى لا تتأثر القياسات بقرارات التوزيع.
export CUDA_VISIBLE_DEVICES=0أثناء التقديم، قم بتفعيل التخزين المؤقت للبادئات والتعبئة المجزأة إذا كان محركك يدعمهما. كلاهما يغير طريقة مشاركة الحالة وجدولتها، وكلاهما يتفاعل مع أي استراتيجية لتخزين الحالة.
vllm serve YOUR_HYBRID_MODEL_ID \
--enable-prefix-caching \
--enable-chunked-prefill \
--max-model-len 8192 \
--gpu-memory-utilization 0.90بالنسبة للفئة الثانية، راجع المصدر. يجب قراءة أي كتل تكوين خاصة بـ ReplaySSM من الوثائق الرسمية بدلاً من استنتاجها، لأن أسماء الحقول والقيم الافتراضية تحدد ما إذا كنت ستقيس الميزة أصلًا.
أمثلة على الاستخدام
مثال 1: سعة التفسير الأساسية
حدد نقطة أساس قبل تغيير أي شيء. يقوم هذا السكريبت بتفسير عدد ثابت من الرموز ويعرض عدد الرموز في الثانية، وهو المقياس الأكثر حساسية للأعباء المتعلقة بمعالجة الحالة.
import time
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "YOUR_HYBRID_MODEL_ID"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id, torch_dtype=torch.bfloat16, device_map="cuda"
).eval()
prompt = "Explain why recurrent state is expensive to branch."
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
steps = 128
with torch.no_grad():
out = model(**inputs, use_cache=True)
next_id = out.logits[:, -1].argmax(-1, keepdim=True)
torch.cuda.synchronize()
start = time.perf_counter()
for _ in range(steps):
out = model(
input_ids=next_id,
past_key_values=out.past_key_values,
use_cache=True,
)
next_id = out.logits[:, -1].argmax(-1, keepdim=True)
torch.cuda.synchronize()
elapsed = time.perf_counter() - start
print(f"{steps / elapsed:.1f} tokens/sec")مثال 2: قياس حجم الحالة
يخبرك استخدام الذاكرة الأقصى بكمية المساحة المتبقية لدفعات أكبر أو المزيد من الفروع. أعد ضبط الإحصائيات قبل المنطقة المقاسة حتى تسجل فقط ما يخصصه حلقة التفسير.
import torch
torch.cuda.reset_peak_memory_stats()
# ... run the decode loop from Example 1 ...
peak_gib = torch.cuda.max_memory_allocated() / 2**30
print(f"peak allocated: {peak_gib:.2f} GiB")اطرح الأوزان من هذا الرقم لتقدير حجم الحالة، ثم قسّم الناتج على عدد التسلسلات المتزامنة للحصول على رقم لكل تسلسل. هذا الرقم هو الذي يحدد أقصى حجم دفعة لديك في إطار استراتيجية تخزين الحالة.
مثال 3: التفسير التخميني والتراجع
هذا هو حجم العمل الذي يكون فيه سياسة التخزين ذات أهمية قصوى. الهيكل أدناه هو تخطيطي؛ قم بتعديل استدعاءات الذاكرة المؤقتة لتتناسب مع واجهة برمجة التطبيقات الخاصة بإطار عملك.
# 1. Save a reference to the state after the accepted prefix.
state_checkpoint = cache.snapshot()
# 2. Propose k draft tokens and run them through the model in one pass.
draft_ids = drafter.generate(prefix_ids, k=4)
candidate_logits = target_model.forward_with_cache(draft_ids, cache)
# 3. Accept the longest prefix that matches the target distribution.
accepted = longest_matching_prefix(candidate_logits, draft_ids)
# 4. If nothing was accepted, restore rather than re-run the whole prefix.
if accepted == 0:
cache.restore(state_checkpoint)
else:
cache.truncate_to(accepted)تكمن نقاط الاختلاف بين الاستدعاءات snapshot وrestore في الاستراتيجيتين المختلفتين. التنفيذ القائم على اللقطات يتطلب استخدام الذاكرة في الخطوة 1.، بينما التنفيذ القائم على إعادة التشغيل يتطلب إعادة الحساب في الخطوة 4.؛ يعتمد الاختيار المناسب على معدل القبول لديك: مع معدل قبول مرتفع، تكون عمليات الإرجاع نادرة وتكون إعادة التشغيل أكثر فعالية؛ أما مع معدل قبول منخفض، فقد تكون اللقطات كافية لتعويض التكاليف.
ما يجب قياسه قبل وبعد التطبيق
لا يستحق تبني أي تغيير في تخزين الحالة إلا إذا كان يؤثر على مقياس تهتم به. تابع قياس هذه المؤشرات بشكل مستمر:
- الوقت لكل رمز ناتج، وليس فقط مدة تأخير الطلب الكلية. يخفي التعبئة المسبقة أي تراجع في عملية فك التشفير.
- أقصى ذاكرة للجهاز عند حجم دفعة ثابت، وهو ما يحدد ما إذا كان بالإمكان زيادة حجم الدفعة.
- أقصى قدرة مستدامة على التزامن، ويتم قياسها عن طريق زيادة حجم الدفعة حتى يحدث خطأ نقص في الذاكرة أو تحقيق هدف معين للتأخير.
- معدل القبول، إذا كان يتم استخدام فك تشفير تخميني. يتحكم هذا المعدل مباشرة في تكرار تفعيل عملية إعادة التشغيل.
- تكلفة الإرجاع، وتُقاس كالتأخير الناتج عن رفض مسودة ما. هذا هو الرقم الذي تسعى التصاميم القائمة على إعادة التشغيل إلى تقليله.
قم بتشغيل كل تكوين على الأقل ثلاث مرات، مع استبعاد جولة الإعداد الأولى. تكون قياسات فك التشفير المختلطة غير دقيقة، كما أن المقارنات بين جلسة واحدة غالبًا ما تؤدي إلى استنتاجات مضللة.
القيود والأسئلة المفتوحة
بعض التحفظات الصريحة. يعتمد فائدة أي تحسين في تخزين الحالة على نسبة طبقات الانتباه إلى طبقات التكرار في نموذجك؛ قد لا يلاحظ النموذج المختلط بشكل خفيف أي تغيير، بينما قد يشهد النموذج ذو التكرار العالي تغييرات كبيرة. يؤدي إعادة التشغيل إلى إعادة الحساب، لذا فإنه يكون مفيدًا فقط عندما يكون نقص الذاكرة أو تأخير الإرجاع هو القيد الرئيسي وليس عدد عمليات الحساب الأساسية. كما أن منحنى التوازن يختلف حسب حجم العمل: فالفترة المثلى لإنشاء نقاط التحقق لعمليات الأخذ الأفضل من المرجح ألا تكون مثالية لعمليات البحث بالشعاع.
السؤالان حول موقع الطريقة المنشورة على تلك المنحنى وأعباء العمل التي تستهدفها هما سؤالان يتناولهما المنشور الأصلي مباشرةً. اعتبر الإطار المقدم هنا كأداة مساعدة لقراءته، وليس بديلاً عنه.
الخلاصة
النماذج الهجينة ليست أكثر صعوبة في التشغيل مقارنة بالترانسفورمرات؛ بل هي أكثر صعوبة في الجدولة. السبب هو أنها تحمل نوعين من الذاكرة غير المتوافقين: ذاكرة KV القابلة للوصول وحالة تكرارية غير قابلة للوصول. كل ما يجعل الخدمة الحديثة فعالة — مثل التصفح، ومشاركة البادئات، وإعادة التراجع التخمينية — تم تصميمه حول النوع الأول.
إسهام ReplaySSM، كما يوضح عنوانه، هو التوقف عن اعتبار النوع الثاني تكلفة لا مفر منها والبدء في اعتباره متغيرًا في التصميم. إذا أمكن إعادة بناء الحالة بتكلفة منخفضة بما فيه الكفاية، فإن التخزين يصبح مجرد عنصر قابل للتعديل وليس حاجزًا، وأعباء العمل المتفرعة التي تتعامل معها النماذج الهجينة بشكل سيء حاليًا تصبح عادية مرة أخرى.
الطريق العملي واضح: أنشئ بيئة نظيفة، قيس معدل الترميز وأقصى استهلاك للذاكرة قبل تغيير أي شيء، عزل تكلفة إعادة التراجع في حلقة ترميز تخمينية، ثم فقط حدد الموقع الذي تريد أعباء العمل الخاصة بك أن تكون عليه على منحنى الذاكرة مقابل إعادة الحساب.



