Skip to content

Commit c6085b4

Browse files
angel-coreGoogle-ML-Automation
authored andcommitted
Isolate Orbax v0 emergency checkpointing.
This change moves the Orbax v0 emergency and replicator checkpoint manager implementations, along with their helper functions, into a new file `emergency_checkpointing.py`. This allows the main `checkpointing.py` to focus on Orbax v1, while still providing backward compatibility for the v0 emergency managers through aliases and dispatch functions. PiperOrigin-RevId: 952439143
1 parent 7b0e798 commit c6085b4

6 files changed

Lines changed: 208 additions & 137 deletions

File tree

src/maxtext/common/checkpointing.py

Lines changed: 19 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,45 @@
1515

1616
"""Create an Orbax CheckpointManager with specified (Async or not) Checkpointer."""
1717

18+
import datetime
1819
import time
1920
from typing import Any
2021

21-
from absl import flags
22-
import datetime
2322
from etils import epath
2423
from flax import nnx
2524
from flax.training import train_state
25+
from grain.experimental import ElasticIterator
2626
import jax
27-
from maxtext.utils.globals import DEFAULT_OCDBT_TARGET_DATA_FILE_SIZE
27+
from maxtext.checkpoint_conversion.utils.load_dynamic import load_safetensors_dynamic_state
28+
from maxtext.common import emergency_checkpointing
29+
from maxtext.common import grain_utility
30+
from maxtext.common import train_state_nnx
2831
from maxtext.input_pipeline.multihost_dataloading import MultiHostDataLoadIterator
2932
from maxtext.input_pipeline.multihost_dataloading import RemoteIteratorWrapper
3033
from maxtext.input_pipeline.synthetic_data_processing import PlaceHolderDataIterator
31-
from maxtext.common import grain_utility
32-
from maxtext.common import train_state_nnx
34+
from maxtext.utils import elastic_utils
3335
from maxtext.utils import exceptions
34-
from maxtext.utils import max_logging
3536
from maxtext.utils import gcs_utils
36-
from maxtext.utils import elastic_utils
37-
from maxtext.checkpoint_conversion.utils.load_dynamic import load_safetensors_dynamic_state
38-
37+
from maxtext.utils import max_logging
38+
from maxtext.utils.globals import DEFAULT_OCDBT_TARGET_DATA_FILE_SIZE
3939
import orbax.checkpoint as ocp
4040
from orbax.checkpoint import v1 as ocp_v1
4141
from orbax.checkpoint._src.arrays import sharding as sharding_utils
4242
from orbax.checkpoint._src.checkpoint_managers import preservation_policy as preservation_policy_lib
4343
from orbax.checkpoint._src.checkpoint_managers import save_decision_policy as save_decision_policy_lib
44-
import orbax.checkpoint.experimental.emergency.checkpoint_manager as emergency_checkpoint_manager
45-
import orbax.checkpoint.experimental.emergency.replicator_checkpoint_manager as emergency_replicator_checkpoint_manager
46-
# pylint: disable=too-many-positional-arguments
4744

48-
from grain.experimental import ElasticIterator
4945

50-
CheckpointManager = ocp.CheckpointManager
5146
CheckpointManagerOptions = ocp.CheckpointManagerOptions
5247
Composite = ocp.args.Composite
5348
PyTreeCheckpointHandler = ocp.PyTreeCheckpointHandler
54-
EmergencyCheckpointManager = emergency_checkpoint_manager.CheckpointManager
55-
LocalCheckpointOptions = emergency_checkpoint_manager.LocalCheckpointOptions
56-
PersistentCheckpointOptions = emergency_checkpoint_manager.PersistentCheckpointOptions
57-
EmergencyReplicatorCheckpointManager = emergency_replicator_checkpoint_manager.ReplicatorCheckpointManager
49+
# Backward compatibility aliases for v0 emergency managers.
50+
EmergencyCheckpointManager = emergency_checkpointing.CheckpointManager
51+
EmergencyReplicatorCheckpointManager = emergency_checkpointing.ReplicatorCheckpointManager
52+
create_orbax_emergency_checkpoint_manager = emergency_checkpointing.create_emergency_checkpoint_manager
53+
create_orbax_emergency_replicator_checkpoint_manager = emergency_checkpointing.create_replicator_checkpoint_manager
54+
55+
# Union of CheckpointManager / the emergency factories return; used in type hints.
56+
CheckpointManager = ocp.CheckpointManager | EmergencyCheckpointManager | EmergencyReplicatorCheckpointManager
5857

