Skip to content

Commit 98306de

Browse files
committed
Add tutorial for RL GRPO gemma4-e4b on v6e 4x8
1 parent c5b135a commit 98306de

6 files changed

Lines changed: 357 additions & 1 deletion

File tree

docs/guides/checkpointing_solutions/convert_checkpoint.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ The following models are supported:
1212
| :---------------------- | :--------------------- | :-------------------: | :---------------------: | :-------------------: | :---------------------: |
1313
| **Gemma2** | 2B, 9B, 27B |||||
1414
| **Gemma3** (Multimodal) | 4B, 12B, 27B |||||
15+
| **Gemma4** | 26B (MoE), 31B |||||
16+
| **Gemma4 Small** | E2B, E4B | - || - ||
1517
| **Llama3.1** | 8B, 70B, 450B |||||
1618
| **Qwen2.5** | 1.5B, 7B, 14B |||||
1719
| **Qwen3** | 0.6B, 4B, 8B, 14B, 32B |||||
@@ -23,6 +25,8 @@ The following models are supported:
2325
| **DeepSeek3.2** | 671B ||| - | - |
2426
| **Qwen3 Next** | 80B |||||
2527

28+
> **Note:** *Gemma4 Small* (E2B / E4B) uses the `gemma4_small` decoder block, which has per-layer KV sharing and is incompatible with `nn.scan`. Only unscanned (`scan_layers=False`) conversion is supported for these variants.
29+
2630
## Prerequisites
2731

2832
- MaxText must be installed in a Python virtual environment using the `maxtext[tpu]` option. For instructions on installing MaxText on your VM, please refer to the official [installation documentation](install-from-source).
@@ -37,6 +41,8 @@ Use the `to_maxtext.py` script to convert a Hugging Face model checkpoint into a
3741

