الضبط التلقائي للدقة المختلطة باستخدام PyTorch

مقدمة

تتطلب النماذج العميقة من التعلم الأكثر قدرة على المعالجة ومتوسطات الذاكرة. وتم تحقيق تعلم الشبكات العميقة الأسرع من خلال تطوير تقنيات جديدة. بدلاً من FP32 (تنسيق الأعداد العشوائية بالمستوى الكامل), يمكنك استخدام FP16 (تنسيق الأعداد العشوائية بالمستوى النصف), ووجدت الباحثون أن إستخدامهما معًا هو خيار أفضل.

يسمى إستخدام الدقة المختلفة المزدوج لأغلب التدريب عن هذه التقنية التي تستخدم كلاً من التمثيلات الواحدة والنصف الدقيقة.

في هذه المرور عن تدريب الدقة المختلفة التلقائية (Amp) مع PyTorch, نبرز كيفية عمل هذه التقنية من خلال ممارسة الAmp خطوة بخطوة, ونتحدث عن تطبيقات أكثر تقنية للAmp بالهياكل البرمجية للمستخدمين للادخال فيها بما يلي برمجياتهم الخاصة.

الأحداث المستلزمة

معرفة بسيطة للPyTorch: تأكد من معرفتك بالPyTorch وبالمبادئ الأساسية مثل الأعداد العشوائية والوحدات والدورة التدريبية.

فهم بنية التعلم العميق: المفاهيم مثل الشبكات العميقة والتدفق الخلفي والتوزيع الأولي.

معرفة التمدنية المخلوطة للتمرين: معرفة المزايا والعيوب للتمدنية المخلوطة للتمرين، بما فيها تخفيض استخدام الذاكرة والحوسبة الأسرع.

الوصول للأجهزة المتوافقة: تمتع بالGPU التي تدعم التمدنية المخلوطة، مثل أجهزة NVIDIA مع خلايا Tensor (على سبيل المثال، الهياكل Volta، Turing، Ampere).

إعدادات Python وCUDA: بيئة Python تعمل بشكل صحيح بموجب تثبيت PyTorch وتكوين CUDA للتسريع بواسطة الGPU.

مقابلة عن التمدنية المخلوطة

مثل معظم أنظمة ال机器学习 عميق، PyTorch تدرب بالحال على بيانات نقطة عشرين بعشرين (FP32). وFP32 ليس دائمًا ضروريًا للنجاح. يمكنك استخدام نقطة عشرين بعشرين لبعض العمليات فقط، حيث تستخدم FP32 الوقت والذاكرة بشكل أكثر.

وفي النهاية، أنجز مهندسو الNVIDIA تقنية تسمح للتمدنية المخلوطة بالتمرين بFP32 لعدد قليل من العمليات بينما يتم إجراء معظم الشبكة بFP16.

  • قم بتحويل النموذج لاستخدام نوع البيانات float16 في كل مكان ممكن.
  • الحفاظ على الوزنات الأساسية بالثلاثية عشرين للتجميع بعمليات الوزنات كل دورة.
  • إستخدام التكبير الخسائري لحفظ قيم التموجيد الصغيرة.

الدقة المخلطة في PyTorch

للتمويل المخلط، يقدم PyTorch مجموعة واسعة من الوصفات البنية بالفعل.
تتحول ما يتم توفيره للوحدة إلى FP16 حين تطلب الطريقة .half()، وتتحول بيانات التوسعة الى FP16 حين تطلب .half() للتوسعة. سيستخدم حسابات FP16 سريعة للقيام بأي عمليات على هذه الوحدات أو التوسعات. يتم دعم جيد لمخابرات NVIDIA (cuBLAS و cuDNN) ب PyTorch. يتم معالجة بيانات قناة FP16 باستخدام أعضاء التوسعة للقيام ب GEMMs وتكاملات. لتستخدم أعضاء التوسعة في cuBLAS، يتوجب أن يكون أبعاد GEMM ([M, K] x [K, N] -> [M, N]) أكثر من ثمانية.

تقديم Apex

