在 A4 Slurm 叢集上微調 Gemma 3

本教學課程會說明如何使用兩個 A4 虛擬機器 (VM) 執行個體,在多節點 Slurm 叢集上微調 Gemma 3 大型語言模型 (LLM)。在本教學課程中,您將執行下列操作:

本教學課程適合機器學習 (ML) 工程師、平台管理員和營運人員,以及有興趣使用 Slurm 工作排程功能處理微調工作負載的資料和 AI 專家。

目標

  1. 使用 Hugging Face 存取 Gemma 3。

  2. 準備環境。

  3. 建立 A4 Slurm 叢集。

  4. 準備工作負載。

  5. 執行微調工作。

  6. 監控工作。

  7. 清除所用資源。

費用

在本文件中,您會使用下列 Google Cloud的計費元件:

如要根據預測用量估算費用,請使用 Pricing Calculator

初次使用 Google Cloud 的使用者可能符合免費試用期資格。

事前準備

  1. 安裝 Google Cloud CLI。

  2. 設定 gcloud CLI,使用您的聯合身分。

    詳情請參閱「使用聯合身分登入 gcloud CLI」。

  3. 執行下列指令,初始化 gcloud CLI:

    gcloud init
  4. 建立或選取 Google Cloud 專案

    選取或建立專案所需的角色

    • 選取專案:選取專案時,不需要具備特定 IAM 角色,只要您在專案中獲派角色,即可選取該專案。
    • 建立專案:如要建立專案,您需要「專案建立者」角色 (roles/resourcemanager.projectCreator),其中包含 resourcemanager.projects.create 權限。瞭解如何授予角色
    • 建立 Google Cloud 專案:

      gcloud projects create PROJECT_ID

      PROJECT_ID 替換為您要建立的 Google Cloud 專案名稱。

    • 選取您建立的 Google Cloud 專案:

      gcloud config set project PROJECT_ID

      PROJECT_ID 替換為 Google Cloud 專案名稱。

  5. 確認專案已啟用計費功能 Google Cloud

  6. 啟用必要的 API:

    啟用 API 時所需的角色

    如要啟用 API,您必須具備 serviceusage.services.enable 權限。如果您建立了專案,可能已透過「擁有者」角色 (roles/owner) 取得這項權限。否則,您可以透過「服務使用情形管理員」角色 (roles/serviceusage.serviceUsageAdmin) 取得這項權限。瞭解如何授予角色

    gcloud services enable compute.googleapis.com file.googleapis.com logging.googleapis.com cloudresourcemanager.googleapis.com servicenetworking.googleapis.com
  7. 將角色授予使用者帳戶。針對下列每個 IAM 角色,執行一次下列指令: roles/compute.admin, roles/iam.serviceAccountUser, roles/file.editor, roles/storage.admin, roles/serviceusage.serviceUsageAdmin

    gcloud projects add-iam-policy-binding PROJECT_ID --member="user:USER_IDENTIFIER" --role=ROLE

    更改下列內容:

  8. 為 Google Cloud 專案啟用預設服務帳戶:
    gcloud iam service-accounts enable PROJECT_NUMBER-compute@ \
        --project=PROJECT_ID

    PROJECT_NUMBER 替換為專案編號。如要查看專案編號,請參閱「 取得現有專案」。

  9. 將編輯者角色 (roles/editor) 授予預設服務帳戶:
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member="serviceAccount:PROJECT_NUMBER-compute@" \
        --role=roles/editor
  10. 為使用者帳戶建立本機驗證憑證:
    gcloud auth application-default login
  11. 為專案啟用 OS 登入功能:
    gcloud compute project-info add-metadata --metadata=enable-oslogin=TRUE
  12. 登入或建立 Hugging Face 帳戶

使用 Hugging Face 存取 Gemma 3

如要使用 Hugging Face 存取 Gemma 3,請按照下列步驟操作:

  1. 簽署同意聲明,即可使用 Gemma 3 12B

  2. 建立 Hugging Face read 存取權杖。 依序點選「你的個人資料」>「設定」>「存取權杖」>「+ 建立新權杖」

  3. 複製並儲存 read access 權杖值。本教學課程稍後會用到這項資訊。

