Sobre este codelab
1. Introdução
Visão geral
Neste codelab, você vai usar jobs do Cloud Run para ajustar um modelo do Gemma e, em seguida, exibir o resultado no Cloud Run usando o vLLM.
Para este codelab, você vai usar um conjunto de dados de texto para SQL, que tem como objetivo fazer com que o LLM responda com uma consulta SQL quando uma pergunta for feita em linguagem natural.
O que você vai aprender
- Como fazer a sintonia fina usando a GPU do Cloud Run Jobs
- Como oferecer um modelo usando o Cloud Run com vLLM
- Como usar a configuração da VPC direta em um job de GPU para fazer o upload e a exibição do modelo mais rapidamente
2. Antes de começar
Ativar APIs
Antes de começar a usar este codelab, ative as seguintes APIs executando:
gcloud services enable run.googleapis.com \
compute.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
secretmanager.googleapis.com \
artifactregistry.googleapis.com
Cota de GPU
Solicite um aumento de cota para uma região com suporte. A cota é nvidia_l4_gpu_allocation_no_zonal_redundancy
, na API Cloud Run Admin.
Observação: se você estiver usando um novo projeto, pode levar alguns minutos entre a ativação da API e a exibição das cotas nesta página.
Hugging face
Este codelab usa um modelo hospedado no Hugging Face. Para receber esse modelo, solicite o token de acesso do usuário do Hugging Face com a permissão "Ler". Você vai se referir a ele mais tarde como YOUR_HF_TOKEN
.
Você também precisa aceitar os termos de uso para usar o modelo: https://huggingface.co/google/gemma-2b
3. Configuração e requisitos
Configure os seguintes recursos:
- Conta de serviço do IAM e permissões associadas do IAM
- Secreto do Secret Manager para armazenar seu token do Hugging Face.
- Bucket do Cloud Storage para armazenar o modelo ajustado e
- Repositório do Artifact Registry para armazenar a imagem que você vai criar para ajustar seu modelo.
- Defina as variáveis de ambiente para este codelab. Preenchemos algumas variáveis para você. Especifique o ID do projeto, a região e o token do Hugging Face.
export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION=<YOUR_REGION>
export HF_TOKEN=<YOUR_HF_TOKEN>
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 - Execute este comando para criar a conta de serviço:
gcloud iam service-accounts create $SERVICE_ACCOUNT \
--display-name="Service account for fine-tuning codelab" - Use o Secret Manager para armazenar o token de acesso do Hugging Face:
gcloud secrets create $SECRET_ID \
--replication-policy="automatic"
printf $HF_TOKEN | gcloud secrets versions add $SECRET_ID --data-file=- - Conceda à conta de serviço o papel de Acessador de secrets do Gerenciador de secrets:
gcloud secrets add-iam-policy-binding $SECRET_ID \
--member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role='roles/secretmanager.secretAccessor' - Crie um bucket que hospedará seu modelo ajustado:
gcloud storage buckets create -l $REGION gs://$BUCKET_NAME
- Conceda à conta de serviço acesso ao bucket:
gcloud storage buckets add-iam-policy-binding gs://$BUCKET_NAME \
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role=roles/storage.objectAdmin - Crie um repositório do Artifact Registry para armazenar a imagem do contêiner:
gcloud artifacts repositories create $AR_REPO \
--repository-format=docker \
--location=$REGION \
--description="codelab for finetuning using CR jobs" \
--project=$PROJECT_ID
4. Criar a imagem do job do Cloud Run
Na próxima etapa, você vai criar o código que faz o seguinte:
- Importa o modelo Gemma do Hugging Face
- Faz o ajuste fino no modelo com o conjunto de dados do Hugging Face. O job usa uma única GPU L4 para ajuste fino.
- Faz o upload do modelo ajustado chamado
new_model
para seu bucket do Cloud Storage.
- Crie um diretório para o código do job de ajuste fino.
mkdir codelab-finetuning-job
cd codelab-finetuning-job - Crie um arquivo chamado
finetune.py
.# Copyright 2024 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 transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
)
from peft import LoraConfig, PeftModel
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-2b")
# The instruction dataset to use
dataset_name = "b-mc2/sql-create-context"
# Fine-tuned model name
new_model = os.getenv("NEW_MODEL", "gemma-2b-sql")
################################################################################
# QLoRA parameters
################################################################################
# LoRA attention dimension
lora_r = int(os.getenv("LORA_R", "4"))
# Alpha parameter for LoRA scaling
lora_alpha = int(os.getenv("LORA_ALPHA", "8"))
# Dropout probability for LoRA layers
lora_dropout = 0.1
################################################################################
# bitsandbytes parameters
################################################################################
# Activate 4-bit precision base model loading
use_4bit = True
# Compute dtype for 4-bit base models
bnb_4bit_compute_dtype = "float16"
# Quantization type (fp4 or nf4)
bnb_4bit_quant_type = "nf4"
# Activate nested quantization for 4-bit base models (double quantization)
use_nested_quant = False
################################################################################
# TrainingArguments parameters
################################################################################
# Output directory where the model predictions and checkpoints will be stored
output_dir = "./results"
# Number of training epochs
num_train_epochs = 1
# Enable fp16/bf16 training (set bf16 to True with an A100)
fp16 = True
bf16 = False
# Batch size per GPU for training
per_device_train_batch_size = int(os.getenv("TRAIN_BATCH_SIZE", "1"))
# Batch size per GPU for evaluation
per_device_eval_batch_size = int(os.getenv("EVAL_BATCH_SIZE", "2"))
# Number of update steps to accumulate the gradients for
gradient_accumulation_steps = int(os.getenv("GRADIENT_ACCUMULATION_STEPS", "1"))
# Enable gradient checkpointing
gradient_checkpointing = True
# Maximum gradient normal (gradient clipping)
max_grad_norm = 0.3
# Initial learning rate (AdamW optimizer)
learning_rate = 2e-4
# Weight decay to apply to all layers except bias/LayerNorm weights
weight_decay = 0.001
# Optimizer to use
optim = "paged_adamw_32bit"
# Learning rate schedule
lr_scheduler_type = "cosine"
# Number of training steps (overrides num_train_epochs)
max_steps = -1
# Ratio of steps for a linear warmup (from 0 to learning rate)
warmup_ratio = 0.03
# Group sequences into batches with same length
# Saves memory and speeds up training considerably
group_by_length = True
# Save checkpoint every X updates steps
save_steps = 0
# Log every X updates steps
logging_steps = int(os.getenv("LOGGING_STEPS", "50"))
################################################################################
# SFT parameters
################################################################################
# Maximum sequence length to use
max_seq_length = int(os.getenv("MAX_SEQ_LENGTH", "512"))
# Pack multiple short examples in the same input sequence to increase efficiency
packing = False
# Load the entire model on the GPU 0
device_map = {'':torch.cuda.current_device()}
# Set limit to a positive number
limit = int(os.getenv("DATASET_LIMIT", "5000"))
dataset = load_dataset(dataset_name, split="train")
if limit != -1:
dataset = dataset.shuffle(seed=42).select(range(limit))
def transform(data):
question = data['question']
context = data['context']
answer = data['answer']
template = "Question: {question}\nContext: {context}\nAnswer: {answer}"
return {'text': template.format(question=question, context=context, answer=answer)}
transformed = dataset.map(transform)
# Load tokenizer and model with QLoRA configuration
compute_dtype = getattr(torch, bnb_4bit_compute_dtype)
bnb_config = BitsAndBytesConfig(
load_in_4bit=use_4bit,
bnb_4bit_quant_type=bnb_4bit_quant_type,
bnb_4bit_compute_dtype=compute_dtype,
bnb_4bit_use_double_quant=use_nested_quant,
)
# Check GPU compatibility with bfloat16
if compute_dtype == torch.float16 and use_4bit:
major, _ = torch.cuda.get_device_capability()
if major >= 8:
print("=" * 80)
print("Your GPU supports bfloat16")
print("=" * 80)
# 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
# Load LLaMA tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
# Load LoRA configuration
peft_config = LoraConfig(
lora_alpha=lora_alpha,
lora_dropout=lora_dropout,
r=lora_r,
bias="none",
task_type="CAUSAL_LM",
target_modules=["q_proj", "v_proj"]
)
# Set training parameters
training_arguments = TrainingArguments(
output_dir=output_dir,
num_train_epochs=num_train_epochs,
per_device_train_batch_size=per_device_train_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
optim=optim,
save_steps=save_steps,
logging_steps=logging_steps,
learning_rate=learning_rate,
weight_decay=weight_decay,
fp16=fp16,
bf16=bf16,
max_grad_norm=max_grad_norm,
max_steps=max_steps,
warmup_ratio=warmup_ratio,
group_by_length=group_by_length,
lr_scheduler_type=lr_scheduler_type,
)
trainer = SFTTrainer(
model=model,
train_dataset=transformed,
peft_config=peft_config,
dataset_text_field="text",
max_seq_length=max_seq_length,
tokenizer=tokenizer,
args=training_arguments,
packing=packing,
)
trainer.train()
trainer.model.save_pretrained(new_model)
# 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 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) - Crie um arquivo
requirements.txt
: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 - Crie uma
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 - Crie o contêiner no repositório do Artifact Registry:
gcloud builds submit \
--tag $REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/$IMAGE_NAME \
--region $REGION
5. Implantar e executar o job
Nesta etapa, você vai criar a configuração YAML do seu job com saída de VPC direta para uploads mais rápidos no Google Cloud Storage.
Esse arquivo contém variáveis que você vai atualizar em uma etapa posterior.
- Crie um arquivo chamado
finetune-job.yaml.tmpl
:apiVersion: run.googleapis.com/v1
kind: Job
metadata:
name: $JOB_NAME
labels:
cloud.googleapis.com/location: $REGION
annotations:
run.googleapis.com/launch-stage: ALPHA
spec:
template:
metadata:
annotations:
run.googleapis.com/execution-environment: gen2
run.googleapis.com/network-interfaces: '[{"network":"default","subnetwork":"default"}]'
spec:
parallelism: 1
taskCount: 1
template:
spec:
serviceAccountName: $SERVICE_ACCOUNT_ADDRESS
containers:
- name: $IMAGE_NAME
image: $REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/$IMAGE_NAME
env:
- name: MODEL_NAME
value: "google/gemma-2b"
- name: NEW_MODEL
value: "gemma-2b-sql-finetuned"
- name: BUCKET_NAME
value: "$BUCKET_NAME"
- name: LORA_R
value: "8"
- name: LORA_ALPHA
value: "16"
- name: GRADIENT_ACCUMULATION_STEPS
value: "2"
- name: DATASET_LIMIT
value: "1000"
- name: LOGGING_STEPS
value: "5"
- name: HF_TOKEN
valueFrom:
secretKeyRef:
key: 'latest'
name: HF_TOKEN
resources:
limits:
cpu: 8000m
nvidia.com/gpu: '1'
memory: 32Gi
volumeMounts:
- mountPath: /finetune/new_model
name: finetuned_model
volumes:
- name: finetuned_model
csi:
driver: gcsfuse.run.googleapis.com
readOnly: false
volumeAttributes:
bucketName: $BUCKET_NAME
maxRetries: 3
timeoutSeconds: '3600'
nodeSelector:
run.googleapis.com/accelerator: nvidia-l4 - Substitua as variáveis no YAML pelas suas variáveis de ambiente executando o seguinte comando:
envsubst < finetune-job.yaml.tmpl > finetune-job.yaml
- Crie o job do Cloud Run:
gcloud alpha run jobs replace finetune-job.yaml
- Execute o job:
gcloud alpha run jobs execute $JOB_NAME --region $REGION --async
O job leva cerca de 10 minutos para ser concluído. É possível verificar o status usando o link fornecido na saída do último comando.
6. Usar um serviço do Cloud Run para oferecer seu modelo ajustado com o vLLM
Nesta etapa, você vai implantar um serviço do Cloud Run. Essa configuração usa a VPC direta para acessar o bucket do Cloud Storage pela rede privada e acelerar os downloads.
Esse arquivo contém variáveis que você vai atualizar em uma etapa posterior.
- Crie um arquivo
service.yaml.tmpl
:apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: serve-gemma-sql
labels:
cloud.googleapis.com/location: $REGION
annotations:
run.googleapis.com/launch-stage: BETA
run.googleapis.com/ingress: all
run.googleapis.com/ingress-status: all
spec:
template:
metadata:
labels:
annotations:
autoscaling.knative.dev/maxScale: '1'
run.googleapis.com/cpu-throttling: 'false'
run.googleapis.com/gpu-zonal-redundancy-disabled: 'true'
run.googleapis.com/network-interfaces: '[{"network":"default","subnetwork":"default"}]'
spec:
containers:
- name: serve-finetuned
image: us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-vllm-serve:20250505_0916_RC00
ports:
- name: http1
containerPort: 8000
resources:
limits:
cpu: 8000m
nvidia.com/gpu: '1'
memory: 32Gi
volumeMounts:
- name: fuse
mountPath: /finetune/new_model
command: ["python3", "-m", "vllm.entrypoints.api_server"]
args:
- --model=/finetune/new_model
- --tensor-parallel-size=1
env:
- name: MODEL_ID
value: 'new_model'
- name: HF_HUB_OFFLINE
value: '1'
volumes:
- name: fuse
csi:
driver: gcsfuse.run.googleapis.com
volumeAttributes:
bucketName: $BUCKET_NAME
nodeSelector:
run.googleapis.com/accelerator: nvidia-l4 - Atualize o arquivo
service.yaml
com o nome do bucket.envsubst < service.yaml.tmpl > service.yaml
- Implante seu serviço do Cloud Run:
gcloud alpha run services replace service.yaml
7. Testar o modelo ajustado
Nesta etapa, você vai pedir ao modelo para testar o ajuste fino.
- Acesse o URL do serviço do Cloud Run:
SERVICE_URL=$(gcloud run services describe serve-gemma-sql --platform managed --region $REGION --format 'value(status.url)')
- Crie o comando para o modelo.
USER_PROMPT="Question: What are the first name and last name of all candidates? Context: CREATE TABLE candidates (candidate_id VARCHAR); CREATE TABLE people (first_name VARCHAR, last_name VARCHAR, person_id VARCHAR)"
- Chame seu serviço usando o CURL para solicitar o modelo:
curl -X POST $SERVICE_URL/generate \
-H "Content-Type: application/json" \
-H "Authorization: bearer $(gcloud auth print-identity-token)" \
-d @- <<EOF
{
"prompt": "${USER_PROMPT}"
}
EOF
Uma resposta semelhante a esta vai aparecer:
{"predictions":["Prompt:\nQuestion: What are the first name and last name of all candidates? Context: CREATE TABLE candidates (candidate_id VARCHAR); CREATE TABLE people (first_name VARCHAR, last_name VARCHAR, person_id VARCHAR)\nOutput:\n CREATE TABLE people_to_candidates (candidate_id VARCHAR, person_id VARCHAR) CREATE TABLE people_to_people (person_id VARCHAR, person_id VARCHAR) CREATE TABLE people_to_people_to_candidates (person_id VARCHAR, candidate_id"]}
8. Parabéns!
Parabéns por concluir o codelab.
Recomendamos a leitura da documentação do Cloud Run.
O que aprendemos
- Como fazer a sintonia fina usando a GPU do Cloud Run Jobs
- Como oferecer um modelo usando o Cloud Run com vLLM
- Como usar a configuração da VPC direta em um job de GPU para fazer o upload e a veiculação do modelo mais rapidamente
9. Limpar
Para evitar cobranças acidentais, por exemplo, se os serviços do Cloud Run forem invocados acidentalmente mais vezes do que sua alocação mensal de invocação do Cloud Run no nível sem custo financeiro, exclua o serviço do Cloud Run criado na etapa 6.
Para excluir o serviço do Cloud Run, acesse o console do Cloud Run em https://console.cloud.google.com/run e exclua o serviço serve-gemma-sql
.
Para excluir o projeto inteiro, acesse Gerenciar recursos, selecione o projeto criado na etapa 2 e escolha "Excluir". Se você excluir o projeto, vai precisar mudar os projetos no Cloud SDK. Para conferir a lista de todos os projetos disponíveis, execute gcloud projects list
.