פריסת מודלים פתוחים באמצעות קונטיינר vLLM בהתאמה אישית

אפשרויות ההפעלה השונות של מודלים ב-Gemini Enterprise Agent Platform מספיקות לתרחישי שימוש רבים, אבל יכול להיות שתצטרכו להשתמש בתמונות קונטיינר משלכם כדי להפעיל מודלים ב-Gemini Enterprise Agent Platform. במאמר הזה נסביר איך להשתמש בתמונה של קונטיינר בהתאמה אישית של vLLM כדי להפעיל מודלים בפלטפורמת Gemini Enterprise Agent במעבדי CPU, במעבדי GPU או במעבדי TPU. מידע נוסף על מודלים נתמכים של vLLM זמין בתיעוד של vLLM.

שרת ה-API של vLLM מטמיע את פרוטוקול ה-API של OpenAI, אבל הוא לא תומך בדרישות של בקשות ותגובות של Gemini Enterprise Agent Platform. לכן, כדי לקבל מסקנות ממודלים שנפרסו ב-Gemini Enterprise Agent Platform, צריך להשתמש בבקשת מסקנה גולמית של Gemini Enterprise Agent Platform באמצעות נקודת קצה של תחזית. מידע נוסף על השיטה Raw Prediction ב-Gemini Enterprise Agent Platform Python SDK זמין במאמרי העזרה של Python SDK.

אפשר להשתמש במודלים מ-Hugging Face ומ-Cloud Storage. הגישה הזו מציעה גמישות, ומאפשרת לכם לנצל את מרכז המודלים (Hugging Face) שמבוסס על קהילה, ואת יכולות העברת הנתונים והאבטחה האופטימליות של Cloud Storage לניהול מודלים פנימי או לגרסאות מותאמות.

‫vLLM מוריד את המודלים מ-Hugging Face אם מסופק אסימון גישה של Hugging Face. אחרת, vLLM מניח שהמודל זמין בדיסק המקומי. קובץ אימג' של קונטיינר בהתאמה אישית מאפשר ל-Gemini Enterprise Agent Platform להוריד את המודל מ-Google Cloud בנוסף ל-Hugging Face.

לפני שמתחילים

  1. ב Google Cloud פרויקט, מפעילים את ממשקי ה-API של Gemini Enterprise Agent Platform ו-Artifact Registry.

    gcloud services enable aiplatform.googleapis.com \
        artifactregistry.googleapis.com
    
  2. מגדירים את Google Cloud CLI עם מזהה הפרויקט ומפעילים את Vertex AI SDK.

    PROJECT_ID = "PROJECT_ID"
    LOCATION = "LOCATION"
    import vertexai
    vertexai.init(project=PROJECT_ID, location=LOCATION)
    
    gcloud config set project {PROJECT_ID}
    
  3. יוצרים מאגר Docker ב-Artifact Registry.

    gcloud artifacts repositories create DOCKER_REPOSITORY \
        --repository-format=docker \
        --location=LOCATION \
        --description="Agent Platform Docker repository"
    
  4. אופציונלי: אם מורידים מודלים מ-Hugging Face, צריך לקבל טוקן של Hugging Face.

    1. אם אין לכם חשבון, יוצרים חשבון ב-Hugging Face.
    2. כדי להשתמש במודלים עם גישה מוגבלת כמו Llama 3.2, צריך לבקש ולקבל גישה ב-Hugging Face לפני שממשיכים.
    3. כדי ליצור טוקן גישה: עוברים אל הפרופיל שלך > הגדרות > טוקנים לגישה.
    4. בוחרים באפשרות New Token (טוקן חדש).
    5. מציינים שם ותפקיד עם הרשאת קריאה לפחות.
    6. לוחצים על יצירת אסימון.
    7. שומרים את הטוקן לשלבי הפריסה.

הכנת קבצים לבניית קונטיינר

הפקודה הבאה Dockerfile יוצרת את קובץ האימג' של הקונטיינר בהתאמה אישית של vLLM עבור יחידות GPU,‏ TPU ומעבדי CPU. המאגר המותאם אישית הזה מוריד מודלים מ-Hugging Face או מ-Cloud Storage.

