انتقل إلى المحتوى الرئيسي

الوحدة 1 — القيود الخاصّة بالاستدلال على الجهاز

قبل التحويل، قبل التكميم، وقبل أن نلمس مفوَّضًا واحدًا، هناك حسبة يجب أن نجريها بأمانة: ماذا يستطيع الهاتف تحمُّله؟ الاستدلال على الخادم يجيب على السؤال بالمال؛ الاستدلال على الجهاز يجيب عليه بالفيزياء. البطّاريّة محدودة، والذاكرة الحيّة أصغر ممّا يظنّ من اعتاد الحاسوب المكتبيّ، والمستخدم لا يقبل تطبيقًا يُسخّن هاتفه في دقيقتَين. سنُحدِّد في هذه الوحدة ميزانيّة مرقَّمة للمصنِّف الذي سنبنيه على مدى الوحدات القادمة، ونعلن الأرقام التي سنحاسب أنفسنا عليها في الوحدة 9.

لماذا الاستدلال على الجهاز أصلًا

يمكن دومًا استدعاء خادم يستضيف نموذجًا كبيرًا ويُعيد الجواب في بضع مئات من المِلّي ثانية. لكنّ أربعة أسباب تجعل الاستدلال على الجهاز خيارًا لا بديل عنه في مسائل بعينها. أوّلًا، غياب الشبكة: مزارع في حقل، أو صيّاد في بحر، أو مسافر في نفق مترو، لا شبكة له وقت الحاجة. ثانيًا، زمن الاستجابة المُدرَك: مسألة تصنيف صورة كاميرا في الوقت الحيّ (كشف QR، ترجمة لوحة معلَنة) تحتاج إجابة في أقلّ من ثلاثين مِلّي ثانية، وذهاب-إياب الشبكة وحده يستهلك أكثر. ثالثًا، الخصوصيّة: صور الوجه، ورسائل الدردشة، وسجلّات الصحّة، كلّها بيانات شخصيّة، والقاعدة الأخلاقيّة أن لا تُغادر الجهاز إلاّ حين لا بدّ. رابعًا، الكلفة على المدى: مليون مستخدم يستدعون نموذجًا كلّ ثانية فاتورة سحابيّة ضخمة؛ الاستدلال على الجهاز يُحوِّلها إلى صفر.

في مقابل ذلك، الاستدلال على الجهاز يفرض عليك تصميمًا مختلفًا: النموذج الذي تعرفه في السحابة (مئات المِيغا، وGPU بذاكرة 24 غيغا) لا يعمل على هاتف بذاكرة 3 غيغا. لا بدّ من تفكير محكم في الحجم، والحساب، والحرارة، والطاقة، قبل أن نبدأ.

أربعة قيود، أربع ميزانيّات

نعرِّف كلّ قيد بجملة، ثمّ نُلحق به هدفًا رقميًّا للخيط الأحمر. ستُقاس هذه الأرقام فعلًا في الوحدة 9، وتُقارن ما توقّعناه بما وقع.

قيد الذاكرة الحيّة. التطبيق كلّه (الشيفرة، وواجهة المستخدم، والصور المُخزَّنة، والنموذج، وذاكرة تشغيله) يجب أن يبقى تحت 200 مِيغا. النموذج نفسه يجب ألاّ يتجاوز 8 مِيغا في القرص، و16 مِيغا في الذاكرة الحيّة عند الاستدلال. يفرض هذا استعمال معماريّة صغيرة (MobileNetV2 بمُضاعف عرض 1.0)، وتكميمًا كاملًا (int8)، وربّما تقليمًا خفيفًا.

قيد الحساب. الهاتف المستهدَف من الفئة الاقتصاديّة: Snapdragon 680، أو ما يعادله عند Samsung أو MediaTek. أربع نوى Cortex-A73 عند 2.2 غيغاهرتز، وGPU Adreno 610. الاستدلال الواحد يجب أن يبقى تحت 60 مِلّي ثانية على CPU، وتحت 25 مِلّي ثانية على GPU. لا نطالب بالوقت الحيّ لأنّ التطبيق ليس كاميرا مستمرّة، بل التقاط صورة ثمّ تصنيف.

قيد الطاقة. التطبيق يعمل في حقل بلا مقبس. المزارع يستعمله لمدّة ساعة، فيلتقط صورة كلّ خمس دقائق تقريبًا، أي اثني عشر استدلالًا في الساعة. مجموع الاستدلالات يجب ألاّ يستهلك أكثر من 5 % من طاقة البطّاريّة. ذلك يترجم إلى ميزانيّة طاقة صارمة لكلّ استدلال، حسبنا لها 40 مِلّي جول كسقف على هاتفنا المرجعيّ.

قيد غياب الشبكة. لا خادم، ولا استلاف من السحابة عند تعذّر مسألة. النموذج يجب أن يعمل على الجهاز حتّى لآخر ورقة. هذا يمنع أيّ استعمال لواجهات برمجيّة سحابيّة، ويجعل تحديث النموذج نفسه (ملفّ .tflite جديد) عمليّة تتمّ حين يعثر الهاتف على واي فاي، بلا استعجال.

زمن الاستجابة المُدرَك ليس زمن النموذج وحده