5958

6059
def _weight_mismatches(want, have, path=()):
@@ -336,7 +335,7 @@ def create_orbax_checkpoint_manager(
336335
async_options = ocp.AsyncOptions(
337336
timeout_secs=int(datetime.timedelta(minutes=60).total_seconds()),
338337
)
339-
manager = CheckpointManager(
338+
manager = ocp.CheckpointManager(
340339
p,
341340
item_names=item_names,
342341
item_handlers=item_handlers,
@@ -356,115 +355,6 @@ def create_orbax_checkpoint_manager(
356355
return manager
357356

358357

359-
def create_orbax_emergency_checkpoint_manager(
360-
local_checkpoint_dir: str,
361-
persistent_checkpoint_dir: str,
362-
global_mesh: jax.sharding.Mesh,
363-
abstract_state: Any,
364-
local_save_interval_steps: int,
365-
persistent_save_interval_steps: int,
366-
orbax_logger: Any = None, # pytype: disable=attribute-error
367-
):
368-
"""Returns an emergency checkpoint manager."""
369-
flags.FLAGS.experimental_orbax_use_distributed_process_id = True
370-
max_logging.log("Creating emergency checkpoint manager...")
371-
372-
# Only create local directories if running on GPUs as the previous directory structure might be assumed by TPUs.
373-
if global_mesh.devices.flatten()[0].platform == "gpu":
374-
# pylint: disable=protected-access
375-
local_checkpoint_dir = f"{local_checkpoint_dir}/{jax._src.distributed.global_state.process_id}"
376-
local_p = epath.Path(local_checkpoint_dir)
377-
local_p.mkdir(exist_ok=True, parents=True)
378-
379-
persistent_p = gcs_utils.mkdir_and_check_permissions(persistent_checkpoint_dir)
380-
381-
# pure_nnx saves via to_checkpoint_dict (Linen params/opt_state/step plus an nnx_aux
382-
# subtree), but the emergency manager restores against the abstract it is built with.
383-
# Convert it the same way so it matches what is on disk; restore reshapes back to NNX.
384-
if isinstance(abstract_state, nnx.State):
385-
abstract_state = train_state_nnx.to_checkpoint_dict(abstract_state)
386-
387-
manager = EmergencyCheckpointManager(
388-
local_checkpoint_dir,
389-
persistent_p,
390-
global_mesh=global_mesh,
391-
abstract_state=abstract_state,
392-
options=emergency_checkpoint_manager.CheckpointManagerOptions(
393-
local=LocalCheckpointOptions(save_interval_steps=local_save_interval_steps),
394-
persistent=PersistentCheckpointOptions(save_interval_steps=persistent_save_interval_steps),
395-
),
396-
logger=orbax_logger,
397-
)
398-
399-
max_logging.log("Emergency checkpoint manager created!")
400-
return manager
401-
402-
403-
def create_orbax_emergency_replicator_checkpoint_manager(
404-
local_checkpoint_dir: str,
405-
save_interval_steps: int,
406-
global_mesh: jax.sharding.Mesh,
407-
colocated_python_checkpointing: bool = False,
408-
):
409-
"""Returns an emergency replicator checkpoint manager."""
410-
flags.FLAGS.experimental_orbax_use_distributed_process_id = True
411-
max_logging.log("Creating emergency replicator checkpoint manager...")
412-
413-
manager = EmergencyReplicatorCheckpointManager(
414-
epath.Path(local_checkpoint_dir),
415-
options=emergency_replicator_checkpoint_manager.ReplicatorCheckpointManagerOptions(
416-
save_interval_steps=save_interval_steps,
417-
use_colocated_python=colocated_python_checkpointing,
418-
),
419-
global_mesh=global_mesh,
420-
)
421-
422-
max_logging.log("Emergency replicator checkpoint manager created!")
423-
return manager
424-
425-
426-
def replicator_error_handler(config: Any):
427-
"""Replicator error handler to handle errors in replicator service."""
428-
if config.enable_multi_tier_checkpointing:
429-
local_dir = config.local_checkpoint_directory
430-
replicator_errors_file = f"{local_dir}/replicator.errors"
431-
replicator_failed_file = f"{local_dir}/replicator.failed"
432-
process_replicator_error_file(replicator_errors_file)
433-
434-
# if the replicator.failed file exists, then we have a fatal error
435-
is_fatal = process_replicator_error_file(replicator_failed_file)
436-
if is_fatal:
437-
raise ValueError("Replicator fatal error found in replicator.failed file.")
438-
439-
440-
def process_replicator_error_file(error_file: str) -> bool:
441-
"""Handles replicator errors by reading, logging, cleaning the error file."""
442-
error_file_path_exists = epath.Path(error_file).exists()
443-
if error_file_path_exists:
444-
max_logging.log(f"replicator_error_handler: file found: {error_file}.")
445-
read_replicator_error_file(error_file)
446-
cleanup_replicator_error_file(error_file)
447-
448-
return error_file_path_exists
449-
450-
451-
def read_replicator_error_file(error_file: str):
452-
"""Read replicator errors file."""
453-
try:
454-
error_data = epath.Path(error_file).read_text()
455-
max_logging.log(f"Contents of replicator error file:\n{error_data}")
456-
except (OSError, ValueError) as e:
457-
max_logging.log("replicator_error_handler: Failed to read contents of failed" f" file: {e}")
458-
459-
460-
def cleanup_replicator_error_file(error_file: str):
461-
"""Clean up replicator errors file."""
462-
try:
463-
epath.Path(error_file).unlink()
464-
except (OSError, ValueError) as e:
465-
max_logging.log("replicator_error_handler: Failed to remove replicator errors file:" f" {e}")
466-
467-
468358
def print_save_message(step, async_checkpointing):
469359
if async_checkpointing:
470360
max_logging.log(f"Started an asynchronous checkpoint save for step {step}")
@@ -944,7 +834,7 @@ def save_checkpoint(checkpoint_manager, step, state, config=None, data_iterator=
944834
case (checkpoint_manager, _, _) if isinstance(
945835
checkpoint_manager, (EmergencyCheckpointManager, EmergencyReplicatorCheckpointManager)
946836
):
947-
replicator_error_handler(config)
837+
emergency_checkpointing.replicator_error_handler(config)
948838
return checkpoint_manager.save(step, args=Composite(state=checkpoint_args), force=force)
949839
case _:
950840
return checkpoint_manager.save(
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Copyright 2023–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+
"""Orbax v0 emergency / multi-tier checkpoint managers (no v1 equivalent).
16+
17+
MaxText's standard checkpointing runs on Orbax v1 (see ``common/checkpointing.py``).
18+
The emergency and multi-tier replicator managers have no v1 counterpart, so they
19+
stay on Orbax v0 — isolated here so the standard path stays v0-free.
20+
"""
21+
22+
from typing import Any
23+
24+
from absl import flags
25+
from etils import epath
26+
from flax import nnx
27+
import jax
28+
from maxtext.common import train_state_nnx
29+
from maxtext.utils import gcs_utils
30+
from maxtext.utils import globals as maxtext_globals
31+
from maxtext.utils import max_logging
32+
import orbax.checkpoint as ocp
33+
import orbax.checkpoint.experimental.emergency.checkpoint_manager as emergency_checkpoint_manager
34+
import orbax.checkpoint.experimental.emergency.replicator_checkpoint_manager as emergency_replicator_checkpoint_manager
35+
36+
37+
DEFAULT_OCDBT_TARGET_DATA_FILE_SIZE = maxtext_globals.DEFAULT_OCDBT_TARGET_DATA_FILE_SIZE
38+
CheckpointManager = emergency_checkpoint_manager.CheckpointManager
39+
LocalCheckpointOptions = emergency_checkpoint_manager.LocalCheckpointOptions
40+
PersistentCheckpointOptions = emergency_checkpoint_manager.PersistentCheckpointOptions
41+
ReplicatorCheckpointManager = emergency_replicator_checkpoint_manager.ReplicatorCheckpointManager
42+
43+
_MANAGERS = (CheckpointManager, ReplicatorCheckpointManager)
44+
45+
46+
def create_emergency_checkpoint_manager(
47+
local_checkpoint_dir: str,
48+
persistent_checkpoint_dir: str,
49+
global_mesh: jax.sharding.Mesh,
50+
abstract_state: Any,
51+
local_save_interval_steps: int,
52+
persistent_save_interval_steps: int,
53+
orbax_logger: Any = None, # pytype: disable=attribute-error
54+
):
55+
"""Returns an emergency checkpoint manager."""
56+
flags.FLAGS.experimental_orbax_use_distributed_process_id = True
57+
max_logging.log("Creating emergency checkpoint manager...")
58+
59+
# Only create local directories if running on GPUs as the previous directory structure might be assumed by TPUs.
60+
if global_mesh.devices.flatten()[0].platform == "gpu":
61+
# pylint: disable=protected-access
62+
local_checkpoint_dir = f"{local_checkpoint_dir}/{jax._src.distributed.global_state.process_id}"
63+
local_p = epath.Path(local_checkpoint_dir)
64+
local_p.mkdir(exist_ok=True, parents=True)
65+
66+
persistent_p = gcs_utils.mkdir_and_check_permissions(persistent_checkpoint_dir)
67+
68+
# pure_nnx saves via to_checkpoint_dict (Linen params/opt_state/step plus an nnx_aux
69+
# subtree), but the emergency manager restores against the abstract it is built with.
70+
# Convert it the same way so it matches what is on disk; restore reshapes back to NNX.
71+
if isinstance(abstract_state, nnx.State):
72+
abstract_state = train_state_nnx.to_checkpoint_dict(abstract_state)
73+
74+
manager = CheckpointManager(
75+
local_checkpoint_dir,
76+
persistent_p,
77+
global_mesh=global_mesh,
78+
abstract_state=abstract_state,
79+
options=emergency_checkpoint_manager.CheckpointManagerOptions(
80+
local=LocalCheckpointOptions(save_interval_steps=local_save_interval_steps),
81+
persistent=PersistentCheckpointOptions(save_interval_steps=persistent_save_interval_steps),
82+
),
83+
logger=orbax_logger,
84+
)
85+
86+
max_logging.log("Emergency checkpoint manager created!")
87+
return manager
88+
89+
90+
def create_replicator_checkpoint_manager(
91+
local_checkpoint_dir: str,
92+
save_interval_steps: int,
93+
global_mesh: jax.sharding.Mesh,
94+
colocated_python_checkpointing: bool = False,
95+
):
96+
"""Returns an emergency replicator checkpoint manager."""
97+
flags.FLAGS.experimental_orbax_use_distributed_process_id = True
98+
max_logging.log("Creating emergency replicator checkpoint manager...")
99+
100+
manager = ReplicatorCheckpointManager(
101+
epath.Path(local_checkpoint_dir),
102+
options=emergency_replicator_checkpoint_manager.ReplicatorCheckpointManagerOptions(
103+
save_interval_steps=save_interval_steps,
104+
use_colocated_python=colocated_python_checkpointing,
105+
),
106+
global_mesh=global_mesh,
107+
)
108+
109+
max_logging.log("Emergency replicator checkpoint manager created!")
110+
return manager
111+
112+
113+
def save(checkpoint_manager, step, state, config, force):
114+
"""v0 emergency save: writes the state under the ``state`` checkpointable."""
115+
chunk_byte_size = (
116+
config.checkpoint_storage_target_data_file_size_bytes if config else DEFAULT_OCDBT_TARGET_DATA_FILE_SIZE
117+
)
118+
checkpoint_args = ocp.args.PyTreeSave(
119+
item=state,
120+
save_args=jax.tree.map(lambda _: ocp.SaveArgs(chunk_byte_size=chunk_byte_size), state),
121+
ocdbt_target_data_file_size=chunk_byte_size,
122+
)
123+
replicator_error_handler(config)
124+
return checkpoint_manager.save(step, args=ocp.args.Composite(state=checkpoint_args), force=force)
125+
126+
127+
def restore(checkpoint_manager, step, abstract_unboxed_pre_state):
128+
"""v0 emergency restore: returns the restored state pytree."""
129+
restore_target = abstract_unboxed_pre_state
130+
if isinstance(abstract_unboxed_pre_state, nnx.State):
131+
restore_target = abstract_unboxed_pre_state.to_pure_dict()
132+
restore_args = jax.tree_util.tree_map(
133+
lambda data: ocp.type_handlers.ArrayRestoreArgs(sharding=data.sharding), restore_target
134+
)
135+
checkpoint_args = ocp.args.PyTreeRestore(item=restore_target, restore_args=restore_args, partial_restore=True)
136+
return checkpoint_manager.restore(step, args=ocp.args.Composite(state=checkpoint_args)).state
137+
138+
139+
def replicator_error_handler(config: Any):
140+
"""Replicator error handler to handle errors in replicator service."""
141+
if config.enable_multi_tier_checkpointing:
142+
local_dir = config.local_checkpoint_directory
143+
replicator_errors_file = f"{local_dir}/replicator.errors"
144+
replicator_failed_file = f"{local_dir}/replicator.failed"
145+
process_replicator_error_file(replicator_errors_file)
146+
147+
# if the replicator.failed file exists, then we have a fatal error
148+
is_fatal = process_replicator_error_file(replicator_failed_file)
149+
if is_fatal:
150+
raise ValueError("Replicator fatal error found in replicator.failed file.")
151+
152+
153+
def process_replicator_error_file(error_file: str) -> bool:
154+
"""Handles replicator errors by reading, logging, cleaning the error file."""
155+
error_file_path_exists = epath.Path(error_file).exists()
156+
if error_file_path_exists:
157+
max_logging.log(f"replicator_error_handler: file found: {error_file}.")
158+
read_replicator_error_file(error_file)
159+
cleanup_replicator_error_file(error_file)
160+
161+
return error_file_path_exists
162+
163+
164+
def read_replicator_error_file(error_file: str):
165+
"""Read replicator errors file."""
166+
try:
167+
error_data = epath.Path(error_file).read_text()
168+
max_logging.log(f"Contents of replicator error file:\n{error_data}")
169+
except (OSError, ValueError) as e:
170+
max_logging.log("replicator_error_handler: Failed to read contents of failed" f" file: {e}")
171+
172+
173+
def cleanup_replicator_error_file(error_file: str):
174+
"""Clean up replicator errors file."""
175+
try:
176+
epath.Path(error_file).unlink()
177+
except (OSError, ValueError) as e:
178+
max_logging.log("replicator_error_handler: Failed to remove replicator errors file:" f" {e}")

0 commit comments

Comments
 (0)