ARG BASE_IMAGE
FROM ${BASE_IMAGE}

ENV DEBIAN_FRONTEND=noninteractive
# Install gcloud SDK
RUN apt-get update && \
    apt-get install -y apt-utils git apt-transport-https gnupg ca-certificates curl \
    && echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \
    && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg \
    && apt-get update -y && apt-get install google-cloud-cli -y \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /workspace/vllm

# Copy entrypoint.sh to the container
COPY ./entrypoint.sh /workspace/vllm/vertexai/entrypoint.sh
RUN chmod +x /workspace/vllm/vertexai/entrypoint.sh

ENTRYPOINT ["/workspace/vllm/vertexai/entrypoint.sh"]

יוצרים את קובץ האימג' של הקונטיינר המותאם אישית באמצעות Cloud Build. בקובץ ההגדרות cloudbuild.yaml הבא מוצגות הוראות ליצירת קובץ האימג' למספר פלטפורמות באמצעות אותו קובץ Dockerfile.

steps:
-   name: 'gcr.io/cloud-builders/docker'
  automapSubstitutions: true
  script: |
      #!/usr/bin/env bash
      set -euo pipefail
      device_type_param=${_DEVICE_TYPE}
      device_type=${device_type_param,,}
      base_image=${_BASE_IMAGE}
      image_name="vllm-${_DEVICE_TYPE}"
      if [[ $device_type == "cpu" ]]; then
        echo "Quietly building open source vLLM CPU container image"
        git clone https://github.com/vllm-project/vllm.git
        cd vllm && DOCKER_BUILDKIT=1 docker build -t $base_image -f docker/Dockerfile.cpu . -q
        cd ..
      fi
      echo "Quietly building container image for: $device_type"
      docker build -t $LOCATION-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/$image_name --build-arg BASE_IMAGE=$base_image . -q
      docker push $LOCATION-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/$image_name
substitutions:
    _DEVICE_TYPE: gpu
    _BASE_IMAGE: vllm/vllm-openai
    _REPOSITORY: my-docker-repo

הקבצים זמינים במאגר googlecloudplatform/vertex-ai-samples GitHub. משכפלים את המאגר כדי להשתמש בהם:

git clone https://github.com/GoogleCloudPlatform/vertex-ai-samples.git

יצירה והעברה בדחיפה של תמונת הקונטיינר

יוצרים את קובץ האימג' של הקונטיינר בהתאמה אישית באמצעות Cloud Build על ידי שליחת הקובץ cloudbuild.yaml. משתמשים בהחלפות כדי לציין את סוג מכשיר היעד, שיכול להיות GPU,‏ TPU או CPU, ואת תמונת הבסיס המתאימה.

GPU

DEVICE_TYPE="gpu"
BASE_IMAGE="vllm/vllm-openai"
cd vertex-ai-samples/notebooks/official/prediction/vertexai_serving_vllm/cloud-build && \
gcloud builds submit \
    --config=cloudbuild.yaml \
    --region=LOCATION \
    --timeout="2h" \
    --machine-type=e2-highcpu-32 \
    --substitutions=_REPOSITORY=DOCKER_REPOSITORY,_DEVICE_TYPE=$DEVICE_TYPE,_BASE_IMAGE=$BASE_IMAGE

TPU

DEVICE_TYPE="tpu"
BASE_IMAGE="vllm/vllm-tpu:nightly"
cd vertex-ai-samples/notebooks/official/prediction/vertexai_serving_vllm/cloud-build && \
gcloud builds submit \
    --config=cloudbuild.yaml \
    --region=LOCATION \
    --timeout="2h" \
    --machine-type=e2-highcpu-32 \
    --substitutions=_REPOSITORY=DOCKER_REPOSITORY,_DEVICE_TYPE=$DEVICE_TYPE,_BASE_IMAGE=$BASE_IMAGE

CPU

DEVICE_TYPE="cpu"
BASE_IMAGE="vllm-cpu-base"
cd vertex-ai-samples/notebooks/official/prediction/vertexai_serving_vllm/cloud-build && \
gcloud builds submit \
    --config=cloudbuild.yaml \
    --region=LOCATION \
    --timeout="2h" \
    --machine-type=e2-highcpu-32 \
    --substitutions=_REPOSITORY=DOCKER_REPOSITORY,_DEVICE_TYPE=$DEVICE_TYPE,_BASE_IMAGE=$BASE_IMAGE

