تمت ترجمة المحتوى الموجود على هذا الموقع باستخدام الذكاء الاصطناعي (AI) أو تقنية الترجمة الآلية، وقد تحتوي على أخطاء.

Skip to content

كيف قمنا بتوسيع نطاق Bert لتلبية أكثر من مليار طلب يومي على وحدات المعالجة المركزية

إليكم مشكلة "الدجاجة والبيضة" الكلاسيكية التي تواجه علماء البيانات ومهندسي التعلم الآلي: عند تطوير نموذج تعلم آلي جديد لعملك، هل تجعله دقيقًا أولاً، ثم تقلق بشأن جعله سريعًا في الإنتاج؟ أم تتأكد أولاً من أنه يمكن أن يكون سريعًا، ثم تجعله دقيقًا؟ اطرح السؤال وستتبع ذلك دائمًا مناقشة حامية!

في أوائل عام 2019، واجهنا هذا السيناريو بالضبط عندما بدأنا في تطوير مصنفات النصوص من الجيل التالي لـ Roblox، باستخدام نماذج التعلم العميق Bert المتطورة (في ذلك الوقت). لم نكن متأكدين مما إذا كانت نماذج Bert، وهي شبكات عصبية عميقة كبيرة يبدأ نموذجها الأساسي "المبتدئ" من 110 ملايين معلمة، قادرة على تلبية متطلباتنا الصارمة من حيث السرعة والإنتاجية في الإنتاج. مضينا قدماً في جعل مصنفات النصوص القائمة على Bert عالية الدقة، وفي الأشهر العصيبة التي تلت ذلك، تمكنا لحسن الحظ من جعلها سريعة بما يكفي للإنتاج. لذا بالنسبة لنا، جاءت "الدجاجة" (الدقة) أولاً، ثم جاء "البيضة" (السرعة) بعدها. على الرغم من أن هذه كانت تجربة مرهقة بالنسبة لنا، إلا أنها لا يجب أن تكون كذلك بالنسبة لك، لأننا سنشارك في هذه المقالة التحسينات التي جعلت استدلال Bert سريعًا بالنسبة لنا. لذا يمكنك البدء بالبيضة (دليل معروف لجعل نماذج Bert معينة سريعة في الإنتاج)، ثم التركيز على الدجاجة (جعل نموذج Bert الخاص بك دقيقًا).

رحلتنا نحو تسريع Bert في الإنتاج

تم توثيق سحر Bert في معالجة اللغة الطبيعية (NLP) بشكل جيد في عام 2019، وهنا في Roblox رأينا هذا السحر يعمل عن قرب على بياناتنا الخاصة. من خلال ضبط نماذج التعلم العميق لـ Bert، قمنا بتحويل العديد من تطبيقات تصنيف النصوص والتعرف على الكيانات المسماة (NER) بشكل جذري، وغالبًا ما أدى ذلك إلى تحسين أداء النماذج (درجات F1) بنسبة 10 نقاط مئوية أو أكثر مقارنة بالنماذج السابقة.

ومع ذلك، في حين تم تسريع تطوير نموذج Bert لدينا بفضل الكثير من الموارد المذهلة والمكتبات الرائعة، لم نجد سوى عدد قليل من الموارد حول كيفية توسيع نطاق Bert على PyTorch لحالات الاستخدام الإنتاجية ذات زمن الوصول المنخفض والإنتاجية العالية. وبالنسبة للعديد من خدمات NLP لدينا، كنا بحاجة إلى معالجة أكثر من 25,000 استدلال في الثانية (وأكثر من مليار استدلال في اليوم)، بزمن وصول أقل من 20 مللي ثانية.

الهدف من هذه المقالة هو مشاركة رحلتنا في تسريع استدلال Bert بأكثر من 30 ضعفًا لتصنيف النصوص في Roblox. من خلال هذه التحسينات (الموجزة أدناه)، تمكنا ليس فقط من جعل Bert سريعًا بما يكفي لمستخدمينا، ولكن أيضًا اقتصاديًا بما يكفي لتشغيله في الإنتاج بتكلفة معقولة على وحدة المعالجة المركزية (CPU).

