الوحدة 9 — التدريب الموزّع على عدّة مسرّعات
حين يسع النموذجَ ذاكرةُ مسرّع واحد لكنّ التدريب يستغرق ثلاثة أيّام، يصبح التوزيع مغريًا. وTensorFlow يجعل كتابته سهلة إلى حدّ يُخفي ضبطَين اثنين لا مفرّ من ضبطهما يدويًّا، وإهمالُ أيٍّ منهما يُخرّب النتيجة بصمت.
المبدأ: تكرار النموذج، وتقسيم الحزمة
الاستراتيجية الأكثر شيوعًا تُكرّر النموذج كاملًا على كلّ مسرّع. تستقبل كلّ نسخة جزءًا من الحزمة، وتحسب تدرّجاتها، ثمّ تُؤخذ متوسّطات كلّ التدرّجات وتُطبَّق بشكل موحّد في كلّ مكان. تبقى النُّسَخ بذلك متزامنةً بدقّة.
هذا هو التوازي على مستوى البيانات. ويفترض أنّ النموذج يسعه مسرّع واحد. ومتى لم يعد الأمر كذلك — كالنماذج اللغوية الكبرى في الدورة 16 — فلا بدّ من توازٍ على مستوى النموذج نفسه، يُقطّع الشبكة إلى أجزاء، وهو خارج نطاق هذه الوحدة.
ثلاثة أسطر من الشيفرة
import tensorflow as tf
from tensorflow import keras
strategie = tf.distribute.MirroredStrategy()
print(f"عدد النسخ: {strategie.num_replicas_in_sync}")
with strategie.scope():
modele = construire_modele()
modele.compile(
optimizer=keras.optimizers.Adam(1e-3),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
modele.fit(jeu, validation_data=jeu_val, epochs=20)
كلّ ما يُنشئ متغيّرات ينبغي أن يكون داخل scope: بناء النموذج وتجميعه. أمّا fit فيبقى خارجًا. فمتغيّر أُنشئ خارج النطاق لا يُكرَّر، والخطأ الناتج عن ذلك يصعب ربطُه بسببه.
| الاستراتيجية | النطاق | الاستعمال |
|---|---|---|
MirroredStrategy | عدّة مسرّعات على جهاز واحد | الحالة الشائعة |
MultiWorkerMirroredStrategy | عدّة أجهزة | تتطلّب إعداد شبكة |
TPUStrategy | وحدات معالجة تنسورية | بعد الاتّصال بالعنقود |
OneDeviceStrategy | جهاز واحد فقط | لتنقيح الشيفرة الموزّعة |
حجم الحزمة عامّ، وهذا مخالف للحدس
الضبط الأوّل الذي لا يجوز إغفاله. batch_size الذي تُمرّره هو الحجم العامّ: يُقسَم بين النُّسَخ.
LOT_PAR_REPLIQUE = 64
LOT_GLOBAL = LOT_PAR_REPLIQUE * strategie.num_replicas_in_sync
jeu = jeu.batch(LOT_GLOBAL).prefetch(tf.data.AUTOTUNE)
الإبقاء على batch_size=64 على أربعة مسرّعات يعطي ستّة عشر مثالًا لكلّ نسخة. عندئذٍ يصير الحساب غير مثمر — إذ تبقى المسرّعات نصف عاطلة — وتتدهور إحصائيات التطبيع بالحزم، لأنّها تُحسَب لكلّ نسخة على حدة لا عامًّا. ومتى نزل الرقم دون اثنين وثلاثين مثالًا لكلّ نسخة صار التطبيع بالحزم صاخبًا بصراحة.
البقيّة تُولّد نُسَخًا متفاوتة الأحجام، وحسب النسخة إمّا خطأً أو اختلالًا صامتًا. احسب دائمًا الحزمة العامّة انطلاقًا من حزمة النسخة، لا العكس.
معدّل التعلّم يجب أن يتبع
الضبط الثاني، وهو الأكثر نسيانًا. مضاعفة حجم الحزمة بأربعة تقسم على أربعة عددَ التحديثات في الحقبة الواحدة. وبمعدّ ل ثابت يتعلّم النموذج إذن أربع مرّات أقلّ في الحقبة، فيبدو التدريب متراجعًا في حين أنّه ببساطة أبطأ.
قاعدتان للتحجيم متداولتان:
- خطّية: ضرب المعدّل في عدد النُّسَخ. مناسبة حتّى حزم من بضعة آلاف مثال.
- جذر تربيعي: الضرب في جذر المعامل. أكثر تحفّظًا على الحزم الضخمة.
TAUX_BASE = 1e-3
taux = TAUX_BASE * strategie.num_replicas_in_sync
with strategie.scope():
modele.compile(optimizer=keras.optimizers.Adam(taux), loss="mse")
معدّل مضروب في أربعة يُطبَّق منذ الحزمة الأولى يُزعزع التدريب. لذلك تصير مرحلة الإحماء المذكو رة في الوحدة 6 هنا شبه ضرورية: الانطلاق من المعدّل الأساسي والوصول إلى المعدّل المُحجَّم في بضع مئات من الخطوات.
الدقّة المختلطة، وهي غالبًا أجدى من التوزيع
قبل إضافة مسرّعات، يوجد رافعة أقلّ تكلفة. تُخزّن الدقّةُ المختلطة الأوزانَ بـfloat32 لكنّها تُجري الحسابات بـfloat16، فتستثمر الوحدات العتاديّة المخصّصة وتقسم الذاكرة المستهلكة تقريبًا إلى النصف.
keras.mixed_precision.set_global_policy("mixed_float16")
يرافق هذا الضبط احتياطٌ واحد: يجب أن تبقى طبقة المخرج في float32. ففي float16 تتشبّع softmax وتفقد الإنتروبيا المتقاطعة دقّتَها العددية.
sortie = layers.Dense(nb_classes, activation="softmax", dtype="float32")(x)
يدير Keras تلقائيًّا تحجيم الخسارة، الذي يمنع تدرّجات صغيرة من الانطفاء داخل المدى الضيّق لـfloat16. أمّا إن أعدت تعريف train_step كما في الوحدة 4 فيغدو هذا التحجيم مسؤوليّتك: optimizer.get_scaled_loss قبل الاشتقاق، ثمّ get_unscaled_gradients بعده.
العنق الأوّل هو دائمًا تقريبًا خطّ بيانات الوحدة 5، وإصلاحه لا يكلّف شيئًا. تليه الدقّة المختلطة، التي تجلب عاملًا يقارب الاثنين مقابل سطر واحد من الشيفرة. أمّا التوزيع فيأتي في المرتبة الثالثة: يُضاعف الفاتورة العتاديّة ويضيف ضبطًا جديدًا. تحقّق بمُنقّح الوحدة 7 أنّ الحساب هو العُنق فعلًا قبل أن تلجأ إليه.
ما يتغيّر على مستوى عدّة أجهزة
MultiWorkerMirroredStrategy تُوسّع المبدأ بين الأجهزة، لكنّها تضيف قيودًا لم تكن موجودة. المتغيّر البيئي TF_CONFIG يجب أن يصف طوبولوجيا العنقود على كلّ عقدة. ويصير عرض النطاق الشبكي حاسمًا، إذ تعبر التدرّجات الشبكةَ عند كلّ خطوة. والأهمّ أنّ كتابة نقاط الحفظ يجب أن تكون منسّقة: يكتب كلّ عامل في دليل مؤقّت خاصّ به، ولا يُبقي على الملفّ النهائي إلّا العامل الرئيس. ويتكفّل keras.callbacks.BackupAndRestore بهذا التنسيق ويُتيح الاستئناف بعد عطب عقدة، وهو ما يصبح إحصائيًّا أمرًا حتميًّا حين نتخطّى بضعة أجهزة.
في الخلاصة
- التوازي على مستوى البيانات يُكرّر النموذج بأكمله ويُوسّط التدرّجات، ويفترض أن يسع النموذجَ مسرّعٌ واحد.
- كلّ ما يُنشئ متغيّرات يكون داخل
scopeالاستراتيجية، بينما يبقىfitخارجًا. batch_sizeهو الحجم العامّ: احسبه من حزمة النسخة، وإلّا تركت المسرّعات نصف عاطلة ودَهْوَرْتَ التطبيع بالحزم.- يجب تحجيم معدّل التعلّم بعدد النُّسَخ مع مرحلة إحماء؛ وقبل التوزيع، جرّب خطّ البيانات ثمّ الدقّة المختلطة، فهما أرخص بكثير.
الوحدة التالية: تصدير النموذج بصيغة SavedModel وخدمته في الإنتاج.