Ajustar o Gemma 3 em um cluster Slurm A4

Neste tutorial, mostramos como ajustar o modelo de linguagem grande (LLM) Gemma 3 em um cluster Slurm de vários nós que usa duas instâncias de máquina virtual (VM) A4. Como parte deste tutorial, você vai fazer o seguinte:

Este tutorial é destinado a engenheiros de machine learning (ML), administradores e operadores de plataforma e especialistas em dados e IA interessados em usar os recursos de programação de jobs do Slurm para lidar com cargas de trabalho de ajuste refinado.

Objetivos

  1. Acesse o Gemma 3 usando o Hugging Face.

  2. Prepare seu ambiente.

  3. Crie um cluster Slurm A4.

  4. Prepare a carga de trabalho.

  5. Execute um job de ajuste.

  6. Monitorar o job.

  7. Fazer a limpeza.

Custos

Neste documento, você vai usar os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços.

Novos usuários do Google Cloud podem estar qualificados para um teste sem custo financeiro.

Antes de começar

  1. Instale a CLI do Google Cloud.

  2. Configure a CLI gcloud para usar sua identidade federada.

    Para mais informações, consulte Fazer login na CLI gcloud com sua identidade federada.

  3. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  4. Crie ou selecione um Google Cloud projeto.

    Funções necessárias para selecionar ou criar um projeto

    • Selecionar um projeto: não é necessário um papel específico do IAM para selecionar um projeto. Você pode escolher qualquer projeto em que tenha recebido um papel.
    • Criar um projeto: para criar um projeto, é necessário ter o papel de Criador de projetos (roles/resourcemanager.projectCreator), que contém a permissão resourcemanager.projects.create. Saiba como conceder papéis.
    • Crie um projeto do Google Cloud :

      gcloud projects create PROJECT_ID

      Substitua PROJECT_ID por um nome para o projeto Google Cloud que você está criando.

    • Selecione o projeto Google Cloud que você criou:

      gcloud config set project PROJECT_ID

      Substitua PROJECT_ID pelo nome do projeto do Google Cloud .

  5. Verifique se o faturamento está ativado para o projeto do Google Cloud .

  6. Ative a API necessária:

    Funções necessárias para ativar APIs

    Para ativar APIs, você precisa da permissão serviceusage.services.enable. Se você criou o projeto, provavelmente já tem essa permissão com o papel de Proprietário (roles/owner). Caso contrário, é possível receber essa permissão com o papel de Administrador do Service Usage (roles/serviceusage.serviceUsageAdmin). Saiba como conceder papéis.

    gcloud services enable compute.googleapis.com file.googleapis.com logging.googleapis.com cloudresourcemanager.googleapis.com servicenetworking.googleapis.com
  7. Atribua papéis à sua conta de usuário. Execute uma vez o seguinte comando para cada um dos seguintes papéis do 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

    Substitua:

  8. Ative a conta de serviço padrão para seu projeto do Google Cloud :
    gcloud iam service-accounts enable PROJECT_NUMBER-compute@ \
        --project=PROJECT_ID

    Substitua PROJECT_NUMBER pelo número do projeto. Para revisar o número do projeto, consulte Receber um projeto atual.

  9. Conceda o papel de editor (roles/editor) à conta de serviço padrão:
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member="serviceAccount:PROJECT_NUMBER-compute@" \
        --role=roles/editor
  10. Crie as credenciais de autenticação local para sua conta de usuário:
    gcloud auth application-default login
  11. Ative o Login do SO no seu projeto:
    gcloud compute project-info add-metadata --metadata=enable-oslogin=TRUE
  12. Faça login ou crie uma conta do Hugging Face.

Acessar o Gemma 3 usando o Hugging Face

Para usar o Hugging Face e acessar o Gemma 3, siga estas etapas:

  1. Assine o contrato de consentimento para usar o Gemma 3 12B.

  2. Crie um token de acesso read do Hugging Face. Clique em Seu perfil > Configurações > Tokens de acesso > +Criar novo token

  3. Copie e salve o valor do token read access. Você vai usar esse valor mais tarde neste tutorial.

Instalar o Cluster Toolkit

Para mais informações sobre como usar gcluster e gerenciar clusters, consulte a visão geral do Cluster Toolkit.

  1. Prepare a versão do 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. Faça o download da versão:

    # 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. Defina o caminho gcluster:

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

Preparar o ambiente

Para preparar o ambiente, siga estas etapas:

  1. Defina as variáveis de ambiente padrão:

    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}"

    Substitua:

    • YOUR_PROJECT_ID: o ID do projetoGoogle Cloud em que você quer criar o bucket do Cloud Storage.

    • YOUR_ZONE: a zona em que a reserva existe.

    • YOUR_REGION: a região em que sua reserva existe.

    • RESERVATION_NAME: o URL ou o nome da reserva que você quer usar para criar o cluster do Slurm.

    • YOUR_CLUSTER_NAME: o nome do cluster do Slurm que você quer criar.

    • YOUR_GCS_BUCKET: um nome para o bucket do Cloud Storage que segue os requisitos de nomenclatura de bucket.

    • YOUR_HF_TOKEN: o token de acesso do Hugging Face que você criou na seção anterior.

  2. Crie um bucket do Cloud Storage:

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