المعيار

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

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

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

  • الكمون: متوسط الوقت الذي يستغرقه تلبية طلب استدلال واحد (لدينا أيضًا معايير داخلية تشمل المئوية 99 وما فوق)
  • الإنتاجية: عدد الاستدلالات التي يمكننا تلبيةها في ثانية واحدة

لإجراء مقارنات متسقة للإنتاجية، أطلق كود المعيار الخاص بنا 32 عملية عاملة بالضبط، قامت كل منها بتشغيل 1000 طلب استدلال بالتتابع. تم تشغيل هذا المعيار على خادم واحد مزود بـ 36 نواة معالج Xeon Scalable.

حاكي هذا المعيار بيئة الإنتاج حيث كانت خدمة تصنيف النصوص مدعومة بمجموعة من عمليات العمال. تحت الحمل، كان كل عامل مشغولاً بمعالجة طلبات الاستدلال المتتالية لفترات زمنية متواصلة. في بيئة الإنتاج الفعلية لدينا، كان لدينا المئات من هؤلاء العمال عبر العديد من الأجهزة، بحيث تمكنا من دعم أكثر من 25,000 طلب في الثانية مع متوسط زمن انتقال أقل من 20 مللي ثانية.

الأدوات

تم تطوير نموذج Bert المُحسّن الخاص بنا باستخدام مكتبة Huggingface Transformers الإصدار 2.3.0. اخترنا Huggingface على وجه التحديد لدعمها لـ PyTorch، ونحن نعمل حاليًا في بيئة الإنتاج باستخدام PyTorch 1.4.0. سهّلت Huggingface علينا تجربة العديد من النماذج الأخرى القائمة على المحولات، بما في ذلك Roberta وXLNet وDistilBert وAlbert وغيرها. وهذه هي الطريقة التي اكتشفنا بها DistilBert الذي حسّن سرعة الاستدلال لدينا بشكل كبير (سنناقش هذا بعد قليل).

أولاً: CPU أم GPU للاستدلال؟

كان قرارنا الأول المهم هو ما إذا كنا سنقوم بتشغيل الاستدلال لمصنف النصوص القائم على Bert على وحدة المعالجة المركزية (CPU) أم وحدة معالجة الرسومات (GPU).

بالنسبة لتدريب نموذجنا، كانت وحدة معالجة الرسومات (GPU) أسرع بكثير من وحدة المعالجة المركزية (CPU) بلا شك. استغرق ضبط (تدريب) نموذج Bert لتصنيف النصوص لدينا وقتًا أطول بعشر مرات على وحدة المعالجة المركزية (CPU) مقارنةً بوحدة معالجة الرسومات (GPU)، حتى عند مقارنة وحدة معالجة الرسومات (GPU) Tesla V100 بخادم كبير يعتمد على وحدة المعالجة المركزية (CPU) Xeon Scalable ذات 36 نواة وبنفس التكلفة.

بالنسبة للاستدلال، يعتمد الاختيار بين وحدة معالجة الرسومات (GPU) ووحدة المعالجة المركزية (CPU) على التطبيق. هذه هي العوامل التي دفعتنا إلى اتخاذ قرار استخدام وحدة المعالجة المركزية (CPU) للاستدلال:

  • البساطة. تعمل وحدات معالجة الرسومات (GPU) بشكل أفضل عندما يتم تجميع المدخلات. ومع ذلك، يعمل مصنف النصوص لدينا في بيئة طلب واستجابة في الوقت الفعلي. لذلك، فإن تجميع هذه الطلبات في الوقت الفعلي لن يؤدي إلا إلى زيادة الأعباء والتعقيد. وقد أتاح لنا تشغيل الاستدلال على وحدة المعالجة المركزية (CPU) مسارًا أبسط للمضي قدمًا، لأن نموذج Bert الخاص بنا كان يؤدي أداءً ممتازًا على وحدة المعالجة المركزية حتى عندما لم نقم بتجميع مدخلاته.
  • تكلفة اقتصادية مواتية. بالنسبة لمصنف النصوص لدينا، كانت التكلفة الاقتصادية للاستدلال على وحدة المعالجة المركزية (CPU) أفضل منها على وحدة معالجة الرسومات (GPU). بعد تطبيق جميع تحسينات التوسع الواردة في هذه المقالة، كان معدل إنتاجية خدمة تصنيف النصوص لدينا أعلى بست مرات لكل دولار على وحدة المعالجة المركزية (CPU) مقارنة بوحدة معالجة الرسومات (GPU). على وجه التحديد، تمكنا من توسيع نطاق خدماتنا القائمة على Bert إلى أكثر من 3000 عملية استدلال في الثانية على خادم Intel Xeon Scalable ذي 36 نواة، مقابل 500 عملية استدلال في الثانية على وحدة معالجة الرسومات (GPU) Tesla V100 ذات التكلفة المماثلة.

