تطوير الويب

تقديم نموذج ML بـ BentoML: التغليف والنشر

1 min de lecture

📌 نظرة عامّة: MLOps حديث: من النموذج إلى الإنتاج

المشكلة: pickle لا يُنشَر وحده

حين يُسَلِّم data scientist ملفًّا model.pkl لفريق الهندسة، يبدأ العمل الحقيقي. الملفّ المُسَلسَل يحوي أوزان النموذج لكن لا أيًّا من اللبنات اللازمة لوضعه في الإنتاج: لا endpoint HTTP، لا تحقّق من المدخلات، لا إدارة تنافس، وخاصّة لا ضمان أنّ نسخة scikit-learn المُستعمَلة في التدريب ستكون نفسها في التنبّؤ. فجوة صغيرة في النسخة قد تقلب خرج classifier من 0.94 إلى 0.61 على نفس متّجه الدخل.

الحاجة الفعلية أربع نقاط: كشف API JSON نظيف، تجميد تبعيّات Python بالضبط، إنتاج صورة Docker قابلة للإعادة، ونشر هذه الصورة في مكان ما. كثير من الفِرَق يُحاولون تجميع هذه اللبنات يدويًّا مع FastAPI وrequirements.txt وDockerfile مكتوب يدويًّا وCI خاصّة. يعمل ستّة أشهر، ثم ينسى شخص إعادة تثبيت نسخة فرعية وينحرف التنبّؤ في ليلة جمعة.

BentoML يعالج هذه المشكلة بفرض تنسيق موحَّد، الـ Bento، الذي يجمع كود الخدمة، النماذج، التبعيّات، وإعداد runtime في artefact وحيد مُؤَرَّخ. مكافئ لـ .jar Java في عالم ML: وحدة نشر حتمية.

لماذا BentoML بدل FastAPI عاريًا

FastAPI framework HTTP ممتاز ويبقى وجيهًا للمعماريات microservices العامّة. لكن فور الحديث تحديدًا عن تقديم نماذج، يترك FastAPI عدّة مشاكل مفتوحة يحلّها BentoML بلا جهد.

أوّلًا: إدارة النماذج. مع FastAPI، يلزم كتابة منطق التحميل والـ versioning وcache الأوزان يدويًّا. BentoML يعرض Model Store محلّيًّا يُؤَرِّخ كلّ حفظ تلقائيًّا ويُتيح الإحالة إلى نموذج بـ tag (مثلًا iris_clf:latest). ثانيًا: التحويل إلى container. BentoML يُولِّد كلّ شيء من ملفّ YAML تصريحي. ثالثًا: تحسين runtime. BentoML يضمّ أصليًّا إدارة workers، batching تكيّفي للطلبات، تسريع GPU، وتنزيل النماذج في build بدل start.

المتطلّبات التقنية

قبل البدء: بيئة Python 3.9 أو أحدث (3.11 موصى به)، pip محدَّث، وDocker مثبَّت محلّيًّا. معرفة أساسية بـ scikit-learn تُساعد. 8 جيغا RAM تكفي. النموذج المُستعمَل مُختزَل والصورة Docker النهائية أقلّ من 800 ميغا.

الخطوة 1 — تثبيت BentoML وتهيئة المشروع

mkdir iris-bentoml && cd iris-bentoml
python -m venv .venv
source .venv/bin/activate

pip install --upgrade pip
pip install "bentoml>=1.4,<2.0" scikit-learn numpy
bentoml --version
# المتوقّع: bentoml, version 1.4.38
touch train.py service.py bentofile.yaml

الخطوة 2 — تدريب النموذج وحفظه في Model Store

Model Store مجلّد محلّي (افتراضيًّا ~/bentoml/models/) يُؤَرِّخ كلّ حفظ. كلّ استدعاء لـ bentoml.sklearn.save_model يُنشئ tag جديدًا بـ hash فريد.

# train.py
import bentoml
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
print(f"Précision test : {model.score(X_test, y_test):.3f}")

saved = bentoml.sklearn.save_model("iris_clf", model)
print(f"Modèle sauvegardé : {saved.tag}")
python train.py
# Précision test : 0.967
# Modèle sauvegardé : iris_clf:abc123xyz

bentoml models list

