Skip to content

Commit b7ae97e

Browse files
angel-coreGoogle-ML-Automation
authored andcommitted
Refactor MaxText's checkpointing system to primarily use the Orbax v1 API, moving away from the legacy v0 implementations.
PiperOrigin-RevId: 940558946
1 parent 5783a8b commit b7ae97e

33 files changed

Lines changed: 1479 additions & 944 deletions

‎src/maxtext/checkpoint_conversion/reshard_checkpoint.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def main(argv: Sequence[str]) -> None:
117117
save_ckpt_path = os.path.join(save_ckpt_directory, str(step_number), "items")
118118
max_logging.log(f"Saved checkpoint: {save_ckpt_path}")
119119
# Upon preemption, exit when and only when all ongoing saves are complete.
120-
checkpoint_manager.wait_until_finished()
120+
checkpointing.wait_until_finished(checkpoint_manager)
121121

122122
max_logging.log(f"Elapse for checkpoint save: {(time.time() - start) / 60:.2f} min")
123123
print_peak_memory()

‎src/maxtext/checkpoint_conversion/standalone_scripts/convert_gemma2_chkpt.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,8 @@ def astype_fn(x):
301301
if checkpointing.save_checkpoint(checkpoint_manager, 0, state_new):
302302
max_logging.log("saved a checkpoint at step 0")
303303
# Upon preemption, exit when and only when all ongoing saves are complete.
304-
if checkpoint_manager.reached_preemption(0):
305-
checkpoint_manager.wait_until_finished()
304+
if checkpointing.reached_preemption(checkpoint_manager, 0):
305+
checkpointing.wait_until_finished(checkpoint_manager)
306306
sys.exit()
307307

308308

‎src/maxtext/checkpoint_conversion/standalone_scripts/convert_gemma3_chkpt.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@ def astype_fn(x):
241241
max_logging.log("saved a checkpoint at step 0")
242242
max_logging.log(f"Checkpoint saved to: {args.maxtext_model_path}")
243243
# Upon preemption, exit when and only when all ongoing saves are complete.
244-
if checkpoint_manager.reached_preemption(0):
245-
checkpoint_manager.wait_until_finished()
244+
if checkpointing.reached_preemption(checkpoint_manager, 0):
245+
checkpointing.wait_until_finished(checkpoint_manager)
246246
sys.exit()
247247

248248

‎src/maxtext/checkpoint_conversion/standalone_scripts/convert_gemma_chkpt.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ def astype_fn(x):
175175
if checkpointing.save_checkpoint(checkpoint_manager, 0, state_new):
176176
max_logging.log("saved a checkpoint at step 0")
177177
# Upon preemption, exit when and only when all ongoing saves are complete.
178-
if checkpoint_manager.reached_preemption(0):
179-
checkpoint_manager.wait_until_finished()
178+
if checkpointing.reached_preemption(checkpoint_manager, 0):
179+
checkpointing.wait_until_finished(checkpoint_manager)
180180
sys.exit()
181181

182182

‎src/maxtext/checkpoint_conversion/standalone_scripts/convert_gpt3_ckpt_from_paxml.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,8 @@ def map_fn(key_path, value):
306306
if checkpointing.save_checkpoint(checkpoint_manager, step_value, converted_state):
307307
max_logging.log(f"saved a checkpoint at step {step_value}")
308308
# Upon preemption, exit when and only when all ongoing saves are complete.
309-
if checkpoint_manager.reached_preemption(step_value):
310-
checkpoint_manager.wait_until_finished()
309+
if checkpointing.reached_preemption(checkpoint_manager, step_value):
310+
checkpointing.wait_until_finished(checkpoint_manager)
311311
sys.exit()
312312

313313
max_logging.log(f"Peak cpu memory in a single process: {fmt_size(memory_metrics['max_cpu_bytes'])}")

‎src/maxtext/checkpoint_conversion/standalone_scripts/llama_ckpt_conversion_inference_only.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ def checkpoint_device_put(arr):
314314
if checkpointing.save_checkpoint(checkpoint_manager, step_number_to_save_new_ckpt, state_new):
315315
max_logging.log(f"saved a checkpoint at step {step_number_to_save_new_ckpt}")
316316
# Upon preemption, exit when and only when all ongoing saves are complete.
317-
if checkpoint_manager.reached_preemption(0):
318-
checkpoint_manager.wait_until_finished()
317+
if checkpointing.reached_preemption(checkpoint_manager, 0):
318+
checkpointing.wait_until_finished(checkpoint_manager)
319319
sys.exit()
320320