توصية بشأن وحدة المعالجة المركزية (CPU)

في اختبارات الأداء التي أجريناها، وجدنا أن معالجات Intel Xeon Scalable من الجيل الثاني كانت الأفضل أداءً بفضل دعمها الممتاز لاستدلال التعلم العميق. على سبيل المثال، تستفيد بعض عمليات التسريع الأكثر تأثيرًا لدينا من تعليمات الشبكة العصبية المتجهة (Vector Neural Network Instructions) ونوى FMA (Fused-Multiply-Add) المزدوجة الموجودة في معالجات Xeon Scalable من الجيل الثاني. جميع النتائج التي نوردها في هذه المقالة هي على رقائق سلسلة معالجات Intel Xeon Scalable. قد تختلف النتائج التي تحصل عليها على تكوينات وحدة المعالجة المركزية (CPU) الأخرى، حيث إن اختباراتنا على الرقائق المختلفة كانت محدودة.

ضبط الخيوط

كان العائق الأول الذي واجهناه عند توسيع نطاق استدلال Bert على وحدة المعالجة المركزية (CPU) هو أنه يجب ضبط خيوط PyTorch بشكل صحيح قبل أن تتمكن عمليات العامل المتعددة من إجراء استدلال متزامن للنموذج. كان ذلك لأن نموذج PyTorch حاول، داخل كل عملية، استخدام نوى متعددة لمعالجة حتى طلب استدلال واحد. لذلك، كانت كل عملية تتنافس على نفس الموارد المحدودة (النوى المادية). أدى ذلك إلى ركود عندما كان عدد كبير جدًا من هذه العمليات العاملة يعمل في وقت واحد على نفس الجهاز.

لحسن الحظ، كان الحل بسيطًا: في كل عملية عاملة تؤدي الاستدلال، قمنا ببساطة بتعيين عدد الخيوط على 1 كما هو موضح في السطر 9 أدناه.

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

السيناريو رقم 1: خط الأساس لـ Bert

كان خط الأساس الأول هو نموذج Bert الأساسي لتصنيف النصوص، أو البنية الموصوفة في ورقة Bert الأصلية. تم إنشاء نموذج Bert هذا باستخدام نموذج BertForSequenceClassication Pytorch من مكتبة Huggingface Transformers 2.3.0. في هذا السيناريو، اتخذنا أيضًا خيارًا تصميميًا بسيطًا عن قصد - قمنا بتعبئة جميع مدخلات التوتر بصفر إلى طول ثابت يبلغ 128 رمزًا. كان السبب وراء القيام بذلك هو أن التعبئة بالصفر مطلوبة عند تجميع المدخلات، بحيث تكون جميع المدخلات بنفس الحجم. نظرًا لأنه من السهل تعبئة المدخلات بالصفر عن طريق الخطأ حتى عندما يكون حجم الدفعة 1، أردنا تحديد مقدار انخفاض الأداء الناتج عن القيام بذلك في سيناريو خط الأساس هذا.

في المربع الوردي أدناه، نعرض نتيجة المعيار القياسي لسيناريو "Bert Baseline" هذا. حتى عند 330 مللي ثانية، كان زمن الوصول مرتفعًا جدًا بالنسبة لاحتياجات الإنتاج لدينا، وكان معدل النقل سيئًا جدًا بالنسبة لعدد النوى المستخدمة في المعيار القياسي.

السيناريو رقم 2: نموذج أصغر (DistilBert)

