Déployer des modèles ouverts avec un conteneur vLLM personnalisé

Bien que les différentes options de diffusion de modèles Vertex AI soient suffisantes pour de nombreux cas d'utilisation, vous devrez peut-être utiliser vos propres images de conteneur pour diffuser des modèles sur Vertex AI. Ce document explique comment utiliser une image de conteneur vLLM personnalisée pour diffuser des modèles sur Vertex AI sur des CPU, des GPU ou des TPU. Pour en savoir plus sur les modèles compatibles avec vLLM, consultez la documentation vLLM.

Le serveur d'API vLLM implémente le protocole de l'API OpenAI, mais il n'est pas compatible avec les exigences de Vertex AI concernant les requêtes et les réponses. Par conséquent, vous devez utiliser une demande d'inférence brute Vertex AI pour obtenir des inférences à partir de modèles déployés sur Vertex AI à l'aide d'un point de terminaison de prédiction. Pour en savoir plus sur la méthode de prédiction brute dans le SDK Vertex AI pour Python, consultez la documentation du SDK Python.

Vous pouvez obtenir des modèles à partir de Hugging Face et de Cloud Storage. Cette approche offre de la flexibilité, ce qui vous permet de profiter du hub de modèles axé sur la communauté (Hugging Face) et des fonctionnalités optimisées de transfert de données et de sécurité de Cloud Storage pour la gestion des modèles internes ou les versions affinées.

vLLM télécharge les modèles depuis Hugging Face si un jeton d'accès Hugging Face est fourni. Sinon, vLLM suppose que le modèle est disponible sur le disque local. L'image de conteneur personnalisée permet à Vertex AI de télécharger le modèle à partir deGoogle Cloud en plus de Hugging Face.

Avant de commencer

  1. Dans votre projet Google Cloud , activez les API Vertex AI et Artifact Registry.

    gcloud services enable aiplatform.googleapis.com \
        artifactregistry.googleapis.com
    
  2. Configurez la Google Cloud CLI avec votre ID de projet et initialisez le SDK Vertex AI.

    PROJECT_ID = "PROJECT_ID"
    LOCATION = "LOCATION"
    import vertexai
    vertexai.init(project=PROJECT_ID, location=LOCATION)
    
    gcloud config set project {PROJECT_ID}
    
  3. créer un dépôt Docker dans Artifact Registry ;

    gcloud artifacts repositories create DOCKER_REPOSITORY \
        --repository-format=docker \
        --location=LOCATION \
        --description="Vertex AI Docker repository"
    
  4. Facultatif : Si vous téléchargez des modèles depuis Hugging Face, obtenez un jeton Hugging Face.

    1. Créez un compte Hugging Face si vous n'en avez pas.
    2. Pour les modèles avec accès restreint comme Llama 3.2, demandez et obtenez l'accès sur Hugging Face avant de continuer.
    3. Générez un jeton d'accès : accédez à Votre profil > Paramètres > Jetons d'accès.
    4. Sélectionnez New Token (Nouveau jeton).
    5. Spécifiez un nom et un rôle d'au moins Lecture.
    6. Sélectionnez Générer un jeton.
    7. Enregistrez ce jeton pour les étapes de déploiement.

Préparer les fichiers de compilation du conteneur

Le Dockerfile suivant crée l'image de conteneur personnalisée vLLM pour les GPU, les TPU et les CPU. Ce conteneur personnalisé télécharge des modèles depuis Hugging Face ou 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"]

Créez l'image de conteneur personnalisée à l'aide de Cloud Build. Le fichier de configuration cloudbuild.yaml suivant montre comment créer l'image pour plusieurs plates-formes à l'aide du même fichier 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

Les fichiers sont disponibles dans le dépôt GitHub googlecloudplatform/vertex-ai-samples. Clonez le dépôt pour les utiliser :

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

Créer et transférer l'image de conteneur

Créez l'image de conteneur personnalisée à l'aide de Cloud Build en envoyant le fichier cloudbuild.yaml. Utilisez des substitutions pour spécifier le type d'appareil cible (GPU, TPU ou CPU) et l'image de base correspondante.

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

Processeur

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

Une fois la compilation terminée, configurez Docker pour qu'il s'authentifie auprès d'Artifact Registry :

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

Importer un modèle dans Model Registry et le déployer

Importez votre modèle dans Vertex AI Model Registry, créez un point de terminaison et déployez le modèle en suivant ces étapes. Cet exemple utilise Llama 3.2 3B, mais vous pouvez l'adapter à d'autres modèles.

  1. Définissez les variables de modèle et de déploiement. Définissez la variable DOCKER_URI sur l'image que vous avez créée à l'étape précédente (par exemple, pour le GPU) :

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

    Définissez les variables pour le jeton Hugging Face et les propriétés du modèle. Par exemple, pour le déploiement de 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. Importez le modèle dans Model Registry. La fonction upload_model varie légèrement en fonction du type d'appareil en raison des différents arguments vLLM et variables d'environnement.

    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. Créez un point de terminaison.

    endpoint = aiplatform.Endpoint.create(display_name=f"model_name-endpoint")
    
  4. Déployez le modèle sur le point de terminaison. Le déploiement du modèle peut prendre entre 20 et 30 minutes.

    # 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,
    )
    

    Pour les TPU, omettez les paramètres accelerator_type et accelerator_count, et utilisez autoscaling_target_request_count_per_minute=60. Pour les processeurs, omettez les paramètres accelerator_type et accelerator_count, et utilisez autoscaling_target_cpu_utilization=60.

Charger des modèles depuis Cloud Storage

Le conteneur personnalisé télécharge le modèle à partir d'un emplacement Cloud Storage au lieu de le télécharger à partir de Hugging Face. Lorsque vous utilisez Cloud Storage :

  • Définissez le paramètre model_id dans la fonction upload_model sur un URI Cloud Storage, par exemple gs://<var>my-bucket</var>/<var>my-models</var>/<var>llama_3_2_3B</var>.
  • Omettez la variable HF_TOKEN de env_vars lorsque vous appelez upload_model.
  • Lorsque vous appelez model.deploy, spécifiez un service_account qui dispose des autorisations nécessaires pour lire les données du bucket Cloud Storage.

Créer un compte de service IAM pour accéder à Cloud Storage

Si votre modèle se trouve dans Cloud Storage, créez un compte de service que les points de terminaison Vertex Prediction peuvent utiliser pour accéder aux artefacts du modèle.

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="Vertex AI Endpoint Service Account"

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

Lors du déploiement, transmettez l'adresse e-mail du compte de service à la méthode deploy : service_account=<var>SERVICE_ACCOUNT_EMAIL</var>.

Obtenir des prédictions à l'aide d'un point de terminaison

Une fois le modèle déployé sur le point de terminaison, vérifiez la réponse du modèle à l'aide de 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)

Exemple de résultat :

{
  "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
}

Étapes suivantes