כוונון של Gemma 4 באשכול GKE A4 מרובה-מארחים

במדריך הזה נסביר איך לבצע כוונון עדין של מודל שפה גדול (google/gemma-4-31b-it) של Gemma 4 31B באשכול Autopilot של Google Kubernetes Engine ‏ (GKE) עם כמה מארחים וכמה יחידות GPU ב- Google Cloud. באשכול הזה נעשה שימוש בשתי מכונות וירטואליות (VM) מסוג A4 (a4-highgpu-8g) עם סך של 16 מעבדי GPU מסוג NVIDIA B200.

שלושת התהליכים העיקריים שמתוארים במדריך הזה הם:

  1. פריסת אשכול GKE מרובה מארחים במצב אוטומטי.
  2. יוצרים קובץ אימג' של קונטיינר מותאם אישית עם יחסי התלות הנדרשים לצורך כוונון עדין באמצעות Cloud Build.
  3. ארגון של עומס עבודה מבוזר של כוונון עדין במארחים מרובים ב-16 יחידות GPU באמצעות JobSet של Kubernetes וספריית Hugging Face Accelerate עם Fully Sharded Data Parallel v2 ‏ (FSDP v2), ושמירת נקודות ביקורת ב-Hugging Face Hub.

המדריך הזה מיועד למהנדסי למידת מכונה (ML), חוקרים, מנהלי פלטפורמות ואופרטורים, ומומחי נתונים ו-AI שפורסים אשכולות GKE ב- Google Cloud כדי לבצע כוונון עדין של מודלים גדולים של שפה (LLM) במספר מארחים.

מטרות

  • אפשר לגשת למודל Gemma 4 באמצעות Hugging Face.

  • מכינים את הסביבה.

  • יצירה ופריסה של אשכול GKE A4 מרובה-מארחים.

  • אפשר לבצע כוונון עדין של מודל Gemma 4 31B ב-16 מעבדי GPU באמצעות Kubernetes JobSet ו-Hugging Face Accelerate עם FSDP v2.

  • עוקבים אחרי העבודה.

  • אפשר לראות את המשקלים של המתאמים שעברו כוונון ב-Hugging Face Hub.

  • לפנות.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Google Cloud, והשימוש בהם כרוך בתשלום:

כדי ליצור הערכת עלויות בהתאם לשימוש החזוי, אתם יכולים להשתמש במחשבון התמחור.

משתמשים חדשים של Google Cloud ? יכול להיות שאתם זכאים לתקופת ניסיון בחינם.

לפני שמתחילים

כדי לקבל את ההרשאות שדרושות להשלמת המדריך הזה, צריך לבקש מהאדמין להקצות לכם בפרויקט את תפקידי ה-IAM הבאים:

להסבר על מתן תפקידים, ראו איך מנהלים את הגישה ברמת הפרויקט, התיקייה והארגון.