安裝 Cluster Toolkit

如要進一步瞭解如何使用 gcluster 和管理叢集,請參閱「Cluster Toolkit 總覽」。

  1. 準備 Cluster Toolkit 版本:

    # Find all available releases at: https://github.com/GoogleCloudPlatform/cluster-toolkit/releases
    # Set the desired version TAG (e.g., v1.97.0)
    export CLUSTER_TOOLKIT_TAG=v1.97.0
    
    # Detect OS (linux or mac)
    case "$(uname -s)" in
      Linux*)     OS="linux" ;;
      Darwin*)    OS="mac" ;;
      *)          echo "Error: Unsupported operating system: $(uname -s)" >&2; exit 1 ;; 
    esac
    
    # Detect Architecture (amd64 or arm64)
    case "$(uname -m)" in
      x86_64)     ARCH="amd64" ;; 
      aarch64|arm64) ARCH="arm64" ;;
      *)          echo "Error: Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
    esac
  2. 下載版本:

    # Download and extract the platform-specific bundle
    curl -LO "https://github.com/GoogleCloudPlatform/cluster-toolkit/releases/download/${CLUSTER_TOOLKIT_TAG}/gcluster_bundle_${OS}_${ARCH}.zip"
    unzip "gcluster_bundle_${OS}_${ARCH}.zip" -d cluster-toolkit/
    rm -f "gcluster_bundle_${OS}_${ARCH}.zip"
  3. 定義 gcluster 路徑:

    export CLUSTER_TOOLKIT_PATH="$(pwd)/cluster-toolkit"
    export PATH="${CLUSTER_TOOLKIT_PATH}:${PATH}"
    gcluster --version

準備環境

如要準備環境,請按照下列步驟操作:

  1. 設定預設環境變數:

    export PROJECT_ID="YOUR_PROJECT_ID"
    export ZONE="YOUR_ZONE"
    export REGION="YOUR_REGION"
    export RESERVATION_URL="RESERVATION_NAME"
    export CLUSTER_NAME="YOUR_CLUSTER_NAME"
    export BUCKET_NAME="YOUR_GCS_BUCKET"
    export HF_TOKEN="YOUR_HF_TOKEN"
    
    gcloud config set project "${PROJECT_ID}"
    gcloud config set billing/quota_project "${PROJECT_ID}"

    更改下列內容:

    • YOUR_PROJECT_ID:您要建立 Cloud Storage bucket 的Google Cloud 專案 ID。

    • YOUR_ZONE:預訂項目所在的可用區。

    • YOUR_REGION:預訂項目所在的區域。

    • RESERVATION_NAME:您要用來建立 Slurm 叢集的預訂網址或名稱。

    • YOUR_CLUSTER_NAME:要建立的 Slurm 叢集名稱。

    • YOUR_GCS_BUCKET:Cloud Storage bucket 的名稱,必須符合bucket 命名規定

    • YOUR_HF_TOKEN:您在上一個章節中建立的 Hugging Face 存取權杖。

  2. 建立 Cloud Storage bucket:

    gcloud storage buckets create "gs://${BUCKET_NAME}" \
      --project="${PROJECT_ID}"

建立 A4 Slurm 叢集