الوظائف المخلطة النوعية ل Apex تهدف لزيادة سرعة التمويل بينما يحافظ على دقة واستقرار التمويل الواحد النوعي. يمكن ل Apex أن يقوم بالعمليات بFP16 أو FP32، ويتم تخزين المعاملات الرئيسية آليًا وتغير مقاربات الخسائر آليًا.

تم إنشاء Apex لجعل من السهل للباحثين أن يشملوا تمويج التعلم بالدقة المزدوجة في نماذجهم. Amp تعني الدقة المزدوجة التي تكون واحدة من خصائص Apex، وهي تمتداً عن تكسيب سهل لPyTorch. يتطلب أقل من بعض خطوات على شبكاتهم لتمتع بالتمويج الدقة المزدوجة مع Amp. أطلقت Apex في CVPR 2018، ويستحق أن نلاحظ أن مجموعة PyTorch أظهرت دعم قوي لApex منذ إصداره.

من خلال جعل المعونات الصغيرة في النموذج التي تجري بها، يجعل Amp بحيث لا يتوجب عليك تقليل أو تحويل الأشياء بين الأنواع أثناء إنشاء أو تشغيل 脚本ك. قواعد Amp قد لا تناسب جيدًا في النماذج التي تستخدم PyTorch بطريقة غير عادية، ولكن يوجد قفلات لتكيف تلك الافتراضات كما يتوجب.

Amp توفر جميع مزايا التمويج الدقة المزدوجة دون حاجة إلى إدارة تكبير الخسارة أو تحويل الأنواع بطريقة واضحة. موقع GitHub لApex يحتوي على تعليمات لعملية تثبيتها، ويمكن الحصول على مستندات API رسمية هنا ..

كيف تعمل Amps

يستخدم Amp نموذج القائمة البيضاء/قائمة السود على المستوى المنطقي. تشمل عمليات تنزيه التعصبات في PyTorch تشمل وظائف الشبكات العصبية مثل torch.nn.functional.conv2d، ووظائف رياضية بسيطة مثل torch.log، ووظائف التوسعات التي تشمل torch.Tensor. add__ وهناك ثلاث تقسيمات رئيسية للوظائف في هذا العالم:

  • قائمة البيضاء: الوظائف التي قد تستفيد من تعزيز سرعة رياضيات FP16. التطبيقات المعتادة تشمل المultiplication المصادفة والتكوين.
  • قائمة السود: يجب أن تكون الأدوات في FP32 للوظائف التي قد لا يكون كافي 16 بits من الدقة.
  • كل شيء else (أي الوظائف التي تبقى): وظائف يمكن أن تتم بFP16، ولكن تكاليف تحويل FP32 -> FP16 لتنفيذها بFP16 لا يساوي ما يكفي لأن الزيادة في السرعة غير كبيرة.

مهمة Amp بسيطة جدًا في المنطق على الأقل. Amp تحدد إذا كانت وظيفة PyTorch مدرجة في القائمة البيضاء أو في القائمة السود أو لا إلاكي، ومن ثم تتم مطالعة جميع المعاملات. يتوجب تحويل جميع الأدوات الى FP16 إذا كانت مدرجة في القائمة البيضاء أو FP32 إذا كانت في القائمة السود. إذا لم يكن أي من هذين، فقط تأكد من أن جميع الأدوات متصلة بنفس النوع. هذه السياسة ليست بسيطة بالتطبيق في الواقع كما تبدو.

استخدام Amp مع نموذج PyTorch

للدخول لل Amp في النص الحالي للنموذج البيتورشي، يتم تتبع هذه الخطوات:

  • استخدم مكتبة Apex للدخول إلى Amp.
  • قم بتكيف Amp حتى يتمكن من تغيير النموذج، والمحاكي، والوظائف الداخلية لل PyTorch.
  • اشارة إلى أي مكان يحدث التردد الخلفي (`.backward()`) لذا يمكن ل Amp أن يقلل الخسائر ويمكن تنظيف الحالة الخاصة بالمرة الواحدة.

الخطوة 1

