الكتابات/tutorial/2026/05
Tutorial22 مايو 2026·30 دقيقة

نشر نماذج الذكاء الاصطناعي على Modal Labs: دليل بايثون لمعالجات GPU بدون خوادم

تعلّم كيفية نشر أحمال عمل الذكاء الاصطناعي على Modal Labs، منصة بدون خوادم لتشغيل بايثون على معالجات GPU السحابية. يغطي الدليل مزخرفات الدوال، اختيار GPU، التخزين المؤقت للنماذج، نقاط النهاية الويب، المهام المجدولة، وإطلاق واجهة API حقيقية لتفريغ Whisper.

تشغيل نموذج مفتوح المصدر على جهازك الخاص ممتع لعطلة نهاية الأسبوع، لكن تقديمه لمستخدمين يدفعون مقابل الخدمة مشكلة مختلفة تمامًا: تحتاج إلى معالجات GPU تستيقظ عند الطلب، تتقلّص إلى الصفر عند انعدام الاستخدام، وتُحاسبك بالثانية لا بالشهر. الإجابة التقليدية هي استئجار عنقود Kubernetes، تثبيت Triton، ضبط التحجيم التلقائي، وخسارة ربع وقت الفريق الهندسي في عمليات التشغيل. الإجابة التي استقرت عليها معظم شركات الذكاء الاصطناعي الناشئة سنة 2025 هي Modal Labs.

تمنحك Modal القدرة على تزيين دالة بايثون بمُزخرف، توجيهها نحو GPU، ثم إطلاقها. لا يوجد Dockerfile لتصونه، ولا عنقود لتديره، ولا حيل بدء بارد لتكتبها. تكتب بايثون، تنفّذ modal deploy، ويتولّى أسطول Firecracker الموزّع عالميًا تشغيل شيفرتك على H100 في أقل من خمس ثوانٍ. هذا الدليل يسير عبر سير العمل كاملًا وينتهي بواجهة API لتفريغ Whisper تستطيع استدعاءها من أي عميل.

ما الذي ستبنيه

خدمة تفريغ Whisper بدون خوادم تقوم بالآتي:

  • تستقبل رابط ملف صوتي عبر HTTP
  • تُحمّل نموذج large-v3 من Whisper على GPU من نوع A10G
  • تُخزّن أوزان النموذج مؤقتًا بحيث تصيب الاستدعاءات التالية حاوية دافئة
  • تُعيد نصوصًا مفرّغة مع طوابع زمنية بصيغة JSON
  • تتوسّع تلقائيًا من صفر إلى عدة حاويات متزامنة
  • تُكلّفك تقريبًا سنتَين عن كل دقيقة صوتية

في النهاية ستفهم النموذج الذهني لـ Modal: الصور، الدوال، الأقراص، الأسرار، ونقاط النهاية الويب، وستكون قد أطلقت واجهة GPU حقيقية يمكنك تسليمها لواجهة أمامية.

المتطلبات المسبقة

  • بايثون 3.11 أو أحدث
  • حساب على Modal من modal.com، الفئة المجانية تتضمن ثلاثين دولارًا من الرصيد الشهري، وهو أكثر من كافٍ لهذا الدليل
  • إلمام أساسي بمزخرفات بايثون وتلميحات الأنواع
  • محطّة طرفية ومحرّر شيفرة

الخطوة 1: تثبيت Modal والمصادقة

تتوزّع Modal كحزمة PyPI واحدة. ثبّتها داخل بيئة افتراضية جديدة.

python -m venv .venv
source .venv/bin/activate
pip install modal

صادق على حسابك. الأمر يفتح نافذة متصفح ويكتب رمزًا إلى ~/.modal.toml.

modal setup

تحقّق من التثبيت بأمر مدمج.

modal profile current

ستظهر لك تسمية مساحة عملك مطبوعة. إن ظهرت، فإن الـ SDK جاهز للنشر.

الخطوة 2: أوّل دالة Modal

أنشئ ملفًا باسم hello.py. النموذج الذهني بسيط: كل تطبيق Modal يبدأ بكائن App، وكل دالة تريد تشغيلها عن بُعد تُزيَّن بـ @app.function.

import modal
 
app = modal.App("hello-modal")
 
@app.function()
def square(x: int) -> int:
    return x * x
 
@app.local_entrypoint()
def main():
    print(square.remote(7))

شغّله.

modal run hello.py