יכול להיות שאפשר לקבל את ההרשאות הנדרשות גם באמצעות תפקידים בהתאמה אישית או תפקידים מוגדרים מראש.

  1. מפעילים את ממשקי ה-API הנדרשים, אם יש כאלה שלא מופעלים כבר:

    תפקידים שנדרשים להפעלת ממשקי API

    כדי להפעיל ממשקי API, צריך את ההרשאה serviceusage.services.enable. אם יצרתם את הפרויקט, סביר להניח שכבר יש לכם את ההרשאה הזו דרך התפקיד 'בעלים' (roles/owner). אחרת, תוכלו לקבל את ההרשאה הזו דרך התפקיד 'אדמין בממשק Service Usage' (roles/serviceusage.serviceUsageAdmin). איך מקצים תפקידים

    gcloud services enable compute.googleapis.com container.googleapis.com artifactregistry.googleapis.com cloudbuild.googleapis.com logging.googleapis.com cloudresourcemanager.googleapis.com servicenetworking.googleapis.com
  2. מפעילים את חשבון השירות שמוגדר כברירת מחדל ב-Compute Engine עבור הפרויקטGoogle Cloud :

    export PROJECT_NUMBER="$(gcloud projects describe "YOUR_PROJECT_ID" --format "value(project_number)")"
    gcloud iam service-accounts enable "${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
        --project=YOUR_PROJECT_ID
  3. מקצים את תפקידי ה-IAM עם ההרשאות המינימליות שחשבון השירות שמוגדר כברירת מחדל ב-Compute Engine צריך כדי ליצור את קובץ אימג' של קונטיינר ולהריץ את עומס העבודה של הכוונון המדויק:

    ROLES=(
      "roles/artifactregistry.writer"
      "roles/cloudbuild.builds.builder"
      "roles/logging.logWriter"
      "roles/monitoring.metricWriter"
      "roles/monitoring.viewer"
      "roles/stackdriver.resourceMetadata.writer"
      "roles/storage.objectViewer"
    )
    for role in "${ROLES[@]}"; do
      gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
          --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
          --role="${role}" 1>/dev/null
    done
    unset ROLES
  4. מוודאים שהתפקידים הוקצו לחשבון השירות שמוגדר כברירת מחדל ב-Compute Engine:

    echo "Displaying roles for ${PROJECT_NUMBER}-compute@developer.gserviceaccount.com:"
    gcloud projects get-iam-policy YOUR_PROJECT_ID \
        --flatten="bindings[].members" \
        --filter="bindings.members:serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
        --format="table(bindings.role)"
  5. יצירת פרטי כניסה לאימות מקומי בחשבון המשתמש:

    ‫
    gcloud auth application-default login
  6. מפעילים את OS Login בפרויקט:

    gcloud compute project-info add-metadata \
        --metadata=enable-oslogin=TRUE \
        --project=YOUR_PROJECT_ID

גישה ל-Gemma 4 באמצעות Hugging Face

כדי להשתמש ב-Hugging Face כדי לגשת ל-Gemma 4, צריך לבצע את השלבים הבאים:

  1. נכנסים לחשבון ב-Hugging Face ומאשרים את הסכם הרישיון של Gemma 4.
  2. יוצרים טוקן גישה ל-Hugging Face write.
    לוחצים על Your Profile > Settings > Access tokens > +Create new token.
  3. מעתיקים ושומרים את הערך של write אסימון הגישה. משתמשים באסימון הזה כדי להוריד את מודל הבסיס ולדחוף את נקודות הבדיקה של המתאמים המכווננים אל Hugging Face Hub לפני ש-GKE מצמצם את מספר צמתי ה-GPU.

הכנת הסביבה

כדי להכין את הסביבה, מגדירים את משתני הסביבה הבאים:

export PROJECT_ID="YOUR_PROJECT_ID"
export CLUSTER_NAME="YOUR_CLUSTER_NAME"
export CLUSTER_REGION="YOUR_REGION"
export RESERVATION="YOUR_RESERVATION_NAME"
export HF_TOKEN="YOUR_HF_TOKEN"
export ARTIFACT_REPO_LOCATION="YOUR_ARTIFACT_REGISTRY_LOCATION"
export NUM_NODES="YOUR_NUMBER_OF_NODES"

gcloud config set project "${PROJECT_ID}"
gcloud config set billing/quota_project "${PROJECT_ID}"

מחליפים את מה שכתוב בשדות הבאים:

  • ‫YOUR_PROJECT_ID: המזהה של Google Cloud הפרויקט שבו רוצים ליצור את אשכול GKE.

  • ‫YOUR_CLUSTER_NAME: השם של אשכול GKE שרוצים ליצור.

  • ‫YOUR_REGION: האזור שבו רוצים ליצור את אשכול GKE. אפשר ליצור את האשכול רק באזור שבו קיימת ההזמנה.

  • ‫YOUR_RESERVATION_NAME: המזהה של הקיבולת המוזמנת.

  • ‫YOUR_HF_TOKEN: אסימון הגישה ל-Hugging Face‏ write שיצרתם בקטע הקודם.

  • ‫YOUR_ARTIFACT_REGISTRY_LOCATION: Google Cloud האזור (לדוגמה, us-central1) שבו רוצים ליצור את מאגר Artifact Registry. כדי לצמצם את זמן האחזור של שליפת התמונה, צריך להשתמש באותו אזור שצוין עבור YOUR_REGION.

  • ‫YOUR_NUMBER_OF_NODES: מספר הצמתים של מכונות וירטואליות מסוג A4 במשימת הכוונון העדין. במדריך הזה לשימוש בכמה מארחים עם 16 יחידות GPU מסוג NVIDIA B200 בשני מופעים של a4-highgpu-8g, צריך להגדיר את הערך הזה ל-2.