الخطوة 3 — تعريف خدمة بـ @bentoml.service و@bentoml.api

واجهة BentoML 1.4 تصريحية: نُعَرِّف class مُزَيَّن بـ @bentoml.service، وكلّ method مكشوف كـ endpoint HTTP مُزَيَّن بـ @bentoml.api. الأنواع Python القياسية تكفي لوصف المدخلات والمخرجات.

# service.py
import bentoml
import numpy as np
from bentoml.models import BentoModel

IRIS_MODEL = BentoModel("iris_clf:latest")

@bentoml.service(
    resources={"cpu": "1"},
    traffic={"timeout": 10},
)
class IrisClassifier:
    model_ref = IRIS_MODEL

    def __init__(self) -> None:
        self.model = bentoml.sklearn.load_model(self.model_ref)

    @bentoml.api
    def classify(self, input_data: np.ndarray) -> np.ndarray:
        return self.model.predict(input_data)

resources={"cpu": "1"} يحجز CPU لكلّ worker. traffic={"timeout": 10} يقطع أيّ طلب يتجاوز 10 ثوانٍ. __init__ يُحَمِّل النموذج مرّة واحدة عند الإقلاع. التكتيب np.ndarray -> np.ndarray يُستَغَلّ لتوليد OpenAPI تلقائيًّا والتحقّق من الطلبات.

الخطوة 4 — اختبار الخدمة محلّيًّا

bentoml serve service:IrisClassifier --reload
# [INFO] Starting development HTTP BentoServer
# [INFO] Listening on http://localhost:3000
curl -X POST http://localhost:3000/classify \
  -H "Content-Type: application/json" \
  -d '{"input_data": [[5.1, 3.5, 1.4, 0.2], [6.7, 3.0, 5.2, 2.3]]}'
# [0, 2]

الخرج [0, 2]: الأولى Setosa (class 0)، الثانية Virginica (class 2). افتح http://localhost:3000 لرؤية واجهة Swagger المُولَّدة تلقائيًّا.

الخطوة 5 — تعريف bentofile.yaml

الملفّ bentofile.yaml هو manifeste build Bento. يُعلن أيّ خدمة، أيّ نسخة Python، أيّ حزم، وأيّ ملفّات نسخها. تَنسيقه يضمن قابلية الإعادة.

# bentofile.yaml
service: "service:IrisClassifier"
labels:
  owner: data-team
  project: iris-demo
include:
  - "service.py"
python:
  requirements_txt: "./requirements.txt"
  lock_packages: true
docker:
  python_version: "3.11"
  distro: "debian"
# requirements.txt
bentoml>=1.4,<2.0
scikit-learn==1.5.2
numpy==1.26.4

docker.python_version يتحكّم في نسخة Python في الصورة النهائية. distro يقبل debian (افتراضي، كامل) أو alpine (مُختزَل، لكن يصادم بعض roues binaires العلمية).

الخطوة 6 — بناء Bento

bentoml build
# Locking PyPI package versions.
# ...
# Successfully built Bento(tag="iris_classifier:xyz789abc")

bentoml list
bentoml get iris_classifier:latest

tag الـ Bento يحوي اسم الخدمة بـ kebab-case ومُعَرِّفًا فريدًا. الـ artefact ثابت: أيّ تعديل في الكود أو التبعيّات يفرض build جديدًا.

الخطوة 7 — تحويل إلى صورة Docker

bentoml containerize iris_classifier:latest
# Building OCI-compliant image for iris_classifier:xyz789abc with docker
# Successfully built docker image "iris_classifier:xyz789abc"

docker images iris_classifier

صورة نمطية لنموذج scikit-learn تزن بين 700 ميغا و1 جيغا. على Mac Apple Silicon، أضف --platform=linux/amd64 إن كانت الصورة تستهدف cluster x86.

الخطوة 8 — تشغيل container واختباره

docker run --rm -p 3000:3000 iris_classifier:latest serve
# [INFO] Starting production HTTP BentoServer
# [INFO] Listening on http://0.0.0.0:3000
curl -X POST http://localhost:3000/classify \
  -H "Content-Type: application/json" \
  -d '{"input_data": [[5.1, 3.5, 1.4, 0.2]]}'
# [0]

الخطوة 9 — نشر على BentoCloud أو دفع إلى registry خاصّ

