SFT on multi-host TPUs#
Supervised fine-tuning (SFT) is a process where a pre-trained large language model is fine-tuned on a labeled dataset to adapt the model to perform better on specific tasks.
This tutorial demonstrates step-by-step instructions for setting up the multi-host TPU environment and then training the model on the Hugging Face dataset using SFT. In this tutorial we use a multi-host TPU such as v6e-256.
We use Tunix, a JAX-based library designed for post-training tasks, to perform SFT.
Let’s get started!
Prerequisites#
Before starting, ensure you have:
Access to a Google Cloud Project with TPU quotas.
IAM Roles required:
Kubernetes Engine Developer (
roles/container.developer) to submit and manage workloads on GKE.Artifact Registry Writer (
roles/artifactregistry.writer) to upload Docker images.Storage Admin (
roles/storage.admin) or Storage Object Admin (roles/storage.objectAdmin) combined with Storage Legacy Bucket Reader (roles/storage.legacyBucketReader) on your GCS bucket to read/write checkpoints and logs. (Note: A bucket-level read permission likestorage.buckets.getis required by JAX/TensorStore to verify bucket existence and metadata; usingroles/storage.objectAdminalone will cause a misleading “bucket not found” error).
A Hugging Face account with an access token for downloading models.
Cluster Toolkit installed and configured. Follow Running MaxText with Cluster Toolkit for
gclustersetup.Important: Modern GKE clusters require the GKE auth plugin. If you encounter
gke-gcloud-auth-plugin not foundwhen runningkubectlcommands, you must install it locally (e.g.,sudo apt-get install google-cloud-cli-gke-gcloud-auth-pluginforaptinstallations, orgcloud components install gke-gcloud-auth-pluginfor standalone archive installations).
A GKE cluster configured for Cluster Toolkit, including healthy Kueue and JobSet components (see Cluster Toolkit documentation, or create a GKE cluster with Pathways when running with Pathways).
Docker installed and configured for sudoless use. Follow the steps to configure sudoless Docker.
Build and upload MaxText Docker image#
For instructions on building and uploading the MaxText Docker image with post-training dependencies, please refer to the official documentation.
Environment configuration#
Set up the following environment variables to configure your training run. Replace placeholders with your actual values.
# -- Model configuration --
# The MaxText model name. See `src/maxtext/configs/types.py` for `ModelName` for a
# full list of supported models.
export MODEL=<MODEL_NAME> # e.g., deepseek3-671b
# Your Hugging Face access token. Required to download gated models like Llama.
# You can generate one at https://huggingface.co/settings/tokens.
export HF_TOKEN=<HF_TOKEN>
# -- MaxText configuration --
# Use a GCS bucket you own to store logs and checkpoints. Ideally in the same
# region as your TPUs to minimize latency and costs.
# You can list your buckets and their locations in the
# [Cloud Console](https://console.cloud.google.com/storage/browser) or via
# `gcloud storage buckets list --format="table(name, location)"`.
export BASE_OUTPUT_DIRECTORY=<GCS_BUCKET> # e.g., gs://my-bucket/maxtext-runs
# An arbitrary string to identify this specific run.
# We recommend to include the model, user, and timestamp.
# Note: Workload names cannot exceed 28 characters (or 22 characters when using Pathways due to Kubernetes 63-byte coordinator label limits) and must be valid DNS labels (lowercase alphanumeric and hyphens).
export RUN_NAME=<RUN_NAME>
# -- Workload configuration --
# Your GCP project ID. Find it on the [Cloud Console Dashboard](https://console.cloud.google.com/home/dashboard).
# If you've already set it in your local config, you can retrieve it via:
# gcloud config get-value project
export PROJECT_ID=<PROJECT_ID>
# The GCP location (region or zone) and name of your
# TPU-enabled GKE cluster. Both can be found on the
# [Cloud Console](https://console.cloud.google.com/kubernetes/list).
export LOCATION=<LOCATION> # e.g., 'europe-west4' (region) or 'us-central1-a' (zone)
export GKE_CLUSTER=<CLUSTER_NAME>
# Number of TPU slices for your workload.
export NUM_SLICES=<NUM_SLICES>
# Cluster Toolkit workload placement. Specify the compute type (machine type)
# and topology matching your TPU node pool.
# For example:
# - v5p-128: COMPUTE_TYPE='ct5p-hightpu-4t', TOPOLOGY='4x4x4'
# - v6e-256: COMPUTE_TYPE='ct6e-hightpu-4t', TOPOLOGY='16x16'
# To inspect the accelerator and topology labels on your GKE cluster nodes:
# kubectl get nodes -l cloud.google.com/gke-tpu-accelerator -o custom-columns=NAME:.metadata.name,ACCELERATOR:.metadata.labels.cloud\\.google\\.com/gke-tpu-accelerator,TOPOLOGY:.metadata.labels.cloud\\.google\\.com/gke-tpu-topology
export COMPUTE_TYPE=<COMPUTE_TYPE>
export TOPOLOGY=<TOPOLOGY>
# The Docker image you pushed in the previous step
export DOCKER_IMAGE=<IMAGE_NAME>
# -- Fine-Tuning configuration --
export STEPS=<STEPS> # e.g., 1000
# -- Dataset configuration --
export DATASET_NAME=<DATASET_NAME> # e.g., HuggingFaceH4/ultrachat_200k
export TRAIN_SPLIT=<TRAIN_SPLIT> # e.g., train_sft
export TRAIN_DATA_COLUMNS=<DATA_COLUMNS> # e.g., ['messages']
Configure GKE cluster#
Configure access to the target cluster with gcloud, then configure the project, cluster, and location with gcluster as described in Running MaxText with Cluster Toolkit:
gcloud container clusters get-credentials ${GKE_CLUSTER?} \
--location ${LOCATION?} \
--project ${PROJECT_ID?}
gcluster job config set project ${PROJECT_ID?}
gcluster job config set cluster ${GKE_CLUSTER?}
gcluster job config set location ${LOCATION?}
Get MaxText model checkpoint#
This section explains how to prepare your model checkpoint for use with MaxText. You have two options: using an existing MaxText checkpoint or converting a Hugging Face checkpoint.
Option 1: Using an existing MaxText checkpoint#
If you already have a MaxText-compatible model checkpoint, simply set the following environment variable and move on to the next section.
export MAXTEXT_CKPT_PATH=<CKPT_PATH> # e.g., gs://my-bucket/my-model-checkpoint/0/items
Note: Make sure that MAXTEXT_CKPT_PATH has the checkpoints created using the correct storage flags:
export USE_PATHWAYS=0 # Set to 1 for Pathways, 0 for McJAX.
checkpoint_storage_use_zarr3=$((1 - USE_PATHWAYS))
checkpoint_storage_use_ocdbt=$((1 - USE_PATHWAYS))
Option 2: Converting a Hugging Face checkpoint#
Refer the steps in Hugging Face to MaxText to convert a hugging face checkpoint to MaxText. Make sure you have correct checkpoint files converted and saved. Similar as Option 1, you can set the following environment and move on.
export MAXTEXT_CKPT_PATH=<CKPT_PATH> # gs://my-bucket/my-checkpoint-directory/0/items
[!IMPORTANT] Automatic
scan_layersResolution: MaxText automatically loadsscan_layersfrom the checkpoint’s saved metadata when resuming (viaload_parameters_path) if you do not explicitly specify it on the command-line.
You do not need to manually supply
scan_layers=False(orscan_layers=True) when loading checkpoints; MaxText will configure this automatically.If you do explicitly provide a
scan_layersargument, it must match the checkpoint’s saved setting or aValueErrormismatch error will be raised. See the Checkpoints concept guide for more details.
Submit workload on GKE cluster#
This section provides the command to run SFT on a GKE cluster.
SFT with Multi-Controller JAX (McJAX)#
gcluster job submit \
--image=${DOCKER_IMAGE?} \
--name=${RUN_NAME?} \
--compute-type=${COMPUTE_TYPE?} \
--topology=${TOPOLOGY?} \
--num-slices=${NUM_SLICES?} \
--command="python3 -m maxtext.trainers.post_train.sft.train_sft \
run_name=${RUN_NAME?} \
base_output_directory=${BASE_OUTPUT_DIRECTORY?} \
model_name=${MODEL?} \
load_parameters_path=${MAXTEXT_CKPT_PATH?} \
hf_access_token=${HF_TOKEN?} \
per_device_batch_size=1 \
steps=${STEPS?} \
profiler=xplane \
hf_path=${DATASET_NAME?} \
train_split=${TRAIN_SPLIT?} \
train_data_columns=${TRAIN_DATA_COLUMNS?}"
Once the fine-tuning is completed, you can access your model checkpoints at ${BASE_OUTPUT_DIRECTORY}/${RUN_NAME}/checkpoints.
SFT with Pathways#
To submit an SFT workload with Pathways using Cluster Toolkit, use gcluster job submit with the --pathways flag:
export USE_PATHWAYS=1
gcluster job submit \
--image=${DOCKER_IMAGE?} \
--name=${RUN_NAME?} \
--pathways \
--compute-type=${COMPUTE_TYPE?} \
--topology=${TOPOLOGY?} \
--num-slices=${NUM_SLICES?} \
--pathways-gcs-location=${BASE_OUTPUT_DIRECTORY?} \
--command="ENABLE_PATHWAYS_PERSISTENCE=1 \
python3 -m maxtext.trainers.post_train.sft.train_sft \
run_name=${RUN_NAME?} \
base_output_directory=${BASE_OUTPUT_DIRECTORY?} \
model_name=${MODEL?} \
load_parameters_path=${MAXTEXT_CKPT_PATH?} \
hf_access_token=${HF_TOKEN?} \
per_device_batch_size=1 \
steps=${STEPS?} \
profiler=xplane \
checkpoint_storage_use_zarr3=$((1 - USE_PATHWAYS)) \
checkpoint_storage_use_ocdbt=$((1 - USE_PATHWAYS)) \
enable_single_controller=True"
Once the fine-tuning is completed, you can access your model checkpoints at ${BASE_OUTPUT_DIRECTORY}/${RUN_NAME}/checkpoints.
Monitor and clean up#
gcluster job list
# Note: For Pathways workloads (> 5 pods), specify --main-only=false to retrieve logs from all pods:
gcluster job logs ${RUN_NAME?} --main-only=false
gcluster job cancel ${RUN_NAME?}
You can also inspect the Kubernetes resources directly:
kubectl get jobset -l gcluster.google.com/workload=${RUN_NAME?}
# In Pathways workloads, use the jobset-name label to select all pods (both pathways-head and worker pods):
kubectl get pods -l jobset.sigs.k8s.io/jobset-name=${RUN_NAME?}