بعد إصدار Bert في أكتوبر 2018، ظهرت العديد من النماذج التي حاولت دفع عجلة التطور من خلال إنشاء نماذج ذات معلمات أكثر. في حين أن Bert base يحتوي على 110 ملايين معلمة، فإن بعض هذه النماذج الأحدث تحتوي على أكثر من مليار معلمة مع أداء إحصائي أعلى بشكل هامشي وأداء حسابي أسوأ بشكل ملحوظ. لكن ما أردناه هو أداء حسابي أفضل، وكنا على استعداد للتضحية بقليل من الأداء الإحصائي للحصول عليه.

لحسن حظنا، في أكتوبر 2019، قدم Sanh وآخرون DistilBert، وهو أحد النماذج العديدة التي خالفت اتجاه النماذج الأكبر الحجم المشابهة لـ Bert. ركزت هذه "النماذج الأصغر" على تقليل حجم Bert، مما أدى إلى استدلال وتدريب أسرع بكثير، مع خسارة ضئيلة في الأداء الإحصائي.

اخترنا DistilBert لسببين رئيسيين. أولاً، DistilBert أسرع بحوالي ضعف سرعة Bert، ومع ذلك كان أداءه الإحصائي (درجة F1) في تصنيف النصوص لدينا في حدود 1٪ من Bert. ثانيًا، جعلت مكتبة Huggingface Transformers من السهل جدًا علينا استبدال DistilBert بـ Bert، مما سمح لنا بالحفاظ على مسارات التدريب والاستدلال لدينا سليمة.

فيما يلي نتيجة الاختبار المعياري عندما استخدمنا نموذج DistilBert الأصغر بدلاً من Bert الأكبر. كما نرى، قمنا بتخفيض زمن الاستجابة إلى النصف تقريبًا مع مضاعفة معدل الإنتاجية تقريبًا.

السيناريو رقم 3: مدخلات أصغر (أشكال ديناميكية)

جاء التحسين التالي من تصغير شيء آخر، وهو في هذه الحالة مدخلات التوتر إلى نموذج DistilBert.

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

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

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

فيما يلي نتيجة الاختبار المعياري عند استخدام DistilBert + Dynamic Shapes. كما نرى، فقد خفضنا زمن الاستجابة إلى أقل من النصف مع مضاعفة معدل الإنتاجية.

السيناريو رقم 4: أوزان أصغر (التكمية)

لقد شهدنا أكبر تحسن في الأداء بفضل الأوزان الأصغر، والتي تم تحقيقها من خلال تقنية تسمى التكمية.

تتضمن التكمية تحسين كفاءة حسابات التعلم العميق من خلال تمثيلات أصغر لأوزان النموذج، على سبيل المثال تمثيل أوزان النقاط العائمة ذات 32 بت كأعداد صحيحة ذات 8 بت. كانت تقنية التكمية المحددة التي استفدنا منها في نموذج DistilBert الخاص بنا هي "التكمية الديناميكية". تتضمن هذه التقنية تكمية الأوزان بعد التدريب، على عكس التكمية أثناء التدريب (والتي تسمى التدريب المراعي للتكمية).

كان دعم التكمية الديناميكية في Pytorch 1.3+ سهل التنفيذ للغاية، وأتاح تغيير سطر واحد في كودنا استخدام تمثيلات الأوزان كأعداد صحيحة ذات 8 بت في جميع الطبقات الخطية لنموذج DistilBert:

فيما يلي تصور لكيفية تغيير هذا التكمية لنموذج DistilBert الأصلي. كما ترى، سيقوم PyTorch باستبدال الطبقات "الخطية" مثل طبقات الاستعلام (Q) والمفتاح (K) والقيمة (V) ذات الانتباه بطبقة "خطية مكمّاة ديناميكية"، والتي ستستخدم الأعداد الصحيحة المكمّاة ذات 8 بت في عمليات الضرب/الجمع الداخلية الخاصة بها.
هذه هي نتيجة الاختبار المعياري عند استخدام Distilbert + Dynamic Shapes + Dynamic Quantization. يمكننا أن نرى أن تأثير هذه التحسينات الثلاثة مجتمعة يؤدي إلى تحسن كبير بمقدار 30 ضعفًا مقارنة بخط الأساس Bert العادي في كل من زمن الاستجابة وسعة النقل.