321321

‎src/maxtext/checkpoint_conversion/utils/utils.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,7 @@ def save_weights_to_checkpoint(
12461246
if checkpointing.save_checkpoint(checkpoint_manager, step_number_to_save_new_ckpt, state_new, config=config):
12471247
max_logging.log(f"saved a checkpoint at step {step_number_to_save_new_ckpt}")
12481248
# Upon preemption, exit when and only when all ongoing saves are complete.
1249-
checkpoint_manager.wait_until_finished()
1249+
checkpointing.wait_until_finished(checkpoint_manager)
12501250

12511251
max_logging.log(f"Elapse for checkpoint save: {(time.time() - start) / 60:.2f} min")
12521252

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Maps MaxText checkpoint config onto the Orbax v1 Context + training policies.
16+
17+
This module is the single place that translates MaxText's flat checkpoint flags
18+
into those objects. It builds configuration only.
19+
"""
20+
import datetime
21+
22+
from orbax.checkpoint import pathways as ocp_pathways
23+
from orbax.checkpoint import v1 as ocp
24+
25+
26+
# v0 PyTreeCheckpointHandler converts `*_concurrent_gb` with GB = 10**9 bytes.
27+
_BYTES_PER_GB = 10**9
28+
29+
# Matches the v0 SingleReplicaArrayHandler broadcast limit (1000 MB) that
30+
# MaxText used when restoring a single replica and broadcasting to the rest.
31+
_SINGLE_REPLICA_BROADCAST_MEMORY_LIMIT_BYTES = 1024 * 1024 * 1000
32+
33+
34+
def build_save_decision_policy(
35+
*,
36+
save_interval_steps: int | None = None,
37+
enable_continuous_checkpointing: bool = False,
38+
enable_autocheckpoint: bool = False,
39+
) -> ocp.training.save_decision_policies.SaveDecisionPolicy:
40+
"""Builds the v1 SaveDecisionPolicy.
41+
42+
- continuous: save as often as possible (async-friendly).
43+
- autocheckpoint: save on preemption OR at the fixed interval (if provided).
44+
- otherwise: save at the fixed interval.
45+
46+
Args:
47+
save_interval_steps: Save every N steps. Optional.
48+
enable_continuous_checkpointing: If true, save as often as possible.
49+
enable_autocheckpoint: If true, save on preemption OR at the fixed interval.
50+
51+
Returns:
52+
A configured ``ocp.training.save_decision_policies.SaveDecisionPolicy``.
53+
"""
54+
policies = ocp.training.save_decision_policies
55+
if enable_continuous_checkpointing:
56+
return policies.ContinuousCheckpointingPolicy() # pyrefly: ignore[bad-return]
57+
if enable_autocheckpoint:
58+
if save_interval_steps is not None:
59+
return policies.AnySavePolicy( # pyrefly: ignore[bad-return]
60+
[
61+
policies.PreemptionCheckpointingPolicy(),
62+
policies.FixedIntervalPolicy(save_interval_steps),
63+
]
64+
)
65+
return policies.PreemptionCheckpointingPolicy() # pyrefly: ignore[bad-return]
66+
if save_interval_steps is None:
67+
raise ValueError("save_interval_steps must be provided for fixed interval checkpointing.")
68+
return policies.FixedIntervalPolicy(interval=save_interval_steps) # pyrefly: ignore[bad-return]
69+
70+
71+
def build_preservation_policy(*, max_to_keep: int) -> ocp.training.preservation_policies.PreservationPolicy:
72+
"""Builds the v1 PreservationPolicy (keep the latest N checkpoints).
73+
74+
Args:
75+
max_to_keep: The maximum number of checkpoints to keep.
76+
77+
Returns:
78+
A configured ``ocp.training.preservation_policies.PreservationPolicy``.
79+
"""
80+
return ocp.training.preservation_policies.LatestN(max_to_keep) # pyrefly: ignore[bad-return]
81+
82+
83+
def build_context(
84+
*,
85+
use_ocdbt: bool = True,
86+
use_zarr3: bool = True,
87+
ocdbt_target_data_file_size_bytes: int | None = None,
88+
checkpoint_storage_concurrent_gb: int | None = None,
89+
enable_continuous_checkpointing: bool = False,
90+
todelete_full_path: str | None = None,
91+
todelete_subdir: str | None = None,
92+
enable_single_replica_ckpt_restoring: bool = False,
93+
replica_axis_index: int = 0,
94+
colocated_python_checkpointing: bool = False,
95+
partial_load: bool = False,
96+
checkpoint_layout: ocp.options.CheckpointLayout | None = None,
97+
) -> ocp.Context:
98+
"""Builds an Orbax v1 ``Context`` from MaxText checkpoint flags.
99+
100+
The returned Context is unfrozen (its options are mutable until it is entered
101+
via ``with ctx:``); callers pass it to ``ocp_v1.training.Checkpointer``, which
102+
applies it to every save/load.
103+
104+
Args:
105+
use_ocdbt: Use OCDBT storage format.
106+
use_zarr3: Use Zarr3 storage format.
107+
ocdbt_target_data_file_size_bytes: Target OCDBT data-file size; also used as
108+
the per-array ``chunk_byte_size`` (matching the v0 ``SaveArgs`` value).
109+
checkpoint_storage_concurrent_gb: Concurrent IO budget in GB; applied to
110+
both write and read as a byte limit (v0 used one value for both).
111+
enable_continuous_checkpointing: If true, set a 60-minute async timeout.
112+
todelete_full_path: GCS soft-delete path.
113+
todelete_subdir: Subdirectory renaming hook for deletions.
114+
enable_single_replica_ckpt_restoring: Restore on one replica and broadcast
115+
to the rest (replaces the v0 ``SingleReplicaArrayHandler``).
116+
replica_axis_index: Mesh axis separating replicas for load-and-broadcast.
117+
colocated_python_checkpointing: Use Pathways colocated-python checkpointing.
118+
partial_load: Restore only the keys present in the abstract tree (the v1
119+
equivalent of v0 ``partial_restore=True``).
120+
checkpoint_layout: On-disk layout (``ORBAX`` or ``SAFETENSORS``) for
121+
loading.
122+
123+
Returns:
124+
A configured, unfrozen ``ocp_v1.Context``.
125+
"""
126+
ctx = ocp.Context()
127+
128+
# Array storage format + file sizing.
129+
ctx.array.saving.use_ocdbt = use_ocdbt
130+
ctx.array.saving.use_zarr3 = use_zarr3
131+
if ocdbt_target_data_file_size_bytes is not None:
132+
ctx.array.saving.ocdbt_target_data_file_size = ocdbt_target_data_file_size_bytes
133+
ctx.array.saving.storage_options.chunk_byte_size = ocdbt_target_data_file_size_bytes
134+
135+
# Concurrent IO budget: v0 GB -> v1 bytes, applied to both directions.
136+
if checkpoint_storage_concurrent_gb is not None:
137+
concurrent_bytes = checkpoint_storage_concurrent_gb * _BYTES_PER_GB
138+
ctx.memory.write_concurrent_bytes = concurrent_bytes
139+
ctx.memory.read_concurrent_bytes = concurrent_bytes
140+
141+
if enable_continuous_checkpointing:
142+
ctx.asynchronous.timeout_secs = int(datetime.timedelta(minutes=60).total_seconds())
143+
144+
if todelete_full_path is not None:
145+
ctx.deletion.gcs_deletion_options.todelete_full_path = todelete_full_path
146+
147+
if todelete_subdir is not None:
148+
raise ValueError("Renaming to subdirectory before deleting (todelete_subdir) is now unsupported by Orbax v1.")
149+
150+
# Single-replica restore (load on one replica, broadcast to the others).
151+
if enable_single_replica_ckpt_restoring:
152+
ctx.array.loading.use_load_and_broadcast = True
153+
ctx.array.loading.load_and_broadcast_options.replica_axis_index = replica_axis_index
154+
ctx.array.loading.load_and_broadcast_options.broadcast_memory_limit_bytes = (
155+
_SINGLE_REPLICA_BROADCAST_MEMORY_LIMIT_BYTES
156+
)
157+
158+
if colocated_python_checkpointing:
159+
ctx.pathways.checkpointing_impl = ocp_pathways.CheckpointingImpl.from_options(
160+
use_colocated_python=True,
161+
)
162+
else:
163+
# v0 only used Pathways handlers when explicitly registered,
164+
# and the persistence handler rejects non-NamedSharding arrays and the
165+
# OCDBT/zarr3 layout MaxText writes. NO_DISPATCHER restores the standard
166+
# controller-side ArrayHandler.
167+
ctx.pathways.checkpointing_impl = ocp_pathways.CheckpointingImpl.NO_DISPATCHER
168+
169+
if partial_load:
170+
ctx.pytree.loading.partial_load = True
171+
172+
if checkpoint_layout is not None:
173+
ctx.checkpoint_layout = checkpoint_layout
174+
175+
return ctx

0 commit comments

Comments
 (0)