如要建立 A4 Slurm 叢集,請按照下列步驟操作:

  1. 建立 a4high-slurm-deployment.yaml 檔案:

    MANIFEST_PATH="${CLUSTER_TOOLKIT_PATH}/examples/machine-learning/a4-highgpu-8g"
    cat <<EOF > "${MANIFEST_PATH}/a4high-slurm-deployment.yaml"
    terraform_backend_defaults:
      type: gcs
      configuration:
        bucket: ${BUCKET_NAME}
    
    vars:
      deployment_name: ${CLUSTER_NAME}
      project_id: ${PROJECT_ID}
      region: ${REGION}
      zone: ${ZONE}
      a4h_cluster_size: 2
      a4h_reservation_name: ${RESERVATION_URL}
    EOF
  2. 準備資訊清單:

    1. 建立 Terraform 資訊清單:

      gcluster create \
        -d "${MANIFEST_PATH}/a4high-slurm-deployment.yaml" \
        "${MANIFEST_PATH}/a4high-slurm-blueprint.yaml" \
        -o "${WORK_DIR}"
    2. 修補資訊清單:

      echo "[$(date)] Patching Filestore deletion protection in ${CLUSTER_NAME}..."
      sed -i '/deletion_protection = {/,/}/ { s/enabled = true/enabled = false/; /reason  = "Avoid data loss"/d; }' "${WORK_DIR}/${CLUSTER_NAME}/cluster-env/main.tf"
  3. 部署叢集:

    gcluster deploy "${WORK_DIR}/${CLUSTER_NAME}" --auto-approve

    gcluster deploy 指令包含兩個階段,如下所示:

    • 第一階段會建構預先安裝所有軟體的自訂映像檔,最多可能需要 45 分鐘才能完成。

    • 第二階段會使用該自訂映像檔部署叢集。這個程序通常比第一階段更快完成。

    如果第一階段成功,但第二階段失敗,您可以嘗試略過第一階段,再次部署 Slurm 叢集:

    gcluster deploy "${CLUSTER_NAME}" --auto-approve --skip "image" -w

準備工作負載

如要準備工作負載,請按照下列步驟操作:

  1. 建立工作負載指令碼

  2. 將指令碼上傳至 Slurm 叢集

  3. 連線至 Slurm 叢集

  4. 安裝架構和工具

建立工作負載指令碼