كثير من المطوّرين يخلطون بين اثنين: زمن استدلال النموذج، وزمن الاستجابة الذي يشعر به المستخدم. الفرق شاسع. إن كان النموذج يستدلّ في 30 مِلّي ثانية، لكنّ تحويل صورة الكاميرا من YUV_420_888 إلى Bitmap ثمّ تصغيرها إلى 224 × 224 بيكسل يستغرق 80 مِلّي ثانية، فالمستخدم يرى 110 مِلّي ثانية، لا 30. المعالجة المسبقة، وتحرّك الخيط الرئيسيّ إلى خيط خلفيّ، ونسخ التنسور من CPU إلى GPU: كلّ هذه الأجزاء غير مرئيّة في القياس الساذج، لكنّها تظهر عند المستخدم.

القاعدة العمليّة: احسب زمن الاستجابة من ضغط الزرّ إلى ظهور النتيجة على الشاشة، لا زمن نداء interpreter.run وحده.

كيف نقيس ميزانيّة صحّتها قبل الشروع

قبل بناء التطبيق كاملًا، نستعمل TFLite Benchmark Tool على نموذج مرجعيّ يشبه هدفنا (MobileNetV2 مُكمَّم int8) للتحقّق من أنّ الأرقام معقولة على الجهاز المستهدَف. البرنامج الآتي يُشغَّل على المكتب لتقدير أوّليّ للحجم، قبل أيّ عمل على الهاتف:

import numpy as np
import tensorflow as tf

# 1) نموذج مرجعيّ من Keras لتقدير الحجم قبل الاستدلال الحقيقيّ
model = tf.keras.applications.MobileNetV2(
input_shape=(224, 224, 3),
include_top=True,
weights="imagenet",
alpha=1.0, # مُضاعف العرض. 0.75 أو 0.5 يُقلِّص أكثر إن لزم
)

# 2) تحويل خام (بلا تكميم بعدُ) لقراءة الحجم الأساسيّ
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

taille_octets = len(tflite_model)
print(f"حجم النموذج الخام : {taille_octets / 1024 / 1024:.2f} Mo")

# 3) تقدير سريع للحساب : عدّ المعاملات القابلة للتدريب
params = model.count_params()
print(f"عدد المعاملات : {params / 1e6:.2f} M")

# 4) قاعدة تقريبيّة : كلّ معامل يُقابل عمليّة ضرب واحدة لكلّ استدلال،
# ونضرب في متوسّط استعمال (~0.3) بسبب الالتفافات المشتركة
mflops_estime = params * 0.3 / 1e6
print(f"MFLOPs تقديريّ : {mflops_estime:.1f}")

هذا التقدير قبل التكميم يعطينا الرقم الأعلى؛ التكميم في int8 سيقلّص الحجم إلى الربع تقريبًا. إن كان الرقم الأوّليّ أكبر من الميزانيّة بعشر مرّات، لا فائدة من الاستمرار بهذه المعماريّة: خفِّض alpha أو غيِّر النموذج.

تقدير المكتب ليس واقع الهاتف

MFLOPs رقم مضلِّل. هاتفان بالعدد نفسه من العمليّات يمكن أن يعطيا زمنَي استجابة يختلفان بضعف. المصفوفة المرجعيّة الحقيقيّة تُبنى في الوحدة 9 على ثلاث فئات من الهواتف. حتّى ذلك الحين، هذه الأرقام تقديرات لا وعود.

الفخّ الأشيع: بدء العمل بلا ميزانيّة

مشروع نموذجيّ يفشل: يبني الفريق نموذجًا في السحابة بدقّة 96 %، ينهي التطوير، ثمّ يحاول نقله إلى الهاتف. الحجم 90 مِيغا، الاستدلال ثلاث ثوانٍ، البطّاريّة تنفد في ساعة. يبدأ التقليم والتكميم في اللحظة الأخيرة، بلا مجال لإعادة التدريب، ويهبط النموذج إلى دقّة 78 %. المنتَج يُلغى.

القاعدة المضادّة: الميزانيّة قبل النموذج. اختر أرقامك، اكتبها في وثيقة، ثمّ لا تتجاوزها إلاّ بقرار موثَّق. النموذج الذي لا يحترم الميزانيّة، غيِّره لا تُلمِّعه.

الخلاصة

  • أربعة قيود مستقلّة: الذاكرة، والحساب، والطاقة، وغياب الشبكة. كلّ واحد يفرض قرارات معماريّة مختلفة، ولا يعوِّض أحدها الآخر.
  • زمن الاستجابة المُدرَك ≠ زمن النموذج: احسب من ضغط الزرّ إلى ظهور النتيجة، وضمِّن المعالجة المسبقة ونسخ التنسور.
  • الميزانيّة تُكتب قبل النموذج: أرقام صريحة (حجم، زمن، طاقة، دقّة أدنى) تُوجِّه القرارات وتُنقذ من إعادة العمل في آخر لحظة.
  • الهاتف المرجعيّ يُحدَّد باكرًا: Snapdragon 680 لهدفنا. اختبار على هاتف الفريق فقط يُنتج أرقامًا لا تصمد أمام واقع المستخدمين.

الوحدة التالية تنقل النموذج من Keras إلى صيغة .tflite وتفتح ملفّ التوقيع والبيانات الوصفيّة.