Skip to content

Commit d7b1cf5

Browse files
A9ishaGoogle-ML-Automation
authored andcommitted
Migrate single-step SPMD RL trainer from //third_party/py/maxrl/trainer/ to //third_party/py/maxtext/src/maxtext/training_engine/, preserving full file revision history via Piper integration.
PiperOrigin-RevId: 948638401
1 parent 7b0e798 commit d7b1cf5

7 files changed

Lines changed: 1550 additions & 0 deletions

File tree

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+
# http://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+
"""Checkpointing utilities for MaxText training engine."""
16+
17+
from typing import Any
18+
from absl import logging
19+
from flax import nnx
20+
import jax
21+
from maxtext.configs import pyconfig
22+
import orbax.checkpoint as ocp
23+
24+
25+
class CheckpointManager:
26+
"""CheckpointManager wrapper for MaxText training engine."""
27+
28+
def __init__(
29+
self,
30+
checkpoint_dir: str,
31+
config: pyconfig.HyperParameters,
32+
) -> None:
33+
"""Initializes the CheckpointManager.
34+
35+
Args:
36+
checkpoint_dir: The root directory for saving checkpoints.
37+
config: The training configuration.
38+
"""
39+
self._checkpoint_manager: ocp.CheckpointManager | None = None
40+
if checkpoint_dir:
41+
self._checkpoint_manager = ocp.CheckpointManager(
42+
directory=checkpoint_dir,
43+
options=ocp.CheckpointManagerOptions(
44+
save_interval_steps=getattr(config, "checkpoint_period", 1),
45+
max_to_keep=getattr(config, "max_num_checkpoints_to_keep", None),
46+
enable_async_checkpointing=getattr(
47+
config, "async_checkpointing", True
48+
),
49+
),
50+
)
51+
52+
def get_latest_step(self) -> int | None:
53+
"""Returns the latest checkpoint step."""
54+
if self._checkpoint_manager:
55+
return self._checkpoint_manager.latest_step()
56+
return None
57+
58+
def save_checkpoint(
59+
self,
60+
step: int,
61+
model: nnx.Module,
62+
optimizer: nnx.optimizer.Optimizer | None,
63+
custom_metadata: Any,
64+
) -> bool:
65+
"""Saves the params for the given step.
66+
67+
Args:
68+
step: The step to save the params for.
69+
model: The model to save.
70+
optimizer: The optimizer to save.
71+
custom_metadata: Custom metadata to save with the checkpoint.
72+
73+
Returns:
74+
Whether the checkpoint was saved.
75+
"""
76+
if self._checkpoint_manager is None:
77+
logging.info("Checkpointing is disabled, skipping save.")
78+
return False
79+
80+
# Check if the checkpoint already exists at the current step.
81+
if self.get_latest_step() == step:
82+
logging.info(
83+
"Checkpoint already saved at step %d, skipping save.",
84+
step,
85+
)
86+
return False
87+
88+
params = nnx.state(model)
89+
jax.block_until_ready(params)
90+
model_cp_args = ocp.args.PyTreeSave(
91+
item=params,
92+
save_args=jax.tree.map(lambda _: ocp.SaveArgs(), params),
93+
)
94+
save_args = {"model_params": model_cp_args}
95+
if optimizer is not None:
96+
optimizer_state = nnx.state(optimizer, nnx.optimizer.OptState)
97+
jax.block_until_ready(optimizer_state)
98+
optimizer_cp_args = ocp.args.PyTreeSave(
99+
item=optimizer_state,
100+
save_args=jax.tree.map(lambda _: ocp.SaveArgs(), optimizer_state),
101+
)
102+
save_args["optimizer_state"] = optimizer_cp_args
103+
104+
return self._checkpoint_manager.save(
105+
step=step,
106+
args=ocp.args.Composite(**save_args),
107+
custom_metadata=custom_metadata,
108+
)
109+
110+
def restore_checkpoint(
111+
self,
112+
model: nnx.Module,
113+
optimizer: nnx.optimizer.Optimizer | None,
114+
step: int | None = None,
115+
) -> Any:
116+
"""Restores items from the checkpoint at the given step.
117+
118+
Args:
119+
model: The model to restore the params to.
120+
optimizer: The optimizer to restore the state to.
121+
step: Optional step index to restore from.
122+
123+
Returns:
124+
The metadata of the restored checkpoint, or None if no checkpoint was
125+
restored.
126+
"""
127+
if self._checkpoint_manager is None:
128+
logging.info("Checkpointing is disabled, skipping restore.")
129+
return None, {}
130+
131+
if step is None:
132+
step = self.get_latest_step()
133+
if step is None:
134+
logging.info("No checkpoint found, skipping restore.")
135+
return None, {}
136+
137+
metadata = self._checkpoint_manager.metadata(step)
138+
139+
abstract_params = nnx.state(model)
140+
model_args = ocp.args.PyTreeRestore(
141+
item=abstract_params,
142+
restore_args=ocp.checkpoint_utils.construct_restore_args(
143+
target=abstract_params
144+
),
145+
)
146+
if optimizer is not None and "optimizer_state" in metadata.item_metadata:
147+
optimizer_state = nnx.state(optimizer, nnx.optimizer.OptState)
148+
optimizer_args = ocp.args.PyTreeRestore(
149+
item=optimizer_state,
150+
restore_args=ocp.checkpoint_utils.construct_restore_args(
151+
target=optimizer_state
152+
),
153+
)
154+
restore_args = {
155+
"model_params": model_args,
156+
"optimizer_state": optimizer_args,
157+
}
158+
else:
159+
restore_args = {"model_params": model_args}
160+
161+
restored_items = self._checkpoint_manager.restore(
162+
step=step,
163+
args=ocp.args.Composite(**restore_args),
164+
)
165+
if "model_params" in restored_items:
166+
nnx.update(model, restored_items["model_params"])
167+
if optimizer is not None and "optimizer_state" in restored_items:
168+
nnx.update(optimizer, restored_items["optimizer_state"])
169+
170+
return step, metadata
171+
172+
def close(self) -> None:
173+
"""Closes the checkpoint manager."""
174+
if self._checkpoint_manager:
175+
self._checkpoint_manager.close()

0 commit comments

Comments
 (0)