التدرجات المحلية تتضاعف
الانتشار الأمامي
الانتشار الأمامي لـ ANDREA-120M يمرر الإدخال عبر سلسلة من العمليات:
x = embed(token_ids) # تضمينات الرموز
for layer in 12_layers:
x = x + attn(LN(x)) # طبقة الاهتمام
x = x + mlp(LN(x)) # طبقة MLP
logits = LN(x) @ embed.T # إسقاط الإخراج المشترك
loss = cross_entropy(logits, targets)
كل عملية تقرأ تسنسورات الإدخال وتنتج تسنسورات الإخراج. تنتهي المرور الأمامي في قيمة واحدة: خسارة الإنتروبيا المتقاطعة لهذا الدفعة.
المرور الخلفي
التدريب يحدّث الأوزان في الاتجاه الذي يقلل الخسارة. للحصول على اتجاهات التحديث، يحتاج المحرك إلى:
∂L/∂W لكل W قابل للتعلم في النموذج
قاعدة السلسلة توفر هذا. بالنسبة لسلسلة loss = f(g(h(x))):
∂L/∂x = (∂L/∂f) * (∂f/∂g) * (∂g/∂h) * (∂h/∂x)
كل عامل هو تدرج محلي: كيف يتغير إخراج عملية واحدة عندما يتغير مدخلها بمقدار صغير. ضرب التدرجات المحلية للخلف عبر الرسم البياني ينقل إشارة الخسارة إلى كل وزن.
التفريق عكسي الاتجاه
يحسب الانتشار الخلفي التدرجات بترتيب عكسي: بدءًا من dL/dlogits = 1، ثم السير للخلف عبر الإنتروبيا المتقاطعة، ثم الإسقاط الخارجي، ثم تطبيع الطبقة، ثم اثنا عشر كتلة محول، ثم الترجمات. في كل خطوة، اضرب التدرج الوارد في يعقوب المحلي.
الوضع العكسي فعال عندما يكون الإخراج قيمة مفردة (الخسارة) وهناك العديد من المدخلات (الأوزان). تمريرة خلفية واحدة تنتج تدرجات لكل وزن في النموذج. الوضع الأمامي سيحتاج إلى تمريرة لكل وزن؛ بالنسبة لـ ANDREA-120M مع ~120M وزن، الوضع الأمامي غير عملي.
لماذا الوضع العكسي
كل عملية أمامية تحصل على توأم عكسي
قاعدة الاقتران
يحتوي microgpt_cuda.cu على نواتين CUDA لكل عملية: واحدة تحسب المخرج الأمامي، وأخرى تحسب تدرجات المدخلات معطاة تدرجات المخرجات. الاقتران واحد لواحد:
| النواة الأمامية | النواة الخلفية | العملية |
|---|---|---|
k_embed_fwd | k_embed_bwd | البحث عن تضمين الرموز |
k_layernorm_fwd | k_layernorm_bwd | تطبيع الطبقة |
k_attn_qkv_fwd | k_attn_qkv_bwd | إسقاطات Q, K, V |
k_attn_fwd | k_attn_bwd | الانتباه بنقطة الضرب المقيسة |
k_attn_out_fwd | k_attn_out_bwd | إسقاط الإخراج W_O |
k_mlp_fwd | k_mlp_bwd | MLP (مع GELU) |
k_residual_add | k_residual_add_bwd | الاتصال المتبقي |
k_loss_fwd | k_loss_bwd | خسارة الإنتروبيا المتقاطعة |
ثمانية أزواج عمليات تغطي المحول بالكامل. بالإضافة إلى بعض النوى الخدمية: k_grad_norm_partial، k_grad_norm_final، k_grad_scale لقص التدرج (انظر النشاط 75).
مهمة نواة عكسية
معطى التدرج المتدفق من الطبقات اللاحقة (grad_output)، تحسب نواة عكسية:
1. grad_input: التدرج بالنسبة لتينسور الإدخال للعملية. يتم تمريره للخلف بشكل أكبر.
2. grad_weight: التدرج بالنسبة للمعاملات القابلة للتعلم في العملية. يذهب إلى حالة المحسن.
يتم حساب كلاهما في إطلاق نواة واحد. تتعاون خيوط CUDA على بلاطات تينسور التدرج بالتوازي.
التينسورات المحفوظة
غالباً ما تحتاج الحسابات الخلفية إلى قيم من المرور الأمامي. على سبيل المثال، k_layernorm_bwd تحتاج إلى المتوسط والتباين المحسوب أثناء الأمامي؛ k_mlp_bwd تحتاج إلى ما قبل تنشيط GELU. يخزن محرك التدريب هذه في مخازن مخصصة أثناء الأمامي، ثم يقرأها أثناء الخلفي.
تكلفة الذاكرة: تقريباً نفس شكل إخراج الـ forward لكل tensor محفوظ. بالنسبة لـ ANDREA-120M مع batch=8، seq=1024، d_model=768، tensor محفوظ واحد هو 8 × 1024 × 768 × 4 bytes = 25 MB. عبر 12 طبقة ومتعدد tensors محفوظة لكل طبقة، الـ activations تهيمن على VRAM أثناء التدريب (~5-10 GB على بطاقة 24 GB).
تتبع خطوة Backward واحدة
أين تعيش التدرجات في الذاكرة
تدرج واحد لكل تينسور وزن
كل تينسور وزن قابل للتعلم في ANDREA-120M لديه تدرج متطابق الشكل. لكل كتلة:
W_Q [768, 768] ↔ grad_W_Q [768, 768]
W_K [768, 768] ↔ grad_W_K [768, 768]
W_V [768, 768] ↔ grad_W_V [768, 768]
W_O [768, 768] ↔ grad_W_O [768, 768]
W_1 [768, 3072] ↔ grad_W_1 [768, 3072]
W_2 [3072, 768] ↔ grad_W_2 [3072, 768]
LN1.gamma [768] ↔ grad_LN1.gamma [768]
LN1.beta [768] ↔ grad_LN1.beta [768]
LN2.gamma [768] ↔ grad_LN2.gamma [768]
LN2.beta [768] ↔ grad_LN2.beta [768]
بالإضافة إلى تضمينات الرموز، تضمينات الموضع، وطبقة تطبيع نهائية. إجمالي ذاكرة مخزن التدرج يطابق ذاكرة الأوزان: ~120M أرقام عائمة، ~480 ميغابايت عند FP32، ~240 ميغابايت عند FP16.
التجميع عبر الدفعات الفرعية الصغيرة
حجم الدفعة في ANDREA = 8 يتناسب مع VRAM عند FP16. الدفعات الفعالة الأكبر تتطلب تجميع التدرجات: تشغيل عدة تمريرات أمامية+خلفية على دفعات صغيرة، جمع التدرجات في نفس المخزن، ثم إجراء خطوة محسن واحدة.
for microbatch in range(n_microbatches):
forward(microbatch)
backward() # يُضيف إلى مخازن التدرج، لا يُكتب فوقها
scale_grads(1.0 / n_microbatches) # متوسط عبر الميكروباتشات
optimizer_step()
zero_grads() # إعادة تعيين للخطوة التدريبية التالية
نوى الانتشار العكسي تستخدم دلالة +=، وليس =. كل استدعاء يضيف مساهمات التدرج إلى المخزن الموجود؛ يحتفظ المخزن بالمجموع التراكمي حتى يمسحه zero_grads().
حالة المحسن
AdamW (النشاط 73) يحتفظ بمخزنين إضافيين لكل وزن: اللحظة الأولى m واللحظة الثانية v. إجمالي الذاكرة أثناء التدريب:
الأوزان: 1× عدد الأوزان
التدرجات: 1× عدد الأوزان
Adam m: 1× عدد الأوزان
Adam v: 1× عدد الأوزان
الأفعال المحفوظة: ~2-4× حسب الطبقات والباتش
──────────────────────────────────────────
الإجمالي: ~6-8× عدد الأوزان
ANDREA-120M عند FP16: ~240 ميغابايت × 4 مخازن (الأوزان، التدرج، m، v) + ~5-10 جيجابايت تنشيطات = ~10-12 جيجابايت إجمالي. بشكل مريح أقل من سقف RTX 4090 البالغ 24 جيجابايت. تم تدريب ANDREA-12M في 1.4 جيجابايت؛ التوسع 10× في المعاملات يجلب ~10× ذاكرة.
تحديد حجم مخازن التدرج
التحكم الكامل في الذاكرة والدقة
تكلفة الإطارات العامة
يجعل PyTorch & JAX autograd مريحًا: اكتب كود Python، احصل على التدرجات تلقائيًا. التكلفة: طبقة توجيه عامة بين كودك & CUDA. كل عملية تمر عبر عبء تفسير Python، تسجيل الإطار، & اختيار النواة الديناميكي. لتدريب نموذج لغة صغير على وحدة معالجة رسوميات واحدة، يهم ذلك العبء.
التكاليف الملموسة التي يتجنبها ANDREA:
1. تأخير مفسر Python. كل عملية PyTorch تعبر حد Python/C++. لـ ~100 إطلاق نواة لكل خطوة تدريب عند ~9 خطوات/دقيقة، هذا ~900 عبور حد لكل دقيقة. الإرسال على مستوى C يقضي على هذا.
2. غير المتوقع في مضعِّب الإطار. مضعِّب التخزين المؤقت في PyTorch يعطي تدفقًا جيدًا في المتوسط لكن ذاكرة قمة غير متوقعة. محرك تدريب ANDREA يخصص كل مخزن مسبقًا عند البدء؛ لا إعادة تخصيص أثناء التدريب، لا تفتيت، لا OOM مفاجئ في الخطوة 100K.
3. اختيار النواة العام. PyTorch يختار النوى في وقت التشغيل عبر ال clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford clifford
4. أنابيب الدقة المختلطة. مسار cuBLAS FP16 في ANDREA-120M وتجارب نوى التنسور FP8 E4M3 في ANDREA تتطلب تحكمًا دقيقًا في التنسورات التي تعيش بأي دقة. الإطارات العامة تعرض هذا التحكم من خلال واجهات برمجية طبقية؛ الكتابة المخصصة لـ CUDA تكتبها مباشرة.
التسوية
تكاليف CUDA المخصصة: كود أكثر للكتابة، أخطاء أكثر للعثور عليها، لا نظام بيئي مجتمعي. ملف microgpt_cuda.cu في ANDREA يتكون من ~6000 سطر من CUDA المكتوب يدويًا استغرق أشهرًا في التصحيح. كل عملية جديدة تتطلب كتابة نواة أمامية، نواة خلفية، واختبارات.
ما يكسبه ANDREA:
- إعادة الإنتاج الكاملة. خط أنابيب التدريب هو ثنائي C واحد بالإضافة إلى وكيل Python واحد. لا انجراف في الإصدارات عبر إصدارات PyTorch، لا تناقضات في إصدار CUDA مع عجلات الإطار.
- استئنافات دقيقة على مستوى البت. SIGTERM يُطلق كتابة نقطة تفتيش تلتقط كل تنسور بالضبط كما يراه GPU. الاستئناف يلتقط نفس مسار الخسارة الذي كان عليه التشغيل.
- ذاكرة متوقعة. تم تدريب ANDREA-120M لمدة 200 ألف خطوة بدون OOMs. تم حساب الذاكرة عند بدء تشغيل المحرك.
- الوصول المباشر إلى الأجهزة. أحجام بلاطات نواة التنسور، إعدادات FP8 E4M3، نسخ الذاكرة غير المتزامنة: كلها قابلة للعنوان مباشرة في CUDA، غامضة في الإطارات العامة.
إعادة الإنتاج كمهمة
يُسرد القسم 9 من ورقة ANDREA البيضاء مكدس إعادة الإنتاج الكامل:
محرك التدريب: microgpt/microgpt_cuda.cu
وكيل التدريب: microgpt/training_proxy.py
إعدادات التجربة: experiments/ANDREA-*-TRAIN.json
خط أنابيب البيانات: scripts/pull-hermes3.py, scripts/prep-megachat.py
لوحة التحكم: scripts/live-loss-dashboard.html
مواصفات Bandit: docs/FIREHOSE-BANDIT.md
وثائق النموذج: docs/ANDREA.md
متطلبات الأجهزة: وحدة معالجة NVIDIA GPU واحدة بذاكرة VRAM ≥8 جيجابايت (RTX 3060 أو أفضل). يمكن لأي شخص إعادة إنتاج ANDREA-12M من هذه الملفات. المسار المخصص لـ CUDA هو جزء من السبب: لا تجميد إصدارات الإطار، لا مفاجآت في التبعيات بعد خمس سنوات.
الإشارات & نقاط التفتيش
يستجيب حلقة تدريب CUDA لإشارتين POSIX:
- SIGTERM: كتابة نقطة تفتيش فورية، ثم الخروج. يُستخدم عند إيقاف التدريب بشكل نظيف.
- SIGUSR1: كتابة نقطة تفتيش فورية، الاستمرار في التدريب. تم استخدامها أثناء الدوران التلميعي في v3 لالتقاط الحالة دون مقاطعة التشغيل.
تنسيق نقطة التفتيش: [int32 step][int32 n_params][n_params × float32 weights][n_params × float32 m][n_params × float32 v]. عداد الخطوة، عدد الأوزان، ثم الأوزان متبوعة بلحظات Adam. يستأنف بدقة البت. الوكيل يحفظ .samples.json & .state.json بشكل منفصل في التلميع؛ .loss.json لا يتم حفظه أبدًا (يتراكم تاريخ التدريب الكامل).