LLM mit Cloud Run-Jobs optimieren

1. Einführung

Übersicht

In diesem Codelab verwenden Sie Cloud Run-Jobs, um ein Gemma 3-Modell abzustimmen und das Ergebnis dann mit vLLM in Cloud Run bereitzustellen.

Aufgaben

Trainieren Sie ein Modell, um mit einem bestimmten Ergebnis auf einen bestimmten Ausdruck zu reagieren. Verwenden Sie dazu das Dataset KomeijiForce/Text2Emoji, das im Rahmen von EmojiLM: Modeling the New Emoji Language erstellt wurde.

Nach dem Training reagiert das Modell auf einen Satz, dem „Translate to emoji: “ vorangestellt ist, mit einer Reihe von Emojis, die dem Satz entsprechen.

Lerninhalte

  • Fine-Tuning mit Cloud Run Jobs GPU durchführen
  • Modell mit Cloud Run und vLLM bereitstellen
  • Konfiguration für die direkte VPC für einen GPU-Job verwenden, um das Modell schneller hochzuladen und bereitzustellen

2. Hinweis

APIs aktivieren

Bevor Sie mit diesem Codelab beginnen können, müssen Sie die folgenden APIs aktivieren:

gcloud services enable run.googleapis.com \
    compute.googleapis.com \
    run.googleapis.com \
    cloudbuild.googleapis.com \
    secretmanager.googleapis.com \
    artifactregistry.googleapis.com

GPU-Kontingent

In der Dokumentation zum GPU-Kontingent finden Sie Informationen zum Anfordern von Kontingenten.

Wenn Sie auf Fehler vom Typ „Sie haben kein Kontingent für die Verwendung von GPUs“ stoßen, prüfen Sie Ihr Kontingent unter g.co/cloudrun/gpu-quota.

Hinweis: Wenn Sie ein neues Projekt verwenden, kann es einige Minuten dauern, bis die Kontingente auf der Kontingentseite angezeigt werden.

Hugging Face

In diesem Codelab wird ein Modell verwendet, das auf Hugging Face gehostet wird. Wenn Sie dieses Modell verwenden möchten, fordern Sie das Hugging Face-Nutzerzugriffstoken mit der Berechtigung „Lesen“ an. Darauf wird später als YOUR_HF_TOKEN Bezug genommen.

Wenn Sie das Modell „gemma-3-1b-it“ verwenden möchten, müssen Sie den Nutzungsbedingungen zustimmen.

3. Einrichtung und Anforderungen

Richten Sie die folgenden Ressourcen ein:

  • IAM-Dienstkonto und zugehörige IAM-Berechtigungen
  • Secret Manager-Secret zum Speichern Ihres Hugging Face-Tokens
  • Cloud Storage-Bucket zum Speichern des feinabgestimmten Modells
  • Artifact Registry-Repository zum Speichern des Images, das Sie zum Feinabstimmen Ihres Modells erstellen.
  1. Umgebungsvariablen für dieses Codelab festlegen Wir haben bereits einige Variablen für Sie vorausgefüllt. Geben Sie Ihre Projekt-ID, Region und Ihr Hugging Face-Token an.
    export PROJECT_ID=<YOUR_PROJECT_ID>
    export REGION=<YOUR_REGION>
    export HF_TOKEN=<YOUR_HF_TOKEN>
    
    export NEW_MODEL=gemma-emoji
    export AR_REPO=codelab-finetuning-jobs
    export IMAGE_NAME=finetune-to-gcs
    export JOB_NAME=finetuning-to-gcs-job
    export BUCKET_NAME=$PROJECT_ID-codelab-finetuning-jobs
    export SECRET_ID=HF_TOKEN
    export SERVICE_ACCOUNT="finetune-job-sa"
    export SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
    
  2. Erstellen Sie das Dienstkonto mit dem folgenden Befehl:
    gcloud iam service-accounts create $SERVICE_ACCOUNT \
      --display-name="Service account for fine-tuning codelab"
    
  3. Hugging Face-Zugriffstoken mit Secret Manager speichern:
    gcloud secrets create $SECRET_ID \
          --replication-policy="automatic"
    
    printf $HF_TOKEN | gcloud secrets versions add $SECRET_ID --data-file=-
    
  4. Weisen Sie Ihrem Dienstkonto die Rolle „Secret Manager Secret Accessor“ zu:
    gcloud secrets add-iam-policy-binding $SECRET_ID \
      --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
      --role='roles/secretmanager.secretAccessor'
    
  5. Erstellen Sie einen Bucket, in dem Ihr feinabgestimmtes Modell gehostet wird:
    gcloud storage buckets create -l $REGION gs://$BUCKET_NAME
    
  6. Gewähren Sie Ihrem Dienstkonto Zugriff auf den Bucket:
    gcloud storage buckets add-iam-policy-binding gs://$BUCKET_NAME \
      --member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
      --role=roles/storage.objectAdmin
    
  7. Erstellen Sie ein Artifact Registry-Repository zum Speichern des Container-Images:
    gcloud artifacts repositories create $AR_REPO \
        --repository-format=docker \
        --location=$REGION \
        --description="codelab for finetuning using CR jobs" \
        --project=$PROJECT_ID
    