يستغرق الاستدعاء الأول بضع ثوانٍ لأن Modal تبني صورة الحاوية، تدفعها إلى السجل، تجدولها على عامل، وتدفق السجلات إلى محطّتك. الاستدعاءات اللاحقة تعيد استخدام الصورة والحاوية الدافئة فتنتهي في أقل من ثانية. الناتج هو 49.

التفصيل المهم هو square.remote(7). الاستدعاء بـ .local() يشغّل الدالة داخل عمليتك المحلية، بينما .remote() يشحن الاستدعاء إلى بنية Modal التحتية. كل شيء في هذا الدليل يعتمد على هذا التمييز الوحيد.

الخطوة 3: تعريف صورة مخصّصة

يحتاج Whisper إلى PyTorch، حزمة openai-whisper، وأداة ffmpeg. تتيح لك Modal وصف الصورة بطريقة تصريحية باستخدام بنّاء انسيابي، دون Dockerfile.

أنشئ ملفًا باسم whisper_api.py.

import modal
 
image = (
    modal.Image.debian_slim(python_version="3.11")
    .apt_install("ffmpeg")
    .pip_install(
        "openai-whisper==20240930",
        "torch==2.5.1",
        "requests==2.32.3",
    )
)
 
app = modal.App("whisper-api", image=image)

تحسب Modal بصمة الصورة وتُخزّن النتيجة. أوّل عملية نشر تبني الطبقات وتدفعها؛ النشرات اللاحقة تعيد استخدامها. يمكنك تجاوز الصورة لكل دالة على حدة إن كانت نقطة نهاية صغيرة لا تحتاج كامل المكدّس.

الخطوة 4: اختيار GPU ووصل قرص

نموذج Whisper large-v3 يبلغ حجمه ثلاثة غيغابايت تقريبًا. تنزيله في كل بداية باردة سيكون هدرًا. تحلّ Modal ذلك عبر الأقراص، تخزين كتلي ثابت يمكن لأي دالة وصله في مسار محدّد.

أضف ما يلي إلى whisper_api.py.

model_cache = modal.Volume.from_name("whisper-cache", create_if_missing=True)
 
@app.function(
    gpu="A10G",
    volumes={"/cache": model_cache},
    timeout=600,
)
def transcribe(audio_url: str) -> dict:
    import whisper
    import requests
    import tempfile
    import os
 
    os.environ["XDG_CACHE_HOME"] = "/cache"
    model = whisper.load_model("large-v3", download_root="/cache/whisper")
 
    response = requests.get(audio_url, timeout=120)
    response.raise_for_status()
 
    with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
        f.write(response.content)
        audio_path = f.name
 
    result = model.transcribe(audio_path, verbose=False)
    return {
        "text": result["text"],
        "segments": [
            {"start": s["start"], "end": s["end"], "text": s["text"]}
            for s in result["segments"]
        ],
        "language": result["language"],
    }

عدّة تفاصيل تستحق الانتباه. الوسيط gpu="A10G" يختار بطاقة Ampere بسعة أربع وعشرين غيغابايت، رخيصة، سريعة، وأكبر من حاجة large-v3. يمكنك كذلك اختيار T4, L4, A100, H100, أو حتى H100:8 لمعالجات متعدّدة. تعيين volumes يصل القرص الثابت إلى /cache داخل الحاوية، وXDG_CACHE_HOME يُحوّل PyTorch وWhisper لكتابة الأوزان هناك. الاستدعاء الأول يُنزّل ثلاثة غيغابايت، وكل استدعاء لاحق يقرأ من الذاكرة المخزّنة في أجزاء من الثانية.

تعيين timeout=600 يرفع سقف الاستدعاء الواحد إلى عشر دقائق لأن الملفات الصوتية الطويلة تأخذ وقتًا.

الخطوة 5: عرض نقطة نهاية ويب

يمكن استدعاء دوال Modal من شيفرة بايثون أخرى، لكن النمط الأكثر شيوعًا هو عرضها كنقاط نهاية HTTPS. المزخرف @modal.fastapi_endpoint يحوّل دالة إلى عنوان عام.

أضف هذا المقطع إلى whisper_api.py.

@app.function(image=image)
@modal.fastapi_endpoint(method="POST", docs=True)
def transcribe_endpoint(audio_url: str):
    return transcribe.remote(audio_url)

انشر التطبيق.

modal deploy whisper_api.py