هناك خطأ واحد للخطوة الأولى:

from apex import amp

الخطوة 2

يتوجب أن يكون معروفا ما يزال النموذج العصبي الرئيسي ومحاكي التدريب معينا لإكمال هذه الخطوة، وهي خطوة واحدة طويلة.

model, optimizer = amp.initialize(model, optimizer, opt_level="O1")

تغييرات إضافية تسمح لك بتنظيم Amp تنظيم المعاملات التوالية وأنواع العمليات. تقبل وظيفة amp.initialize() خيارات عديدة، من التي سنقدم ثلاثة منها فقط:

  • models (torch.nn.Module أو قائمة من torch.nn.Modules) – النموذجات التي يتم تعديلها/تحويلها.
  • محسنون (اختياري، torch.optim.Optimizer أو قائمة بمحسنون torch.optim.Optimizers) – المحسنون الذين يحتاجون إلى تعديل/تحويل. مطلوب للتدريب، خياري للتحكم في المخرجات.
  • opt_level (كلمة, خياري، الإفتراضي=“O1”) – مستوى التحكم في الدقة النقية أو المخلوط. القيم المقبولة هي “O0”, “O1”, “O2”, و“O3”, وهم يشرحون في المقال العلمي ذاته. هناك أربعة مستويات للتحكم:

O0 للتدريب بالنسبة لFP32: هذا عمل بدون تأثير. لا يتوجب عليك قلق حول هذا لأن نموذجك المقترح يجب أن يكون FP32 بالفعل، وO0 قد يساعد في إنشاء أساس للدقة.

O1 للدقة المخلوطة (يوصل للاستخدام الاعتيادي): تغيير جميع وصفات Tensor ووصفات Torch لاستخدام نظام تقوم بالتصدير البيني للمدخلات. في FP16، يتم إنجاز الما في القائمة البينية مثل تشغيل جهاز التنقل السريع GEMMs والشعائل التعاملية. على سبيل المثال، يتم التحكم في FP32 للمعاملات التي تتضمن القائمة السوداء، مثل الSoftmax. إلا إذا كان ذكر بواقعية أخرى، فإن O1 يستخدم أيضًا تنمية تناسبية للخسائر.

O2 للدقة التقريبة بFP16: O2 يقوم بتحويل وزنات النموذج الى FP16، يصنع تصدير الطريقة الأمامية للوزنات المدرجة لFP16، يحتفظ بالبتائس الجماعية في FP32، يحتف

O3 لتدريب FP16: قد لا يكون O3 مستقر بقدر الذي تحدثه O1 و O2 بشأن الدقة الحقيقية المخلوطة. وفي النهاية، قد يكون من المفاجئ أن تحدد سرعة أساسية لمدلأ بها تمامًا ، حيث يمكن تقييم فعالية O1 و O2.
قد يساعد خاصية التشغيل الإضافية keep_batchnorm_fp32=True في O3 على تحديد “سرعة الضوء” إذا كان لمدلأك تستخدم تكافؤ المجموعات، مما يسمح لـ cudnn batchnorm.

O0 و O3 ليست المخلوطة الحقيقية المتوازية لكنهما يساعدان على تحديد الأساسيات للدقة والسرعة بالمرة. تعرف التطبيق المخلوط بين الدقات المختلفة كـ O1 و O2.
يمكنك تجربة الاثنين ومعرفة أي منهما يحسن الأداء والدقة أكثر لمدلأك الخاص.

الخطوة 3

تأكد من تحديد مكان تأتي مرور الخلفية في قوانينك.
ستظهر بعض أعمال البرمجيات القليلة تبدو كهذه:

loss = criterion(…)
loss.backward()
optimizer.step()

الخطوة 4

باستخدام مدير الcontext Amp، يمكنك تمكين التكامل الخام بتغليف المرور الخلفي:

loss = criterion(…)
with amp.scale_loss(loss, optimizer) as scaled_loss:
    scaled_loss.backward()
optimizer.step()

هذا كل شيء. يمكنك الآن تشغيل 脚本ك مع تدريب المخلوطة الحقيقية مفعلًا.