bentoml cloud login
bentoml deploy iris_classifier:latest -n iris-prod
# Deployment 'iris-prod' created.
# URL : https://iris-prod-xxx.bentoml.ai
docker tag iris_classifier:xyz789abc \
  registry.example.com/ml/iris_classifier:xyz789abc

docker login registry.example.com
docker push registry.example.com/ml/iris_classifier:xyz789abc

المنفذ المكشوف 3000، والأمر للـ container serve.

الخطوة 10 — مراقبة السجلّات والطلبات

docker logs -f $(docker ps -q --filter ancestor=iris_classifier:latest)
# [INFO] [api_server:1] 192.168.1.10 - POST /classify 200 OK 12ms
curl http://localhost:3000/metrics | head -20
# bentoml_service_request_total{...} 1284
# bentoml_service_request_duration_seconds_bucket{le="0.05"} 1198

القناة الثالثة /healthz: ردّ 200 يُشير إلى أنّ الخدمة جاهزة. لدفع المقاييس إلى Prometheus + Grafana، أعلِن ServiceMonitor في Kubernetes يُشير إلى /metrics. لكشف drift، راجع دليل Evidently.

أخطاء شائعة

العَرَض السبب الحلّ
ModuleNotFoundError: No module named 'bentoml.io' كود لـ API 1.0/1.1 غير متوافق مع 1.4 هاجر إلى نمط @bentoml.service + @bentoml.api
RuntimeError: Model 'iris_clf:latest' not found Model Store فارغ شَغِّل python train.py ثم bentoml models list
docker: Cannot connect to the Docker daemon Docker متوقّف شَغِّل Docker، تحقّق بـ docker ps
صورة Docker ضخمة جدًّا (> 2 جيغا) تبعيّات غير مُثَبَّتة فعِّل lock_packages: true وثَبِّت torch/scipy
Timeout على batch ضخم traffic.timeout منخفض زده في @bentoml.service(traffic={"timeout": 60})
container لا يُقلِع على ARM صورة buildée لـ amd64 على arm64 أعد build بـ --platform linux/arm64

مصادر رسمية

توثيق BentoML شامل: صفحة Services لخيارات decorators المتقدّمة، Bento build options لكلّ مفاتيح bentofile.yaml، Scale with BentoCloud للنشر المُدار. مستودع bentoml/BentoML على GitHub يضمّ عشرات الأمثلة (XGBoost، PyTorch، Hugging Face، vLLM للـ LLM).

الأدلّة المرتبطة

FAQ

أيّ نسخة Python؟ Python 3.9 كحدّ أدنى. 3.11 الأكثر استقرارًا للتبعيّات العلمية.

أيمكن تقديم PyTorch أو Hugging Face بنفس النمط؟ نعم، النمط مطابق. فقط استدعاء الحفظ يختلف: bentoml.pytorch.save_model، bentoml.transformers.save_model، bentoml.xgboost.save_model.

Model Store قابل للمشاركة؟ محلّيًّا لا، مجلّد قرصي. للمشاركة: دفع إلى BentoCloud (bentoml models push) أو في Bento مُؤَرَّخ في registry Docker.

هل نُحَوِّل دائمًا إلى container؟ BentoCloud يقبل Bentos مباشرة. لأيّ نشر آخر (Kubernetes، Cloud Run، ECS)، يجب المرور عبر bentoml containerize.

كيف نُدير عدّة نسخ متوازية؟ أَحِل إلى tag دقيق (iris_clf:abc123xyz) بدل :latest. لـ A/B test، Bentos اثنان بنموذجَين مختلفَين خلف reverse proxy.

BentoML يدعم batching تلقائي؟ نعم، عبر @bentoml.api(batchable=True, batch_dim=0). BentoML يُجَمِّع الطلبات المتزامنة في نافذة قصيرة ويُنَفِّذها دفعة واحدة على GPU.

ماذا لو غيّرت النموذج دون rebuild؟ إن أَحَل الخدمة إلى iris_clf:latest، save_model جديد يُحَوِّل latest إلى النسخة الجديدة لكن الـ Bento المُبَنى يبقى ثابتًا على النسخة المُضَمَّنة وقت build. هذا متعمَّد: الـ Bento ثابت.

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité