1. מבוא
סקירה כללית
בשיעור הקוד הזה תלמדו איך להשתמש במשימות של Cloud Run כדי לשפר את המודל של Gemma, ואז להציג את התוצאה ב-Cloud Run באמצעות vLLM.
במסגרת הקודלאב הזה, תשתמשו במערך נתונים מסוג טקסט ל-SQL, שנועד לגרום ל-LLM להשיב בשאילתת SQL כששואלים אותו שאלה בשפה טבעית.
מה תלמדו
- איך מבצעים שיפורים באמצעות GPU של Cloud Run Jobs
- איך להציג מודל באמצעות Cloud Run עם vLLM
- איך משתמשים בהגדרה של VPC ישיר למשימה ב-GPU כדי להעלות את המודל ולספק אותו מהר יותר
2. לפני שמתחילים
הפעלת ממשקי API
לפני שמתחילים להשתמש בקודלאב הזה, צריך להפעיל את ממשקי ה-API הבאים:
gcloud services enable run.googleapis.com \
compute.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
secretmanager.googleapis.com \
artifactregistry.googleapis.com
מכסת GPU
מגישים בקשה להגדלת המכסה באזור נתמך. המכסה היא nvidia_l4_gpu_allocation_no_zonal_redundancy
, בקטע Cloud Run Admin API.
הערה: אם אתם משתמשים בפרויקט חדש, יכול להיות שיחלפו כמה דקות בין הפעלת ה-API לבין הופעת המכסות בדף הזה.
פרצוף מחבק
בקודלאב הזה נעשה שימוש במודל שמתארח ב-Hugging Face. כדי לקבל את המודל הזה, צריך לבקש אסימון גישה של משתמש ב-Hugging Face עם הרשאת 'קריאה'. בהמשך נתייחס אליו בתור YOUR_HF_TOKEN
.
כדי להשתמש במודל, תצטרכו גם לאשר את תנאי השימוש: https://huggingface.co/google/gemma-2b
3. הגדרה ודרישות
מגדירים את המשאבים הבאים:
- חשבון שירות ב-IAM והרשאות IAM משויכות,
- סוד ב-Secret Manager לאחסון הטוקן של Hugging Face,
- קטגוריה של Cloud Storage לאחסון המודל שעבר שינוי ועדכון,
- מאגר Artifact Registry לאחסון קובץ האימג' שתיצרו כדי לשפר את המודל.
- מגדירים את משתני הסביבה ל-Codelab הזה. מילאנו מראש כמה משתנים. מציינים את מזהה הפרויקט, האזור ואת האסימון של 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
- יוצרים את חשבון השירות באמצעות הפקודה הבאה:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Service account for fine-tuning codelab"
- משתמשים ב-Secret Manager כדי לאחסן אסימון גישה של Hugging Face:
gcloud secrets create $SECRET_ID \ --replication-policy="automatic" printf $HF_TOKEN | gcloud secrets versions add $SECRET_ID --data-file=-
- מקצים לחשבון השירות את התפקיד 'גישה לסוד ב-Secret Manager':
gcloud secrets add-iam-policy-binding $SECRET_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role='roles/secretmanager.secretAccessor'
- יוצרים קטגוריה שתארחת את המודל שעבר כוונון עדין:
gcloud storage buckets create -l $REGION gs://$BUCKET_NAME
- מעניקים לחשבון השירות גישה לקטגוריה:
gcloud storage buckets add-iam-policy-binding gs://$BUCKET_NAME \ --member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/storage.objectAdmin
- יוצרים מאגר ב-Artifact Registry לאחסון קובץ האימג' בקונטיינר:
gcloud artifacts repositories create $AR_REPO \ --repository-format=docker \ --location=$REGION \ --description="codelab for finetuning using CR jobs" \ --project=$PROJECT_ID
4. יצירת קובץ האימג' של המשימה ב-Cloud Run
בשלב הבא תיצורו את הקוד שמבצע את הפעולות הבאות:
- ייבוא מודל Gemma מ-Hugging Face
- ביצוע שיפורים קטנים במודל באמצעות מערך הנתונים מ-Hugging Face. המשימה משתמשת ב-GPU יחיד מסוג L4 לצורך כוונון עדין.
- העלאת המודל המכוונן שנקרא
new_model
לקטגוריה שלכם ב-Cloud Storage
- יוצרים ספרייה לקוד המשימה של השיפור.
mkdir codelab-finetuning-job cd codelab-finetuning-job
- יוצרים קובץ בשם
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)
- יוצרים קובץ
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
- יצירת
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
- יוצרים את הקונטיינר במאגר Artifact Registry:
gcloud builds submit \ --tag $REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/$IMAGE_NAME \ --region $REGION
5. פריסה והפעלה של המשימה
בשלב הזה, תיצורו את קובץ התצורה של ה-YAML למשימה עם תעבורת נתונים יוצאת (egress) ישירה ב-VPC, כדי להעלות נתונים מהר יותר ל-Google Cloud Storage.
שימו לב שהקובץ הזה מכיל משתנים שתעדכנו בשלב הבא.
- יוצרים קובץ בשם
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
- מריצים את הפקודה הבאה כדי להחליף את המשתנים ב-YAML במשתני הסביבה:
envsubst < finetune-job.yaml.tmpl > finetune-job.yaml
- יוצרים את המשימה ב-Cloud Run:
gcloud alpha run jobs replace finetune-job.yaml
- מריצים את המשימה:
gcloud alpha run jobs execute $JOB_NAME --region $REGION --async
השלמת המשימה תימשך כ-10 דקות. אפשר לבדוק את הסטטוס באמצעות הקישור שסופק בפלט של הפקודה האחרונה.
6. שימוש בשירות Cloud Run כדי להציג את המודל שעבר שינוי ותיקון באמצעות vLLM
בשלב הזה, פורסים שירות Cloud Run. בהגדרה הזו נעשה שימוש ב-VPC ישיר כדי לגשת לקטגוריה של Cloud Storage דרך רשת פרטית, וכך להאיץ את ההורדות.
שימו לב שהקובץ הזה מכיל משתנים שתעדכנו בשלב מאוחר יותר.
- יוצרים קובץ
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
- מעדכנים את קובץ
service.yaml
בשם הקטגוריה.envsubst < service.yaml.tmpl > service.yaml
- פורסים את שירות Cloud Run:
gcloud alpha run services replace service.yaml
7. בדיקת המודל שעבר כוונון עדין
בשלב הזה, תבקשו מהמודל לבדוק את השיפורים.
- מקבלים את כתובת ה-URL של השירות ב-Cloud Run:
SERVICE_URL=$(gcloud run services describe serve-gemma-sql --platform managed --region $REGION --format 'value(status.url)')
- יוצרים את ההנחיה למודל.
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)"
- קוראים לשירות באמצעות CURL כדי להפעיל את המודל:
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
התגובה אמורה להיראות כך:
{"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. מעולה!
כל הכבוד על השלמת ה-Codelab!
מומלץ לעיין במסמכי העזרה של Cloud Run.
מה עסקנו בו
- איך מבצעים שיפורים באמצעות GPU של Cloud Run Jobs
- איך להציג מודל באמצעות Cloud Run עם vLLM
- איך משתמשים בהגדרה של VPC ישיר למשימה ב-GPU כדי להעלות את המודל ולספק אותו מהר יותר
9. הסרת המשאבים
כדי להימנע מחיובים לא מכוונים, לדוגמה, אם שירותי Cloud Run מופעלים בטעות יותר פעמים מההקצאה החודשית שלכם להפעלות של Cloud Run ברמה החינמית, אתם יכולים למחוק את שירות Cloud Run שיצרתם בשלב 6.
כדי למחוק את שירות Cloud Run, נכנסים למסוף Cloud של Cloud Run בכתובת https://console.cloud.google.com/run ומוחקים את השירות serve-gemma-sql
.
כדי למחוק את הפרויקט כולו, עוברים אל Manage Resources (ניהול משאבים), בוחרים את הפרויקט שיצרתם בשלב 2 ובוחרים באפשרות Delete (מחיקה). אם תמחקו את הפרויקט, תצטרכו לשנות את הפרויקטים ב-Cloud SDK. כדי להציג את רשימת כל הפרויקטים הזמינים, מריצים את הפקודה gcloud projects list
.