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

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

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

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

אם מספקים טוקן גישה ל-Hugging Face, ‏vLLM מוריד את המודלים מ-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. יוצרים אסימון גישה: עוברים אל Your Profile > Settings > Access Tokens (הפרופיל שלך > הגדרות > אסימוני גישה).
    4. לוחצים על New Token (אסימון חדש).
    5. מציינים שם ותפקיד עם הרשאת קריאה לפחות.
    6. לוחצים על יצירת טוקן.
    7. שומרים את הטוקן הזה לשלבי הפריסה.

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

הקוד הבא של 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 מוסבר איך ליצור את קובץ האימג' לכמה פלטפורמות באמצעות אותו קובץ Docker.

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

פיתוח והעברה בדחיפה של קובץ האימג' של הקונטיינר

שולחים את הקובץ cloudbuild.yaml כדי ליצור את קובץ האימג' של הקונטיינר בהתאמה אישית באמצעות Cloud Build. משתמשים בהחלפות כדי לציין את סוג מכשיר היעד (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. למעבדי CPU, משמיטים את הפרמטרים 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>.
  • צריך להשמיט את המשתנה HF_TOKEN מ-env_vars כשמפעילים את upload_model.
  • כשמפעילים את model.deploy, צריך לציין service_account עם הרשאות קריאה מהקטגוריה של Cloud Storage.

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

אם המודל נמצא ב-Cloud Storage, צריך ליצור חשבון שירות שנקודות הקצה להסקת מסקנות ב-Gemini Enterprise Agent Platform יוכלו להשתמש בו כדי לגשת לארטיפקטים של המודל.

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"

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

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

אחרי שפורסים את המודל בנקודת הקצה, מאמתים את התשובה של המודל באמצעות 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
}

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