التسجيل لمدامي الاتصال

تفادي برنامج PyTorch النقص في وجود شكل ثابت للموديل الرسمي أو تشكيل الرسم التواصلي للتلاقي وإدماج التحويلات المذكورة أعلاه لأنه معروف بالتمكين والتنشيط الحالي. من خلال “تصحيح القرد المصطنع” المرجعين للمهام المطلوبة، يمكن للAMP أن يستطيع التصدي وتحويل المادة البيانية بشكل ديناميكي.

على سبيل المثال، يمكنك استخدام البرمجيات التالية لضمان أن تتم تحويل المادة المدرجة لـ torch.nn.functional.linear دائمًا إلى fp16:

orig_linear = torch.nn.functional.linear
def wrapped_linear(*args):
 casted_args = []
  for arg in args:
    if torch.is_tensor(arg) and torch.is_floating_point(arg):
      casted_args.append(torch.cast(arg, torch.float16))
    else:
      casted_args.append(arg)
  return orig_linear(*casted_args)
torch.nn.functional.linear = wrapped_linear

بالرغم من أن الAMP قد يضيف تنظيمات أكثر تعقيدًا لجعل البرمجيات أكثر قوةً، سيحدث اتصال Amp.init() بشكل فعال تسبب تصحيحات القرد المصطنع في جميع الوظائف المتعلقة بPyTorch حتى يتم تحويل المادة البيانية بشكل صحيح في الوقت الحالي.

تخفيض التحويلات

سيتم تحويل كل وزن من الFP32 إلى FP16 مرة واحدة فقط في كل تجربة بسبب الAMP التي تبقى قاعدة تخزينية خاصة بكل التحويلات المتعلقة بالمادة البيانية وتتم إعادة استخدامها بما يكفي. في كل تجربة، توليد المدير السياسي للمسار الخلفي تشير الى الAMP عندما يتوجب تنظيف القاعدة التخزينية.

تشغيل التحويل الآلي وتوسيع المعادلات باستخدام PyTorch

“التrening المزاد الدقيقة التلقائية” يشير إلى تركيبة بين torch.cuda.amp.autocast و torch.cuda.amp.GradScaler. باستخدام torch.cuda.amp.autocast, يمكنك إعداد التلقائية التعبئة فقط لبعض المناطق. التعبئة التلقائية تختار بشكل تلقائي الدقيقة المناسبة للمعاملات الGPU لتحسين الكفاءة بينما تحافظ على الدقة.

النسخ المتعددة من torch.cuda.amp.GradScaler تجعل تلك الخطوات أسهل عملها. تقليل التحول السطحي يساعد الشبكات ذات المعاملات الfloat16 على التوافق الأفضل.

هذا بعض البرمجيات التي توضح كيفية استخدام التلقائية التعبئة() للحصول على الدقيقة المزاد التلقائية في PyTorch:

# تم إنشاء النموذج والمحاكم بالدقيقة الافتراضية
model = Net().cuda()
optimizer = optim.SGD(model.parameters(), ...)

# تم إنشاء مقياس للمعاملات التلقائية مرة واحدة في بداية التrening.
scaler = GradScaler()

for epoch in epochs:
    for input, target in data:
        optimizer.zero_grad()

        # تم تنفيذ المسار الأمامي بواسطة التلقائية التعبئة.
        with autocast(device_type='cuda', dtype=torch.float16):
            output = model(input)
            loss = loss_fn(output, target)

        # تتم تنفيذ العمليات الخلاء بنفس النوع الدقيقي الذي اخترته التلقائية التعبئة للمعاملات الأمامية المتماثلة.
        scaler.scale(loss).backward()

        # scaler.step() أولاً يتم تعديل المعاملات التلقائية لمتغيرات المحاكم المعينة.
   
        scaler.step(optimizer)

        # تم تحديث المقياس للمرة القادمة.
        scaler.update()

