Skip to content

Commit 7ac8f66

Browse files
Merge pull request #4133 from AI-Hypercomputer:feat/nnx-e2e-fixes
PiperOrigin-RevId: 931227563
2 parents 0eadd97 + 9ddf5d4 commit 7ac8f66

3 files changed

Lines changed: 38 additions & 7 deletions

File tree

src/maxtext/trainers/pre_train/train.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
# Placeholder: internal
4646

4747
# pylint: disable=too-many-positional-arguments
48-
from maxtext.layers.multi_token_prediction import calculate_mtp_acceptance_rate, calculate_mtp_loss
48+
from maxtext.layers.multi_token_prediction import calculate_mtp_acceptance_rate, calculate_mtp_loss, mtp_acceptance, mtp_losses
4949
from maxtext.common import checkpointing, profiler
5050
from maxtext.common.goodput import (
5151
GoodputEvent,
@@ -197,6 +197,16 @@ def loss_fn(model, config, data, dropout_rng, params, sparsity_state=None, is_tr
197197
intermediates = nnx.pop(model, nnx.Intermediate)
198198
intermediate_outputs = intermediates.to_pure_dict()
199199

200+
# MTP sows mtp_losses/mtp_acceptance as custom Variable subclasses, not
201+
# Intermediate, so the nnx.pop above misses them. Pop them here under their
202+
# collection names so calculate_mtp_loss / calculate_mtp_acceptance_rate
203+
# find them. Otherwise the MTP loss is silently zeroed. They are also
204+
# excluded from the returned state below so they don't leak into
205+
# out_shardings.
206+
if config.mtp_num_layers > 0:
207+
intermediate_outputs["mtp_losses"] = nnx.pop(model, mtp_losses).to_pure_dict()
208+
intermediate_outputs["mtp_acceptance"] = nnx.pop(model, mtp_acceptance).to_pure_dict()
209+
200210
if config.num_vocab_tiling > 1:
201211
hidden_state_key = ("decoder", "hidden_states")
202212
hidden_states = maxtext_utils.get_nested_value(intermediate_outputs, hidden_state_key)[0]
@@ -532,9 +542,10 @@ def move(path, value):
532542

533543
if isinstance(model, nn.Module):
534544
return new_state, metrics
535-
# Drop Intermediates (e.g. sowed max_logits for QK-Clip) before returning;
536-
# they're absent from state_mesh_shardings and would cause a leaf-count mismatch.
537-
return nnx.state(new_state, nnx.Not(nnx.Intermediate)), metrics
545+
# Drop Intermediates (e.g. sowed max_logits for QK-Clip) and the MTP sown
546+
# vars (mtp_losses/mtp_acceptance) before returning. They're absent from
547+
# state_mesh_shardings and would cause a leaf-count / structure mismatch.
548+
return nnx.state(new_state, nnx.Not(nnx.Any(nnx.Intermediate, mtp_losses, mtp_acceptance))), metrics
538549

539550

540551
def eval_step(model, config, state, data, dropout_rng=None):

src/maxtext/utils/maxtext_utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1670,7 +1670,23 @@ def _make_named_sharding(v):
16701670
local_rules = metadata.get("sharding_rules", ())
16711671
if context_rules or local_rules:
16721672
rules = composite_rules(context_rules, local_rules)
1673-
pspec = PartitionSpec(*from_sharding_rules(out_sharding, rules))
1673+
raw_sharding = from_sharding_rules(out_sharding, rules)
1674+
mesh_axis_names = mesh.axis_names if mesh is not None else ()
1675+
1676+
# from_sharding_rules leaves a logical name with no matching rule unchanged, so a
1677+
# name missing from logical_axis_rules (e.g. concat_embed on the MTP kernel)
1678+
# reaches NamedSharding and is rejected as an unknown mesh axis. Map any such
1679+
# leftover name to None (replicated), matching Linen, whose logical_to_mesh_axes
1680+
# replicates unmatched names.
1681+
def _sanitize(x):
1682+
if isinstance(x, list):
1683+
x = tuple(x)
1684+
if x is None or (isinstance(x, str) and x in mesh_axis_names) or isinstance(x, tuple):
1685+
return x
1686+
return None
1687+
1688+
sanitized_sharding = [_sanitize(x) for x in raw_sharding]
1689+
pspec = PartitionSpec(*sanitized_sharding)
16741690
else:
16751691
pspec = PartitionSpec(*out_sharding)
16761692
return v.replace(NamedSharding(mesh, pspec))

src/maxtext/utils/train_utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,12 @@ def create_train_state_fn():
278278
)
279279
if config.pure_nnx:
280280
with nn_partitioning.axis_rules(config.logical_axis_rules):
281-
# train_state is instance of TrainStateNNX
282-
state_graphdef, _ = nnx.get_abstract_model(init_state_fn, mesh)
281+
# We only need the graphdef here; it's merged with state below. Avoid
282+
# nnx.get_abstract_model: it eagerly builds a NamedSharding for every variable
283+
# under jax.set_mesh(mesh) and rejects any logical name missing from
284+
# logical_axis_rules (e.g. concat_embed on the MTP kernel). Tracing shapes
285+
# without a mesh skips sharding resolution, so it avoids the crash.
286+
state_graphdef = nnx.graphdef(nnx.eval_shape(init_state_fn))
283287
_, state_params, _ = nnx.split(state.model, nnx.Param, ...)
284288
_, state_mesh_shardings_params, _ = nnx.split(state_mesh_shardings.model, nnx.Param, ...)
285289
else:

0 commit comments

Comments
 (0)