Skip to content

Commit b6622f3

Browse files
committed
NNX: fail fast for pipeline parallelism under pure_nnx
The NNX decoder has no pipeline path yet, so under pure_nnx the scanned-layers axis is sharded by 'stage' and dies with a cryptic IndivisibleError at state init. Raise a clear NotImplementedError at config validation pointing users to ici_pipeline_parallelism=1 or the Linen path. NNX pipeline support is tracked as PR11.5.
1 parent b0520da commit b6622f3

3 files changed

Lines changed: 24 additions & 36 deletions

File tree

src/maxtext/common/checkpointing.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
1516
"""Create an Orbax CheckpointManager with specified (Async or not) Checkpointer."""
1617

1718
import time
@@ -844,9 +845,7 @@ def map_to_pspec(data):
844845
(EmergencyCheckpointManager, EmergencyReplicatorCheckpointManager),
845846
):
846847
checkpoint_path = str(checkpoint_manager.directory / str(step) / "items")
847-
with handle_checkpoint_mismatch(
848-
"restore NNX checkpoint", checkpoint_path
849-
):
848+
with handle_checkpoint_mismatch("restore NNX checkpoint", checkpoint_path):
850849
restored_nnx = _load_linen_checkpoint_into_nnx(
851850
checkpoint_path,
852851
abstract_unboxed_pre_state,
@@ -882,9 +881,7 @@ def map_to_pspec(data):
882881
EmergencyReplicatorCheckpointManager,
883882
),
884883
):
885-
restored = checkpoint_manager.restore(
886-
step, args=Composite(state=checkpoint_args)
887-
).state
884+
restored = checkpoint_manager.restore(step, args=Composite(state=checkpoint_args)).state
888885
_assert_no_shaped_dtype_struct(restored)
889886
return (
890887
restored,
@@ -912,9 +909,7 @@ def map_to_pspec(data):
912909
# Case 3: Default/Fallback case.
913910
# This case acts as a wildcard ('_') and matches if none of the preceding cases were met.
914911
case _:
915-
restored = checkpoint_manager.restore(
916-
step, args=Composite(items=checkpoint_args)
917-
)
912+
restored = checkpoint_manager.restore(step, args=Composite(items=checkpoint_args))
918913
_assert_no_shaped_dtype_struct(restored)
919914
return (restored, None)
920915

@@ -924,9 +919,7 @@ def map_to_pspec(data):
924919
else:
925920
params = abstract_unboxed_pre_state.params
926921

927-
with handle_checkpoint_mismatch(
928-
"load parameters", load_parameters_from_path
929-
):
922+
with handle_checkpoint_mismatch("load parameters", load_parameters_from_path):
930923
restored_params = load_params_from_path(
931924
load_parameters_from_path,
932925
params,
@@ -938,9 +931,7 @@ def map_to_pspec(data):
938931
return None, restored_params
939932
elif load_full_state_from_path != "":
940933
max_logging.log(f"Loading full state from path: {load_full_state_from_path}")
941-
with handle_checkpoint_mismatch(
942-
"load full state", load_full_state_from_path
943-
):
934+
with handle_checkpoint_mismatch("load full state", load_full_state_from_path):
944935
restored_state = _load_full_state_from_path(
945936
path=load_full_state_from_path,
946937
abstract_unboxed_pre_state=abstract_unboxed_pre_state,

src/maxtext/configs/types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2784,6 +2784,15 @@ def calculate_global_batch_sizes(per_device_batch_size, expansion_factor, num_de
27842784

27852785
self.using_pipeline_parallelism = self.ici_pipeline_parallelism > 1 or self.dcn_pipeline_parallelism > 1
27862786
if self.using_pipeline_parallelism:
2787+
if self.pure_nnx:
2788+
# The NNX decoder has no pipeline path yet, so the scanned-layers axis ends up
2789+
# sharded by 'stage' and fails with a cryptic IndivisibleError at state init.
2790+
# Fail fast with a clear message instead. NNX pipeline support is tracked as PR11.5.
2791+
raise NotImplementedError(
2792+
"Pipeline parallelism is not yet supported on the NNX path. Set "
2793+
"ici_pipeline_parallelism=1 and dcn_pipeline_parallelism=1, or use the Linen path "
2794+
"(pure_nnx=False enable_nnx=False)."
2795+
)
27872796
num_stages = int(self.ici_pipeline_parallelism * self.dcn_pipeline_parallelism)
27882797
if self.num_pipeline_repeats == -1:
27892798
num_pipeline_repeats, remainder = divmod(

tests/unit/train_state_nnx_checkpoint_test.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ def _build_linen_state(self, num_steps):
351351
def _invoke_maybe_save(self, state, pure_nnx):
352352
"""Call maybe_save_checkpoint with save_checkpoint patched, return {step, state} captured."""
353353
# checkpoint_period=1 keeps force_ckpt_save False regardless of actual_step.
354-
config = SimpleNamespace(pure_nnx=pure_nnx, checkpoint_period=1, async_checkpointing=False)
354+
config = SimpleNamespace(pure_nnx=pure_nnx, checkpoint_period=1, async_checkpointing=False, enable_diloco=False)
355355
mgr = mock.MagicMock()
356356
mgr.reached_preemption.return_value = False
357357

@@ -417,7 +417,7 @@ def test_maybe_save_checkpoint_skips_if_already_saved(self):
417417
state = self._build_nnx_state(self.N_STEPS)
418418
actual_step = self.N_STEPS - 1
419419

420-
config = SimpleNamespace(pure_nnx=True, checkpoint_period=1, async_checkpointing=False)
420+
config = SimpleNamespace(pure_nnx=True, checkpoint_period=1, async_checkpointing=False, enable_diloco=False)
421421
mgr = mock.MagicMock()
422422
mgr.reached_preemption.return_value = False
423423
# Mock latest_step to return the same actual_step
@@ -436,7 +436,7 @@ def test_maybe_save_checkpoint_saves_if_not_already_saved(self):
436436
state = self._build_nnx_state(self.N_STEPS)
437437
actual_step = self.N_STEPS - 1
438438

439-
config = SimpleNamespace(pure_nnx=True, checkpoint_period=1, async_checkpointing=False)
439+
config = SimpleNamespace(pure_nnx=True, checkpoint_period=1, async_checkpointing=False, enable_diloco=False)
440440
mgr = mock.MagicMock()
441441
mgr.reached_preemption.return_value = False
442442
# Mock latest_step to return a different step (or None)
@@ -460,9 +460,7 @@ def _nnx_pure(self):
460460
return {
461461
"model": {
462462
"decoder": {"norm": {"scale": jnp.ones((3,))}},
463-
"dropout": {
464-
"rngs": {"default": {"key": jnp.ones((2,), dtype=jnp.uint32)}}
465-
}, # NNX-only
463+
"dropout": {"rngs": {"default": {"key": jnp.ones((2,), dtype=jnp.uint32)}}}, # NNX-only
466464
},
467465
"optimizer": {
468466
"step": jnp.asarray(7, dtype=jnp.uint32),
@@ -481,9 +479,7 @@ def test_to_linen_layout(self):
481479
linen = train_state_nnx.to_linen_checkpoint_dict(self._nnx_pure())
482480
self.assertEqual(set(linen.keys()), {"params", "step", "opt_state"})
483481
self.assertIn("params", linen["params"]) # params/params/ collection wrap
484-
self.assertNotIn(
485-
"dropout", linen["params"]["params"]
486-
) # NNX-only rngs/dropout stripped
482+
self.assertNotIn("dropout", linen["params"]["params"]) # NNX-only rngs/dropout stripped
487483
self.assertEqual(linen["step"].dtype, jnp.int32) # Linen step is int32
488484
# opt_state is a list with None for the EmptyState slot, mu/nu wrapped under params.
489485
self.assertIsInstance(linen["opt_state"], list)
@@ -493,19 +489,11 @@ def test_to_linen_layout(self):
493489

494490
def test_round_trip_preserves_values(self):
495491
nnx_pure = self._nnx_pure()
496-
back = train_state_nnx.from_linen_checkpoint_dict(
497-
train_state_nnx.to_linen_checkpoint_dict(nnx_pure)
498-
)
492+
back = train_state_nnx.from_linen_checkpoint_dict(train_state_nnx.to_linen_checkpoint_dict(nnx_pure))
499493
self.assertEqual(set(back.keys()), {"model", "optimizer"})
500-
self.assertEqual(
501-
back["optimizer"]["step"].dtype, jnp.uint32
502-
) # NNX step back to uint32
503-
self.assertEqual(
504-
set(back["optimizer"]["opt_state"].keys()), {0, 2}
505-
) # int-keyed dict, EmptyState dropped
506-
self.assertNotIn(
507-
"params", back["optimizer"]["opt_state"][0]["mu"]
508-
) # mu/nu unwrapped
494+
self.assertEqual(back["optimizer"]["step"].dtype, jnp.uint32) # NNX step back to uint32
495+
self.assertEqual(set(back["optimizer"]["opt_state"].keys()), {0, 2}) # int-keyed dict, EmptyState dropped
496+
self.assertNotIn("params", back["optimizer"]["opt_state"][0]["mu"]) # mu/nu unwrapped
509497
self.assertTrue(
510498
jnp.array_equal(
511499
nnx_pure["model"]["decoder"]["norm"]["scale"],

0 commit comments

Comments
 (0)