Skip to content

Commit 171a3c6

Browse files
committed
Checkpoint utility: add gpt-oss to_maxtext & refactor code
1 parent 2f77e7b commit 171a3c6

9 files changed

Lines changed: 694 additions & 500 deletions

File tree

end_to_end/tpu/deepseek/v2-16b/test_deepseek.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# Example Usage: export HF_TOKEN=<huggingface_access_token>; export BASE_OUTPUT_PATH=<GCS_bucket_path>; bash test_deepseek.sh
1111

1212
# The golden logit can be generated by:
13-
# python3 -m MaxText.scratch_code.generate_hf_golden_logits --model-id=deepseek-ai/DeepSeek-V2-Lite --output-path=golden_data_deepseek2-16b.jsonl --prompts='I love to;Today is a;What is the' --hf-model-path=$local_bf16_path --not-trust-remote-code
13+
# python3 -m MaxText.scratch_code.generate_hf_golden_logits --model-id=deepseek-ai/DeepSeek-V2-Lite --output-path=golden_data_deepseek2-16b.jsonl --prompts='I love to;Today is a;What is the' --hf-model-path=$local_bf16_path --trust-remote-code=False
1414

1515
set -ex
1616

end_to_end/tpu/deepseek/v3-671b/2_test_deepseek.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# 2. Run logit check, pre-training, fine-tuning, and decoding.
99

1010
# The golden logit can be generated by:
11-
# python3 -m MaxText.scratch_code.generate_hf_golden_logits --model-id=deepseek-ai/DeepSeek-V3 --output-path=golden_data_deepseek3-671b.jsonl --prompts='I love to' --hf-model-path=$local_bf16_path --not-trust-remote-code --hf-load-dtype=bfloat16
11+
# python3 -m MaxText.scratch_code.generate_hf_golden_logits --model-id=deepseek-ai/DeepSeek-V3 --output-path=golden_data_deepseek3-671b.jsonl --prompts='I love to' --hf-model-path=$local_bf16_path --trust-remote-code=False --hf-load-dtype=bfloat16
1212

1313
set -ex
1414

src/MaxText/scratch_code/generate_hf_golden_logits.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import numpy as np
4848
from google.cloud import storage
4949
from PIL import Image
50+
from MaxText.inference_utils import str2bool
5051

5152
# Load the tokenizer and model from Hugging Face
5253

