المقال ده اتكتب من جديد في أكتوبر 2026. النسخة اللي قبلها كانت بتعرض أرقام الذاكرة والـ latency والتسريع على إنها قياسات، وكانت بتقول إن واحد من الموديلات مش مستخدم. الأرقام دي كانت تقديرات، والموديل ده بيخدم نسبة حقيقية من الـ requests، فاحتفظنا بيه. النسخة دي بتوصف التغييرات اللي نزلت على الـ production.
الـ service بتصنّف المشاعر في بوستات السوشيال ميديا لتقارير Twiscope: بالعربي، ومن ضمنه اللهجات الخليجية والمصرية، وبالإنجليزي. وبتشغّل تلات موديلات pretrained من Hugging Face ورا FastAPI — موديل لهجات من CAMeL-Lab للعربي، وموديل Twitter RoBERTa للإنجليزي، وموديل multilingual للنصوص اللي بتجمع أكتر من لغة أو مكتوبة بلغات تانية. مفيش أي موديل بيتدرّب هنا؛ الشغل هنا هو إننا نشغّلهم بشكل موثوق.
1. تحميل الموديلات في الـ lifespan، مش وقت الـ import
النموذج الأولي كان بيحمّل الموديلات وقت ما الـ module بيتعمله import، فخطأ في الـ download كان بيحصل قبل ما دورة تشغيل التطبيق تبدأ بشكل واضح. نقل التحميل للـ lifespan في FastAPI خلّاه جزء واضح من بدء التشغيل: لو اكتمل، الخدمة تبدأ تستقبل requests؛ ولو فشل، الـ orchestrator يتعامل معاه كفشل في بدء التشغيل.
from contextlib import asynccontextmanager
from fastapi import FastAPI, Response
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.ready = False
app.state.models = load_models(settings) # pinned revisions, see below
app.state.ready = True
yield
app.state.models = None # release memory on shutdown
app = FastAPI(lifespan=lifespan)
@app.get("/ready")
def ready(response: Response):
if not app.state.ready:
response.status_code = 503
return {"ready": app.state.ready}لو حصل exception جوه الـ lifespan، التطبيق مش هيبدأ، وده السلوك المقصود: يفشل بسرعة والـ orchestrator يقدر يعيد تشغيله. والـ readiness endpoint هنا بيعبّر عن حالة التطبيق بعد الـ startup؛ مش بيتنادى أثناء تحميل الموديلات، لأن FastAPI مبيستقبلش requests قبل ما الـ lifespan يوصل لـ yield.
2. تثبيت الـ revision بتاع كل موديل
أي model id على الـ Hugging Face Hub بيشاور على آخر حاجة اتعملها push، مهما كانت. وفي منتج تحليلات، نفس النص المفروض ياخد نفس الـ label الشهر الجاي، عشان كده كل موديل بيتحمّل على revision متثبّت ومحفوظ في الـ configuration.
model = AutoModelForSequenceClassification.from_pretrained(
cfg.model_id,
revision=cfg.revision, # commit SHA or tag, reviewed like any dependency bump
)3. حفظ ملفات الموديلات على volume دائم
من غير cache دائم، كل restart للـ container كان بيعمل download للموديلات من تاني. ووجود named volume متعمله mount على إنه الـ cache بتاع Hugging Face بيخلّي الـ restarts والـ redeploys تعيد استخدام اللي موجود أصلًا.
services:
sentiment-api:
environment:
HF_HOME: /models-cache
volumes:
- hf-cache:/models-cache
volumes:
hf-cache:4. الـ half precision بس في المكان اللي بيفيد فيه
الـ float16 بيقلّل ذاكرة الـ weights للنصف تقريبًا وبيسرّع الـ inference على الـ GPUs. على الـ CPUs غالبًا مش بيفيد، وممكن يكون أبطأ، عشان كده الـ service بتختار الـ dtype على حسب الـ device: half precision على CUDA أو Apple MPS، و float32 على الـ CPU. الـ hosts اللي شغالة على CPU بتتصرف بالضبط زي ما كانت قبل التغيير.
def model_dtype(device: str) -> torch.dtype:
return torch.float16 if device in ("cuda", "mps") else torch.float325. إرسال النصوص على دفعات
الـ callers بيبعتوا النصوص في batches (الـ backend بتاع Twiscope بيبعت لحد 100 نص في المرة)، والـ service بتعمل batched inference، وده بيوزّع تكلفة الـ tokenisation والـ overhead بتاع الموديل أحسن بكتير من request لكل بوست.
ليه احتفظنا بالموديل متعدد اللغات
الـ optimisation المغري لتوفير الذاكرة كان إننا نشيل الموديل الـ multilingual ونسيب موديلات العربي والإنجليزي بس. الـ logs وضّحت إننا لسه محتاجينه: البوستات اللي بتجمع أكتر من لغة أو مكتوبة بلغات تانية — والفريق قدّر إنها 20–30% من الـ calls — بتعتمد عليه. راجعوا استخدام أي مكوّن قبل ما تقرروا تستغنوا عنه.
اللي سبناه لبعدين عن قصد
- زيادة عدد الـ worker processes. كل worker في uvicorn بيحمّل نسخة خاصة بيه من كل موديل، فالذاكرة المطلوبة بتتضرب في عدد الـ workers. PyTorch أصلًا بيستخدم كذا thread في كل process للـ inference، عشان كده منزوّدش processes غير بعد ما القياس يثبت إن الـ process الواحدة هي الـ bottleneck.
- الـ ONNX Runtime والـ INT8 quantization. الاتنين ممكن يقلّلوا الـ latency والذاكرة بشكل كبير، بس كل واحد فيهم محتاج فحص للـ accuracy على بيانات اللهجات العربية قبل ما يبقى يستاهل الـ migration.
- إعادة كتابة الـ service بلغة تانية. الـ bottleneck هو الموديل، مش الـ request handling في Python.
التغييرات دي بسيطة كل واحدة لوحدها، لكن مع بعض جهّزت الخدمة للتشغيل بشكل أكثر استقرارًا: تحميل واضح للموديلات، وإصدارات محددة، وإعادة استخدام الملفات بعد كل deployment. وخيارات التوسّع الباقية لسه محتاجة قياس قبل تنفيذها.