如要建立微調工作負載使用的指令碼,請按照下列步驟操作:

  1. 如要設定 Python 虛擬環境,請建立 install_environment.sh 檔案,並加入下列內容:

    #!/bin/bash
    # This script should be run ONCE on the login node to set up the
    # shared Python virtual environment.
    
    set -e
    echo "--- Creating Python virtual environment in /home ---"
    python3 -m venv ~/.venv
    echo "--- Activating virtual environment ---"
    source ~/.venv/bin/activate
    
    echo "--- Installing build dependencies ---"
    pip install --upgrade pip wheel packaging
    
    echo "--- Installing PyTorch for CUDA 12.8 ---"
    pip install torch --index-url https://download.pytorch.org/whl/cu128
    
    echo "--- Installing application requirements ---"
    pip install -r requirements.txt
    
    echo "--- Environment setup complete. You can now submit jobs with sbatch. ---"
  2. 如要指定微調工作的設定,請建立 accelerate_config.yaml 檔案,並加入下列內容:

    # Default configuration for a 2-node, 8-GPU-per-node (16 total GPUs) FSDP training job.
    
    compute_environment: "LOCAL_MACHINE"
    distributed_type: "FSDP"
    downcast_bf16: "no"
    fsdp_config:
      fsdp_auto_wrap_policy: "TRANSFORMER_BASED_WRAP"
      fsdp_backward_prefetch: "BACKWARD_PRE"
      fsdp_cpu_ram_efficient_loading: true
      fsdp_forward_prefetch: false
      fsdp_offload_params: false
      fsdp_sharding_strategy: "FULL_SHARD"
      fsdp_state_dict_type: "FULL_STATE_DICT"
      fsdp_transformer_layer_cls_to_wrap: "Gemma3DecoderLayer"
      fsdp_use_orig_params: true
    machine_rank: 0
    main_training_function: "main"
    mixed_precision: "bf16"
    num_machines: 2
    num_processes: 16
    rdzv_backend: "static"
    same_network: true
    tpu_env: []
    use_cpu: false
  3. 如要指定工作在 Slurm 叢集上執行的工作,請建立 submit.slurm 檔案,並加入下列內容:

    #!/bin/bash
    #SBATCH --job-name=gemma3-finetune
    #SBATCH --nodes=2
    #SBATCH --ntasks-per-node=1 # 1 task per node
    #SBATCH --gpus-per-node=8   # 8 GPUs per node
    #SBATCH --partition=a4high
    #SBATCH --output=slurm-%j.out
    #SBATCH --error=slurm-%j.err
    
    set -e
    echo "--- Slurm Job Started ---"
    
    # --- STAGE 1: Copy Environment to Local SSD on all nodes ---
    srun --ntasks=$SLURM_NNODES --ntasks-per-node=1 bash -c '
      echo "Setting up local environment on $(hostname)..."
      LOCAL_VENV="/mnt/localssd/venv_job_${SLURM_JOB_ID}"
      LOCAL_CACHE="/mnt/localssd/hf_cache_job_${SLURM_JOB_ID}"
      rsync -a --info=progress2 ~/./.venv/ ${LOCAL_VENV}/
      mkdir -p ${LOCAL_CACHE}
      echo "Setup on $(hostname) complete."
    '
    
    # --- STAGE 2: Run the Training Job using the Local Environment ---
    echo "--- Starting Training ---"
    
    LOCAL_VENV="/mnt/localssd/venv_job_${SLURM_JOB_ID}"
    LOCAL_CACHE="/mnt/localssd/hf_cache_job_${SLURM_JOB_ID}"
    LOCAL_OUTPUT_DIR="/mnt/localssd/outputs_${SLURM_JOB_ID}"
    mkdir -p ${LOCAL_OUTPUT_DIR}
    
    # This is the main training command.
    srun --ntasks=$SLURM_NNODES --ntasks-per-node=1 bash -c '
      # Ensure nccl-gib is at version 1.1.2-1
      INSTALLED_VERSION=$(dpkg-query -W -f="\${Version}" nccl-gib 2>/dev/null || echo "not_installed")
      if [ "${INSTALLED_VERSION}" != "1.1.2-1" ]; then
        echo "Upgrading nccl-gib to 1.1.2-1..."
        sudo apt-get update && sudo apt-get install -y --allow-change-held-packages nccl-gib=1.1.2-1
      fi
    
      source '"${LOCAL_VENV}"'/bin/activate
    
      export HF_HOME='"${LOCAL_CACHE}"'
      export HF_DATASETS_CACHE='"${LOCAL_CACHE}"'
    
      if [ -f /usr/local/gib/scripts/set_nccl_env.sh ]; then
        echo "Sourcing set_nccl_env.sh"
        source /usr/local/gib/scripts/set_nccl_env.sh
      fi
      export LD_LIBRARY_PATH=/usr/local/gib/lib64:${LD_LIBRARY_PATH:-}
    
      export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
      export MASTER_PORT=29500
    
      # Use accelerate launch to properly run the distributed FSDP job
      accelerate launch \
        --config_file ~/accelerate_config.yaml \
        --machine_rank $SLURM_NODEID \
        --main_process_ip $MASTER_ADDR \
        --main_process_port $MASTER_PORT \
        --num_machines $SLURM_NNODES \
        --num_processes $((SLURM_NNODES * 8)) \
        ~/train.py \
        --model_id google/gemma-3-12b-pt \
        --output_dir '"${LOCAL_OUTPUT_DIR}"' \
        --per_device_train_batch_size 1 \
        --gradient_accumulation_steps 8 \
        --num_train_epochs 3 \
        --learning_rate 1e-5 \
        --save_strategy steps \
        --save_steps 100
    '
    
    # --- STAGE 3: Copy Final Model from Local SSD to Home Directory ---
    echo "--- Copying final model from local SSD to /home ---"
    # This command runs only on the first node of the job allocation
    # and copies the final model back to the persistent shared directory.
    srun --nodes=1 --ntasks=1 --ntasks-per-node=1 bash -c "
      rsync -a --info=progress2 ${LOCAL_OUTPUT_DIR}/ ~/gemma-12b-text-to-sql-finetuned/
    "
    
    echo "--- Slurm Job Finished ---"
  4. 如要指定微調工作的依附元件,請建立 requirements.txt 檔案,並加入下列內容:

    # Hugging Face Libraries (Pinned to recent, stable versions for reproducibility)
    transformers==4.53.3
    datasets==4.0.0
    accelerate==1.9.0
    evaluate==0.4.5
    bitsandbytes==0.46.1
    trl==0.19.1
    peft==0.16.0
    
    # Other dependencies
    tensorboard==2.20.0
    protobuf==6.31.1
    sentencepiece==0.2.0
  5. 如要指定工作指令,請建立 train.py 檔案,並加入下列內容:

    import torch
    import argparse
    from datasets import load_dataset
    from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig, AutoConfig
    from peft import LoraConfig, prepare_model_for_kbit_training, get_peft_model
    from trl import SFTTrainer, SFTConfig
    from huggingface_hub import login
    
    
    def get_args():
        parser = argparse.ArgumentParser()
        parser.add_argument("--model_id", type=str, default="google/gemma-3-12b-pt", help="Hugging Face model ID")
        parser.add_argument("--hf_token", type=str, default=None, help="Hugging Face token for private models")
        parser.add_argument("--dataset_name", type=str, default="philschmid/gretel-synthetic-text-to-sql", help="Hugging Face dataset name")
        parser.add_argument("--output_dir", type=str, default="gemma-12b-text-to-sql", help="Directory 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=16, 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_seq_length", type=int, default=512, 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=8, help="Batch size per device during training")
        parser.add_argument("--gradient_accumulation_steps", type=int, default=1, 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=100, help="Save checkpoint every X steps")
    
        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 ---
        # The SFTTrainer will use the `formatting_func` to apply the chat template.
        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
            torch_dtype_str = "bfloat16"
        else:
            torch_dtype_obj = torch.float16
            torch_dtype_str = "float16"
    
        tokenizer = AutoTokenizer.from_pretrained(args.model_id)
        tokenizer.pad_token = tokenizer.eos_token
    
        gemma_chat_template = (
            "{% for message in messages %}"
            "{% if message['role'] == 'user' %}"
            "{{ '<start_of_turn>user\n' + message['content'] + '<end_of_turn>\n' }}"
            "{% elif message['role'] == 'assistant' %}"
            "{{ '<start_of_turn>model\n' + message['content'] + '<end_of_turn>\n' }}"
            "{% endif %}"
            "{% endfor %}"
            "{% if add_generation_prompt %}"
            "{{ '<start_of_turn>model\n' }}"
            "{% endif %}"
        )
        tokenizer.chat_template = gemma_chat_template
    
        # --- 4. Define the Formatting Function ---
        # This function will be used by the SFTTrainer to format each sample
        # from the dataset into the correct chat template format.
        def formatting_func(example):
            # The create_conversation logic is now implicitly handled by this.
            # We need to construct the messages list here.
            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 Quantized Model and Apply PEFT ---
    
        # Define the quantization configuration
        config = AutoConfig.from_pretrained(args.model_id)
        config.use_cache = False
    
        # Load the base model in native precision
        print("Loading base model...")
        model = AutoModelForCausalLM.from_pretrained(
            args.model_id,
            config=config,
            attn_implementation="sdpa",
            torch_dtype=torch_dtype_obj,
        )
    
        # Configure LoRA.
        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"],
            task_type="CAUSAL_LM",
        )
    
        # Apply the PEFT config to the model
        print("Applying PEFT configuration...")
        model = get_peft_model(model, peft_config)
        model.print_trainable_parameters()
    
        # --- 6. Configure Training Arguments ---
        training_args = SFTConfig(
            output_dir=args.output_dir,
            ddp_timeout=300.0,
            max_seq_length=args.max_seq_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,
            gradient_checkpointing=True,
            gradient_checkpointing_kwargs={"use_reentrant": False},
            optim="adamw_torch",
            fp16=True if torch_dtype_obj == torch.float16 else False,
            bf16=True if torch_dtype_obj == torch.bfloat16 else False,
            max_grad_norm=0.3,
            warmup_ratio=0.03,
            lr_scheduler_type="constant",
            push_to_hub=False,
            report_to="tensorboard",
            dataset_kwargs={
                "add_special_tokens": False,
                "append_concat_token": True,
            }
        )
    
        # --- 7. Create Trainer and Start Training ---
        trainer = SFTTrainer(
            model=model,
            args=training_args,
            train_dataset=dataset["train"],
            eval_dataset=dataset["test"],
            formatting_func=formatting_func,
        )
    
        print("Starting training...")
        trainer.train()
        print("Training finished.")
    
        # --- 8. Save the final model ---
        print(f"Saving final model to {args.output_dir}")
        trainer.save_model()
    
    if __name__ == "__main__":
        main()