إذا كانت تلك المراسلة الأولية لما يتم فيه إجراء مع مدخلات float16، فإن المراسلة الخلافية لهذه العملية تنتج float16 للمتغيرات، وقد لا يكون الfloat16 قادرًا على تعبير معدلات المتغيرات الصغيرة.

ستخسر القيم المتعلقة بالمتغيرات المرتبطة إذا تم جزء تلك القيم إلى صفر (تحديدًا “تحريك الأسفل”).

تستخدم تقنية تقوية المتغيرات وهي تستخدم عامل تقوية لتمرير خسائر الشبكة ومن ثم تقوم بالمراسلة الخلافية على الخسائر المتقوية لتتجنب تحريك الأسفل. ويتوجب أيضًا تقنية تقوية تدفق المتغيرات الخلفية خلال الشبكة بهذا النسب المتقوية وجديد. نتيجةً لهذا، يكون قيم المتغيرات تحجيمًا أكبر، مما يمنعها من التحريك الى صفر.

قبل تحديث المتغيرات، يجب عدم تقنية تقوية المتغيرات (خصيصًا الخصائص .grad). ويمكن استخدام autocast و GradScaler بشكل مستقل لأنهما مكونات مجموعية.

العمل مع المتغيرات غير المقنعة

تقليل المتغيرات

يمكننا تغيير كل تقنيات المسافات باستخدام طريقة Scaler.scale(Loss).backward(). علاقة .grad لمعلومات المادة بين الدالة backward() و scaler.step(optimizer) يجب عليك تعيد تقليد المسافات قبل أي تغيير أو فحصها. إذا كنت تريد تحديد قيمة القيمة العالمية (رؤية torch.nn.utils.clip_grad_norm_()) أو أقصى كمية الحد الأقصى (رؤية torch.nn.utils.clip_grad_value_()) لمجموعة المعلومات التي تحمل معدلات المسافات الأقل أو أقل أو أقصى مقدار معين، يمكنك استخدام تقنية تُدعى “تقليد المعلومات التي تحمل معدلات المسافات”.

سينتج تقليد دون تعيد تقليد المعلومات المعدلات/أقصى مقدار معين لأن المعدلات التي كنت تحملها من المعلومات ستكون متغيرة، معينًا لمعدلات المعلومات التي لم تكن مقدارها معينًا. المعلومات التي تحملها خاصة بالمحاكد تتعيد تقليدها بواسطة scaler.unscale (optimizer).
يمكنك تعيد تقليد معلومات المعلومات الأخرى التي كانت قد أعطيت لمحاكد آخر (مثل محاكد optimizer1) باستخدام scaler.unscale (optimizer1). يمكننا توضيح هذا المبدأ بإضافة سطرين من البرمجيات:

# تعيد تقليد معلومات محاكد المحاكد بمكان
        scaler.unscale_(optimizer)
# لأن معلومات محاكد المحاكد تم تقليدها، يمكنك قماً بتقليد كم المعدلات كما تريد:. 
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)

العمل بالتقاطعات المدرجة

تجميع التقاطعات

تجميع التقاطعات يعتمد على مبدأ بسيط للغاية. بدلاً عن تحديث ما يتم بموجبه تحديد المادة الخاصة بالنموذج، يتواجد التقاطع ويتم تجميع التقاطعات عبر الألواح التالية للحساب الخاطئ والتقاطعات.

بعد عدد معين من الألواح، يتم تحديث المادة الخاصة بالنموذج وفقاً للتقاطع الجمعي. هذا مقطع من الشيء عن كيفية استخدام تجميع التقاطعات باستخدام pytorch:

scaler = GradScaler()

