Skip to content

Commit 80b5fab

Browse files
committed
docs: QLoRA Documentation and Notebooks
1 parent 7c8d658 commit 80b5fab

3 files changed

Lines changed: 911 additions & 3 deletions

File tree

docs/tutorials/posttraining/lora.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ export DATASET_NAME=<DATASET_NAME> # e.g., openai/gsm8k
6060
export TRAIN_SPLIT=<TRAIN_SPLIT> # e.g., train
6161
export HF_DATA_DIR=<DATASET_PATH> # e.g., main
6262
export TRAIN_DATA_COLUMNS=<DATA_COLUMNS> # e.g., ['question','answer']
63-
export CHAT_TEMPLATE_PATH=<TEMPLATE_PATH> # e.g., maxtext/examples/chat_templates/math_qa.json
6463

6564
# -- LoRA Conversion configuration (Optional) --
6665
export HF_LORA_ADAPTER_PATH=<HF_LORA_ADAPTER_PATH> # e.g., 'username/adapter-name'
@@ -118,7 +117,6 @@ python3 -m maxtext.trainers.post_train.sft.train_sft \
118117
per_device_batch_size="${PER_DEVICE_BATCH_SIZE?}" \
119118
max_target_length="${MAX_TARGET_LENGTH?}" \
120119
learning_rate="${LEARNING_RATE?}" \
121-
chat_template_path="${CHAT_TEMPLATE_PATH?}" \
122120
enable_nnx=True \
123121
pure_nnx_decoder=True \
124122
lora.enable_lora=True \
@@ -176,7 +174,6 @@ python3 -m maxtext.trainers.post_train.sft.train_sft \
176174
per_device_batch_size="${PER_DEVICE_BATCH_SIZE?}" \
177175
max_target_length="${MAX_TARGET_LENGTH?}" \
178176
learning_rate="${LEARNING_RATE?}" \
179-
chat_template_path="${CHAT_TEMPLATE_PATH?}" \
180177
enable_nnx=True \
181178
pure_nnx_decoder=True \
182179
lora.enable_lora=True \
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
<!--
2+
Copyright 2023–2026 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
# LoRA Fine-tuning on multi-host TPUs
18+
19+
**Low-Rank Adaptation (LoRA)** is a Parameter-Efficient Fine-Tuning (PEFT) technique designed to optimize large language models while minimizing resource consumption.
20+
21+
Unlike traditional full-parameter fine-tuning, LoRA:
22+
23+
- **Freezes the pre-trained model weights**, preserving the original knowledge.
24+
- **Injects trainable rank decomposition matrices** into the Transformer layers.
25+
26+
This tutorial provides step-by-step instructions for setting up the multi-host TPU environment and performing LoRA fine-tuning on a Hugging Face dataset using MaxText. In this tutorial we use a multi-host TPU such as `v6e-256`.
27+
28+
We use [Tunix](https://github.com/google/tunix), a JAX-based library, to power these post-training tasks.
29+
30+
Let's get started!
31+
32+
## Prerequisites
33+
34+
Before starting, ensure you have:
35+
36+
- Access to a Google Cloud Project with TPU quotas.
37+
- A Hugging Face account with an access token for downloading models.
38+
- Permissions for Google Artifact Registry (Artifact Registry Writer role).
39+
- Prerequisites for XPK installed (follow [official documentation](https://github.com/AI-Hypercomputer/xpk/blob/main/docs/installation.md#1-prerequisites)).
40+
- A Pathways-ready GKE cluster (see [create GKE cluster](https://docs.cloud.google.com/ai-hypercomputer/docs/workloads/pathways-on-cloud/create-gke-cluster)).
41+
- **Docker** installed and configured for sudoless use. Follow the steps to [configure sudoless Docker](https://docs.docker.com/engine/install/linux-postinstall/).
42+
43+
## Build and upload MaxText Docker image
44+
45+
For instructions on building and uploading the MaxText Docker image with post-training dependencies, please refer to the [official documentation](https://maxtext.readthedocs.io/en/latest/build_maxtext.html).
46+
47+
## Create GKE cluster
48+
49+
Use a pathways ready GKE cluster as described [here](https://docs.cloud.google.com/ai-hypercomputer/docs/workloads/pathways-on-cloud/create-gke-cluster).
50+
51+
## Environment configuration
52+
53+
Set up the following environment variables to configure your training run. Replace placeholders with your actual values.
54+
55+
```bash
56+
# -- Model configuration --
57+
# The MaxText model name. See `src/maxtext/configs/types.py` for `ModelName` for a
58+
# full list of supported models.
59+
export MODEL=<MODEL_NAME> # e.g., 'gemma4-26b'
60+
61+
# Your Hugging Face access token. Required to download gated models like Gemma.
62+
# You can generate one at https://huggingface.co/settings/tokens.
63+
export HF_TOKEN=<HF_TOKEN>
64+
65+
# -- MaxText configuration --
66+
# Use a GCS bucket you own to store logs and checkpoints. Ideally in the same
67+
# region as your TPUs to minimize latency and costs.
68+
# You can list your buckets and their locations in the
69+
# [Cloud Console](https://console.cloud.google.com/storage/browser) or via
70+
# `gcloud storage buckets list --format="table(name, location)"`.
71+
export BASE_OUTPUT_DIRECTORY=<GCS_BUCKET> # e.g., gs://my-bucket/maxtext-runs
72+
73+
# An arbitrary string to identify this specific run.
74+
# We recommend to include the model, user, and timestamp.
75+
# Note: Kubernetes requires workload names to be valid DNS labels (lowercase, no underscores or periods).
76+
export RUN_NAME=<RUN_NAME>
77+
78+
# -- Workload configuration --
79+
# Your GCP project ID. Find it on the [Cloud Console Dashboard](https://console.cloud.google.com/home/dashboard).
80+
# If you've already set it in your local config, you can retrieve it via:
81+
# gcloud config get-value project
82+
export PROJECT_ID=<PROJECT_ID>
83+
84+
# The GCP location (listed as "Location" in the UI) and name of your
85+
# TPU-enabled GKE cluster. Both can be found on the
86+
# [Cloud Console](https://console.cloud.google.com/kubernetes/list).
87+
export ZONE=<ZONE> # e.g., 'us-central1'
88+
export GKE_CLUSTER=<CLUSTER_NAME>
89+
90+
# For a full list of MaxText-supported TPU types, see: `src/maxtext/utils/accelerator_to_spec_map.py`. To see the TPU type
91+
# of your cluster:
92+
93+
# 1. Connect to the cluster (required for kubectl commands later):
94+
# gcloud container clusters get-credentials ${GKE_CLUSTER?} --location ${ZONE?} --project ${PROJECT_ID?}
95+
96+
# 2. Find your TPU type (e.g., 'v6e-256') by checking the accelerator labels on your nodes:
97+
# kubectl get nodes -l cloud.google.com/gke-tpu-accelerator -o jsonpath='{.items[*].metadata.labels.cloud\.google\.com/gke-tpu-accelerator}' | tr ' ' '\n' | sort -u
98+
export TPU_TYPE=<TPU_TYPE>
99+
export NUM_SLICES=<NUM_SLICES>
100+
101+
# The Docker image you pushed in the prerequisite step
102+
export CLOUD_IMAGE_NAME=<IMAGE_NAME>
103+
export DOCKER_IMAGE="gcr.io/${PROJECT_ID?}/${CLOUD_IMAGE_NAME?}"
104+
105+
# -- Fine-Tuning configuration --
106+
export STEPS=<STEPS> # e.g., 1000
107+
export PER_DEVICE_BATCH_SIZE=<BATCH_SIZE_PER_DEVICE> # e.g., 1
108+
export LORA_RANK=<LORA_RANK> # e.g., 16
109+
export LORA_ALPHA=<LORA_ALPHA> # e.g., 32.0
110+
export LEARNING_RATE=<LEARNING_RATE> # e.g., 3e-6
111+
export MAX_TARGET_LENGTH=<MAX_TARGET_LENGTH> # e.g., 1024
112+
113+
# -- Dataset configuration --
114+
export DATASET_NAME=<DATASET_NAME> # e.g., openai/gsm8k
115+
export TRAIN_SPLIT=<TRAIN_SPLIT> # e.g., train
116+
export HF_DATA_DIR=<DATASET_PATH> # e.g., main
117+
export TRAIN_DATA_COLUMNS=<DATA_COLUMNS> # e.g., ['question','answer']
118+
119+
# -- LoRA Conversion configuration (Optional) --
120+
export HF_LORA_ADAPTER_PATH=<HF_LORA_ADAPTER_PATH> # e.g., 'username/adapter-name'
121+
```
122+
123+
## Customizing Trainable Layers (Optional)
124+
125+
By default, MaxText determines which layers to apply LoRA to based on the model's architecture by reading `src/maxtext/configs/post_train/lora_module_path.yml`.
126+
127+
If you need to fine-tune specific components (e.g., targeting only Attention layers to optimize memory usage), you can override these defaults through the following hierarchy:
128+
129+
### Configuration Hierarchy
130+
131+
1. **Command Line Argument**: Pass the `lora_module_path` argument directly in your training command.
132+
2. **Task-Specific Config (`sft.yml`)**: Define the `lora_module_path` parameter in `src/maxtext/configs/post_train/sft.yml`.
133+
3. **Global Defaults**: Automatic detection via the model-to-regex mapping defined in `lora_module_path.yml`.
134+
135+
## Get MaxText model checkpoint
136+
137+
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.
138+
139+
### Option 1: Using an existing MaxText checkpoint
140+
141+
If you already have a MaxText-compatible model checkpoint, simply set the following environment variable and move on to the next section.
142+
143+
```bash
144+
export MAXTEXT_CKPT_PATH=<CKPT_PATH> # e.g., gs://my-bucket/my-model-checkpoint/0/items
145+
```
146+
147+
**Note:** Make sure that `MAXTEXT_CKPT_PATH` has the checkpoints created using the correct storage flags:
148+
149+
```bash
150+
export USE_PATHWAYS=0 # Set to 1 for Pathways, 0 for McJAX.
151+
checkpoint_storage_use_zarr3=$((1 - USE_PATHWAYS))
152+
checkpoint_storage_use_ocdbt=$((1 - USE_PATHWAYS))
153+
```
154+
155+
### Option 2: Converting a Hugging Face checkpoint
156+
157+
Refer to the steps in [Hugging Face to MaxText](https://maxtext.readthedocs.io/en/maxtext-v0.2.1/guides/checkpointing_solutions/convert_checkpoint.html#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.
158+
159+
```bash
160+
export MAXTEXT_CKPT_PATH=<CKPT_PATH> # gs://my-bucket/my-checkpoint-directory/0/items
161+
```
162+
163+
## Submit workload on GKE cluster
164+
165+
This section provides the command to run LoRA Fine-Tuning on a GKE cluster.
166+
167+
### Run a Fresh LoRA Fine-Tuning on Hugging Face Dataset
168+
169+
#### LoRA with Multi-Controller JAX (McJAX)
170+
171+
```bash
172+
xpk workload create \
173+
--cluster=${GKE_CLUSTER?} \
174+
--project=${PROJECT_ID?} \
175+
--zone=${ZONE?} \
176+
--docker-image=${DOCKER_IMAGE?} \
177+
--workload=${RUN_NAME?} \
178+
--tpu-type=${TPU_TYPE?} \
179+
--num-slices=${NUM_SLICES?} \
180+
--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?} hf_path=${DATASET_NAME?} train_split=${TRAIN_SPLIT?} hf_data_dir=${HF_DATA_DIR?} train_data_columns=${TRAIN_DATA_COLUMNS?} steps=${STEPS?} per_device_batch_size=${PER_DEVICE_BATCH_SIZE?} max_target_length=${MAX_TARGET_LENGTH?} learning_rate=${LEARNING_RATE?} chat_template_path=${CHAT_TEMPLATE_PATH?} enable_nnx=True pure_nnx_decoder=True lora.enable_lora=True lora.lora_rank=${LORA_RANK?} lora.lora_alpha=${LORA_ALPHA?}"
181+
```
182+
183+
Once the fine-tuning is completed, you can access your model checkpoints at `${BASE_OUTPUT_DIRECTORY}/${RUN_NAME/checkpoints`.
184+
185+
#### LoRA with Pathways
186+
187+
```bash
188+
export USE_PATHWAYS=1
189+
190+
xpk workload create-pathways \
191+
--cluster=${GKE_CLUSTER?} \
192+
--project=${PROJECT_ID?} \
193+
--zone=${ZONE?} \
194+
--docker-image=${DOCKER_IMAGE?} \
195+
--workload=${RUN_NAME?} \
196+
--tpu-type=${TPU_TYPE?} \
197+
--num-slices=${NUM_SLICES?} \
198+
--command="JAX_PLATFORMS=proxy JAX_BACKEND_TARGET=grpc://127.0.0.1:29000 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?} hf_path=${DATASET_NAME?} train_split=${TRAIN_SPLIT?} hf_data_dir=${HF_DATA_DIR?} train_data_columns=${TRAIN_DATA_COLUMNS?} steps=${STEPS?} per_device_batch_size=${PER_DEVICE_BATCH_SIZE?} max_target_length=${MAX_TARGET_LENGTH?} learning_rate=${LEARNING_RATE?} chat_template_path=${CHAT_TEMPLATE_PATH?} enable_nnx=True pure_nnx_decoder=True lora.enable_lora=True lora.lora_rank=${LORA_RANK?} lora.lora_alpha=${LORA_ALPHA?} checkpoint_storage_use_zarr3=$((1 - USE_PATHWAYS)) checkpoint_storage_use_ocdbt=$((1 - USE_PATHWAYS)) enable_single_controller=True"
199+
```
200+
201+
Once the fine-tuning is completed, you can access your model checkpoints at `${BASE_OUTPUT_DIRECTORY}/${RUN_NAME}/checkpoints`.
202+
203+
### (Optional) Resume from a previous LoRA checkpoint
204+
205+
If you want to resume training from a previous run or further fine-tune an existing LoRA adapter, you can specify the LoRA checkpoint path.
206+
207+
#### Step 1: Convert HF LoRA adapter to MaxText format with Multi-Controller JAX (McJAX)
208+
209+
If your LoRA adapter is currently in Hugging Face format, you must convert it to MaxText format before it can be loaded. Use the integrated conversion utility:
210+
211+
```sh
212+
xpk workload create \
213+
--cluster=${GKE_CLUSTER?} \
214+
--project=${PROJECT_ID?} \
215+
--zone=${ZONE?} \
216+
--docker-image=${DOCKER_IMAGE?} \
217+
--workload=${RUN_NAME?} \
218+
--tpu-type=${TPU_TYPE?} \
219+
--num-slices=${NUM_SLICES?} \
220+
--command="python3 -m maxtext.checkpoint_conversion.to_maxtext model_name=${MODEL?} hf_lora_adapter_path=${HF_LORA_ADAPTER_PATH?} base_output_directory=${BASE_OUTPUT_DIRECTORY?}/converted_adapter hf_access_token=${HF_TOKEN?} hardware=cpu skip_jax_distributed_system=True"
221+
```
222+
223+
#### Step 2: Set the restore path
224+
225+
Point `LORA_RESTORE_PATH` to the converted MaxText adapter directory (the directory containing the `0/items` or Orbax files).
226+
227+
- **load_parameters_path**: Points to the frozen base model weights (the original model).
228+
- **lora_restore_path**: Points to the previous LoRA adapter weights you wish to load.
229+
230+
```sh
231+
export LORA_RESTORE_PATH=<LORA_RESTORE_PATH> # e.g., gs://my-bucket/run-1/checkpoints/0/items or /path/to/run-1/checkpoints/0/items
232+
```
233+
234+
#### Step 3-1: Run LoRA Fine-Tuning with the Restore Path through Multi-Controller JAX (McJAX)
235+
236+
Once your environment variables and checkpoints are ready, you can start the LoRA fine-tuning process.
237+
238+
Execute the following command to begin training:
239+
240+
```sh
241+
xpk workload create \
242+
--cluster=${GKE_CLUSTER?} \
243+
--project=${PROJECT_ID?} \
244+
--zone=${ZONE?} \
245+
--docker-image=${DOCKER_IMAGE?} \
246+
--workload=${RUN_NAME?} \
247+
--tpu-type=${TPU_TYPE?} \
248+
--num-slices=${NUM_SLICES?} \
249+
--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?} hf_path=${DATASET_NAME?} train_split=${TRAIN_SPLIT?} hf_data_dir=${HF_DATA_DIR?} train_data_columns=${TRAIN_DATA_COLUMNS?} steps=${STEPS?} per_device_batch_size=${PER_DEVICE_BATCH_SIZE?} max_target_length=${MAX_TARGET_LENGTH?} lora.lora_restore_path=${LORA_RESTORE_PATH?} learning_rate=${LEARNING_RATE?} chat_template_path=${CHAT_TEMPLATE_PATH?} enable_nnx=True pure_nnx_decoder=True lora.enable_lora=True lora.lora_rank=${LORA_RANK?} lora.lora_alpha=${LORA_ALPHA?}"
250+
```
251+
252+
#### Step 3-2: Run LoRA Fine-Tuning with the Restore Path through Pathways
253+
254+
```bash
255+
export USE_PATHWAYS=1
256+
257+
xpk workload create-pathways \
258+
--cluster=${GKE_CLUSTER?} \
259+
--project=${PROJECT_ID?} \
260+
--zone=${ZONE?} \
261+
--docker-image=${DOCKER_IMAGE?} \
262+
--workload=${RUN_NAME?} \
263+
--tpu-type=${TPU_TYPE?} \
264+
--num-slices=${NUM_SLICES?} \
265+
--command="JAX_PLATFORMS=proxy JAX_BACKEND_TARGET=grpc://127.0.0.1:29000 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?} hf_path=${DATASET_NAME?} train_split=${TRAIN_SPLIT?} hf_data_dir=${HF_DATA_DIR?} train_data_columns=${TRAIN_DATA_COLUMNS?} steps=${STEPS?} per_device_batch_size=${PER_DEVICE_BATCH_SIZE?} max_target_length=${MAX_TARGET_LENGTH?} lora.lora_restore_path=${LORA_RESTORE_PATH?} learning_rate=${LEARNING_RATE?} chat_template_path=${CHAT_TEMPLATE_PATH?} enable_nnx=True pure_nnx_decoder=True lora.enable_lora=True lora.lora_rank=${LORA_RANK?} lora.lora_alpha=${LORA_ALPHA?} checkpoint_storage_use_zarr3=$((1 - USE_PATHWAYS)) checkpoint_storage_use_ocdbt=$((1 - USE_PATHWAYS)) enable_single_controller=True"
266+
```
267+
268+
Your fine-tuned model checkpoints will be saved here: `$BASE_OUTPUT_DIRECTORY/$RUN_NAME/checkpoints`.
269+
270+
## (Optional) Convert Fine-tuned LoRA to Hugging Face Format with Multi-Controller JAX (McJAX)
271+
272+
After completing the fine-tuning process, your LoRA weights are stored in MaxText/Orbax format. To use these weights with the Hugging Face ecosystem (e.g., for inference or sharing), convert them back using the `to_huggingface.py` script.
273+
274+
```sh
275+
xpk workload create \
276+
--cluster=${GKE_CLUSTER?} \
277+
--project=${PROJECT_ID?} \
278+
--zone=${ZONE?} \
279+
--docker-image=${DOCKER_IMAGE?} \
280+
--workload="${RUN_NAME?}-to-hf" \
281+
--tpu-type=${TPU_TYPE?} \
282+
--num-slices=1 \
283+
--command="python3 -m maxtext.checkpoint_conversion.to_huggingface \
284+
model_name=${MODEL?} \
285+
lora.lora_restore_path=${BASE_OUTPUT_DIRECTORY?}/${RUN_NAME?}/checkpoints/<STEPS>/model_params \
286+
base_output_directory=${BASE_OUTPUT_DIRECTORY?}/hf_lora_adapter \
287+
hf_access_token=${HF_TOKEN?}"
288+
289+
```
290+
291+
- `lora.lora_restore_path`: Point this to the specific checkpoint directory (e.g., `.../checkpoints/1000/items`) that you want to export.
292+
- `base_output_directory`: The local or GCS directory where the Hugging Face `adapter_model.safetensors` and `adapter_config.json` will be saved.
293+
- `lora.lora_rank` / `lora.lora_alpha`: Must match the values used during the training phase to ensure the `adapter_config.json` is generated correctly.
294+
295+
## A Note on Multi-Host Resharding
296+
297+
When running LoRA fine-tuning in a **multi-host environment** (e.g., a TPU pod with 64 hosts managing 256 TPUs, such as Pathways or McJAX), special care must be taken when resharding arrays.
298+
299+
In a single-host environment, the host has a global view of all devices, so a standard `jax.device_put` can easily distribute slices of data to all local TPUs. However, in a multi-host setup:
300+
301+
- **Addressability:** A host only has a local view of its directly attached devices and cannot push data directly to TPUs managed by other hosts.
302+
- **Memory Constraints:** If every host tries to load the entire weight matrix into RAM just to extract its local piece, the host CPUs will run out of memory (OOM).
303+
304+
To solve this, MaxText uses `jax.make_array_from_callback` for a "safe reshard." Instead of pushing data *to* the devices, this flips the paradigm. It creates a global `jax.Array` construct where each host locally executes a callback (`lambda idx: val[idx]`) to load **only the specific slice** of the data that its attached TPUs need. This completely bypasses cross-host `device_put` limitations and prevents OOMs since each host only indexes what it requires.

0 commit comments

Comments
 (0)