將指令碼上傳至 Slurm 叢集

如要將上一節建立的指令碼上傳至 Slurm 叢集,請按照下列步驟操作:

  1. 擷取叢集的登入節點名稱,然後設定 LOGIN_NODE 變數:

    LOGIN_NODE="$(gcloud compute instances list \
                    --project="${PROJECT_ID}" \
                    --filter="labels.ghpc_deployment='${CLUSTER_NAME}' AND labels.slurm_instance_role='login'" \
                    --format="value(name)" | head -n 1)"

    LOGIN_NODE 變數會儲存類似 ${CLUSTER_NAME}-login-001 的值。

  2. 將指令碼上傳至登入節點的主目錄:

    gcloud compute scp \
      --project="${PROJECT_ID}" \
      --zone="${ZONE}" \
      --tunnel-through-iap \
      ./install_environment.sh \
      ./requirements.txt \
      ./submit.slurm \
      ./accelerate_config.yaml \
      ./train.py \
      "${LOGIN_NODE}":~/

連線至 Slurm 叢集

連線至登入節點,並將 Hugging Face 權杖傳播至新工作階段:

gcloud compute ssh "${LOGIN_NODE}" \
    --project="${PROJECT_ID}" \
    --tunnel-through-iap \
    --zone="${ZONE}" \
    -- -t "export HF_TOKEN='${HF_TOKEN}'; bash -l"