for epoch in epochs:
    for i, (input, target) in enumerate(data):
        with autocast():
            output = model(input)
            loss = loss_fn(output, target)
            # تحويل الخسارة 
            loss = loss / iters_to_accumulate

        # يتم تجميع التقاطعات المدرجة.
        scaler.scale(loss).backward()
          # تحديث الوزنات
        if (i + 1) % iters_to_accumulate == 0:
            # قد يتم تخزين هنا إذا أراد القيام بذلك 
            scaler.step(optimizer)
            scaler.update()
            optimizer.zero_grad()
  • تجميع التقاطعات يضيف التقاطعات عبر حجم القطعة المناسب ل batch_per_iter * iters_to_accumulate.
    يتوجب تنظيم المقياس للحجم الفعال؛ هذا يتطلب تفقد للمعلومات الجذرية/الخطأ وتجنب الخطوة إذا تم الاكتشاف أي معلومات الجذرية/الخطأ، وتحديث المقياس إلى دقة حجم القطعة الفعالة.
    ومن المهم أيضًا إبقاء التقاطعات في مقياس مدرج وموازين دقيقة واحدة حين يتم إضافة التقاطعات لقطعة من التقاطعات الفعالة المحددة.

إذا كانت تخزينات التخرجات غير مقارنة (أو يتغير معامل التقارن) قبل الانتهاء من التجميع، فسيضيف المسار الخلفي التالي تقارنات التخرجات المقارنة بالتقارن للتخرجات غير المقارنة (أو التخرجات المقارنة بمعامل آخر)، وبعدها يصعب إستعيد تخزينات التخرجات الغير مقارنة التقارير التي يتم تطبيقها. يتوجب تطبيق الخطوة التالية.

  • يمكنك تخزين التخرجات بواسطة التخزين الخاص قبل الخطوة، بعد أن تكملت تخزين التخرجات المقارنة للخطوة القادمة.
    لضمان الجرعة الفعالة الكاملة، يمكنك مجرد مساعدة التحديث في نهاية كل تجربة حيث أدى مساعدة الخطوة السابقة في المرة الأولى update.step
  • enumerate(data) الوظيفة تسمح لنا بالتعقب في معدل الجرعة بينما نتجه خلال البيانات.
  • قسم الخسران المستدام بiters_to_accumulate(loss / iters_to_accumulate) هذا يقلل من مساهمة كل مجرد بلوك البيانات التي نمعال بها من خلال تنظيم الخسران. إذا توازنت الخسران داخل كل جرعة، فإن التقسيم بالفعل صحيح ولا يتوجب عمل تنظيم أي أكثر. قد يكون هذا الخطوة غير ضرورية وفقاً لكيف تحاول حساب الخسران.
  • عندما نستخدم scaler.scale(loss).backward() في PyTorch، يتم تجميع الgradients المقسمون وتخزينهم حتى نطلب optimizer.zero_grad().

العقوبة الخاصة بالتقاطع الأسلوبي

عند تنفيذ العقوبة الخاصة بالتقاطع الأسلوبي، يستخدم torch.autograd.grad() لبناء الgradients، الذين يتم تركيزهم لتشكيل قيمة العقوبة، ومن ثم إضافتها إلى الخسارة. يظهر تقاطع القيمة L2 بدون تقليل أو التشغيل التلقائي في المثال التالي.

for epoch in epochs:
    for input, target in data:
        optimizer.zero_grad()
        output = model(input)
        loss = loss_fn(output, target)

        # ينشئ الgradients
        grad_prams = torch.autograd.grad(outputs=loss,
                                          inputs=model.parameters(),
                                          create_graph=True)

        # يحاسب جزء العقوبة ويضافه إلى الخسارة
        grad_norm = 0
        for grad in grad_prams:
            grad_norm += grad.pow(2).sum()
        grad_norm = grad_norm.sqrt()
        loss = loss + grad_norm

        loss.backward()

        # يمكنك تنقيح الgradients هنا

        optimizer.step()

يتوجب تقليل التوانات التي يتم تقديمها لtorch.autograd.grad() لتنفيذ العقوبة الخاصة بالتقاطع الأسلوبي. ومن الضروري تعويض الgradients قبل تركيزهم لحصول قيمة العقوبة. لأن تخزين جزء العقوبة هو جزء من المرور الأولي، يجب أن يحدث داخل سياق التلقائي التعاملي.
لنفس العقوبة L2، هذا ما يبدو مثله:

scaler = GradScaler()

