حاولت تحميل نموذج أو إقامة خادم، فمات التشغيل على جدار من النص الأحمر ينتهي بـ torch.cuda.OutOfMemoryError: CUDA out of memory. هو أكثر عطل يصادفه الناس حين ينتقلون من API إلى تشغيل الأوزان بأنفسهم، والرسالة أصدق مما تبدو. الـGPU طلب ذاكرة لم تكن لديه. السؤال المفيد ليس هل نفدت منك الذاكرة، فقد نفدت فعلاً. السؤال هو أيّ جزء من العبء التهمها، لأن الحل يعتمد على ذلك كلياً.
قبل أن تغيّر أي شيء، شغّل nvidia-smi. يُظهر إجمالي الـVRAM، وما المستخدَم الآن، وأي العمليات تحتجزه. هذا الأمر وحده يخبرك إن كانت مهمة أخرى جاثمة على الكرت، وإن كنت متجاوزاً قليلاً أم بإفراط، وإن كان الرقم يعقل أصلاً للنموذج الذي تحمّله. كل ما يلي يفترض أنك نظرت.
الأشياء الخمسة التي تلتهم الـVRAM
ذاكرة الـGPU أثناء الاستدلال تذهب إلى ثلاثة دلاء: أوزان النموذج، والـKV cache الذي يحمل حالة الانتباه لكل رمز قيد المعالجة، والتنشيطات للتمريرة الأمامية الحالية. خطأ نفاد الذاكرة هو نمو أحد هذه الثلاثة فوق ما يحمله الكرت. وإليك كيف ينقسم ذلك عملياً، مع العَرَض الذي يشير إلى كل سبب والحل الذي يستهدفه فعلاً.
| السبب | العَرَض | الحل | الـflag الدقيق |
|---|---|---|---|
| الأوزان تتجاوز الـVRAM | نفاد ذاكرة لحظة تحميل النموذج، قبل أي طلب | كمِّم الأوزان | --quantization awq |
| الـKV cache أكبر من اللازم (سياق طويل) | يُحمَّل بسلاسة ثم ينفد عند أول أمر طويل أو تحت الحمل | حُدّ طول السياق | --max-model-len 4096 |
| حجم الدفعة كبير جداً | نفاد ذاكرة فقط حين تعمل عدة طلبات معاً | قلّل التزامن | --max-num-seqs 16 |
| تجزّؤ الذاكرة | نفاد ذاكرة رغم أن nvidia-smi يُظهر ذاكرة فارغة | عطِّل CUDA graphs | --enforce-eager |
| بلا تكميم | نموذج كان يُفترض أن يتّسع يعمل بدقة FP16/FP32 | حمِّل بناءً بتكميم 4-bit/8-bit | --quantization awq |
خذ الأول حرفياً. نموذج 70B بدقة FP16 يحتاج نحو 140GB من الـVRAM للأوزان وحدها: بايتان لكل معامل، و70 ملياراً منها. لا كرت استهلاكي واحد، ولا كرت مركز بيانات واحد بسعة 80GB، يحمل ذلك. ونسخة شائعة من هذا الفخّ أدقّ: قصدت تحميل بناء بتكميم 4-bit فحمّلت بيئة التشغيل أوزان BF16 الكاملة بدلاً منه، فنموذج توقّعت أن يتّسع عند 40GB يحاول حجز 140GB ويموت عند التحميل. إن حدث نفاد الذاكرة قبل أن ترسل طلباً واحداً، فالسبب الأوزان، والتكميم هو الجواب.
الـKV cache هو الجزء الذي يفوت الناس
السبب الثاني هو ما يوقع الحذرين، لأن النموذج يُحمَّل بنظافة ثم يسقط لاحقاً. الجاني هو الـKV cache. خادم مثل vLLM يحجز الـKV cache مسبقاً لأسوأ حالة أُخبر بتوقّعها: max_model_len مضروباً في أقصى دفعة قد يخدمها. ذلك الحجز قد يضاهي الأوزان نفسها.
الأرقام الملموسة تجعله حقيقياً. Llama-3-8B بدقة FP16 نحو 16GB من الأوزان. اخدِمه بسياق 8K ودفعة 16، فإن الـKV cache لتلك الحالة الأسوأ يصل إلى نحو 16GB وحده — فيحتاج الخادم نحو 32GB قبل أن يجيب أي شيء، على عبء أوزانه نصف ذلك فقط. على كرت بسعة 24GB يُحمَّل ثم يموت لحظة وصول حركة حقيقية. لهذا "يعمل النموذج نفسه على حاسوبي المحمول" ثم ينفد في الإنتاج: الدفعة والسياق أكبر في الإنتاج.
الحل الذي يستهدف هذا مباشرةً هو --max-model-len. اضبطه على ما تخدمه فعلاً — --max-model-len 4096 إن لم تتجاوز أوامرك 4K — فيتقلّص الحجز بالتناسب. وهذا يهم لسبب يعثر فيه الجميع تقريباً: التكميم يقلّص الأوزان لكنه لا يفعل شيئاً للـKV cache. إن كان نفاد ذاكرتك آتياً من الـKV cache، فبإمكانك أن تُكمّم طوال اليوم وتظل تنفد. تقليص السياق هو الحركة الأنجع. وعلى كروت من فئة Hopper تستطيع أيضاً تخزين الـcache نفسه بدقة FP8 بـ --kv-cache-dtype fp8، ما يقصّ ذاكرة الـKV بنحو 40-50٪ بكلفة جودة ضئيلة، كما توثّق مقالات الـKV cache في vLLM.
الحلول، بالترتيب من المجاني إلى الملاذ الأخير
لا تمدّ يدك إلى المطرقة الكبيرة أولاً. اعمل بهذه القائمة وتوقّف عند أول خطوة تُوسِّع النموذج على كرتك. كل خطوة هنا لا تكلّف إلا تغيير إعداد أو ملف نموذج مختلف — والشيء الوحيد الذي تنفقه قليلٌ من السرعة.
--quantization awq أو بناء GGUF بتكميم Q4. يقصّ ذاكرة الأوزان 50-75٪.
--max-model-len 4096. يهاجم الـKV cache مباشرةً — عادةً المكسب الأكبر.
--max-num-seqs 16. تسلسلات متزامنة أقل، تنشيطات وcache أصغر.
--enforce-eager يُسقط CUDA graphs، يحرّر ~1.5-2GB، ينهي نفاد الذاكرة من التجزّؤ.
--gpu-memory-utilization 0.92، ثم --cpu-offload-gb 4 كملاذ أخير بطيء.
كمِّم أولاً. تحميل أوزان AWQ أو GPTQ بدقة 4-bit، أو بناء GGUF بتكميم Q4، يقصّ ذاكرة الأوزان بنسبة 50-75٪ مقابل FP16 بهبوط جودة لا تلحظه معظم أعباء العمل. في vLLM يكون ذلك --quantization awq موجَّهاً إلى نقطة فحص مُكمَّمة مسبقاً؛ ومع llama.cpp يكفي تنزيل ملف Q4 بدل الكامل. وإن كان النموذج أكبر بقليل، فهذا وحده يُصلحه عادةً. ونموذج أصغر لكنه جيد كثيراً ما يكون الجواب الأنظف من مصارعة كبير. تلك هي الحجة التي يبنيها مقال النماذج اللغوية الصغيرة بالتفصيل.
ثم قلّص السياق. إن كان النموذج يُحمَّل ثم ينفد لاحقاً، فهذه رافعتك، لا التكميم. --max-model-len 4096 يحجم حجز الـKV على سياق ستستخدمه فعلاً. اقرنه بـ --kv-cache-dtype fp8 على كرت Hopper لتوفير 40-50٪ أخرى من الـcache.
ثم قلّل التزامن. --max-num-seqs 16 يحدّ كم تسلسلاً يعمل دفعةً واحدة. التنشيطات والـKV كلاهما يتمدّد مع الدفعة، فقيمة افتراضية عالية على كرت صغير سبب شائع وصامت لنفاد الذاكرة تحت الحمل.
ثم عالِج التجزّؤ. إن أظهر nvidia-smi ذاكرة فارغة وظل ينفد منك، فالمساحة الفارغة غير متّصلة. التقاط CUDA graph هو السبب المعتاد، و--enforce-eager يعطّله، فيحرّر نحو 1.5-2GB ويزيل التجزّؤ. وضبط PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True في البيئة يساعد PyTorch على إعادة استخدام الكتل المجزّأة أيضاً. وضع eager أبطأ قليلاً لكل رمز، وتلك هي المقايضة.
ثم احجز هامشاً. vLLM يحجز 90٪ من الـVRAM افتراضياً. على كرت يقود أيضاً شاشة أو عملية أخرى، هذا يتجاوز الحدّ. --gpu-memory-utilization 0.92 يضبط الحصة — خفّضها إن احتاج شيء آخر الكرت، وارفعها بحذر إن كان الـGPU مخصّصاً.
والتفريغ ملاذٌ أخير فقط. --cpu-offload-gb 4 يسكب ذلك العدد من الغيغابايتات من الأوزان إلى ذاكرة النظام كي يعمل نموذج أكبر بقليل. ينجح، لكن كل طبقة مُفرَّغة تعبر ناقل PCIe عند كل رمز، فتهبط الإنتاجية بقوة. استخدمه لتفكّ عالقك، لا كحالة دائمة.
لما تحتاج GPU أكبر
أحياناً يكون النموذج ببساطة أكبر من أي كرت تملكه، ولا قدر من الضبط يسدّ الفجوة. نموذج 70B بتكميم 4-bit ما زال يريد نحو 40GB للأوزان زائد متّسع للـcache؛ ولن يتّسع ذلك على كرت بسعة 24GB مهما قصّرت السياق. عند تلك النقطة غادرت عالم حلول الإعدادات. والخيار الصادق بين شراء كرت أكبر واستئجار واحد.
لمهمة عابرة — ضبط دقيق، أو تشغيل دفعي، أو أسبوع تقييم — يفوز الاستئجار بالحساب. كرت A100 بسعة 80GB بنحو 1.39 دولار للساعة وH100 بنحو 2.89 دولار، وكرت بسعة 80GB يحمل نموذج 70B بتكميم 4-bit مع متّسع للـKV cache. قارن ذلك بآلاف الدولارات لعتاد ستستخدمه بين الحين والآخر. وإن كنت ستشغّل استدلالاً محلياً ثقيلاً يومياً وبلا نهاية، فإن امتلاك الكرت يغيّر المعادلة — ودليل تشغيل النماذج على جهازك يعمل على أين تقع نقطة التعادل تلك. ولاختيار نموذج بحجم الكرت الذي لديك، يسرد فهرس النماذج نوافذ السياق وأعداد المعاملات جنباً إلى جنب.
النقطة الأوسع: خطأ نفاد ذاكرة CUDA مشكلة تحجيم، لا طريق مسدود. اقرأ nvidia-smi لترى أي دلو فاض، كمِّم وقلّص السياق لتستعيد الغيغابايتات السهلة، حرّر التجزّؤ بوضع eager، ولا تستأجر كرتاً أكبر إلا حين لا يتّسع النموذج فعلاً على أي شيء تملكه. ومعظم الوقت لن تصل إلى تلك الخطوة الأخيرة.