אחרי שה-build מסתיים, מגדירים את Docker לאימות ב-Artifact Registry:

gcloud auth configure-docker LOCATION-docker.pkg.dev --quiet

העלאת מודל למרשם המודלים ופריסה

כדי להעלות את המודל ל-מרשם המודלים, ליצור נקודת קצה ולפרוס את המודל, מבצעים את השלבים הבאים. בדוגמה הזו נעשה שימוש ב-Llama 3.2 3B, אבל אפשר להתאים אותה למודלים אחרים.

  1. מגדירים את המשתנים של המודל ושל הפריסה. מגדירים את המשתנה DOCKER_URI לתמונה שיצרתם בשלב הקודם (לדוגמה, ל-GPU):

    DOCKER_URI = f"LOCATION-docker.pkg.dev/PROJECT_ID/DOCKER_REPOSITORY/vllm-gpu"
    

    מגדירים משתנים עבור טוקן Hugging Face ומאפייני המודל. לדוגמה, כדי לפרוס GPU:

    hf_token = "your-hugging-face-auth-token"
    model_name = "gpu-llama3_2_3B-serve-vllm"
    model_id = "meta-llama/Llama-3.2-3B"
    machine_type = "g2-standard-8"
    accelerator_type = "NVIDIA_L4"
    accelerator_count = 1
    
  2. מעלים את המודל למרשם המודלים. הפונקציה upload_model משתנה מעט בהתאם לסוג המכשיר, בגלל ארגומנטים שונים של vLLM ומשתני סביבה שונים.

    from google.cloud import aiplatform
    
    def upload_model_gpu(model_name, model_id, hf_token, accelerator_count, docker_uri):
        vllm_args = [
            "python3", "-m", "vllm.entrypoints.openai.api_server",
            "--host=0.0.0.0", "--port=8080", f"--model={model_id}",
            "--max-model-len=2048", "--gpu-memory-utilization=0.9",
            "--enable-prefix-caching", f"--tensor-parallel-size={accelerator_count}",
        ]
        env_vars = {
            "HF_TOKEN": hf_token,
            "LD_LIBRARY_PATH": "$LD_LIBRARY_PATH:/usr/local/nvidia/lib64",
        }
        model = aiplatform.Model.upload(
            display_name=model_name,
            serving_container_image_uri=docker_uri,
            serving_container_args=vllm_args,
            serving_container_ports=[8080],
            serving_container_predict_route="/v1/completions",
            serving_container_health_route="/health",
            serving_container_environment_variables=env_vars,
            serving_container_shared_memory_size_mb=(16 * 1024),  # 16 GB
            serving_container_deployment_timeout=1800,
        )
        return model
    
    def upload_model_tpu(model_name, model_id, hf_token, tpu_count, docker_uri):
        vllm_args = [
            "python3", "-m", "vllm.entrypoints.openai.api_server",
            "--host=0.0.0.0", "--port=8080", f"--model={model_id}",
            "--max-model-len=2048", "--enable-prefix-caching",
            f"--tensor-parallel-size={tpu_count}",
        ]
        env_vars = {"HF_TOKEN": hf_token}
        model = aiplatform.Model.upload(
            display_name=model_name,
            serving_container_image_uri=docker_uri,
            serving_container_args=vllm_args,
            serving_container_ports=[8080],
            serving_container_predict_route="/v1/completions",
            serving_container_health_route="/health",
            serving_container_environment_variables=env_vars,
            serving_container_shared_memory_size_mb=(16 * 1024),  # 16 GB
            serving_container_deployment_timeout=1800,
        )
        return model
    
    def upload_model_cpu(model_name, model_id, hf_token, docker_uri):
        vllm_args = [
            "python3", "-m", "vllm.entrypoints.openai.api_server",
            "--host=0.0.0.0", "--port=8080", f"--model={model_id}",
            "--max-model-len=2048",
        ]
        env_vars = {"HF_TOKEN": hf_token}
        model = aiplatform.Model.upload(
            display_name=model_name,
            serving_container_image_uri=docker_uri,
            serving_container_args=vllm_args,
            serving_container_ports=[8080],
            serving_container_predict_route="/v1/completions",
            serving_container_health_route="/health",
            serving_container_environment_variables=env_vars,
            serving_container_shared_memory_size_mb=(16 * 1024),  # 16 GB
            serving_container_deployment_timeout=1800,
        )
        return model
    
    # Example for GPU:
    vertexai_model = upload_model_gpu(model_name, model_id, hf_token, accelerator_count, DOCKER_URI)
    
  3. יוצרים נקודת קצה.

    endpoint = aiplatform.Endpoint.create(display_name=f"model_name-endpoint")
    
  4. פורסים את המודל לנקודת קצה. פריסת המודל עשויה להימשך 20 עד 30 דקות.

    # Example for GPU:
    vertexai_model.deploy(
        endpoint=endpoint,
        deployed_model_display_name=model_name,
        machine_type=machine_type,
        accelerator_type=accelerator_type,
        accelerator_count=accelerator_count,
        traffic_percentage=100,
        deploy_request_timeout=1800,
        min_replica_count=1,
        max_replica_count=4,
        autoscaling_target_accelerator_duty_cycle=60,
    )
    

    ב-TPU, משמיטים את הפרמטרים accelerator_type ו-accelerator_count ומשתמשים ב-autoscaling_target_request_count_per_minute=60. למעבדים, משמיטים את הפרמטרים accelerator_type ו-accelerator_count ומשתמשים בפרמטר autoscaling_target_cpu_utilization=60.