for epoch in epochs:
    for input, target in data:
        optimizer.zero_grad()
        with autocast():
            output = model(input)
            loss = loss_fn(output, target)

        # تنسيق خسائر للتوراد في المرور الخلفي لautograd.grad, وهذا ينتج #scaled_grad_prams
        scaled_grad_prams = torch.autograd.grad(outputs=scaler.scale(loss),
                                                 inputs=model.parameters(),
                                                 create_graph=True)

        # يخلق grad_prams قبل حساب العقبة(grad_prams يجب أن يكون #unscaled). 
        # لأنه لا يمتلك أي محاكي تحسين, يستخدم التقسيم التقليدي بدلاً من scaler.unscale_:
        inv_scaled = 1./scaler.get_scale()
        grad_prams = [p * inv_scaled for p in scaled_grad_prams]

        # يحسب العقبة المرافقة ويضيف إلى الخسائر. 
        with autocast():
            grad_norm = 0
            for grad in grad_prams:
                grad_norm += grad.pow(2).sum()
            grad_norm = grad_norm.sqrt()
            loss = loss + grad_norm

        # يطبق التنسيق على المرور الخلفي.
        # يتم تجميع التقاطير التنسيقية من الناحية الجذرية.
        scaler.scale(loss).backward()

        # يمكنك تنسيق هنا 

        # step() و update() يستمرون في القيام بالعمل كما هو عادة.
        scaler.step(optimizer)
        scaler.update()

العمل مع معظم نماذج, الخسائر والمحاكم

يجب أن يطلق مصادر.قيمة في كل خسارة في شبكتك إذا كان لديك عدد كبير منها.
إذا كان لديك عدد كبير من المحسنين في شبكتك، قد تقوم بتنفيذ مصادر.تعيين على أي منهم، ويتوجب عليك طلب مصادر.خطوة على كل واحد منهم. ومع ذلك، يتم استخدام مصادر.تحديث مرة واحدة فقط، بعد خطوات جميع المحسنين المستخدمين في هذه التجربة:

scaler = torch.cuda.amp.GradScaler()

for epoch in epochs:
    for input, target in data:
        optimizer1.zero_grad()
        optimizer2.zero_grad()
        with autocast():
            output1 = model1(input)
            output2 = model2(input)
            loss1 = loss_fn(2 * output1 + 3 * output2, target)
            loss2 = loss_fn(3 * output1 - 5 * output2, target)

       # علماً بأن التمويل الترسيخي لا يتصل بamp، لكنه موجود في هذا المثال لأن كلا الدعواء() الخلفية تشارك مناطق معينة من الرسم البياني. 
        scaler.scale(loss1).backward(retain_graph=True)
        scaler.scale(loss2).backward()

        # إذا كنت ترغب في مشاهدة أو تعديل المعادلات التقدمية للمعاملات التي يمتلكونها، يمكنك تحديد أي محسنين يحصلون على التعيين البارد. .
        scaler.unscale_(optimizer1)

        scaler.step(optimizer1)
        scaler.step(optimizer2)

        scaler.update()

يبحث كل محسن عن معادلاته الخاصة للأشياء اللامعادلة/NaNs ويقوم بقرار فريد إذا كان يجب أو لا يتجنب الخطوة. قد تتجنب الخطوة بعض المحسنين بينما لا تفعل الآخرون ذلك. يحدث التجنب من الخطوة مرة واحدة كل مئات التجربات؛ لذلك لن يؤثر على التوحد. لنماذج المحسنين المتعددين، يمكنك إبلاغ عن المشكلة إذا رأيت تحوّل سيئ بعد إضافة تقوية المعاملات.

العمل مع عدة جهازات الGPU

أحد المشاكل الكبري التي يواجهها نماذج التعلم العميق هو أنها تزداد حجماً لا تستطيع تدريبها على جهاز واحد من الGPU. قد يتطلب وقت طويل لتدريب نموذج على جهاز واحد من الGPU، ويتوجب تدريب متعدد الGPU للحصول على النماذج بسرعة قدرتها. قام باحث معروف بتخفيض فترة تدريب ImageNet من أسبوعين إلى 18 دقيقة أو تدريب أكبر وأكثر تطوراً لنموذج Transformer-XL في أسبوعين بدلاً من أربع سنوات.