@@ -184,11 +185,11 @@ def main(raw_args=None) -> None:
184185
default="float32",
185186
help="model_class.from_pretrained: dtype",
186187
)
187-
# variable `args.trust_remote_code` is True by default, False only if with flag `--not-trust-remote-code`
188188
parser.add_argument(
189-
"--not-trust-remote-code",
190-
dest="trust_remote_code",
191-
action="store_false",
189+
"--trust-remote-code",
190+
type=str2bool,
191+
required=False,
192+
default=True,
192193
help="model_class.from_pretrained: trust_remote_code",
193194
)
194195
parser.add_argument(

src/MaxText/utils/ckpt_conversion/README.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ This guide provides instructions for using the scripts that convert model checkp
66

77
The following models are supported:
88

9-
- Gemma2 (2B, 9B, 27B).
10-
- Gemma3 multimodal (4B, 12B, 27B).
11-
- Qwen3 (0.6B, 4B, 8B, 14B, 32B).
12-
- Mixtral (8x7B, 8x22B).
9+
| Model Family | Sizes | HF $\to$ Orbax (scan) | HF $\to$ Orbax (unscan) | Orbax (scan) $\to$ HF | Orbax (unscan) $\to$ HF |
10+
| :--- | :--- | :---: | :---: | :---: | :---: |
11+
| **Gemma2** | 2B, 9B, 27B |||||
12+
| **Gemma3** (Multimodal) | 4B, 12B, 27B | - || - ||
13+
| **Llama3.1** | 8B, 70B, 450B |||||
14+
| **Qwen3** | 0.6B, 4B, 8B, 14B, 32B |||||
15+
| **Qwen3 MoE** | 30B, 235B, 480B |||||
16+
| **Mixtral** | 8x7B, 8x22B |||||
17+
| **GPT-OSS** | 20B, 120B |||||
18+
| **DeepSeek3** | 671B | - | - || - |
19+
1320

1421
## Prerequisites
1522
- Hugging Face requires Pytorch.
@@ -42,8 +49,9 @@ python3 -m MaxText.utils.ckpt_conversion.to_maxtext src/MaxText/configs/base.yml
4249
* `use_multimodal`: Indicates if multimodality is used, important for Gemma3.
4350
* `hf_access_token`: Your Hugging Face token.
4451
* `base_output_directory`: The path where the converted Orbax checkpoint will be stored; it can be Googld Cloud Storage (GCS) or local. If not set, the default output directory is `Maxtext/tmp`.
52+
* `--lazy_load_tensors` (optional): If `true`, loads Hugging Face weights on-demand to minimize RAM usage.
53+
* `--hf_model_path` (optional): Specifies a local directory containing the model weights. If unspecified, we use the [default Hugging Face repository ID](https://github.com/AI-Hypercomputer/maxtext/blob/2f77e7b5fcc4b580bc2d109525c362f3d9056ec9/src/MaxText/utils/ckpt_conversion/utils/utils.py#L54-L82) (e.g., openai/gpt-oss-20b). This is necessary for locally dequantized models like GPT-OSS or DeepSeek.
4554

46-
\*\**It only converts the official version of Hugging Face model. You can refer the supported official version in HF_IDS in `src/MaxText/utils/ckpt_conversion/utils/utils.py`*
4755

4856
## MaxText to Hugging Face
4957

@@ -62,6 +70,7 @@ python3 -m MaxText.utils.ckpt_conversion.to_huggingface src/MaxText/configs/base
6270
scan_layers=false \
6371
use_multimodal=false \
6472
hf_access_token=<your-hf-token> \
73+
weight_dtype=bfloat16
6574
```
6675

6776
**Key arguments:**
@@ -72,6 +81,7 @@ python3 -m MaxText.utils.ckpt_conversion.to_huggingface src/MaxText/configs/base
7281
* `hf_access_token`: Your Hugging Face token.
7382
* `use_multimodal`: Indicates if multimodality is used, important for Gemma3.
7483
* `base_output_directory`: The path where the converted Orbax checkpoint will be stored; it can be Googld Cloud Storage (GCS), Hugging Face Hub or local. If not set, the default output directory is `Maxtext/tmp`.
84+
* `weight_dtype`: dtype for MaxText weights. It affects the resulting HF weight dtype. Default value is `float32`. We recommend using `bfloat16` to save memory and speed up conversion.
7585

7686

7787
## Verifying conversion correctness
@@ -87,11 +97,11 @@ python3 -m tests.forward_pass_logit_checker src/MaxText/configs/base.yml \
8797
model_name=<MODEL_NAME> \
8898
scan_layers=false \
8999
max_prefill_predict_length=4 \
90-
max_target_length=8 \
100+
max_target_length=8 \
91101
use_multimodal=false \
92102
--run_hf_model=True \
93103
--hf_model_path=<path-to-HF-checkpoint> \
94-
--max_kl_div=0.015 \
104+
--max_kl_div=0.015
95105
```
96106

97107
**Key arguments:**

src/MaxText/utils/ckpt_conversion/to_huggingface.py

Lines changed: 12 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,12 @@
7272
)
7373
from MaxText.utils.ckpt_conversion.utils.hf_shape import HF_SHAPE
7474
from MaxText.utils.ckpt_conversion.utils.hf_model_configs import HF_MODEL_CONFIGS
75-
from MaxText.utils.ckpt_conversion.utils.utils import process_maxtext_param, save_model_files, HF_IDS
76-
75+
from MaxText.utils.ckpt_conversion.utils.utils import (
76+
validate_and_filter_param_map_keys,
77+
process_maxtext_param,
78+
save_model_files,
79+
HF_IDS,
80+
)
7781

7882
os.environ["JAX_PLATFORMS"] = "cpu"
7983
os.environ["XLA_FLAGS"] = "--xla_force_host_platform_device_count=16"
@@ -107,59 +111,6 @@ def _get_model_mappings(
107111
}
108112

109113

110-
def _check_param_map_keys(param_map_keys, maxtext_state_keys):
111-
"""Validates map coverage, handles N-to-1 mappings, and filters unused keys.
112-
113-
Ensures every MaxText checkpoint key (`maxtext_state_keys`) is covered by
114-
the flattened parameter map. Keys in the map that are not present in the
115-
checkpoint (common for multi-variant maps like gemma3, qwen3, deepseek) are skipped.
116-
117-
Tuple keys represent N-to-1 mappings (multiple MaxText keys combining into one
118-
target key) and are only returned if all constituent keys exist in the checkpoint.
119-
120-
Args:
121-
param_map_keys: Keys from the parameter mapping (strings or N-to-1 tuples).
122-
maxtext_state_keys: Set of parameter keys loaded from the MaxText checkpoint.
123-
124-
Returns:
125-
A list of 'filtered' mapping keys (strings or tuples) that are fully present
126-
and valid based on `maxtext_state_keys`.
127-
128-
Raises:
129-
ValueError: If `maxtext_state_keys` is NOT a subset of the flattened
130-
`param_map_keys`.
131-
"""
132-
flattened_map_keys = set()
133-
for key in param_map_keys:
134-
if isinstance(key, tuple):
135-
flattened_map_keys.update(key)
136-
else:
137-
flattened_map_keys.add(key)
138-
139-
# every maxtext state key must be covered by param map
140-
missing_keys = maxtext_state_keys - flattened_map_keys
141-
if missing_keys:
142-
raise ValueError(
143-
"maxtext_state_dict must be a subset of flattened param_map"
144-
+ f"\nparam map\n{param_map_keys}"
145-
+ f"\nmaxtext:\n{maxtext_state_keys}"
146-
)
147-
148-
# param map may have extra keys
149-
extra_keys = flattened_map_keys - maxtext_state_keys
150-
if extra_keys:
151-
max_logging.log(f"Warning: extra keys in param_map are skipped: {extra_keys}")
152-
153-
# skip extra keys in param map
154-
filtered_map_keys = []
155-
for key in param_map_keys:
156-
if (isinstance(key, str) and key in maxtext_state_keys) or (
157-
isinstance(key, tuple) and all(k in maxtext_state_keys for k in key)
158-
):
159-
filtered_map_keys.append(key)
160-
return filtered_map_keys
161-
162-
163114
def main(argv: Sequence[str]) -> None:
164115
"""Main function to convert a MaxText checkpoint to HuggingFace format.
165116
@@ -180,6 +131,7 @@ def main(argv: Sequence[str]) -> None:
180131
config.load_full_state_path == ""
181132
), "This script expects parameters, not a full state. Use generate_param_only_checkpoint first if needed."
182133
max_utils.print_system_information()
134+
overall_start = time.time()
183135

184136
# Load Maxtext checkpoint
185137
max_logging.log("\nLoading Orbax checkpoint...")
@@ -189,7 +141,7 @@ def main(argv: Sequence[str]) -> None:
189141
rng, rng_load_params = jax.random.split(rng)
190142
# load params from maxengine
191143
loaded_params_from_engine = engine.load_params(rng_load_params)
192-
max_logging.log(f"Elapse: {(time.time() - start) / 60:.2f} min")
144+
max_logging.log(f"Elapse for checkpoint load: {(time.time() - start) / 60:.2f} min")
193145

194146
if not config.base_output_directory:
195147
output_directory = f"tmp/{config.run_name}"
@@ -239,7 +191,7 @@ def main(argv: Sequence[str]) -> None:
239191
# The param_map may contain tuples as keys, which represent N-to-1 mappings from maxtext to huggingface
240192
# Check maxtext_state_dict is a subset of flattened param_map
241193
# Skip extra keys from param_map
242-
filtered_map_keys = _check_param_map_keys(param_map.keys(), maxtext_state_dict.keys())
194+
filtered_map_keys = validate_and_filter_param_map_keys(param_map.keys(), maxtext_state_dict.keys())
243195

244196
# Iterate through the parameter map to transform and collect weights.
245197
# This loop handles both simple 1-to-1 mappings and complex N-to-1 mappings
@@ -260,7 +212,7 @@ def main(argv: Sequence[str]) -> None:
260212
processed_params_list.extend(processed_params)
261213

262214
transformed_hf_weights = dict(processed_params_list)
263-
max_logging.log(f"Elapse: {(time.time() - start) / 60:.2f} min")
215+
max_logging.log(f"Elapse for transform: {(time.time() - start) / 60:.2f} min")
264216

265217
# 5. Save in HuggingFace Format
266218
if not transformed_hf_weights:
@@ -277,7 +229,8 @@ def main(argv: Sequence[str]) -> None:
277229
output_dir=output_directory,
278230
)
279231
max_logging.log(f"✅ MaxText model successfully saved in HuggingFace format at {output_directory}")
280-
max_logging.log(f"Elapse: {(time.time() - start) / 60:.2f} min")
232+
max_logging.log(f"Elapse for save: {(time.time() - start) / 60:.2f} min")
233+
max_logging.log(f"Overall Elapse: {(time.time() - overall_start) / 60:.2f} min")
281234

282235

283236
if __name__ == "__main__":

0 commit comments

Comments
 (0)