טעינת מודלים מ-Cloud Storage

הקונטיינר המותאם אישית מוריד את המודל ממיקום ב-Cloud Storage במקום להוריד אותו מ-Hugging Face. כשמשתמשים ב-Cloud Storage:

  • מגדירים את הפרמטר model_id בפונקציה upload_model ל-URI של Cloud Storage, לדוגמה, gs://<var>my-bucket</var>/<var>my-models</var>/<var>llama_3_2_3B</var>.
  • כשמתקשרים אל upload_model, לא כוללים את המשתנה HF_TOKEN ב-env_vars.
  • כשקוראים ל-model.deploy, צריך לציין service_account עם הרשאות קריאה מקטגוריה של Cloud Storage.

יצירת חשבון שירות ב-IAM לגישה ל-Cloud Storage

אם המודל נמצא ב-Cloud Storage, צריך ליצור חשבון שירות שנקודות הקצה של Vertex Prediction יכולות להשתמש בו כדי לגשת לארטיפקטים של המודל.

SERVICE_ACCOUNT_NAME = "vertexai-endpoint-sa"
SERVICE_ACCOUNT_EMAIL = f"SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com"
gcloud iam service-accounts create SERVICE_ACCOUNT_NAME \
    --display-name="Agent Platform Endpoint Service Account"

# Grant storage read permission
gcloud projects add-iam-policy-binding PROJECT_ID \
    --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
    --role="roles/storage.objectViewer"

כשמבצעים פריסה, מעבירים את כתובת האימייל של חשבון השירות לשיטה deploy: service_account=<var>SERVICE_ACCOUNT_EMAIL</var>.

קבלת חיזויים באמצעות נקודת קצה

אחרי שמבצעים פריסה מוצלחת של המודל לנקודת הקצה, מאמתים את התגובה של המודל באמצעות raw_predict.

import json

PROMPT = "Distance of moon from earth is "
request_body = json.dumps(
    {
        "prompt": PROMPT,
        "temperature": 0.0,
    },
)

raw_response = endpoint.raw_predict(
    body=request_body, headers={"Content-Type": "application/json"}
)
assert raw_response.status_code == 200
result = json.loads(raw_response.text)

for choice in result["choices"]:
    print(choice)

פלט לדוגמה:

{
  "index": 0,
  "text": "384,400 km. The moon is 1/4 of the earth's",
  "logprobs": null,
  "finish_reason": "length",
  "stop_reason": null,
  "prompt_logprobs": null
}

המאמרים הבאים