تطبع Modal عنوانًا يشبه https://your-workspace--whisper-api-transcribe-endpoint.modal.run. علم docs=True يعني أن إضافة /docs إلى العنوان تمنحك واجهة Swagger تفاعلية مجانًا.

اختبره من محطّتك.

curl -X POST "https://your-workspace--whisper-api-transcribe-endpoint.modal.run?audio_url=https://example.com/sample.mp3"

الاستدعاء الأول يستغرق نحو عشرين ثانية بينما تُهيّئ Modal الـ GPU وتُنزّل النموذج. الاستدعاءات التالية ضمن نافذة الإبقاء على الحاوية دافئة تنتهي في ثانيتين إلى أربع ثوانٍ لمقطع مدّته دقيقة.

الخطوة 6: التزامن والحاويات والتحجيم

افتراضيًا، تعالج كل حاوية طلبًا واحدًا في كل مرة. Whisper مقيد بالـ GPU ويستفيد من التجميع، لكن للوضوح سنُبقي طلبًا واحدًا لكل حاوية ونترك Modal تتوسّع أفقيًا.

عدّل المزخرف.

@app.function(
    gpu="A10G",
    volumes={"/cache": model_cache},
    timeout=600,
    min_containers=1,
    max_containers=20,
    scaledown_window=300,
)
def transcribe(audio_url: str) -> dict:
    ...

ثلاثة مفاتيح مهمّة:

  • min_containers=1 يُبقي حاوية دافئة دائمًا. تختفي البدايات الباردة مقابل تشغيل GPU واحدة باستمرار. اضبط القيمة على صفر للحصول على دفع حسب الاستخدام الصافي.
  • max_containers=20 يضع سقفًا للمقياس التلقائي. حاجز مفيد ضد ارتفاع حركة المرور والفواتير الجامحة.
  • scaledown_window=300 يُبقي الحاويات حيّة خمس دقائق بعد آخر طلب، حتى لا تدفع حركة المرور المتقطّعة ضريبة البدء البارد مرارًا.

لتجميع عدّة طلبات داخل حاوية واحدة، استخدم @modal.concurrent(max_inputs=4) على الدالة. يتعامل Whisper مع ذلك بشكل جيد لأن ذاكرة الـ GPU تتجاوز حزمة الصوت بكثير.

الخطوة 7: إضافة الأسرار

تحتاج واجهات API الحقيقية إلى مصادقة. تُخزّن Modal الأسرار في خزنة مُدارة وتحقنها كمتغيّرات بيئة وقت التشغيل. أنشئ سرًّا من لوحة التحكم أو عبر CLI.

modal secret create whisper-api API_KEY=your-long-random-token-here

اربطه بنقطة النهاية وتحقّق منه داخلها.

from fastapi import HTTPException, Header
import os
 
@app.function(
    image=image,
    secrets=[modal.Secret.from_name("whisper-api")],
)
@modal.fastapi_endpoint(method="POST")
def transcribe_endpoint(
    audio_url: str,
    authorization: str = Header(None),
):
    expected = f"Bearer {os.environ['API_KEY']}"
    if authorization != expected:
        raise HTTPException(status_code=401, detail="Invalid API key")
    return transcribe.remote(audio_url)

أعد النشر. الاستدعاءات بدون رأس Authorization الصحيح تُعيد الآن 401.

الخطوة 8: المهام المجدولة

حالة استخدام شائعة ثانية لـ Modal هي العمل المجدول على نمط cron. يتولّى المزخرف @app.function(schedule=...) ذلك دون بنية تحتية خارجية.

@app.function(schedule=modal.Cron("0 3 * * *"))
def nightly_cleanup():
    import datetime
    print(f"Running cleanup at {datetime.datetime.now()}")

بعد النشر، تُشغّل Modal هذه الدالة عند الساعة 03:00 UTC يوميًا. ترث الدالة صورة التطبيق، ويمكنك تجاوز ذلك لكل دالة بوسيط image= منفصل. لا يوجد تقويم لضبطه ولا مخطط Airflow DAG لكتابته، فالمزخرف هو الجدول.

الخطوة 9: حلقة التطوير المحلية

التكرار على GPU بعيدة يبدو بطيئًا. تحلّ Modal الحلقة بـ modal serve.

modal serve whisper_api.py