安裝架構和工具

連線至登入節點後,請設定 Python 虛擬環境,並安裝必要的依附元件:

chmod +x install_environment.sh
./install_environment.sh

啟動微調工作負載

如要啟動微調工作負載,請按照下列步驟操作:

  1. 將工作提交至 Slurm 排程器,並收集工作 ID:

    JOB_ID="$(sbatch submit.slurm 2>&1 \
      | tee /dev/tty \
      | grep -oP 'Submitted batch job \K\d+')"
  2. 在 Slurm 叢集的登入節點上,您可以檢查 home 目錄中建立的輸出檔案,監控工作進度:

    tail -f "slurm-${JOB_ID}.out" "slurm-${JOB_ID}.err"

    如果工作順利啟動,.err 檔案會顯示進度列,並隨著工作進度更新。

監控工作負載

您可以監控 Slurm 叢集中的 GPU 使用情形,確認微調作業是否有效率地執行。如要這麼做,請在瀏覽器中開啟下列連結:

https://console.cloud.google.com/monitoring/metrics-explorer?project=PROJECT_ID&pageState=%7B%22xyChart%22%3A%7B%22dataSets%22%3A%5B%7B%22timeSeriesFilter%22%3A%7B%22filter%22%3A%22metric.type%3D%5C%22agent.googleapis.com%2Fgpu%2Futilization%5C%22%20resource.type%3D%5C%22gce_instance%5C%22%22%2C%22perSeriesAligner%22%3A%22ALIGN_MEAN%22%7D%2C%22plotType%22%3A%22LINE%22%7D%5D%7D%7D

監控工作負載時,您可以查看下列資訊:

  • GPU 使用率:如果微調工作正常運作,您應該會看到所有 16 個 GPU (叢集中每個 VM 有 8 個 GPU) 的使用率在訓練期間上升並穩定在特定程度。

  • 工作時間:這項工作大約需要一小時才能完成。

清除所用資源

為避免因為本教學課程所用資源,導致系統向 Google Cloud 帳戶收取費用,請刪除含有相關資源的專案,或者保留專案但刪除個別資源。

刪除 Slurm 叢集

如要刪除 Slurm 叢集,請按照下列步驟操作:

gcluster destroy "${CLUSTER_NAME}" --auto-approve

如要刪除與專案相關聯的所有虛擬私有雲網路、防火牆規則、路由器、IP 和子網路,請按照下列步驟操作:

echo "========================================================================="
echo " STARTING AUTOMATED NETWORK CLEANUP FOR CLUSTER: ${CLUSTER_NAME}"
echo "========================================================================="

echo "Discovering all VPC networks linked to the cluster..."
NETWORKS=$(gcloud compute networks list --project="${PROJECT_ID}" --format="value(name)" | grep "^${CLUSTER_NAME}" || true)

if [ -z "${NETWORKS}" ]; then
    echo "No VPC networks found starting with ${CLUSTER_NAME}. Everything is already clean!"
    exit 0
fi

echo "Found the following networks to process:"
echo "${NETWORKS}"
echo "-------------------------------------------------------------------------"

echo "=== 1. Wiping Global Firewall Rules ==="
FIREWALL_RULES=$(gcloud compute firewall-rules list \
    --project="${PROJECT_ID}" \
    --filter="network ~ ^${CLUSTER_NAME} OR name ~ ^${CLUSTER_NAME}" \
    --format="value(name)" || echo "")

if [ -n "${FIREWALL_RULES}" ]; then
    echo "Deleting matching firewall rules:"
    echo "${FIREWALL_RULES}"
    echo "${FIREWALL_RULES}" | xargs -r gcloud compute firewall-rules delete --project="${PROJECT_ID}" --quiet
else
    echo "No matching firewall rules found."
fi

echo "=== 2. Tearing Down Network-Specific Infrastructure ==="
echo "${NETWORKS}" | while read -r net_name; do
    [ -z "${net_name}" ] && continue
    echo "Processing resources for network: ${net_name}"

    ROUTERS=$(gcloud compute routers list \
        --project="${PROJECT_ID}" \
        --regions="${REGION}" \
        --filter="network=${net_name}" \
        --format="value(name)" || echo "")

    if [ -n "${ROUTERS}" ]; then
        echo "  -> Deleting routers: ${ROUTERS}"
        echo "${ROUTERS}" | xargs -r gcloud compute routers delete --region="${REGION}" --project="${PROJECT_ID}" --quiet
    fi

    IPS=$(gcloud compute addresses list \
        --project="${PROJECT_ID}" \
        --regions="${REGION}" \
        --filter="name ~ ^${net_name}" \
        --format="value(name)" || echo "")

    if [ -n "${IPS}" ]; then
        echo "  -> Deleting IP reservations: ${IPS}"
        echo "${IPS}" | xargs -r gcloud compute addresses delete --region="${REGION}" --project="${PROJECT_ID}" --quiet
    fi

    SUBNETS=$(gcloud compute networks subnets list \
        --project="${PROJECT_ID}" \
        --regions="${REGION}" \
        --filter="network=${net_name}" \
        --format="value(name)" || echo "")

    if [ -n "${SUBNETS}" ]; then
        echo "  -> Deleting subnetworks:"
        echo "${SUBNETS}"
        echo "${SUBNETS}" | xargs -r gcloud compute networks subnets delete --region="${REGION}" --project="${PROJECT_ID}" --quiet
    fi
done

echo "-------------------------------------------------------------------------"
echo "Waiting 15 seconds for Google Cloud API dependencies to unlock..."
sleep 15

echo "=== 3. Final VPC Networks Destruction ==="
echo "${NETWORKS}" | while read -r net_name; do
    [ -z "${net_name}" ] && continue
    echo "Deleting core VPC network: ${net_name}..."
    gcloud compute networks delete "${net_name}" --project="${PROJECT_ID}" --quiet || \
    echo "Warning: Could not delete ${net_name} yet. If a lock occurred, please rerun in 1 minute."
done

echo "========================================================================="
echo " SUCCESS: All network resources for cluster ${CLUSTER_NAME} have been wiped!"
echo "========================================================================="

刪除專案

刪除 Google Cloud 專案:

gcloud projects delete PROJECT_ID

後續步驟