3131
3232from maxtext .utils import max_logging
3333from maxtext .utils import maxtext_utils
34- from maxtext .common import checkpointing
34+ # Reuse MaxText's native checkpointing logic.
35+ from maxtext .common .checkpointing import GrainCheckpointHandler , GrainCheckpointSave , GrainCheckpointRestore
3536from tunix .sft import checkpoint_manager as tunix_checkpoint_manager
3637from tunix .sft import peft_trainer
3738
@@ -507,9 +508,7 @@ def compute_loss(
507508 log_t_p_T_sparse = jax .nn .log_softmax (t_logits / temperature , axis = - 1 )
508509
509510 # 2. Gather Student unnormalized logits at the Teacher's exact Top-K indices
510- s_logits_sparse = jnp .take_along_axis (
511- s_logits , teacher_output .top_k_indices , axis = - 1
512- ) # pyrefly: ignore[bad-argument-type]
511+ s_logits_sparse = jnp .take_along_axis (s_logits , teacher_output .top_k_indices , axis = - 1 ) # pyrefly: ignore[bad-argument-type]
513512
514513 # 3. Normalize Student probabilities only over the exact same Top-K subset
515514 log_s_T_sparse = jax .nn .log_softmax (s_logits_sparse / temperature , axis = - 1 )
@@ -559,9 +558,7 @@ def compute_loss(
559558 s_features_sliced = s_features_sliced .astype (jnp .float32 )
560559 t_features_sliced = t_features_sliced .astype (jnp .float32 )
561560
562- feature_loss = beta_feature * self .feature_loss_fn (
563- s_features_sliced , t_features_sliced , mask
564- ) # pyrefly: ignore[not-callable]
561+ feature_loss = beta_feature * self .feature_loss_fn (s_features_sliced , t_features_sliced , mask ) # pyrefly: ignore[not-callable]
565562
566563 total_loss = base_logit_loss + feature_loss
567564
@@ -650,9 +647,8 @@ def create_labels(self, targets, targets_segmentation=None, **kwargs):
650647class MaxTextCheckpointManager (tunix_checkpoint_manager .CheckpointManager ):
651648 """Custom CheckpointManager that uses MaxText's native handlers.
652649
653- Model and optimizer are delegated to Tunix's v1 ``Checkpointer`` unchanged.
654- The Grain input pipeline is added as an extra ``"iter"`` checkpointable via
655- ``GrainCheckpointable``, which wraps MaxText's ``GrainCheckpointHandler``.
650+ This manager extends Tunix to support saving/restoring the MaxText input pipeline
651+ (Grain) alongside the model and optimizer.
656652 """
657653
658654 def __init__ (
@@ -666,6 +662,32 @@ def __init__(
666662 self .student_config = student_config
667663 self ._iterator = raw_iterator
668664
665+ # Re-initialize internal Orbax manager with MaxText's Grain handler
666+ # pylint: disable=access-member-before-definition
667+ # pytype: disable=attribute-error
668+ if self ._checkpoint_manager is not None :
669+ root_directory = self ._checkpoint_manager .directory
670+
671+ if options is None :
672+ options = getattr (self ._checkpoint_manager , "options" , None )
673+
674+ item_handlers = {
675+ "model_params" : checkpoint .PyTreeCheckpointHandler (),
676+ "optimizer_state" : checkpoint .PyTreeCheckpointHandler (),
677+ "custom_metadata" : checkpoint .JsonCheckpointHandler (),
678+ # Use MaxText's handler for the iterator
679+ "iter" : GrainCheckpointHandler (),
680+ }
681+
682+ self ._checkpoint_manager .close ()
683+ self ._checkpoint_manager = checkpoint .CheckpointManager (
684+ root_directory ,
685+ item_handlers = item_handlers ,
686+ options = options ,
687+ )
688+ # pytype: enable=attribute-error
689+ # pylint: enable=access-member-before-definition
690+
669691 def save (
670692 self ,
671693 step ,
@@ -675,8 +697,10 @@ def save(
675697 force = False ,
676698 custom_metadata = None ,
677699 ):
678- """Saves model, optimizer and the Grain input pipeline state."""
679- if self ._checkpointer is None :
700+ """Saves the checkpoint including the input pipeline state (if available)."""
701+ if self ._checkpoint_manager is None :
702+ return False
703+ if not force and not self ._checkpoint_manager .should_save (step ):
680704 return False
681705
682706 # Standard Tunix Logic for Model/Optimizer.
@@ -687,12 +711,21 @@ def save(
687711 else :
688712 params = nnx .state (target_model )
689713
690- checkpointables : dict [str , Any ] = {"model_params" : params }
691- # Exclude optimizer state when learn_to_init_mode is active.
714+ # Define standard SaveArgs once to reuse
715+ default_save_args = checkpoint .SaveArgs ()
716+ cp_save_args = {
717+ "model_params" : checkpoint .args .PyTreeSave (
718+ item = params , save_args = jax .tree .map (lambda _ : default_save_args , params )
719+ ),
720+ }
721+ # Exclude optimizer state if the flag is set OR if learn_to_init_mode is active.
692722 exclude_opt = self .student_config .learn_to_init_mode
693723
694724 if optimizer is not None and not exclude_opt :
695- checkpointables ["optimizer_state" ] = nnx .state (optimizer , nnx .optimizer .OptState )
725+ optimizer_state = nnx .state (optimizer , nnx .optimizer .OptState )
726+ cp_save_args ["optimizer_state" ] = checkpoint .args .PyTreeSave (
727+ item = optimizer_state , save_args = jax .tree .map (lambda _ : default_save_args , optimizer_state )
728+ )
696729
697730 if self ._iterator is not None :
698731 # Follow MaxText's logic to handle multi-process saving
@@ -710,11 +743,15 @@ def save(
710743 local_iter = data_iter .local_iterator if hasattr (data_iter , "local_iterator" ) else data_iter
711744 grain_iters_to_save .append ((local_iter , process_index , process_count_total ))
712745
713- checkpointables ["iter" ] = checkpointing .GrainCheckpointable (
714- save_args = checkpointing .GrainCheckpointSave (item = grain_iters_to_save ) # pyrefly: ignore[bad-assignment]
715- )
746+ # Use GrainCheckpointSave wrapper
747+ cp_save_args ["iter" ] = GrainCheckpointSave (item = grain_iters_to_save ) # pyrefly: ignore[bad-assignment]
716748
717- return self ._save_checkpointables (step , checkpointables , force , custom_metadata )
749+ return self ._checkpoint_manager .save (
750+ step ,
751+ args = checkpoint .args .Composite (** cp_save_args ),
752+ custom_metadata = custom_metadata or {},
753+ force = force ,
754+ )
718755
719756 def maybe_restore ( # pyrefly: ignore[bad-override]
720757 self ,
@@ -729,12 +766,12 @@ def maybe_restore( # pyrefly: ignore[bad-override]
729766 Returns:
730767 (restored step, custom_metadata dict). Step is 0 if no checkpoint exists.
731768 """
732- if self ._checkpointer is None :
769+ if self ._checkpoint_manager is None :
733770 return 0 , {}
734771
735772 target_model = getattr (model , "student_model" , model )
736773
737- step , custom_metadata = super ().maybe_restore (
774+ step , _ = super ().maybe_restore (
738775 model = target_model , # pyrefly: ignore[bad-argument-type]
739776 optimizer = optimizer ,
740777 restore_only_lora_params = restore_only_lora_params ,
@@ -744,14 +781,20 @@ def maybe_restore( # pyrefly: ignore[bad-override]
744781
745782 max_logging .log (f"Restored from checkpoint step { step } ." )
746783
747- return step , dict (custom_metadata or {})
784+ metadata = self ._checkpoint_manager .metadata (step )
785+ if metadata and hasattr (metadata , "custom_metadata" ) and metadata .custom_metadata is not None :
786+ custom_metadata = metadata .custom_metadata
787+ else :
788+ custom_metadata = {}
789+
790+ return step , dict (custom_metadata )
748791
749792 def restore_iterator (self ):
750793 """Restores the iterator using MaxText's logic."""
751- if self ._checkpointer is None or self ._iterator is None :
794+ if self ._checkpoint_manager is None or self ._iterator is None :
752795 return None
753796
754- step = self .latest_step ()
797+ step = self ._checkpoint_manager . latest_step ()
755798 if step is None :
756799 return None
757800
@@ -761,10 +804,9 @@ def restore_iterator(self):
761804 data_iter = self ._iterator
762805 local_iter = data_iter .local_iterator if hasattr (data_iter , "local_iterator" ) else data_iter
763806
764- self ._checkpointer .load_checkpointables (
765- step ,
766- {"iter" : checkpointing .GrainCheckpointable (restore_args = checkpointing .GrainCheckpointRestore (item = local_iter ))},
767- )
807+ restore_args = GrainCheckpointRestore (item = local_iter )
808+
809+ self ._checkpoint_manager .restore (step , args = checkpoint .args .Composite (iter = restore_args ))
768810 # Since Grain restores in-place via set_state(), we return the original object
769811 return self ._iterator
770812
@@ -774,5 +816,5 @@ def restore_iterator(self):
774816
775817 def wait_until_finished (self ):
776818 """Blocks until all outstanding checkpoint operations are complete."""
777- if self ._checkpointer is not None :
778- self ._checkpointer . wait ()
819+ if self ._checkpoint_manager is not None :
820+ self ._checkpoint_manager . wait_until_finished ()
0 commit comments