3842
> **Note:** For more information, checkout [qwen3-4b example script](https://github.com/AI-Hypercomputer/maxtext/blob/main/tests/end_to_end/tpu/qwen3/4b/test_qwen3_to_mt.sh) and [gemma3-4b example script](https://github.com/AI-Hypercomputer/maxtext/blob/main/tests/end_to_end/tpu/gemma3/4b/test_gemma3_to_mt.sh).
3943
44+
> **Note (Gemma 4 E2B / E4B):** The `gemma4-e2b` and `gemma4-e4b` variants use the `gemma4_small` decoder block, which has per-layer KV sharing and is **incompatible with `nn.scan`**. Conversion must therefore be run with `scan_layers=False` (unscanned), and any downstream training/inference run must also use `scan_layers=False`. Additionally, if you want to convert the **base** (pretrained) model rather than the instruction-tuned default, pass `--hf_model_path=google/gemma-4-E4B` (or `google/gemma-4-E2B`) explicitly — `HF_IDS` defaults to the `-it` repository. See [convert_gemma4_base.sh](https://github.com/AI-Hypercomputer/maxtext/blob/main/tests/end_to_end/tpu/gemma4/e4b/convert_gemma4_base.sh) for a full example.
45+
4046
### Setup Environment
4147

4248
```bash
@@ -94,6 +100,8 @@ Use the `to_huggingface.py` script to convert a MaxText checkpoint into the Hugg
94100

95101
> **Note:** For more information, checkout [qwen3-4b example script](https://github.com/AI-Hypercomputer/maxtext/blob/main/tests/end_to_end/tpu/qwen3/4b/test_qwen3_to_hf.sh).
96102
103+
> **Note (Gemma 4 E2B / E4B):** The `gemma4-e2b` and `gemma4-e4b` variants use the `gemma4_small` decoder block and must be converted with `scan_layers=False`. If your MaxText checkpoint was fine-tuned from the base model, also pass `--hf_model_path=google/gemma-4-E4B` (or `google/gemma-4-E2B`) so the exported checkpoint bundles the base tokenizer — otherwise `to_huggingface` sources the tokenizer from `HF_IDS[gemma4-e4b]` = `google/gemma-4-E4B-it`.
104+
97105
### Setup Environment
98106

99107
```bash
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
# Reinforcement Learning with gemma4-e4b on Multi-Host TPUs
18+
19+
This tutorial provides step-by-step instructions for setting up the environment
20+
and training the gemma4-e4b model with GRPO on the [OpenMathInstruct-2 dataset](https://huggingface.co/datasets/nvidia/OpenMathInstruct-2) on a Cloud TPU v6e (Trillium) GKE cluster using a `v6e-32` (4x8) slice.
21+
22+
## Prerequisites
23+
24+
Before starting, ensure you have:
25+
26+
- Access to a Google Cloud Project with TPU quotas.
27+
- A Hugging Face account with an access token for downloading models (the `google/gemma-4-E4B` and `google/gemma-4-E4B-it` repositories are gated; request access before proceeding).
28+
- Permissions for Google Artifact Registry (Artifact Registry Writer role).
29+
- Prerequisites for XPK installed (follow [official documentation](https://github.com/AI-Hypercomputer/xpk/blob/main/docs/installation.md#1-prerequisites)).
30+
- A Pathways-ready GKE cluster (see [create GKE cluster](https://docs.cloud.google.com/ai-hypercomputer/docs/workloads/pathways-on-cloud/create-gke-cluster)).
31+
- **Docker** installed and configured for sudoless use. Follow the steps to [configure sudoless Docker](https://docs.docker.com/engine/install/linux-postinstall/).
32+
33+
## Setup Environment Variables
34+
35+
Set up the following environment variables to configure your training run. Replace
36+
placeholders with your actual values.
37+
38+
```bash
39+
# Your GCP project ID.
40+
# If you've already set it in your local config, you can retrieve it via:
41+
# gcloud config get-value project
42+
export PROJECT_ID=<PROJECT_ID>
43+
44+
# The name of your GKE cluster.
45+
export CLUSTER_NAME=<CLUSTER_NAME>
46+
47+
# The GCP location of your GKE cluster.
48+
export ZONE=<ZONE> # e.g., 'us-central1' or 'us-central1-a'
49+
50+
# Use a GCS bucket you own to store logs and checkpoints.
51+
export BASE_OUTPUT_DIRECTORY=<GCS_BUCKET> # e.g., gs://my-bucket/maxtext-runs
52+
```
53+
54+
## Authenticate with Hugging Face
55+
56+
To download the `gemma4-e4b` model checkpoint from Hugging Face, you need to authenticate using your Hugging Face account credentials. Run the following command and follow the prompts to log in:
57+
58+
```bash
59+
hf auth login
60+
```
61+
62+
## Get Your MaxText Compatible Model Checkpoint
63+
64+
### Option 1: Using an existing MaxText checkpoint
65+
66+
If you already have a MaxText-compatible model checkpoint, simply set the
67+
following environment variable and move on to the next section.
68+
69+
```bash
70+
export MAXTEXT_CKPT_PATH=<CKPT_PATH> # e.g., gs://my-bucket/my-model-checkpoint/0/items
71+
```
72+
73+
### Option 2: Converting from a Hugging Face checkpoint
74+
75+
Refer to [Hugging Face to MaxText](hf-to-maxtext) to convert a Hugging Face checkpoint to MaxText format. After conversion finishes, set `MAXTEXT_CKPT_PATH` to the converted MaxText checkpoint path.
76+
77+
```bash
78+
export MAXTEXT_CKPT_PATH=<CKPT_PATH> # e.g., gs://my-bucket/my-model-checkpoint/0/items
79+
```
80+
81+
> **Note (Gemma 4 E4B specifics):**
82+
>
83+
> - For the `gemma4-e4b` model, you must run the conversion with `scan_layers=False` (the `gemma4_small` decoder block is incompatible with `nn.scan`); the resulting unscanned checkpoint matches the `scan_layers=False` setting used for the RL run below.
84+
> - This RL recipe fine-tunes the **base** model (`google/gemma-4-E4B`), not the instruction-tuned default. Pass `--hf_model_path=google/gemma-4-E4B` explicitly when running `to_maxtext` — otherwise MaxText defaults to `HF_IDS[gemma4-e4b]` = `google/gemma-4-E4B-it`.
85+
86+
## Chat Template Configuration
87+
88+
Unlike an instruction-tuned tokenizer, the `google/gemma-4-E4B` base tokenizer does **not** ship with a chat template, so the RL run must supply one explicitly. The [run_gemma4_e4b_rl.sh](https://github.com/AI-Hypercomputer/maxtext/blob/main/src/maxtext/trainers/post_train/rl/scripts/run_gemma4_e4b_rl.sh) script points at two files bundled with the repo (and therefore baked into your Docker image):
89+
90+
- `data_template_path=maxtext/examples/chat_templates/openmathinstruct2_rl.json` — a stripped-down data template that injects only the system prompt and question (no turn markers). This avoids the doubled `<start_of_turn>` delimiters that would occur if the default `gsm8k_rl.json` (which bakes literal Gemma turn markers into the message content) were combined with the tokenizer's Jinja chat template.
91+
- `chat_template_path=maxtext/examples/chat_templates/gemma-3-27b-chat_template.json` — the Gemma 3 chat template, wrapped in a JSON object with a `chat_template` key. It is applied by the tokenizer as the single source of truth for turn-marker formatting.
92+
93+
Both files are already included under `src/maxtext/examples/chat_templates/`, so no additional setup is required. If you want to customize the prompt formatting, edit these files (or point the config at your own) before building the Docker image.
94+
95+
## Run RL Workload
96+
97+
### Build and Upload MaxText Docker Image
98+
99+
For instructions on building and uploading the MaxText Docker image with post-training dependencies, please refer to the [official documentation](../../build_maxtext.md).
100+
101+
### Submit your workload
102+
103+
```bash
104+
# The Docker image you pushed in the previous step
105+
export CLOUD_IMAGE_NAME=<IMAGE_NAME>
106+
export DOCKER_IMAGE="gcr.io/${PROJECT_ID?}/${CLOUD_IMAGE_NAME?}"
107+
108+
# Run the RL training script on your cluster
109+
run_tutorial maxtext/trainers/post_train/rl/scripts/run_gemma4_e4b_rl.sh
110+
```
111+
112+
> **Note:** The `run_gemma4_e4b_rl.sh` script pins the Pathways component images to specific versions via the xpk `--server-image` and `--proxy-server-image` flags (set through the `PATHWAYS_SERVER_IMAGE` and `PATHWAYS_PROXY_SERVER_IMAGE` variables at the top of the script). The `--server-image` is used for both the Pathways resource-manager server and the workers (the reference config uses the same image for both). Update these variables if you need a different Pathways release.
113+
114+
### Monitor your workload
115+
116+
To monitor your job's progress, you can use `kubectl` to check the `Jobset` status and stream logs directly from the pods.
117+
118+
```bash
119+
kubectl get jobset -n default ${WORKLOAD_NAME}
120+
121+
# List pods to find the specific name
122+
kubectl get pods | grep ${WORKLOAD_NAME}
123+
124+
# stream the logs from the running pod (replace <POD_NAME> with the name you found)
125+
kubectl logs -f <POD_NAME>
126+
```
127+
128+
Alternatively, after running the bash script, you will also get a link to the Google Cloud Console to view your workload logs. Follow the link to view logs and monitor your workload's progress in the Cloud Console.
129+
130+
### Monitor RL Metrics
131+
132+
During RL training, you can monitor key metrics to track model convergence, reward trends, and hardware performance.
133+
134+
To enable Tunix-managed metrics measurement, set `enable_tunix_perf_metrics` to `true` in RL configurations. Note that this flag is already set to `True` by default for this tutorial workload. When enabled, Tunix automatically collects and uploads these metrics to TensorBoard.
135+
136+
For a complete list of collected metrics, see the [Tunix Metrics Documentation](https://tunix.readthedocs.io/en/latest/metrics.html). Key metrics to monitor include:
137+
138+
- **Model Quality & Reward Metrics:**
139+
- `rewards/mean`: The average reward across the batch (crucial for tracking learning progress).
140+
- `score/mean`: The average raw score from the reward model before applying the KL penalty.
141+
- **Rollout & Generation Metrics:**
142+
- `rollout_time`: How long each rollout step takes.
143+
- `completions/mean_length`: The average token length of generated completions.
144+
- `actor_dequeue_time`: The time spent waiting for data from the rollout workers (relevant when async rollout is enabled).
145+
- **Performance & Efficiency Metrics:**
146+
- `step_time_sec`: The execution time for a single training step.
147+
148+
## Convert Checkpoint to Hugging Face Format
149+
150+
Refer to [MaxText to Hugging Face](maxtext-to-hf) to convert a MaxText checkpoint back to Hugging Face format.
151+
152+
> **Note (Gemma 4 E4B specifics):** Because this recipe fine-tunes the **base** model, pass `--hf_model_path=google/gemma-4-E4B` to `to_huggingface` so the exported checkpoint bundles the base tokenizer. Without it, `to_huggingface` sources the tokenizer from `HF_IDS[gemma4-e4b]` = `google/gemma-4-E4B-it` (the instruction-tuned model). Also keep `scan_layers=False`, since the `gemma4_small` decoder block is not compatible with scanned layers.

src/maxtext/checkpoint_conversion/to_huggingface.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@
108108
"with values from the MaxText config. If False, raises a ValueError on mismatch.",
109109
)
110110

111+
flags.DEFINE_string(
112+
"hf_model_path",
113+
None,
114+
"(Optional) Customized remote HF repo (or local path) to source the tokenizer/processor "
115+
"from. If not specified, defaults to maxtext.utils.globals.HF_IDS[model_name]. Use this to "
116+
"bundle a different tokenizer than the default (e.g., the base model's tokenizer instead of "
117+
"the instruction-tuned one).",
118+
)
119+
111120
FLAGS = flags.FLAGS
112121

113122

@@ -493,7 +502,7 @@ def main(argv: Sequence[str]) -> None:
493502
if model_key not in HF_IDS:
494503
raise ValueError(f"HF Tokenizer ID not found for model key: {model_key}")
495504
hf_token = config.hf_access_token
496-
hf_tokenizer_id = HF_IDS[model_key]
505+
hf_tokenizer_id = FLAGS.hf_model_path or HF_IDS[model_key]
497506
tokenizer = AutoTokenizer.from_pretrained(hf_tokenizer_id, token=hf_token)
498507

499508
# For multi-modal case:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"chat_template": "{{ bos_token }}\n{%- if messages[0]['role'] == 'system' -%}\n {%- if messages[0]['content'] is string -%}\n {%- set first_user_prefix = messages[0]['content'] + '\n\n' -%}\n {%- else -%}\n {%- set first_user_prefix = messages[0]['content'][0]['text'] + '\n\n' -%}\n {%- endif -%}\n {%- set loop_messages = messages[1:] -%}\n{%- else -%}\n {%- set first_user_prefix = \"\" -%}\n {%- set loop_messages = messages -%}\n{%- endif -%}\n{%- for message in loop_messages -%}\n {%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) -%}\n {{ raise_exception(\"Conversation roles must alternate user/assistant/user/assistant/...\") }}\n {%- endif -%}\n {%- if (message['role'] == 'assistant') -%}\n {%- set role = \"model\" -%}\n {%- else -%}\n {%- set role = message['role'] -%}\n {%- endif -%}\n {{ '<start_of_turn>' + role + '\n' + (first_user_prefix if loop.first else \"\") }}\n {%- if message['content'] is string -%}\n {{ message['content'] | trim }}\n {%- elif message['content'] is iterable -%}\n {%- for item in message['content'] -%}\n {%- if item['type'] == 'image' -%}\n {{ '<start_of_image>' }}\n {%- elif item['type'] == 'text' -%}\n {{ item['text'] | trim }}\n {%- endif -%}\n {%- endfor -%}\n {%- else -%}\n {{ raise_exception(\"Invalid content type\") }}\n {%- endif -%}\n {{ '<end_of_turn>\n' }}\n{%- endfor -%}\n{%- if add_generation_prompt -%}\n {{'<start_of_turn>model\n'}}\n{%- endif -%}\n"
3+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"SYSTEM_PROMPT": "You are given a problem. Think about the problem and provide your reasoning. Place it between {reasoning_start_token} and {reasoning_end_token}. Then, provide the final answer (i.e., just one numerical value) between {solution_start_token} and {solution_end_token}.",
3+
"TEMPLATE": "{system_prompt}\n\n{question}"
4+
}

0 commit comments

Comments
 (0)