4. Cloud Run-Job-Image erstellen

Im nächsten Schritt erstellen Sie den Code, der Folgendes ausführt:

  • Importiert das Gemma-Modell aus Hugging Face
  • Führt die Feinabstimmung des Modells mit dem Dataset von Hugging Face durch. Für die Feinabstimmung wird eine einzelne L4-GPU verwendet.
  • Lädt das feinabgestimmte Modell mit dem Namen new_model in Ihren Cloud Storage-Bucket hoch
  1. Erstellen Sie ein Verzeichnis für den Code Ihres Feinabstimmungsjobs.
    mkdir codelab-finetuning-job
    cd codelab-finetuning-job
    
  2. Erstellen Sie eine Datei mit dem Namen finetune.py.
    # Copyright 2025 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    import os
    
    import torch
    from datasets import load_dataset
    from peft import LoraConfig, PeftModel
    from transformers import (
        AutoModelForCausalLM,
        AutoTokenizer,
        BitsAndBytesConfig,
        TrainingArguments,
    )
    from trl import SFTTrainer
    
    # Cloud Storage bucket to upload the model
    bucket_name = os.getenv("BUCKET_NAME", "YOUR_BUCKET_NAME")
    
    # The model that you want to train from the Hugging Face hub
    model_name = os.getenv("MODEL_NAME", "google/gemma-3-1b-it")
    
    # The instruction dataset to use
    dataset_name = "KomeijiForce/Text2Emoji"
    
    # Fine-tuned model name
    new_model = os.getenv("NEW_MODEL", "gemma-emoji")
    
    ############################ Setup ############################################
    
    # Load the entire model on the GPU 0
    device_map = {"": torch.cuda.current_device()}
    
    # Limit dataset to a random selection
    dataset = load_dataset(dataset_name, split="train").shuffle(seed=42).select(range(1000))
    
    # Setup input formats: trains the model to respond to "Translate to emoji:" with emoji output.
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    
    def format_to_chat(example):
        return {
            "conversations": [
                {"role": "user", "content": f"Translate to emoji: {example['text']}"},
                {"role": "assistant", "content": example["emoji"]},
            ]
        }
    
    formatted_dataset = dataset.map(
        format_to_chat,
        batched=False,                        # Process row by row
        remove_columns=dataset.column_names,  # Optional: Keep only the new column
    )
    
    def apply_chat_template(examples):
        texts = tokenizer.apply_chat_template(examples["conversations"], tokenize=False)
        return {"text": texts}
    
    final_dataset = formatted_dataset.map(apply_chat_template, batched=True)
    
    ############################# Config #########################################
    
    # Load tokenizer and model with QLoRA configuration
    bnb_4bit_compute_dtype = "float16"  # Compute dtype for 4-bit base models
    compute_dtype = getattr(torch, bnb_4bit_compute_dtype)
    
    bnb_config = BitsAndBytesConfig(
        load_in_4bit=True,  # Activate 4-bit precision base model loading
        bnb_4bit_quant_type="nf4",  # Quantization type (fp4 or nf4)
        bnb_4bit_compute_dtype=compute_dtype,
        bnb_4bit_use_double_quant=False,  # Activate nested quantization for 4-bit base models (double quantization)
    )
    
    # Load base model
    model = AutoModelForCausalLM.from_pretrained(
        model_name,
        quantization_config=bnb_config,
        device_map=device_map,
        torch_dtype=torch.float16,
    )
    model.config.use_cache = False
    model.config.pretraining_tp = 1
    
    ############################## Train ##########################################
    
    # Load LoRA configuration
    peft_config = LoraConfig(
        lora_alpha=16,     # Alpha parameter for LoRA scaling
        lora_dropout=0.1,  # Dropout probability for LoRA layers,
        r=8,               # LoRA attention dimension
        bias="none",
        task_type="CAUSAL_LM",
        target_modules=["q_proj", "v_proj"],
    )
    
    # Set training parameters
    training_arguments = TrainingArguments(
        output_dir="./results",
        num_train_epochs=1,
        per_device_train_batch_size=1,  # Batch size per GPU for training
        gradient_accumulation_steps=2,  # Number of update steps to accumulate the gradients for
        optim="paged_adamw_32bit",
        save_steps=0,
        logging_steps=5,
        learning_rate=2e-4,    # Initial learning rate (AdamW optimizer)
        weight_decay=0.001,    # Weight decay to apply to all layers except bias/LayerNorm weights
        fp16=True, bf16=False, # Enable fp16/bf16 training
        max_grad_norm=0.3,     # Maximum gradient normal (gradient clipping)
        warmup_ratio=0.03,     # Ratio of steps for a linear warmup (from 0 to learning rate)
        group_by_length=True,  # Group sequences into batches with same length # Saves memory and speeds up training considerably
        lr_scheduler_type="cosine",
    )
    
    trainer = SFTTrainer(
        model=model,
        train_dataset=final_dataset,
        peft_config=peft_config,
        dataset_text_field="text",
        max_seq_length=512,  # Maximum sequence length to use
        tokenizer=tokenizer,
        args=training_arguments,
        packing=False,       # Pack multiple short examples in the same input sequence to increase efficiency
    )
    
    trainer.train()
    trainer.model.save_pretrained(new_model)
    
    ################################# Save ########################################
    
    # Reload model in FP16 and merge it with LoRA weights
    base_model = AutoModelForCausalLM.from_pretrained(
        model_name,
        low_cpu_mem_usage=True,
        return_dict=True,
        torch_dtype=torch.float16,
        device_map=device_map,
    )
    model = PeftModel.from_pretrained(base_model, new_model)
    model = model.merge_and_unload()
    
    # push results to Cloud Storage
    file_path_to_save_the_model = "/finetune/new_model"
    model.save_pretrained(file_path_to_save_the_model)
    tokenizer.save_pretrained(file_path_to_save_the_model)
    
    
  3. requirements.txt-Datei erstellen:
    accelerate==0.34.2
    bitsandbytes==0.45.5
    datasets==2.19.1
    transformers==4.51.3
    peft==0.11.1
    trl==0.8.6
    torch==2.3.0
    
  4. Erstellen Sie ein Dockerfile:
    FROM nvidia/cuda:12.6.2-runtime-ubuntu22.04
    
    RUN apt-get update && \
        apt-get -y --no-install-recommends install python3-dev gcc python3-pip git && \
        rm -rf /var/lib/apt/lists/*
    
    COPY requirements.txt /requirements.txt
    
    RUN pip3 install -r requirements.txt --no-cache-dir
    
    COPY finetune.py /finetune.py
    
    ENV PYTHONUNBUFFERED 1
    
    CMD python3 /finetune.py --device cuda
    
  5. Erstellen Sie den Container in Ihrem Artifact Registry-Repository:
    gcloud builds submit \
      --tag $REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/$IMAGE_NAME \
      --region $REGION
    

5. Job bereitstellen und ausführen

In diesem Schritt erstellen Sie den Job mit ausgehendem Direct VPC-Traffic für schnellere Uploads in Google Cloud Storage.

  1. Cloud Run-Job erstellen:
    gcloud run jobs create $JOB_NAME \
      --region $REGION \
      --image $REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/$IMAGE_NAME \
      --set-env-vars BUCKET_NAME=$BUCKET_NAME \
      --set-secrets HF_TOKEN=$SECRET_ID:latest \
      --cpu 8.0 \
      --memory 32Gi \
      --gpu 1 \
      --add-volume name=finetuned_model,type=cloud-storage,bucket=$BUCKET_NAME \
      --add-volume-mount volume=finetuned_model,mount-path=/finetune/new_model \
      --service-account $SERVICE_ACCOUNT_ADDRESS
    
  2. Job ausführen:
    gcloud run jobs execute $JOB_NAME --region $REGION --async
    

Die Ausführung des Jobs dauert etwa 10 Minuten. Den Status können Sie über den Link in der Ausgabe des letzten Befehls prüfen.

6. Cloud Run-Dienst zum Bereitstellen Ihres feinabgestimmten Modells mit vLLM verwenden

In diesem Schritt stellen Sie einen Cloud Run-Dienst bereit. Bei dieser Konfiguration wird Direct VPC verwendet, um über ein privates Netzwerk auf den Cloud Storage-Bucket zuzugreifen und so schnellere Downloads zu ermöglichen.

  • Cloud Run-Dienst bereitstellen:
    gcloud run deploy serve-gemma-emoji \
      --image us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-vllm-serve:20250601_0916_RC01 \
      --region $REGION \
      --port 8000 \
      --set-env-vars MODEL_ID=new_model,HF_HUB_OFFLINE=1 \
      --cpu 8.0 \
      --memory 32Gi \
      --gpu 1 \
      --add-volume name=finetuned_model,type=cloud-storage,bucket=$BUCKET_NAME \
      --add-volume-mount volume=finetuned_model,mount-path=/finetune/new_model \
      --service-account $SERVICE_ACCOUNT_ADDRESS \
      --max-instances 1 \
      --command python3 \
      --args="-m,vllm.entrypoints.api_server,--model=/finetune/new_model,--tensor-parallel-size=1" \
      --no-gpu-zonal-redundancy \
      --labels=dev-tutorial=codelab-tuning \
      --no-invoker-iam-check
    

7. Abgestimmtes Modell testen

In diesem Schritt fordern Sie Ihr Modell auf, die Feinabstimmung mit „curl“ zu testen.

  1. Rufen Sie die Dienst-URL für Ihren Cloud Run-Dienst ab:
    SERVICE_URL=$(gcloud run services describe serve-gemma-emoji \
        --region $REGION --format 'value(status.url)')
    
  2. Erstellen Sie den Prompt für Ihr Modell.
    USER_PROMPT="Translate to emoji: I ate a banana for breakfast, later I'm thinking of having soup!"
    
  3. Rufen Sie Ihren Dienst mit curl auf, um Ihr Modell zu prompten und die Ergebnisse mit jq zu filtern:
    curl -s -X POST ${SERVICE_URL}/v1/chat/completions \
    -H "Content-Type: application/json" \
    -H "Authorization: bearer $(gcloud auth print-identity-token)" \
    -d @- <<EOF | jq ".choices[0].message.content"
    {   "model": "${NEW_MODEL}",
        "messages": [{
            "role": "user",
            "content": [ { "type": "text", "text": "${USER_PROMPT}"}]
        }]
    }
    EOF
    
    

Die Antwort sieht ungefähr so aus:

🍌🤔😋🥣

8. Glückwunsch!

Herzlichen Glückwunsch zum Abschluss des Codelabs!

Wir empfehlen, die Dokumentation zu Cloud Run Jobs – GPU zu lesen.

Behandelte Themen

  • Fine-Tuning mit Cloud Run Jobs GPU durchführen
  • Modell mit Cloud Run und vLLM bereitstellen
  • Konfiguration für die direkte VPC für einen GPU-Job verwenden, um das Modell schneller hochzuladen und bereitzustellen

9. Bereinigen

Um unbeabsichtigte Gebühren zu vermeiden, z. B. wenn die Cloud Run-Dienste versehentlich öfter aufgerufen werden als Ihre monatliche Cloud Run-Aufrufkontingent im kostenlosen Kontingent, können Sie den in Schritt 6 erstellten Cloud Run-Dienst löschen.

Wenn Sie den Cloud Run-Dienst löschen möchten, rufen Sie die Cloud Run Console unter https://console.cloud.google.com/run auf und löschen Sie den serve-gemma-emoji-Dienst.

Wenn Sie das gesamte Projekt löschen möchten, rufen Sie Ressourcen verwalten auf, wählen Sie das Projekt aus, das Sie in Schritt 2 erstellt haben, und klicken Sie auf „Löschen“. Wenn Sie das Projekt löschen, müssen Sie das Projekt in Ihrem Cloud SDK ändern. Sie können die Liste aller verfügbaren Projekte mit gcloud projects list aufrufen.