مُلخّص
الاستدلال الدُفعي غير التفاعلي هو مهمة شائعة في الصناعة لتطبيقات التعلُّم العميق، لكن قد يكون من الصعب ضمان الاستقرار والأداء عند التعامل مع كميات كبيرة من البيانات وخطوط استدلال معقّدة. تقدّم هذه الورقة AntBatchInfer، وهو إطار عمل للاستدلال الدُفعي المرِن، مُحسَّن خصيصًا للعناقيد غير المُخصَّصة. يُعالج AntBatchInfer هذه التحديات عبر تقديم قدرات تحمُّل أعطال متعددة المستويات، ما يُمكِّن من تنفيذ مهام الاستدلال المتنوّعة وطويلة الأمد بثبات. كما يُعزّز كفاءة الاستدلال عبر التشغيل على شكل خطّ أنابيب والتوسّع داخل العقدة و跨 العقد. ويُحسّن الإطار الأداء في سيناريوهات الاستدلال الدُفعي المعقّدة متعدّدة النماذج. من خلال تجارب مكثّفة وقياسات واقعية، نُبرز تفوُّق إطارنا من حيث الاستقرار والكفاءة. في التجارب، تفوّق على الخطّ الأساسي بما لا يقلّ عن 2\( \times \) في استدلال النموذج المفرد وحوالي 6\( \times \) في الاستدلال متعدّد النماذج. كما أنه يُستخدم على نطاق واسع داخل عنقود Ant، مع آلاف الوظائف اليومية في سيناريوهات متنوّعة، بما في ذلك DLRM، ورؤية الحاسوب (CV)، ومعالجة اللغات الطبيعية (NLP)، بما يبرهن على قابليته للتطبيق صناعيًا.
المقدّمة
في الصناعة، يمكن تصنيف نشر نماذج التعلُّم العميق إلى نوعين: الاستدلال الدُفعي غير التفاعلي والاستدلال التفاعلي المتّصل. على عكس الاستدلال المتّصل ذي الحساسية الزمنية العالية، فإن الاستدلال الدُفعي أقلّ تأثُّرًا بالزمن لكنه يتطلّب إنتاجية عالية. هذا يجعله مثاليًا لأحمال العمل التجارية الضخمة التي لا تتطلّب نتائج فورية، وهو منتشر على نطاق واسع في البيئات الصناعية. على سبيل المثال، تُتيح دفعات الاستدلال إجراء الاستدلال على الرسم البياني الكامل للشبكات العصبية البيانية (GNN) الصناعية التي قد تحتوي على ملايين أو حتى مليارات العُقَد، لاكتشاف العلاقات الاجتماعية المحتملة (zhangagl).
للأسف، معظم الأعمال والأنظمة الحالية مكرّسة للاستدلال المتّصل، في حين نادرًا ما تتناول الأعمال منهجية الاستدلال الدُفعي في الإنتاج، على الرغم من أهميته للتطبيقات الصناعية. إحدى الطرق المباشرة هي تطبيق خطّ أنابيب الاستدلال المتّصل على مهام الدُفعات. ومع ذلك، للاستدلال الدُفعي خصائصُ تُميّزه عن الاستدلال المتّصل، مثل أحمال العمل الضخمة غير الحسّاسة للزمن ومتطلّبات التحكّم في التكلفة (azure_batch_infer). على سبيل المثال، قد تبلغ البيانات المطلوب معالجتها تيرابايتات في بيئات الإنتاج. وهناك طريقة أخرى تعتمد على أنظمة معالجة الدُفعات مثل MapReduce وSpark، التي تستطيع معالجة مجموعات بيانات ضخمة مع ضمان الكفاءة وتحمُّل الأخطاء. إلّا أنها لا تُناسب جيدًا استدلال النماذج الكبيرة أو المعقّدة. فغالبًا ما تحتاج نماذج التوصية العميقة إلى توزيع معلمات ضخمة مُتناثِرة عبر عدة خوادم معلمات (li2014scaling). علاوة على ذلك، تفتقر الأنظمة المستندة إلى MapReduce إلى المرونة عند تنفيذ خطوط أنابيب استدلال معقّدة متعدّدة النماذج ذات مستويات تعقيد متباينة. لذا يبدو الحلّ الأمثل هو تشغيل هذه النماذج في عنقود حاويات مثل Kubernetes.
ومع ذلك، تواجه أنظمة الاستدلال الدُفعي الحالية على Kubernetes مشكلتين رئيسيتين: الاستقرار (تحمُّل الأخطاء) والكفاءة. يقوم النهج التقليدي على توزيع مجموعة البيانات بالتساوي بين العُمّال داخل الحاويات ومعالجة الحسابات بموازاة البيانات. أولًا، تحمُّل الأخطاء أمر أساسي في الاستدلال الدُفعي على العناقيد غير المُخصّصة (أو المشتركة)، حيث قد يقوم المُجدوِل بإخلاء الحاويات لتلبية اتفاقيات مستوى الخدمة للوظائف الحسّاسة (bernstein2014containers). وبينما تُوفّر معظم خدمات الاستدلال الدُفعي السحابية (aws_sagemaker, vertex_inference) تحمُّل أخطاء على مستوى الحاوية ومرونة داخل العقدة (توسيع/تقليص ديناميكي)، فإنها لا تتعامل مع تحمُّل الأخطاء على مستوى التطبيق وقت التشغيل، كأخطاء التحميل أو انتهاء المهلة. ثانيًا، لا تستفيد هذه الأنظمة استفادة كاملة من الموارد الحاسوبية، لا سيّما في سيناريوهات الاستدلال متعدّد النماذج أو أساليب التجزئة. فعلى سبيل المثال، يؤدّي تخصيص وحدة معالجة رسومات لكل عملية استدلال إلى هدر موارد عندما يكون النموذج بسيطًا. وفي سيناريو الاستدلال متعدّد النماذج، مثل التعرّف على الوجه، يتطلّب خطّ العمل مرحلتين: كشف الكائنات ثم تصنيف الصور. لكن في الأنظمة الحالية، مثل Azure Batch (azure_batch_infer) وGoogle Vertex (vertex_inference)، غالبًا ما يُدمَج النموذجان في المتنبّئ نفسه رغم اختلاف أحمال العمل بينهما.
لذلك، نُقدّم نظام استدلال دُفعي مبنيًّا على Kubernetes يُعالج منهجيًا مشكلات الاستقرار والأداء من منظور الإطار. أولًا، صمّمنا آلية تحمُّل أعطال دقيقة الحُبَيْبات لضمان الاستقرار على امتداد خطّ أنابيب الاستدلال. ثانيًا، نقترح خطوط أنابيب تستفيد كامل الاستفادة من الموارد الحاسوبية، مع التوسّع داخل العقدة وعبر العقد لكلٍّ من الاستدلال أحاديّ النموذج ومتعدّد النماذج. أخيرًا، نُقدّم واجهة استخدام بسيطة متكاملة مع واجهات خلفية متعدّدة، ونُثبت تفوّق نظامنا في الاستقرار والكفاءة من خلال تجارب مكثّفة.
تحليل المشكلة
لنأخذ في الاعتبار خطّ أنابيب الاستدلال الدُفعي النموذجي الذي يتكوّن من استيعاب البيانات، وتجهيزها، واستدلال النموذج، ثم حفظ النتائج. تقرأ وحدة استيعاب البيانات العينات من مصادر متعددة مثل تخزين الكائنات وقواعد البيانات. ثم تُجهّز وحدة تجهيز البيانات هذه العينات بتنفيذ مهام مثل الترميز في معالجة اللغات الطبيعية أو تعزيز البيانات في رؤية الحاسوب، يليها استدلال النموذج. أخيرًا، تُحفَظ نتائج التنبؤ في نظام التخزين لاستخدامها في التطبيقات اللاحقة. عادةً ما يحتفظ الاستدلال الدُفعي الموزّع بنسخة كاملة من معلمات النموذج في كل عقدة ويُنفّذ الاستدلال على أجزاء بيانات موزّعة مسبقًا.
دعونا نحلّل أكثر المشكلات المتعلقة بالاستقرار والكفاءة عبر هذا الخطّ. من ناحية الاستقرار، هناك خطر كبير بفشل الوظائف طويلة الأمد، مما يسبّب محاولات إعادة تشغيل متكرّرة. وهذا يؤثّر سلبًا في كفاءة الاستدلال، لا سيّما في العناقيد المشتركة. نُصنّف هذه الأخطاء إلى ثلاث فئات: أخطاء على مستوى الحاوية، وأخطاء التطبيق، وأخطاء البيانات. أولًا، تُعزى أخطاء الحاوية إلى إخفاقات الأجهزة، ومشكلات الإدخال/الإخراج، وإخلاء الحاويات. ثانيًا، قد تواجه تطبيقات التعلُّم العميق أخطاء أثناء معالجة مجموعات البيانات الكبيرة، مثل قيم NaN في العينات، أو أخطاء التحليل، أو العمليات المُعلَّقة. هذه الأخطاء شائعة في مختلف مراحل الخطّ، لكنها تختلف عن أخطاء الحاوية لأنها لا تتطلّب استبدال الحاوية. ثالثًا، يجب تصميم تحمُّل أخطاء البيانات بعناية، حتى لا تضيع عينات أو تتكرّر عند معالجة الاستبدالات، ما قد يضرّ بسلامة البيانات.
كما أنّ تصميم الأنظمة الحالية يطرح تحدّيات في تحقيق الأداء الأمثل لمهام الاستدلال الدُفعي. أولًا، الوحدات المكثّفة للإدخال/الإخراج مثل الاستيعاب وإعادة الكتابة تُثقِل كاهل الـ I/O، بينما تجهيز البيانات واستدلال النموذج مكثّفان للحساب، ولكلٍّ منهما خصائص موارد مميّزة؛ فاستدلال النموذج يرتكز عادةً على الـ GPU وتجهيز البيانات على الـ CPU. لذا من غير الفعّال تجميع هذه الوحدات المتباينة في حاوية واحدة، إذ قد يصبح خطّ الأنابيب عنق زجاجة. ثانيًا، في سيناريوهات الاستدلال متعدّد النماذج، تختلف تعقيدات النماذج بشكل كبير، ومن غير الفعّال دمجها في الوحدة نفسها. ثالثًا، عادةً ما يُنفَّذ الاستدلال الدُفعي على عناقيد غير مُخصّصة، حيث قد تظهر ظاهرة المُتخلّفين (stragglers) بسبب تنافس التطبيقات على الموارد في أوقات الذروة. وهذا يُولِّد مشكلة الذيل الطويل، إذ إنّ حتى التوزيع المتساوي للبيانات يجعل الحاويات السريعة تنتظر الأبطأ، ما يحدّد وقت الانتهاء بالعقدة الأبطأ. أخيرًا، يمكن الاستفادة من الموارد الفارغة في العناقيد خلال فترات انخفاض الحمل لتسريع المهام الدُفعيّة.
إطار عملنا
هندسة الإطار
لضمان استقرار وكفاءة الاستدلال الدُفعي، نقترح إطار العمل AntBatchInfer. كما هو موضَّح في الشكل [fig:arch]، يتكوّن الإطار من أربع وحدات: خدمة تقسيم البيانات الحافظة للحالة (Stateful DDS)، ومعالج البيانات، والمتحكِّم المرِن، وجدولة المتنبّئ المرِنة. استندنا في التصميم إلى معماريّة السيّد–العامل، حيث تعمل خدمة تقسيم البيانات الحافظة للحالة والمتحكّم المرِن على خادم مخصّص (السيّد)، في حين تنتشر الوحدات الأخرى على عُقَد العامل.
خدمة تقسيم البيانات الحافظة للحالة (Stateful DDS) تُوزّع عينات البيانات بمرونة على العُمّال بناءً على قدراتهم، وتُعالج دورة حياة كل «شريحة» بيانات. فهي تحتفظ بقائمة رسائل عالمية تُقسِّم مجموعة البيانات على مستوى الشرائح، وتُدرِج كل شريحة (التي تحتوي على فهارس العينات فقط) في القائمة ليستهلكها العُمّال. يُيسِّر هذا إعادة توازن الأحمال بين العُقَد القوية والضعيفة، مع تجاوز مشكلة الذيل الطويل المرتبطة بالتقسيم المتساوي. كذلك، تُدير الخدمة حالة كل شريحة (قيد الانتظار "TODO"، قيد المعالجة "DOING"، مكتملة "DONE")، مما يُعزّز تحمُّل أخطاء البيانات عبر إعادة توزيع الشرائح عند حدوث فشل.
معالج البيانات مسؤول عن الـ I/O وتجهيز البيانات كثيفة الحساب. يتعاون مع الـ DDS لتحميل البيانات ومزامنة الحالة. تحديدًا، يجلب المعالج في كل عامل العينات الفعلية وفقًا للبيانات التعريفية، ثم يُجهّزها مسبقًا ويضع النتائج في قائمة انتظار للاستدلال. وقد أضفنا تحسينات لملفات صغيرة عبر الدمج والتخزين المؤقّت القريب قبل الاستدلال. وأخيرًا، يُبلّغ عن اكتمال الشريحة بعد كتابة النتائج في التخزين.
المتحكِّم المرِن يُدير موارد العُقَد طوال وظيفة الاستدلال الدُفعي ويتحكّم في تحمُّل أخطاء الحاويات. يُراقب أحداث Kubernetes عبر عقدة السيّد (Master)، بما في ذلك خصائص الموارد وإطلاق الحاويات، ويُعيد تشغيل الحاويات عند استثناءات قابلة لإعادة المحاولة (أعطال الأجهزة، أخطاء الشبكة، الإخلاء) أو يتوقّف عن إعادة التشغيل عند أخطاء غير قابلة للإصلاح (أخطاء التهيئة أو البرمجية). كما يسمح بالتوسّع أو التقليل الديناميكي لعُقَد الحوسبة عبر تقدير دوري للاحتياج من الموارد، ما يُسرّع الدُفعات في ساعات الذروة المنخفضة. عند فشل عقدة، يُحوِّل المُتحكِّم الدُفعة إلى عقدة جديدة بالتنسيق مع DDS لضمان سلامة البيانات وتفادي التكرار.
جدولة المتنبّئ المرِنة تُوفّر توسّعًا داخل العقدة لوظائف استدلال النموذج. صُمّم هذا المكوّن لثلاثة أهداف: أولًا، إدارة التزامن داخل العملية عبر ضبط عدد خيوط/عمليات التحميل، والاستدلال، والكتابة استنادًا إلى تقدير الموارد. ثانيًا، تحمُّل أخطاء التطبيق عن طريق إعادة تشغيل العمليات المُعلَّقة والتعامل مع تسريبات الذاكرة. ثالثًا، دعم مستويات توازٍ مختلفة للاستدلال متعدّد النماذج عبر قائمة انتظار خالية من الإقفال للتنسيق بين المتنبّئين.
التحسين من أجل الاستقرار
تستعرض هذه الفقرة آليات تحمُّل الأخطاء متعددة المستويات في AntBatchInfer. نُصنّف التحمُّل إلى ثلاثة مستويات: تحمُّل أخطاء الحاوية، وتحمُّل أخطاء التطبيق، وتحمُّل أخطاء البيانات.
تحمُّل أخطاء الحاوية
يُراقب المُتحكِّم المرِن دوريًا أحداث الحاويات عبر السيّد في Kubernetes، ويُصنّف الانهيارات إلى أخطاء قابلة لإعادة المحاولة (شبكة، أجهزة، إخلاء الحاويات) وأخطاء غير قابلة لإعادة المحاولة (أخطاء تهيئة أو برمجية). عند وقوع خطأ قابل لإعادة المحاولة، يبدأ المُتحكِّم حاوية جديدة ويسحب شريحة بيانات مناسبة من DDS. وعند ورود أحداث تقليل الموارد، يُوقِف الحاوية المتأثّرة.
تحمُّل أخطاء التطبيق
تُراقب جدولة المتنبّئ المرِنة حالة العمليات محليًا أثناء الاستدلال. أولًا، نلتقط الأخطاء القابلة للتحمّل عبر خطّ الأنابيب (أخطاء جلب البيانات، أو التحليل، أو الاستدلال) ونربطها بالعينات ذات الصلة ونخزّنها في التخزين مع معلومات الخطأ لمساعدة المستخدمين في تشخيص المشكلات. ثانيًا، نُعيد تشغيل العمليات عند حدوث أخطاء غير متوقّعة باستخدام آلية إعادة المحاولة مع مهلات، مثل العمليات المُعلَّقة أو تسريبات الذاكرة في شيفرة المستخدم.
تحمُّل أخطاء البيانات
عند فشل عامل، يلتقط عامل جديد شرائح بيانات في حالة “TODO” من DDS ويبدأها. تُعلَّم الشريحة بـ “DOING” عند بدء الاستدلال، ويُنفِّذ المتنبّئ حساب النموذج. بعد تسجيل النتائج في التخزين، يُبلِّغ معالج البيانات عن الشريحة وتُعلَّم “DONE” في DDS. وإذا اكتشف المُتحكِّم المرِن فشل عقدة أو حدث توسّع/تقليص، تُعاد الشريحة المعلَّمة “DOING” إلى “TODO” في نهاية قائمة DDS، مما يضمن عدم فقدان البيانات أو تكرارها.
التحسين من أجل الكفاءة
تقليل الوقت الكلي لإتمام العمل
يُقلِّل DDS الوقت الكلّي لإتمام المهمّة ويستفيد من الموارد عبر تخصيص ديناميكي للعينات لكل عامل حسب معدل الإنتاجية الفعلي. هذا يُحقّق توازن الأحمال طبيعيًا ويُقلِّص مدة الإنجاز التي غالبًا ما تُحدِّدها العُقَد الأبطأ في التقسيم المتساوي. إضافةً إلى ذلك، يدعم المُتحكِّم المرِن توسيع عدد العُمّال لتحسين الكفاءة عند انخفاض التحميل على العنقود.
تسريع الاستدلال الدُفعي لنموذج واحد
يُسرِّع AntBatchInfer استدلال نموذج واحد عبر تقسيمه إلى ثلاث مراحل متوازية: التحميل، والاستدلال، والكتابة. تُنفَّذ هذه المراحل في خيوط أو عمليات منفصلة، ويضبط المُجدول داخل العقدة مستويات التزامن استنادًا إلى قائمة انتظار خالية من الإقفال وخوارزمية تقديرية. فعندما تكون قائمة انتظار الاستدلال شبه فارغة، يُزاد عدد عمليات التحميل، وعندما تمتلئ، تُزاد عمليات الاستدلال إذا توفّر CPU/GPU. ويُزاد خيط الكتابة عند امتلاء قائمة الكتابة، مما يُعظّم استخدام الموارد ويُقصِّر زمن المعالجة الإجمالي.
تسريع خطّ أنابيب الاستدلال الدُفعي للنماذج المتعدّدة
لتسريع الاستدلال الدُفعي المتعدّد، نُغلِّف كل نموذج في متنبّئ مستقل ضمن رسم بياني مُوجّه يعكس التتابع المنطقي. كل متنبّئ يتعامل مع نموذج واحد ويمكنه تعديل عدد وحدات المعالجة الرسومية المستخدمة بحسب تعقيد النموذج. وعند وصول حجم الدُفعة إلى القيمة المستهدفة في أي مرحلة، يبدأ المتنبّئ التالي بالمعالجة فورًا. وتُجمَّع النتائج قبل الكتابة عبر قائمة انتظار مشتركة في الذاكرة، ما يُجنِّب إعادة تهيئة CUDA المتكرّرة عند تغيّر حجم الدُفعة. على سبيل المثال، يُخرِج نموذج كشف الأجسام عددًا متغيّرًا من الكائنات، تُستخدم لاحقًا في نموذج التصنيف.
العرض التوضيحي
في العرض التوضيحي، نُبرز بساطة واجهة الاستخدام لـ AntBatchInfer ونُقدّم سيناريو استدلال دُفعي لمهمة تصنيف الصور، كما في الشكل 3. ويمكن تطبيق التكوينات التالية على مهام ودُفعات أخرى بسهولة. 1) يُحدّد EngineConfig موارد الأجهزة، مع مُعامِل أولوية يتيح تخصيص نسبة (مثل 60%) موارد مضمونة والباقي موارد فُرصيّة (Spot). 2) يضبط DataHandler مصدر البيانات وعدد العُمّال (num_workers) للتحكّم بالتزامن، بديلًا عن DataLoader في PyTorch وDataset في TensorFlow. 3) يُحدّد WriterConfig نظام التخزين المستهدف وعدد خيوط الكتابة. 4) يُحدّد ElasticPredictionRunnerConfig مسار النموذج وعدد المتنبّئين، بالإضافة إلى وظائف المعالجة المُسبقة واللاحقة. يمكن للمستخدم تفعيل التوسّع التلقائي داخل العقدة أو تحديد عدد العمليات يدويًا. كما نوفّر واجهة ويب رسومية لغير المتخصّصين، مع تفاصيل إضافية في الفيديو التوضيحي.
التجارب
في هذا القسم، نعرض كفاءة AntBatchInfer، مع إبراز قدرات تحمُّل الأخطاء متعددة المستويات والمرونة عبر مقاطع توضيحية باستخدام TensorFlow وPyTorch وONNX كواجهات خلفية. أولًا، نقيم أداء AntBatchInfer في مهمة استدلال دُفعي لنموذج شبكي (GNN) من نوع TGAT (xu2020inductive) مع نصف مليار عقدة و6 مليارات حافة، حيث تُعالَج 260 مليون عيّنة يوميًا في عنقود CPU غير مُخصّص. تُظهر النتائج أن AntBatchInfer أسرع بما لا يقلّ عن مرّتين من الخطّ الأساسي بفضل خطوط الأنابيب والتوسّع داخل العقدة، إذ بلغ معدّل الاستعلامات 1200 مقابل 550 استعلام/ثانية. ثانيًا، نجري استدلالًا دُفعيًا في سيناريو متعدّد النماذج على وحدات Nvidia A100، حيث المرحلة الأولى كشف الأجسام بنموذج SCRFD (guo2021sample) والمرحلة الثانية تصنيف الصور بـ ResNet (he2016deep)؛ وتبيّن أن AntBatchInfer أسرع بحوالي ست مرّات (398 مقابل 68 استعلام/ثانية). ثالثًا، نقارن وقت الانتهاء بين التقسيم المتساوي وطريقة DDS في سيناريو النماذج المتعدّدة، فحقّقت DDS تسريعًا من 12% إلى 30% على A100، مع فجوة أكبر في العناقيد غير المُخصّصة. أخيرًا، يُظهر التوسّع الخطي حتى 120 عقدة CPU (كلٌّ منها 20 نواة) أن تكلفة المزامنة بين DDS وعُقَد العُمّال ضئيلة.