لاحظنا أيضًا تأثيرًا سلبيًا طفيفًا على F1 (< 1٪) بعد التكمية، لكن التحسن في الإنتاجية وزمن الاستجابة جعل الأمر يستحق العناء.

السيناريو رقم 5: عدد أقل من الطلبات (التخزين المؤقت)

كان التحسين الأخير هو تقليل عدد الطلبات التي يتم إرسالها إلى نموذج DistilBert بشكل فعال. حققنا ذلك عن طريق التخزين المؤقت للردود الخاصة بمدخلات النص الشائعة باستخدام معرفات الرموز كمفتاح. نجح هذا لأن استجابة النموذج لنفس مدخلات النص تكون دائمًا هي نفسها. علاوة على ذلك، تزداد فعاليته كلما كان توزيع النص الأساسي غير منتظم.

بالنسبة لبياناتنا، لاحظنا تجريبيًا معدل نجاح تخزين مؤقت بنسبة 40٪ في الإنتاج عندما قمنا بتخزين مليون إدخال مؤقتًا في ذاكرة المعالجة. وبالنظر إلى أن نجاح التخزين المؤقت يعني تكلفة فعالة تساوي صفرًا للاستدلال، فقد ضاعف التخزين المؤقت لدينا معدل الإنتاجية تقريبًا.

لم ندرج تأثير التخزين المؤقت في معيارنا، لأنه يعتمد على البيانات. لكننا أردنا التأكد من ذكر التأثير الكبير لذاكرة التخزين المؤقت البسيطة.

ملاحظة حول قابلية التوسع الأفقي

جميع نتائج المعيار المرجعي التي رأيتها في المقالة جاءت من التشغيل باستخدام 32 عاملاً متزامنًا على نفس الخادم. للتوسع إلى أكثر من مليار طلب يوميًا في الإنتاج، قمنا ببساطة بتوسيع نطاق عمالنا أفقيًا عبر العديد من الخوادم.

الاحتمالات المستقبلية

على الرغم من أننا ناقشنا الكثير من التحسينات التفصيلية اللازمة لنقل نماذج Bert PyTorch من مختبراتنا إلى الإنتاج، فإننا ندرك أن هناك الكثير من الابتكارات التي تحدث في هذا المجال والتي قد تؤثر على استراتيجية قابلية التوسع لدينا في المستقبل. على سبيل المثال، نراقب Onnx Runtime عن كثب لأنه كان أداءً قويًا مقارنة بمعاييرنا غير المقيسة. حتى وقت كتابة هذا المقال، كانت Microsoft تعمل على جعل تحسينات Bert لـ Onnx Runtime مفتوحة المصدر، ونحن نعمل أيضًا عن كثب مع Intel لاستكشاف التحسينات المحتملة مع OpenVino.

الخلاصة والنقاط الرئيسية

استنتاجنا الرئيسي هو أن هناك قصة جيدة عن قابلية التوسع لـ DistilBert/Bert على وحدة المعالجة المركزية (CPU)، خاصةً لتصنيف النصوص في الوقت الفعلي. نصف في هذا المقال كيف حققنا زيادة بمقدار 30 ضعفًا على الأقل في زمن انتقال تصنيف النصوص في Bert والإنتاجية من خلال تصغير الحجم - نموذج أصغر (DistilBert)، ومدخلات أصغر (Dynamic Shapes)، وأوزان أصغر (التكمية). نحن سعداء جدًا بأننا نستطيع تلبية أكثر من مليار طلب يوميًا باستخدام مصنف التعلم العميق الخاص بنا، وأننا قادرون على القيام بذلك بتكلفة معقولة على وحدة المعالجة المركزية (CPU) بزمن انتقال متوسط أقل من 20 مللي ثانية.

شكرًا جزيلاً لـ Edin Mulalić و Saša Anđelković على مساعدتهما في استكشاف العديد من هذه التقنيات.

لا تؤيد شركة Roblox Corporation ولا هذا المدونة أي شركة أو خدمة. كما لا يتم تقديم أي ضمانات أو وعود فيما يتعلق بدقة أو موثوقية أو اكتمال المعلومات الواردة في هذا المدونة.

نُشرت هذه المدونة في الأصل على مدونة Roblox Tech Blog.