Criar um cluster Slurm A4

Para criar um cluster do Slurm A4, siga estas etapas:

  1. Crie o arquivo 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. Prepare os manifestos:

    1. Crie os manifestos do Terraform:

      gcluster create \
        -d "${MANIFEST_PATH}/a4high-slurm-deployment.yaml" \
        "${MANIFEST_PATH}/a4high-slurm-blueprint.yaml" \
        -o "${WORK_DIR}"
    2. Adicione patches aos manifestos:

      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. Implante o cluster:

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

    O comando gcluster deploy é um processo de duas fases:

    • A primeira fase cria uma imagem personalizada com todo o software pré-instalado, o que pode levar até 45 minutos.

    • A segunda fase implanta o cluster usando essa imagem personalizada. Esse processo geralmente leva menos tempo para ser concluído do que a primeira fase.

    Se a primeira fase for concluída, mas a segunda falhar, tente implantar o cluster do Slurm novamente pulando a primeira fase:

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

Preparar sua carga de trabalho

Para preparar sua carga de trabalho, siga estas etapas:

  1. Crie scripts de carga de trabalho.

  2. Faça upload dos scripts para o cluster do Slurm.

  3. Conecte-se ao cluster do Slurm.

  4. Instale frameworks e ferramentas.

Criar scripts de carga de trabalho

Para criar os scripts que sua carga de trabalho de ajuste refinado vai usar, siga estas etapas:

  1. Para configurar o ambiente virtual Python, crie o arquivo install_environment.sh com o seguinte conteúdo:

    #!/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. Para especificar as configurações do job de ajuste refinado, crie o arquivo accelerate_config.yaml com o seguinte conteúdo:

    # 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. Para especificar as tarefas que os jobs vão executar no cluster Slurm, crie o arquivo submit.slurm com o seguinte conteúdo:

    #!/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. Para especificar as dependências do job de ajuste refinado, crie o arquivo requirements.txt com o seguinte conteúdo:

    # 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. Para especificar as instruções do seu job, crie o arquivo train.py com o seguinte conteúdo:

    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()

Fazer upload de scripts para o cluster Slurm

Para fazer upload dos scripts criados na seção anterior para o cluster do Slurm, siga estas etapas:

  1. Defina a variável LOGIN_NODE recuperando o nome do nó de login do cluster:

    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)"

    A variável LOGIN_NODE armazena um valor semelhante a ${CLUSTER_NAME}-login-001.

  2. Faça upload dos scripts para o diretório principal do nó de login:

    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}":~/

Conectar-se ao cluster do Slurm

Conecte-se ao nó de login e propague seu token do Hugging Face para a nova sessão:

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

Instalar frameworks e ferramentas

Depois de se conectar ao nó de login, configure um ambiente virtual do Python com as dependências necessárias:

chmod +x install_environment.sh
./install_environment.sh

Iniciar a carga de trabalho de ajuste refinado

Para iniciar sua carga de trabalho de ajuste refinado, siga estas etapas:

  1. Envie a tarefa para o escalonador do Slurm e colete o código da tarefa:

    JOB_ID="$(sbatch submit.slurm 2>&1 \
      | tee /dev/tty \
      | grep -oP 'Submitted batch job \K\d+')"
  2. No nó de login do cluster do Slurm, monitore o progresso do job verificando os arquivos de saída criados no diretório home:

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

    Se o job for iniciado com sucesso, o arquivo .err vai mostrar uma barra de progresso que é atualizada conforme o job avança.

Monitore sua carga de trabalho

É possível monitorar o uso das GPUs no cluster do Slurm para verificar se o job de ajuste refinado está sendo executado de maneira eficiente. Para fazer isso, abra o seguinte link no navegador:

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

Ao monitorar sua carga de trabalho, você pode ver o seguinte:

  • Uso de GPUs: para um job de ajuste refinado saudável, é esperado que o uso de todas as 16 GPUs (oito GPUs para cada VM no cluster) aumente e se estabilize em um nível específico durante todo o treinamento.

  • Duração do job: o job leva aproximadamente uma hora para ser concluído.

Limpar

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados no tutorial, exclua o projeto que os contém ou mantenha o projeto e exclua os recursos individuais.

Excluir o cluster do Slurm

Para excluir o cluster do Slurm:

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

Se você precisar excluir todas as redes VPC, regras de firewall, roteadores, IPs e sub-redes associados ao projeto, faça o seguinte:

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 "========================================================================="

Excluir o projeto

Excluir um projeto do Google Cloud :

gcloud projects delete PROJECT_ID

A seguir