ينشر هذا نسخة مؤقتة تُعاد تحميلها مع كل حفظ ملف، تطبع السجلات محليًا، وتمنحك عنوانًا مؤقتًا. تُحرّر، تحفظ، ويصل الطلب التالي إلى الشيفرة الجديدة في نحو ثانيتين. إنها تجربة تطوير GPU بدون خوادم الأسرع المتاحة سنة 2026.

استعمل modal serve أثناء البناء، ثم modal deploy حين تجهز للإنتاج.

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

كل استدعاء دالة يُنتج سجلات مهيكلة قابلة للعرض في ثلاثة أماكن.

تدفّق الـ CLI أثناء التطوير.

modal app logs whisper-api

لوحة الويب على modal.com/apps، مع تبويبات لكل دالة للاستدعاءات والأخطاء واستخدام الـ GPU.

الوصول البرمجي عبر Modal Python SDK إن أردت شحن السجلات إلى مكدّس مراقبتك الخاص مثل Datadog أو Grafana.

لتتبّع أعمق، غلّف المقاطع الحرجة بكتل with modal.Status("doing thing"):؛ يظهر نصّ الحالة في جدول لوحة التحكم الزمني حتى تستطيع رؤية أي خطوة من دالة طويلة قيد التنفيذ حاليًا.

اختبار تنفيذك

شغّل سير العمل الكامل على عيّنة صوتية حقيقية.

modal deploy whisper_api.py
curl -X POST \
  -H "Authorization: Bearer your-long-random-token-here" \
  "https://your-workspace--whisper-api-transcribe-endpoint.modal.run?audio_url=https://github.com/openai/whisper/raw/main/tests/jfk.flac"

ستتلقّى JSON يحوي النصّ الكامل، قائمة بالمقاطع مع الطوابع الزمنية، واللغة المكتشفة. المسار البارد: نحو عشرين ثانية. المسار الدافئ: نحو ثلاث ثوانٍ لمقطع JFK بطول خمس عشرة ثانية.

افحص لوحة التحكم. ينبغي أن ترى الاستدعاء، رسمًا بيانيًا لاستخدام GPU يرتفع أثناء التفريغ، وتكلفة لكل استدعاء قرب نصف سنت.

استكشاف الأخطاء وإصلاحها

أوّل نشر يعلق عند "Building image". تبني Modal صورة PyTorch وهي ضخمة. توقّع ثلاث إلى خمس دقائق في البناء الأوّل. النشرات اللاحقة تعيد استخدام الطبقات المخزّنة وتنتهي في ثوانٍ.

أخطاء CUDA out of memory. بدّل الـ GPU إلى بطاقة أكبر مثل A100 أو استعمل whisper.load_model("medium") بدلًا من large-v3 أثناء التنقيح.

البدايات الباردة لا تزال بطيئة رغم التخزين. وصل القرص سريع، لكن PyTorch يستورد مكتبات CUDA وهو ما يستغرق عدّة ثوانٍ. استخدم min_containers=1 للإبقاء على حاوية دافئة لنقاط النهاية الحسّاسة للزمن.

يفشل modal deploy بأخطاء استيراد محليًا. لا تستورد Modal ملفك بالطريقة نفسها التي تستوردها بايثون. الاستيرادات داخل جسم الدالة، مثل import whisper داخل transcribe، تعمل في الحاوية لا محليًا، ولذلك وضعنا الاستيرادات الثقيلة هناك.

الخطوات التالية

  • أضف خطّافًا غير متزامن لتفريغ الملفات الطويلة: أعِد مُعرّف مهمّة فورًا واجعل دالة ثانية ترسل النتيجة إلى عنوان استدعاء معكوس عند الانتهاء.
  • ابثّ نصوصًا جزئية بالتحوّل إلى faster-whisper واستخدام دعم yield في Modal داخل المولّدات.
  • ضع طبقة Next.js أو Streamlit فوق ذلك، فنقاط نهاية Modal هي HTTPS عادية، ويمكن لأي عميل استدعاؤها.
  • استكشف وكلاء ومهام Mastra AI لتنسيق نقاط نهاية Modal من وكيل TypeScript.

الخاتمة

تختصر Modal Labs قصّة نشر GPU من "أطلق Kubernetes، اضبط التحجيم التلقائي، صارع سجلات الحاويات" إلى "زخرف دالة بايثون ونفّذ modal deploy". لاستدلال الذكاء الاصطناعي، المهام المجدولة، وأي حمل عمل يحتاج دفعات GPU عرضية، تُزيل Modal من الاحتكاك أكثر من أي منصة أخرى في السوق سنة 2026.

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