יצירת אשכול GKE עם כמה מארחים במצב Autopilot

יצירת אשכול GKE עם כמה מארחים במצב Autopilot:

gcloud container clusters create-auto "${CLUSTER_NAME}" \
    --project="${PROJECT_ID}" \
    --location="${CLUSTER_REGION}"

יצירת אשכול GKE עשויה להימשך כמה דקות. כדי לוודא ש- Google Cloud סיים ליצור את האשכול, עוברים אל Kubernetes clusters במסוף Google Cloud .

הגדרת kubectl לתקשורת עם אשכול GKE

מגדירים את kubectl לתקשורת עם אשכול GKE:

gcloud container clusters get-credentials "${CLUSTER_NAME}" \
    --location="${CLUSTER_REGION}"

יצירת סוד ב-Kubernetes לפרטי הכניסה של Hugging Face

יוצרים סוד של Kubernetes לאחסון הטוקן של Hugging Face:

kubectl create secret generic hf-secret \
    --from-literal=hf_api_token="${HF_TOKEN}" \
    --dry-run=client -o yaml | kubectl apply -f -

הכנת עומס העבודה

כדי להכין את עומס העבודה, מבצעים את הפעולות הבאות:

  1. יצירת סקריפטים של עומסי עבודה.

  2. שימוש ב-Docker וב-Cloud Build כדי ליצור קונטיינר לכוונון עדין.

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

