نفاد ذاكرة VRAM في كرت الشاشة: ماذا تفعل؟
ذاكرة VRAM في كرت الشاشة هي عنق الزجاجة في أحمال الذكاء الاصطناعي والرسوميات. تعرّف على الذاكرة الموحدة وتفريغ VRAM والتبديل عبر NVMe.

أكثر رسالة خطأ شيوعاً في تعلم الآلة ليست traceback في Python ولا عدم تطابق الأبعاد، بل CUDA out of memory. إما أن النموذج كبير جداً، أو حجم الدفعة (batch size) أكبر من اللازم، أو أن التفعيلات الوسيطة (intermediate activations) لا تتسع في VRAM الخاصة بكرت الشاشة. كرت RTX 4090 فيه 24 GB، ونموذج بـ 70 مليار معامل بدقة نصف (half precision) يحتاج نحو 140 GB. الحسبة لا تستقيم، وصرف المال على كروت أكبر يؤجل المشكلة فقط، فحتى H100 بذاكرة 80 GB لا يستوعب أكبر النماذج في كرت واحد.
لكن ماذا لو أمكنك توسيع ذاكرة كرت الشاشة بشكل شفاف باستخدام ذاكرة النظام، أو حتى تخزين NVMe؟ أدوات مثل Greenboost من NVIDIA ومشاريع مشابهة تفعل ذلك تحديداً، فهي تستغل تسلسل الذاكرة (VRAM ← ذاكرة النظام ← SSD) لتشغيل أحمال لا يفترض أن تتسع في عتادك. مقايضات الأداء حقيقية، لكنها قابلة للإدارة بشكل مفاجئ في كثير من الحالات. ولفهم ذلك يجب أولاً فهم تسلسل ذاكرة كرت الشاشة نفسه.
لماذا تختلف ذاكرة كرت الشاشة عن ذاكرة المعالج
تخدم ذاكرة المعالج وذاكرة كرت الشاشة أنماط وصول مختلفة جوهرياً. أحمال المعالج حساسة للزمن (latency)، فالخيط الواحد يحتاج قطعة بيانات ويتوقف حتى تصل. وقد صُممت ذاكرات التخزين المؤقت (cache) في المعالج لتقليل زمن الوصول للأنماط العشوائية.
أحمال كرت الشاشة حساسة للإنتاجية (throughput): آلاف الخيوط يحتاج كل منها بياناته، ويستطيع كرت الشاشة التبديل بين الخيوط لإخفاء زمن الانتظار. صُممت ذاكرة كرت الشاشة (HBM في كروت مراكز البيانات، وGDDR في الكروت الاستهلاكية) للنطاق الترددي (bandwidth)، أي تسليم كميات هائلة من البيانات في الثانية، حتى لو استغرق أي وصول فردي وقتاً أطول من إصابة ذاكرة التخزين المؤقت في المعالج.
Memory bandwidth comparison (approximate):
RTX 4090 GDDR6X: 1,000 GB/s
H100 HBM3: 3,350 GB/s
DDR5 System RAM: 50 GB/s
PCIe 5.0 x16: 64 GB/s (theoretical max)
NVMe SSD: 7 GB/s
The bandwidth cliff between VRAM and system RAM is ~20x.
Between VRAM and NVMe it's ~140x.
This is why naive offloading to system RAM kills performance —
you're trying to feed a 1,000 GB/s appetite through a 50 GB/s straw.
هذه الفجوة في النطاق الترددي هي سبب فشل فكرة جعل ذاكرة كرت الشاشة 'افتراضية' بشكل ساذج، أي نقل البيانات إلى ذاكرة النظام بالطريقة التي يتعامل بها المعالج مع القرص. يتحمل المعالج أخطاء الصفحات (page faults) بتباطؤ يبلغ نحو 10 أضعاف، أما كرت الشاشة الذي يصل إلى ذاكرة النظام بدلاً من VRAM فيخسر نحو 20 ضعفاً من النطاق الترددي. وهذا يعني، في الأحمال المقيدة بالنطاق الترددي (وهي معظم استدلال تعلم الآلة)، تباطؤاً بنحو 20 ضعفاً.
كيف يعمل تفريغ VRAM فعلياً
الحيلة ليست في معاملة ذاكرة النظام كأنها VRAM بطيئة، بل في جلب البيانات (prefetch) من ذاكرة النظام إلى VRAM قبل أن يحتاجها كرت الشاشة، فيُخفى زمن النقل خلف الحوسبة. هذه هي الفكرة الجوهرية وراء كل تقنية عملية لتوسيع VRAM.
أثناء استدلال الشبكات العصبية تكون الحوسبة متسلسلة عبر الطبقات. وبينما يعالج كرت الشاشة الطبقة 5، يعرف أن الطبقة 6 هي التالية. يستطيع نظام تفريغ ذكي أن يبدأ نقل أوزان الطبقة 6 من ذاكرة النظام إلى VRAM أثناء حوسبة الطبقة 5. وإذا استغرقت الحوسبة وقتاً أطول من النقل، وهذا يحدث غالباً مع الطبقات الكبيرة، يُخفى النقل تماماً فلا يتوقف كرت الشاشة أبداً.
# Conceptual overlap of compute and transfer
# (simplified pseudocode)
def inference_with_offloading(model, input_data):
# Only 2 layers fit in VRAM at a time
# Rest are in system RAM
for i, layer in enumerate(model.layers):
# Start async transfer of NEXT layer while computing current
if i + 1 < len(model.layers):
async_transfer_to_gpu(model.layers[i + 1])
# Compute on current layer (GPU is busy, transfer happens in parallel)
output = layer.forward(input_data)
# Evict current layer from VRAM (it's done)
transfer_to_ram(layer)
# Wait for next layer transfer to complete (usually already done)
sync_transfer()
input_data = output
return output
ينجح هذا النهج القائم على الأنابيب (pipeline) جيداً في الاستدلال لأن رسم الحوسبة قابل للتنبؤ، فأنت تعرف بالضبط أي الأوزان مطلوبة لاحقاً. أما التدريب فأصعب، لأن مرحلة الانتشار العكسي (backward pass) تحتاج تفعيلات من مرحلة الانتشار الأمامي، ما يخلق أنماط حركة بيانات أكثر تعقيداً.
المناهج العملية
ذاكرة CUDA الموحدة (CUDA Unified Memory)
تنشئ ذاكرة CUDA الموحدة من NVIDIA فضاء عناوين واحداً يمتد على VRAM وذاكرة النظام معاً. يتولى CUDA runtime نقل الصفحات بينهما تلقائياً بحسب أنماط الوصول. وعندما يصل كرت الشاشة إلى صفحة موجودة في ذاكرة النظام، يحدث خطأ صفحة (page fault) فتُنقل الصفحة إلى VRAM.
الميزة أن الأمر شفاف للتطبيق، فكود CUDA لا يحتاج إلى إدارة مواقع البيانات. أما العيب فهو أن أخطاء الصفحات مكلفة، وأن خوارزميات النقل في runtime لا تتطابق دائماً مع نمط الوصول في التطبيق. في الأحمال القابلة للتنبؤ مثل استدلال الشبكات العصبية، تتفوق الإدارة الصريحة على النقل التلقائي.
التفريغ طبقة بطبقة
أدوات مثل Hugging Face Accelerate وDeepSpeed ZeRO-Inference وخيار --mmap في llama.cpp تطبق تفريغ الطبقات صراحةً. تُبقي هذه الأدوات الطبقات النشطة فقط في VRAM وتبث البقية من ذاكرة النظام أو القرص. لا يحتاج النموذج إلى أن يتسع بالكامل في VRAM، بل يكفيه أن يتسع طبقة أو طبقتين في كل مرة.
وهكذا تعمل تشغيل نماذج 70B على العتاد الاستهلاكي فعلياً. يحتاج نموذج 70B مكمّم بـ 4 بت نحو 40 GB إجمالاً، لكن أي طبقة منفردة لا تحتاج سوى نحو 1-2 GB. ومع 24 GB من VRAM والجلب المسبق، يمكنك تشغيل النموذج بتأثير معقول على الأداء، ربما بتباطؤ 30-50% مقارنة بالتشغيل الكامل داخل VRAM.
NVMe كذاكرة موسّعة
الأسلوب الأكثر جرأة يستخدم أقراص NVMe كمستوى ثالث من ذاكرة كرت الشاشة. النطاق الترددي أسوأ بكثير من VRAM (نحو 7 GB/s مقابل نحو 1,000 GB/s)، لكن السعة شبه غير محدودة. قرص NVMe بسعة 4 TB يكلف 200 دولار، ويمكنه تخزين عشرات النماذج الكبيرة في الوقت نفسه.
تنفذ مشاريع مثل Greenboost من NVIDIA هذا بشفافية، إذ يتمدد نظام ذاكرة كرت الشاشة ليشمل NVMe، مع جلب مسبق ذكي للتقليل من أثر محدودية النطاق الترددي. وفي أحمال الاستدلال التي يقضي فيها كرت الشاشة وقتاً كبيراً في الحوسبة لا في نقل البيانات فقط، يمكن إخفاء زمن NVMe بالكامل عبر تداخل الحوسبة مع النقل.
يعتمد الأداء بقوة على طبيعة الحمل. العمليات المقيدة بالحوسبة (مثل ضرب المصفوفات الكبيرة) تخفي زمن النقل جيداً، أما العمليات المقيدة بالذاكرة (مثل آليات الانتباه attention مع سياق طويل) فلا تفعل ذلك. عملياً، يعمل تفريغ NVMe بأفضل صورة مع الاستدلال المجمّع (batch inference) للنماذج الكبيرة بأحجام دفعات صغيرة، وهذه تحديداً حالة استخدام الاستدلال المحلي لنماذج اللغة الكبيرة.
ميزة الذاكرة الموحدة عند Apple
تتبنى بنية الذاكرة الموحدة في شرائح Apple Silicon نهجاً مختلفاً تماماً، وهو إلغاء التمييز بين VRAM والذاكرة. يتشارك المعالج وكرت الشاشة مجمّع الذاكرة الفيزيائي نفسه، فلا يوجد 'تفريغ' لأنه لا يوجد فصل أصلاً، فكرت الشاشة يصل إلى الذاكرة نفسها التي يستخدمها المعالج.
هذا لا يلغي مشكلة النطاق الترددي، إذ إن عرض نطاق ذاكرة شرائح M (نحو 400 GB/s في M4 Max) أقل من كرت الشاشة المخصص، لكنه يلغي عنق زجاجة PCIe الذي يجعل تفريغ كروت الشاشة المنفصلة بطيئاً. يستطيع جهاز Mac بذاكرة موحدة 128 GB أن يستوعب نموذجاً بـ 70 مليار معامل بالكامل في الذاكرة التي يصل إليها كرت الشاشة، من دون أي عبء تفريغ.
المقايضة هنا: إنتاجية قصوى أقل للأحمال التي تتسع بالكامل في VRAM لكرت مخصص، مقابل أداء أفضل بكثير للأحمال التي لا تتسع. في استدلال النماذج الكبيرة، حين يتجاوز النموذج VRAM الكرت المنفصل، كثيراً ما يكون Apple Silicon أسرع من كرت منفصل مع التفريغ، رغم أن حوسبته الخام أقل.
ماذا يعني هذا للمطورين
إذا كنت تبني تطبيقات تعتمد على كروت الشاشة، مثل استدلال تعلم الآلة والرسوميات والحوسبة العلمية، فإن قيد VRAM يؤثر على قرارات معمارية محددة وملموسة.
- اعرف حجم مجموعة العمل (working set). قِس استخدام ذاكرة كرت الشاشة، لا الحد الأقصى للتخصيص، بل مجموعة العمل في أي لحظة. إذا كان الحد الأقصى 48 GB لكن لا تحتاج أي عملية منفردة أكثر من 8 GB من البيانات النشطة، فسيعمل التفريغ بكفاءة. أما إذا كانت عملية واحدة تحتاج فعلاً 48 GB في الوقت نفسه، فأنت بحاجة إلى كرت أكبر.
- اختر استراتيجية التفريغ بحسب نمط الوصول. الوصول المتسلسل (الاستدلال طبقة بطبقة) يعمل بشكل ممتاز مع الجلب المسبق، أما الوصول العشوائي (الانتباه عبر سياقات كبيرة) فلا يفعل ذلك. اعرف النمط الذي يتبعه حملك.
- التكميم غالباً أرخص من التفريغ. تقليل النموذج من FP16 إلى INT4 يخفض الذاكرة بمقدار 4 أضعاف مع تأثير معتدل على الجودة. التفريغ إلى ذاكرة النظام يضيف زمن استجابة من دون أي تأثير على الجودة، لكن وفره في الذاكرة محدود. ابدأ بالتكميم أولاً، ثم فكّر في التفريغ.
- حجم الدفعة (batch size) هو مفتاح الضبط. الدفعات الأكبر تحتاج ذاكرة أكثر لكنها توزع التكاليف الثابتة بكفاءة أكبر. الدفعات الأصغر تحتاج ذاكرة أقل لكنها تعالج عناصر أقل في الثانية. وعندما تقترب من حد VRAM، فإن تقليل حجم الدفعة هو أبسط حل.
- راقب تفتت الذاكرة (fragmentation). قد يفتّت تخصيص ذاكرة CUDA الـ VRAM مع الوقت، خاصة مع مدخلات متغيرة الطول. قد تملك 8 GB حرة في المجموع دون كتلة متصلة واحدة بسعة 2 GB. تعرض
torch.cuda.memory_stats()في PyTorch حالة التفتت، ويمكن أن تساعدtorch.cuda.empty_cache()، لكنها ليست حلاً شاملاً.
ستظل ذاكرة كرت الشاشة دائماً عنق الزجاجة في أحمال الحوسبة واسعة النطاق، فالنماذج تنمو أسرع من VRAM. لكن أدوات إدارة هذا العنق، من ذاكرة موحدة وتفريغ ذكي وتخزين مؤقت متعدد المستويات، أصبحت جيدة بما يكفي لتجعل عبارة 'لا تتسع في VRAM' حاجزاً غير صلب. إنها مقايضة في الأداء، وأصبحت بشكل متزايد قابلة للإدارة.


