|
| 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