כדי ליצור את קובצי התצורה והסקריפטים שעומס העבודה של הכוונון העדין משתמש בהם, מבצעים את השלבים הבאים:

  1. יוצרים ספרייה לסקריפטים של עומס העבודה. משתמשים בספרייה הזו כספריית העבודה.

    mkdir llm-finetuning-gemma
    cd llm-finetuning-gemma
  2. יוצרים את הקובץ cloudbuild.yaml כדי ליצור את קובץ האימג' של הקונטיינר של עומס העבודה באמצעות Cloud Build ולהעביר אותו בדחיפה ל-Artifact Registry:

    steps:
    - name: 'gcr.io/cloud-builders/docker'
      args:
      - 'build'
      - '-t'
      - '$_ARTIFACT_REPO_LOCATION-docker.pkg.dev/$PROJECT_ID/gemma/finetune-gemma-multihost-gpu:2.0.0'
      - '.'
    images:
    - '$_ARTIFACT_REPO_LOCATION-docker.pkg.dev/$PROJECT_ID/gemma/finetune-gemma-multihost-gpu:2.0.0'
    options:
      logging: CLOUD_LOGGING_ONLY
  3. יוצרים קובץ Dockerfile כדי להגדיר את הסביבה ולהתקין את התלות שנדרשת להשלמת משימת הכוונון העדין:

    FROM nvidia/cuda:12.8.1-cudnn-devel-ubuntu24.04
    RUN apt-get update && \
        apt-get -y install python3 python3-dev gcc python3-pip \
            python3-venv git curl vim && \
        rm -rf /var/lib/apt/lists/*
    RUN python3 -m venv /opt/venv
    ENV PATH="/opt/venv/bin:/usr/local/nvidia/bin:$PATH"
    ENV LD_LIBRARY_PATH="/usr/local/nvidia/lib64:$LD_LIBRARY_PATH"
    RUN pip3 install setuptools wheel packaging ninja
    RUN pip3 install torch torchvision torchaudio \
        --index-url https://download.pytorch.org/whl/cu128
    RUN pip3 install \
        "transformers>=5.5.0" \
        trl==0.29.1 \
        peft==0.18.1 \
        accelerate==1.13.0 \
        bitsandbytes==0.49.2 \
        datasets==4.8.4 \
        evaluate==0.4.5 \
        tensorboard==2.20.0 \
        protobuf==6.31.1 \
        sentencepiece==0.2.0
    WORKDIR /workspace
    COPY finetune.py /workspace/finetune.py
    COPY accel_fsdp_gemma4_config.yaml /workspace/accel_fsdp_gemma4_config.yaml
    CMD ["accelerate", "launch", "--config_file", "/workspace/accel_fsdp_gemma4_config.yaml", "/workspace/finetune.py"]
  4. יוצרים את קובץ ה-accel_fsdp_gemma4_config.yaml. ההגדרה הזו מכוונת את Hugging Face Accelerate לחלוקת Gemma4TextDecoderLayer בין 16 מעבדי GPU בשני מארחים באמצעות FSDP גרסה 2:

    compute_environment: LOCAL_MACHINE
    debug: false
    distributed_type: FSDP
    downcast_bf16: 'no'
    enable_cpu_affinity: false
    fsdp_config:
      fsdp_activation_checkpointing: false
      fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
      fsdp_cpu_ram_efficient_loading: false
      fsdp_offload_params: true
      fsdp_reshard_after_forward: true
      fsdp_state_dict_type: FULL_STATE_DICT
      fsdp_transformer_layer_cls_to_wrap: Gemma4TextDecoderLayer
      fsdp_version: 2
    machine_rank: 0
    main_training_function: main
    mixed_precision: bf16
    num_machines: 2
    num_processes: 16
    rdzv_backend: static
    same_network: true
    tpu_env: []
    tpu_use_cluster: false
    tpu_use_sudo: false
    use_cpu: false
  5. יוצרים את מניפסט finetune.yaml KubernetesJobSet:

    apiVersion: resource.k8s.io/v1
    kind: ResourceClaimTemplate
    metadata:
      name: mrdma
    spec:
      spec:
        devices:
          requests:
          - name: mrdma
            exactly:
              deviceClassName: mrdma.google.com
    ---
    apiVersion: jobset.x-k8s.io/v1alpha2
    kind: JobSet
    metadata:
      name: finetune-jobset
      namespace: default
    spec:
      failurePolicy:
        maxRestarts: 2
      replicatedJobs:
      - name: workers
        replicas: 1
        template:
          spec:
            parallelism: ${NUM_NODES}
            completions: ${NUM_NODES}
            backoffLimit: 0
            template:
              metadata:
                annotations:
                  kubectl.kubernetes.io/default-container: finetuner
              spec:
                terminationGracePeriodSeconds: 600
                restartPolicy: OnFailure
                nodeSelector:
                  cloud.google.com/compute-class: "Accelerator"
                  cloud.google.com/gke-accelerator: "nvidia-b200"
                  cloud.google.com/reservation-name: ${RESERVATION}
                  cloud.google.com/reservation-affinity: "specific"
                  cloud.google.com/gke-gpu-driver-version: latest
                containers:
                - name: finetuner
                  image: $IMAGE_URL
                  command: ["bash", "-c"]
                  args:
                  - |
                    NUM_PROCESSES=$(( ${NUM_NODES} * 8 ))
                    accelerate launch \
                      --config_file /workspace/accel_fsdp_gemma4_config.yaml \
                      --num_machines ${NUM_NODES} \
                      --num_processes ${NUM_PROCESSES} \
                      --machine_rank ${JOB_COMPLETION_INDEX} \
                      --main_process_ip finetune-jobset-workers-0-0.finetune-jobset.default.svc.cluster.local \
                      --main_process_port 29500 \
                      /workspace/finetune.py \
                      --base_model google/gemma-4-31b-it \
                      --new_model gemma-31b-text-to-sql \
                      --per_device_train_batch_size 4 \
                      --gradient_accumulation_steps 4 \
                      --num_train_epochs 3 \
                      --learning_rate 1e-5 \
                      --save_strategy steps \
                      --save_steps 15 \
                      --push_to_hub
                  resources:
                    limits:
                      nvidia.com/gpu: "8"
                      memory: "1000Gi"
                      ephemeral-storage: "350Gi"
                    requests:
                      nvidia.com/gpu: "8"
                      memory: "1000Gi"
                      ephemeral-storage: "350Gi"
                  env:
                  - name: HF_TOKEN
                    valueFrom:
                      secretKeyRef:
                        name: hf-secret
                        key: hf_api_token
                  - name: NUM_NODES
                    value: "${NUM_NODES}"
                  volumeMounts:
                  - mountPath: /dev/shm
                    name: dshm
                volumes:
                - name: dshm
                  emptyDir:
                    medium: Memory
                    sizeLimit: 64Gi
  6. יוצרים את סקריפט הכוונון המפוקח finetune.py(SFT):

    import argparse
    import torch
    from datasets import load_dataset
    from huggingface_hub import login
    from peft import LoraConfig
    from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
    from trl import SFTConfig, SFTTrainer
    
    
    def get_args():
        def str2bool(v):
            if isinstance(v, bool):
                return v
            return v.lower() in ("yes", "true", "t", "1")
    
        parser = argparse.ArgumentParser()
        parser.add_argument(
            "--base_model",
            "--model_id",
            dest="base_model",
            type=str,
            default="google/gemma-4-31b-it",
            help="Hugging Face model ID",
        )
        parser.add_argument(
            "--hf_token",
            type=str,
            default=None,
            help="Hugging Face token for gated models and Hub uploads",
        )
        parser.add_argument(
            "--trust_remote",
            type=str2bool,
            default=False,
            help="Trust remote code when loading tokenizer",
        )
        parser.add_argument(
            "--use_fast",
            type=str2bool,
            default=True,
            help="Determines if a fast Rust-based tokenizer should be used",
        )
        parser.add_argument(
            "--dataset_name",
            type=str,
            default="philschmid/gretel-synthetic-text-to-sql",
            help="Hugging Face dataset name",
        )
        parser.add_argument(
            "--new_model",
            "--output_dir",
            dest="new_model",
            type=str,
            default="gemma-31b-text-to-sql",
            help="Directory and repository name to save model checkpoints",
        )
    
        # LoRA arguments
        parser.add_argument(
            "--lora_r", type=int, default=16, help="LoRA attention dimension"
        )
        parser.add_argument(
            "--lora_alpha", type=int, default=32, help="LoRA alpha scaling factor"
        )
        parser.add_argument(
            "--lora_dropout",
            type=float,
            default=0.05,
            help="LoRA dropout probability",
        )
        # SFTConfig arguments
        parser.add_argument(
            "--max_length",
            type=int,
            default=1024,
            help="Maximum sequence length",
        )
        parser.add_argument(
            "--num_train_epochs",
            type=int,
            default=3,
            help="Number of training epochs",
        )
        parser.add_argument(
            "--per_device_train_batch_size",
            type=int,
            default=4,
            help="Batch size per device during training",
        )
        parser.add_argument(
            "--gradient_accumulation_steps",
            type=int,
            default=4,
            help="Gradient accumulation steps",
        )
        parser.add_argument(
            "--learning_rate", type=float, default=1e-5, help="Learning rate"
        )
        parser.add_argument(
            "--logging_steps", type=int, default=10, help="Log every X steps"
        )
        parser.add_argument(
            "--save_strategy",
            type=str,
            default="steps",
            help="Checkpoint save strategy",
        )
        parser.add_argument(
            "--save_steps",
            type=int,
            default=15,
            help="Save checkpoint every X steps",
        )
        parser.add_argument(
            "--push_to_hub",
            action="store_true",
            help="Push model back up to Hugging Face Hub",
        )
        parser.add_argument(
            "--hub_private_repo",
            type=str2bool,
            default=True,
            help="Push to a private repository on Hugging Face Hub",
        )
        return parser.parse_args()
    
    
    def main():
        args = get_args()
        # --- 1. Setup and Login ---
        if args.hf_token:
            login(args.hf_token)
        # --- 2. Create and prepare the fine-tuning dataset ---
        dataset = load_dataset(args.dataset_name, split="train")
        dataset = dataset.shuffle().select(range(12500))
        dataset = dataset.train_test_split(test_size=2500 / 12500)
        # --- 3. Configure Model and Tokenizer ---
        if torch.cuda.is_available() and torch.cuda.get_device_capability()[0] >= 8:
            torch_dtype_obj = torch.bfloat16
        else:
            torch_dtype_obj = torch.float16
        tokenizer = AutoTokenizer.from_pretrained(
            args.base_model,
            trust_remote_code=args.trust_remote,
            use_fast=args.use_fast,
        )
        if tokenizer.pad_token is None:
            tokenizer.pad_token = tokenizer.eos_token
    
        # --- 4. Define the Formatting Function ---
        def formatting_func(example):
            system_message = (
                "You are a text to SQL query translator. Users will ask you "
                "questions in English and you will generate a SQL query based "
                "on the provided SCHEMA."
            )
            user_prompt = (
                "Given the <USER_QUERY> and the <SCHEMA>, generate the "
                "corresponding SQL command to retrieve the desired data, "
                "considering the query's syntax, semantics, and schema "
                "constraints.\n\n<SCHEMA>\n{context}\n</SCHEMA>\n\n"
                "<USER_QUERY>\n{question}\n</USER_QUERY>\n"
            )
    
            messages = [
                {"role": "system", "content": system_message},
                {
                    "role": "user",
                    "content": user_prompt.format(
                        question=example["sql_prompt"],
                        context=example["sql_context"],
                    ),
                },
                {"role": "assistant", "content": example["sql"]},
            ]
            return tokenizer.apply_chat_template(messages, tokenize=False)
    
        # --- 5. Load Model and Configure LoRA ---
        config = AutoConfig.from_pretrained(args.base_model)
        config.use_cache = False
        print("Loading base model...")
        model = AutoModelForCausalLM.from_pretrained(
            args.base_model,
            config=config,
            attn_implementation="sdpa",
            torch_dtype=torch_dtype_obj,
        )
    
        peft_config = LoraConfig(
            lora_alpha=args.lora_alpha,
            lora_dropout=args.lora_dropout,
            r=args.lora_r,
            bias="none",
            target_modules=[
                "q_proj",
                "k_proj",
                "v_proj",
                "o_proj",
                "gate_proj",
                "up_proj",
                "down_proj",
            ],
            exclude_modules=r".*(vision_tower|embed_vision|audio_tower|embed_audio).*",
            task_type="CAUSAL_LM",
        )
        # --- 6. Configure Training Arguments ---
        training_args = SFTConfig(
            output_dir=args.new_model,
            max_length=args.max_length,
            num_train_epochs=args.num_train_epochs,
            per_device_train_batch_size=args.per_device_train_batch_size,
            gradient_accumulation_steps=args.gradient_accumulation_steps,
            learning_rate=args.learning_rate,
            logging_steps=args.logging_steps,
            save_strategy=args.save_strategy,
            save_steps=args.save_steps,
            packing=False,
            label_names=["domain"],
            gradient_checkpointing=True,
            gradient_checkpointing_kwargs={"use_reentrant": False},
            optim="adamw_torch",
            fp16=torch_dtype_obj == torch.float16,
            bf16=torch_dtype_obj == torch.bfloat16,
            max_grad_norm=0.3,
            warmup_steps=0.03,
            lr_scheduler_type="constant",
            push_to_hub=args.push_to_hub,
            hub_private_repo=args.hub_private_repo,
            report_to="tensorboard",
        )
        # --- 7. Create Trainer and Start Training ---
        trainer = SFTTrainer(
            model=model,
            args=training_args,
            peft_config=peft_config,
            train_dataset=dataset["train"],
            eval_dataset=dataset["test"],
            processing_class=tokenizer,
            formatting_func=formatting_func,
        )
        print("Starting training...")
        trainer.train()
        print("Training finished.")
        # --- 8. Save the final model ---
        print(f"Saving final model to {args.new_model}")
        if trainer.is_fsdp_enabled:
            trainer.accelerator.state.fsdp_plugin.set_state_dict_type(
                "FULL_STATE_DICT"
            )
        trainer.save_model(args.new_model)
        if torch.distributed.is_initialized():
            torch.distributed.destroy_process_group()
    
    
    if __name__ == "__main__":
        main()

שימוש ב-Docker וב-Cloud Build ליצירת קונטיינר לכוונון עדין

  1. יוצרים מאגר Docker ב-Artifact Registry:

    gcloud artifacts repositories create gemma \
        --repository-format=docker \
        --location="${ARTIFACT_REPO_LOCATION}" \
        --description="Repository for Gemma fine tuning workload containers" || true
  2. מתקינים את JobSetהגדרות המשאבים המותאמים אישית (CRD) שנדרשות לניהול עומסי עבודה בכמה מארחים:

    kubectl apply --server-side \
        -f https://github.com/kubernetes-sigs/jobset/releases/download/v0.12.0/manifests.yaml
  3. בספרייה llm-finetuning-gemma שיצרתם בשלב קודם, שולחים את גרסת ה-build של הקונטיינר אל Cloud Build:

    gcloud builds submit . \
        --substitutions=_ARTIFACT_REPO_LOCATION="${ARTIFACT_REPO_LOCATION}"
  4. מייצאים את כתובת ה-URL של קובץ אימג' של קונטיינר עם כמה מארחים. תשתמשו בו בשלב מאוחר יותר במדריך הזה, כשפורסים את מניפסט JobSet:

    IMAGE_REGISTRY="${ARTIFACT_REPO_LOCATION}-docker.pkg.dev/${PROJECT_ID}"
    export IMAGE_URL="${IMAGE_REGISTRY}/gemma/finetune-gemma-multihost-gpu:2.0.0"

התחלת עומס העבודה של הכוונון העדין

כדי לפרוס ולנטר את עומס העבודה המבוזר של הכוונון העדין, מבצעים את השלבים הבאים:

  1. מחליפים משתני סביבה במניפסט של הכוונון העדין כדי ליצור את משימת הכוונון העדין:

    envsubst '${RESERVATION} ${IMAGE_URL} ${NUM_NODES}' < finetune.yaml \
        | kubectl apply -f -

    האשכול פועל במצב GKE Autopilot, ולכן יכול להיות שייקח כמה דקות להקצות את שני צמתי A4 עם GPU ולשלוף את קובץ אימג' של קונטיינר.

  2. צופים בתרמילי העובדים עד ששני התרמילים עוברים לסטטוס Running:

    watch kubectl get pods
  3. אחרי שה-pods של העובדים עוברים למצב Running, מזרים את יומני האימונים:

    kubectl logs -l "job-name=finetune-jobset-workers-0" -f

מעקב אחר עומס העבודה

אתם יכולים לעקוב אחרי השימוש ב-GPU באשכול GKE כדי לוודא שכל 16 יחידות ה-GPU בשני המארחים מסוג A4 מעבדות באופן פעיל שלבי אימון. יוצרים את הקישור לניתוח נתונים ופותחים אותו בדפדפן:

echo "https://console.cloud.google.com/kubernetes/clusters/details/${CLUSTER_REGION}/${CLUSTER_NAME}/observability?mods=monitoring_api_prod&project=${PROJECT_ID}&pageState=(\"timeRange\":(\"duration\":\"PT1H\"),\"nav\":(\"section\":\"gpu\"),\"groupBy\":(\"groupByType\":\"namespacesTop5\"))"

כשעוקבים אחרי עומס העבודה, צפויים הביצועים הבאים:

  • ניצול GPU: כדי שמשימת כוונון עדין מבוזרת תפעל בצורה תקינה, אפשר לצפות לניצול GPU בכל 16 יחידות ה-GPU מסוג NVIDIA B200, שיעלה ויתייצב בטווח של 95% עד 100% במהלך שלבי האימון.
  • משך העבודה: ב-2 צמתי a4-highgpu-8g (16 מעבדים גרפיים מסוג B200), עבודת הכוונון העדין של 3 תקופות נמשכת כשעתיים וחצי.

צפייה במשקלים של המתאמים שעברו כוונון עדין

בסיום האימון, אפשר לראות את המשקלים של מתאם ה-LoRA ואת נקודות הבדיקה (checkpoint) ב-Hugging Face Hub בכתובת https://huggingface.co/YOUR_HF_USERNAME/gemma-31b-text-to-sql.

הסרת המשאבים

כדי להימנע מחיובים נוספים, מוחקים את המשאבים שנוצרו במהלך המדריך הזה.

מחיקת המשאבים

  1. מחיקת הכוונון העדין JobSet:

    kubectl delete jobset finetune-jobset
  2. מחיקת אשכול GKE:

    gcloud container clusters delete "${CLUSTER_NAME}" \
        --region="${CLUSTER_REGION}"
  3. מחיקת מאגר Artifact Registry:

    gcloud artifacts repositories delete gemma \
        --location="${ARTIFACT_REPO_LOCATION}" \
        --quiet

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