@@ -351,6 +351,22 @@ class TestMaybeSaveCheckpointStepAlignment(unittest.TestCase):
351351 def setUp (self ):
352352 self .tx = optax .adam (1e-3 )
353353
354+ def _config (self , ** overrides ):
355+ """Builds a minimal checkpoint config for maybe_save_checkpoint tests."""
356+ values = {
357+ "pure_nnx" : True ,
358+ "checkpoint_period" : 10 ,
359+ "async_checkpointing" : False ,
360+ "enable_diloco" : False ,
361+ "enable_continuous_checkpointing" : False ,
362+ "enable_emergency_checkpoint" : False ,
363+ "enable_multi_tier_checkpointing" : False ,
364+ "local_checkpoint_period" : 0 ,
365+ "enable_autocheckpoint" : False ,
366+ }
367+ values .update (overrides )
368+ return SimpleNamespace (** values )
369+
354370 def _build_nnx_state (self , num_steps ):
355371 """Build an nnx.State flattened from TrainStateNNX after num_steps gradient applications."""
356372 model = MockModel (rngs = nnx .Rngs (0 ))
@@ -380,12 +396,7 @@ def _build_linen_state(self, num_steps):
380396 def _invoke_maybe_save (self , state , pure_nnx ):
381397 """Call maybe_save_checkpoint with save_checkpoint patched, return {step, state} captured."""
382398 # checkpoint_period=1 keeps force_ckpt_save False regardless of actual_step.
383- config = SimpleNamespace (
384- pure_nnx = pure_nnx ,
385- checkpoint_period = 1 ,
386- async_checkpointing = False ,
387- enable_diloco = False ,
388- )
399+ config = self ._config (pure_nnx = pure_nnx , checkpoint_period = 1 )
389400 mgr = mock .MagicMock ()
390401 mgr .reached_preemption .return_value = False
391402
@@ -451,12 +462,7 @@ def test_maybe_save_checkpoint_skips_if_already_saved(self):
451462 state = self ._build_nnx_state (self .N_STEPS )
452463 actual_step = self .N_STEPS - 1
453464
454- config = SimpleNamespace (
455- pure_nnx = True ,
456- checkpoint_period = 1 ,
457- async_checkpointing = False ,
458- enable_diloco = False ,
459- )
465+ config = self ._config (checkpoint_period = 1 )
460466 mgr = mock .MagicMock ()
461467 mgr .reached_preemption .return_value = False
462468 # Mock latest_step to return the same actual_step
@@ -475,12 +481,7 @@ def test_maybe_save_checkpoint_saves_if_not_already_saved(self):
475481 state = self ._build_nnx_state (self .N_STEPS )
476482 actual_step = self .N_STEPS - 1
477483
478- config = SimpleNamespace (
479- pure_nnx = True ,
480- checkpoint_period = 1 ,
481- async_checkpointing = False ,
482- enable_diloco = False ,
483- )
484+ config = self ._config (checkpoint_period = 1 )
484485 mgr = mock .MagicMock ()
485486 mgr .reached_preemption .return_value = False
486487 # Mock latest_step to return a different step (or None)
@@ -495,6 +496,101 @@ def test_maybe_save_checkpoint_saves_if_not_already_saved(self):
495496 # Assert that save_checkpoint WAS called!
496497 save_checkpoint_mock .assert_called_once ()
497498
499+ def test_maybe_save_checkpoint_skips_non_checkpoint_step_before_state_work (
500+ self ,
501+ ):
502+ """Non-checkpoint steps should not query latest_step or build save args."""
503+ state = mock .Mock ()
504+ config = self ._config ()
505+ mgr = mock .MagicMock ()
506+ mgr .reached_preemption .return_value = False
507+
508+ with mock .patch .object (checkpointing , "save_checkpoint" ) as save_checkpoint_mock :
509+ checkpointing .maybe_save_checkpoint (mgr , state , config , data_iterator = None , step = 3 )
510+
511+ mgr .latest_step .assert_not_called ()
512+ mgr .reached_preemption .assert_called_once_with (3 )
513+ mgr .wait_until_finished .assert_not_called ()
514+ state .to_pure_dict .assert_not_called ()
515+ save_checkpoint_mock .assert_not_called ()
516+
517+ def test_maybe_save_checkpoint_handles_preemption_on_non_checkpoint_step (
518+ self ,
519+ ):
520+ """Non-checkpoint steps must still honor preemption handling."""
521+ state = mock .Mock ()
522+ config = self ._config ()
523+ mgr = mock .MagicMock ()
524+ mgr .reached_preemption .return_value = True
525+
526+ with mock .patch .object (checkpointing , "save_checkpoint" ) as save_checkpoint_mock :
527+ with self .assertRaises (checkpointing .exceptions .StopTraining ):
528+ checkpointing .maybe_save_checkpoint (mgr , state , config , data_iterator = None , step = 3 )
529+
530+ mgr .latest_step .assert_not_called ()
531+ mgr .reached_preemption .assert_called_once_with (3 )
532+ mgr .wait_until_finished .assert_called_once_with ()
533+ state .to_pure_dict .assert_not_called ()
534+ save_checkpoint_mock .assert_not_called ()
535+
536+ def test_maybe_save_checkpoint_allows_local_checkpoint_period (self ):
537+ """Emergency and multi-tier local checkpoint periods dispatch save work."""
538+ for checkpoint_flag in (
539+ "enable_emergency_checkpoint" ,
540+ "enable_multi_tier_checkpointing" ,
541+ ):
542+ with self .subTest (checkpoint_flag = checkpoint_flag ):
543+ state = mock .Mock ()
544+ state .to_pure_dict .return_value = {
545+ "model" : {},
546+ "optimizer" : {"step" : 5 },
547+ }
548+ config = self ._config (
549+ checkpoint_period = 100 ,
550+ local_checkpoint_period = 5 ,
551+ ** {checkpoint_flag : True },
552+ )
553+ mgr = mock .MagicMock ()
554+ mgr .latest_step .return_value = None
555+ mgr .reached_preemption .return_value = False
556+ save_checkpoint_mock = mock .MagicMock (return_value = False )
557+
558+ with mock .patch .object (checkpointing , "save_checkpoint" , save_checkpoint_mock ):
559+ checkpointing .maybe_save_checkpoint (mgr , state , config , data_iterator = None , step = 5 )
560+
561+ mgr .latest_step .assert_called_once_with ()
562+ mgr .reached_preemption .assert_called_once_with (5 )
563+ mgr .wait_until_finished .assert_not_called ()
564+ state .to_pure_dict .assert_called_once_with ()
565+ save_checkpoint_mock .assert_called_once ()
566+
567+ def test_maybe_save_checkpoint_allows_mtc_period_with_continuous_policy (
568+ self ,
569+ ):
570+ """Continuous checkpointing should not suppress MTC local saves."""
571+ state = mock .Mock ()
572+ state .to_pure_dict .return_value = {"model" : {}, "optimizer" : {"step" : 5 }}
573+ config = self ._config (
574+ checkpoint_period = 100 ,
575+ enable_continuous_checkpointing = True ,
576+ enable_multi_tier_checkpointing = True ,
577+ local_checkpoint_period = 5 ,
578+ )
579+ mgr = mock .MagicMock ()
580+ mgr .should_save .return_value = False
581+ mgr .latest_step .return_value = None
582+ mgr .reached_preemption .return_value = False
583+ save_checkpoint_mock = mock .MagicMock (return_value = False )
584+
585+ with mock .patch .object (checkpointing , "save_checkpoint" , save_checkpoint_mock ):
586+ checkpointing .maybe_save_checkpoint (mgr , state , config , data_iterator = None , step = 5 )
587+
588+ mgr .should_save .assert_called_once_with (5 )
589+ mgr .latest_step .assert_called_once_with ()
590+ mgr .reached_preemption .assert_called_once_with (5 )
591+ state .to_pure_dict .assert_called_once_with ()
592+ save_checkpoint_mock .assert_called_once ()
593+
498594
499595class TestLinenCheckpointFormatConverters (unittest .TestCase ):
500596 """to_linen_checkpoint_dict / from_linen_checkpoint_dict (NNX <-> Linen on-disk layout)."""
0 commit comments