DataParallel و DistributedDataParallel

بدون تضييع الجودة، يوفر PyTorch أفضل مزيج من سهولة الاستخدام والسيطرة. nn.DataParallel و nn.parallel.DistributedDataParallel تحتوي على ميزات من خلالها PyTorch لتوزيع التمرين عبر أجهزة الGPU المتعددة. يمكنك استخدام هذه الأغشام السهلة الاستخدام والتغيرات لتمرير تمرير الشبكة على عدة GPUs.

DataParallel في عملية واحدة

في الحاسبة الواحدة، يساعد DataParallel على توزيع التمرين عبر العديد من الGPUs.
دعونا ننظر أقرب إلى كيفية عمل DataParallel فعلاً في الممارسات.
عند تطبيق DataParallel لتمرير شبكة الأعصاب العصبية، تحدث مراحل تلك الأتجاهات:

مصدر:

  • يتم توزيع المجموعة الصغيرة على GPU:0.
  • يتم تنقسم وتوزيع المجموعة الصغيرة على جميع الGPUs المتاحة.
  • يتم نسخ النموذج إلى الGPUs.
  • يتم إجراء المرور الأولي على جميع الGPUs.
  • يتم حساب الخسارة بالنسبة للخروجات التي ينتجها النظام على GPU:0، وأيضًا عندها يتم إرجاع الخسارات إلى الGPUs المختلفة. يتم حساب التقارير على كل من الGPUs.
  • تخزين مجموعة المستويات على الGPU:0 وتطبيق المحاكي لتحديث النموذج.

يجب أن تلاحظ أن القلق الموجود هنا ينطبق فقط على autocast. سيظل تصرف GradScaler مستقر. لا يهم إن أن torch.nn.DataParallel يخلق treads لكل جهاز للقيام بالمرور الأول. يتم تواصل الحالة الخاصة بautocast في كل واحد منهم، وسيعمل التالي:

model = Model_m()
p_model = nn.DataParallel(model)

# تعيين autocast في ال tread الرئيسي
with autocast():
    # سيكون هناك autocasting في p_model. 
    output = p_model(input)
    # loss_fn أيضًا سيكون autocast
    loss = loss_fn(output)

DistributedDataParallel، جهاز واحد لكل عملية

يوصي التوثيق لـ torch.nn.parallel.DistributedDataParallel باستخدام وحدة معالجة رسومات واحدة لكل عملية لتحقيق أفضل أداء. في هذه الحالة، لا يطلق DistributedDataParallel مؤشرات داخلية؛ وبالتالي لا يتأثر استخدام autocast وGradScaler.

DistributedDataParallel، وحدات معالجة رسومات متعددة لكل عملية

هنا قد يقوم torch.nn.parallel.DistributedDataParallel بإنشاء مؤشر جانبي لتشغيل تمرير forward على كل جهاز، مثل torch.nn.DataParallel. الحل هو نفسه: تطبيق autocast كجزء من دالة forward لنموذجك لضمان تمكينه في المؤشرات الجانبية.

الخاتمة

في هذه المقالة، قمنا بـ:

  • تقديم Apex.
  • رؤية كيفية عمل Amps.
  • رؤية كيفية تنفيذ موازنة التدرج، قص التدرج، تراكم التدرج وعقوبة التدرج.
  • رؤية كيفية العمل مع نماذج متعددة، خسائر ومحسّنين.
  • رؤية كيفية تنفيذ DataParallel في عملية واحدة عند العمل مع وحدات معالجة رسومات متعددة.

المراجعات

https://developer.nvidia.com/blog/apex-pytorch-easy-mixed-precision-training/
https://nvidia.github.io/apex/amp.html
https://discuss.pytorch.org/t/accumulating-gradients/30020
https://towardsdatascience.com/how-to-scale-training-on-multiple-gpus-dae1041f49d2

Source:
https://www.digitalocean.com/community/tutorials/automatic-mixed-precision-using-pytorch