Skip to content

Commit 0f09e3a

Browse files
committed
NNX: correctness fixes, enable feature paths, and vocab tiling on NNX
Bug fixes (run as no-op while pure_nnx=False stays default): - nnx_wrappers.py: add _refresh_variable_trace_state + is_linen_initializing; call from ToLinen after nnx.update to fix "Cannot extract graph node from different trace level" when grad tracers leak into Variable._trace_state. - gpt_oss.py / olmo3.py: replace inline nn.Dropout(...) with self.dropout = linears.Dropout(...) in __init__ to fix CallCompactUnboundModuleError. - normalizations.py: Qwen3NextRMSNorm signature: eps -> epsilon, accept shard_mode/kernel_axes/parameter_memory_host_offload for callsite parity. - attentions.py / qwen3.py: callsites eps= -> epsilon=. - moe.py: per_expert_scale block moved into the unfused-kernel else branch (was scaling wo even when fused_kernel was active). - models.py: build MTP block as MultiTokenPredictionBlock(...) directly (drop the ToNNX(linen) + lazy_init wrap); pass multimodal_input whole to NNXDecoder instead of unpacking 5 fields. - gradient_accumulation.py: ZeRO-1+GA all-reduce annotation deferred until after lax.scan (reduced/unreduced PartitionSpec is rejected inside scan carry); use nnx.merge(..., copy=True) to avoid Variable reuse. - diloco.py: NNX-aware state handling — state.params -> state.model.filter (nnx.Param), step counter at state.optimizer.step, replace_nnx_model_params helper for jax.lax.cond pytree-structure parity. - train_compile.py: new _collect_nnx_activation_shardings helper (forward pass populates _ACTIVATION_SHARDINGS_DUMP — get_abstract_state_nnx only traces __init__); NNX path now passes 2-arg shaped_train_args (no rng); diloco path patched to handle the 2-vs-3 length difference. - muon_utils.py: get_model_mdn default pure_nnx=True; wrap NNX result as {"params": nnx.to_pure_dict(...)} for parity with Linen tree shape. - nnx_decoders.py: FP8+NNX scan fix — Linen FP8 ops (fp8_nanoo, fp8_gpu) retain tracers in Linen scope across re-traces. Skip jax.checkpoint and use a Python for-loop instead of jax.lax.scan when quantization is FP8. Makes FP8 quantization usable on the NNX path. - train.py (pre-train train_step): return nnx.state(new_state, nnx.Not (nnx.Intermediate)) so sowed forward-pass artifacts (e.g. max_logits for QK-Clip) don't break leaf-count parity with state_mesh_shardings. - llama2.py: pass parameter_memory_host_offload to pre_self_attention_layer _norm RMSNorm (was missing on this norm only). - base.yml: add 4 pipeline-related logical_axis_rules — layers_outside _pipeline, layers_per_stage, num_activations, circular_repeats. Additive, no-op without use_nnx_pipeline=True. NNX feature enablements (clear all 17 "Pure NNX support has not been implemented yet" NotImplementedError sites by routing Linen-coupled utilities to the Linen path; their on-disk format is Linen): - layerwise_quantization.py (2 sites): operates on Linen-format checkpoints via DeepSeek*ToLinen layers. - lora_utils.py (1 site): downstream get_lora_abstract_state expects Linen tree shape; LoRA adapters on disk are Linen. - standalone_checkpointer.py (2 sites): add_entropy_to_checkpoint accesses state.opt_state[0]._replace(mu=..., nu=...) — Linen-only. - generate_param_only_checkpoint.py (3 sites): _possibly_unroll_params and _save_decode_checkpoint use state.params["params"]["decoder"] — Linen. - convert_gpt3_ckpt_from_paxml.py (2 sites): keystr_map targets Linen tree paths (.params['params'], .opt_state.mu['params']). - maxengine.py (3 sites): inference engine uses state.params and serves Linen-format inference checkpoints. - grpo_trainer.py (4 sites): RL trainer is end-to-end Linen-shaped; route to Linen with a clear log warning since NNX-format checkpoints will fail at restore time. Vocab tiling on NNX (real implementation, not just routing): - models.py: add Transformer.logits_from_hidden_states on the NNX Transformer class — wraps NNXDecoder.apply_output_head with the token_embedder; mirrors TransformerLinenPure.logits_from_hidden_states. - vocabulary_tiling.py: add vocab_tiling_nnx_loss — chunks the vocab axis via jax.lax.scan and calls model.logits_from_hidden_states(chunk) per chunk. The NNX model carries its parameters internally so no explicit FSDP gather is needed (unlike the Linen gathered_params pattern). MVP uses default autograd; custom_vjp memory-savings optimization is a follow-up if backward memory becomes a concern. - train.py (NNX loss_fn): replace the NotImplementedError with the call to vocab_tiling_nnx_loss using hidden_states from intermediates. - pyconfig_deprecated.py / configs/types.py: drop the num_vocab_tiling > 1 and enable_nnx validation guards (no longer needed). DPO + NNX retained as NotImplementedError but with a much more informative message (points users at pure_nnx=False workaround). Full implementation is deferred — needs a new TrainState shape carrying both policy and reference NNX models plus an NNX dpo_loss_fn. Stats: 26 source files modified, +406 / -171 lines. Linen invariant verified: pure_nnx / enable_nnx / pure_nnx_decoder still default to False; Linen-path UTs unaffected (3 pre-existing failures on the parent branch remain unchanged — sharding_compare_test::deepseek2-16b, optimizers_test::test_model_integration_kimi-k2-1t, diloco_test::two _slices x2). All "Pure NNX support has not been implemented yet" NotImplementedError sites cleared (was 17, now 0).
1 parent e641bea commit 0f09e3a

27 files changed

Lines changed: 399 additions & 143 deletions

src/maxtext/checkpoint_conversion/standalone_scripts/convert_gpt3_ckpt_from_paxml.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,12 @@ def convert(paxml_ckpt_path, maxtext_model_name, base_output_directory, run_name
8787
devices_array = maxtext_utils.create_device_mesh(cfg)
8888
mesh = Mesh(devices_array, cfg.mesh_axes)
8989

90+
# This conversion script reads paxml-format weights and emits a Linen-format
91+
# MaxText checkpoint (downstream uses `.params['params']`, `.opt_state.mu['params']`,
92+
# `.opt_state.nu['params']` keystr paths; the keystr_map below targets the Linen
93+
# tree shape). Use the Linen path regardless of pure_nnx.
9094
quant = quantizations.configure_quantization(cfg)
91-
if cfg.pure_nnx:
92-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
93-
else:
94-
model = transformer_as_linen(cfg, mesh, quant=quant, model_mode=MODEL_MODE_TRAIN)
95+
model = transformer_as_linen(cfg, mesh, quant=quant, model_mode=MODEL_MODE_TRAIN)
9596
learning_rate_schedule = maxtext_utils.create_learning_rate_schedule(cfg)
9697
tx = optimizers.get_optimizer(cfg, learning_rate_schedule)
9798

@@ -102,11 +103,7 @@ def convert(paxml_ckpt_path, maxtext_model_name, base_output_directory, run_name
102103
cfg.checkpoint_period,
103104
)
104105

105-
if cfg.pure_nnx:
106-
# NNX has a different function to init the training state.
107-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
108-
else:
109-
init_state_fn = functools.partial(maxtext_utils.init_initial_state, model, tx, cfg, True, init_rng)
106+
init_state_fn = functools.partial(maxtext_utils.init_initial_state, model, tx, cfg, True, init_rng)
110107
state, _, _, _ = maxtext_utils.setup_training_state(None, cfg, mesh, checkpoint_manager, init_state_fn)
111108
max_logging.log("start")
112109
max_utils.print_mem_stats("After params initialized")

src/maxtext/configs/base.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,13 @@ logical_axis_rules: [
560560
['tokens_per_page', []],
561561
['paged_kv_head_dim_size', []],
562562
# ==========================================
563+
# Pipeline Parallelism
564+
# ==========================================
565+
['layers_outside_pipeline', []],
566+
['layers_per_stage', []],
567+
['num_activations', []],
568+
['circular_repeats', []],
569+
# ==========================================
563570
# Deprecated / Scheduled for Removal
564571
# ==========================================
565572
['mlp_no_fsdp', ['tensor', 'tensor_sequence', 'autoregressive']],

src/maxtext/configs/pyconfig_deprecated.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,9 @@ def validate_expert_shard_attention_option(expert_shard_attention_option: str) -
195195

196196

197197
def validate_vocab_tiling(num_vocab_tiling: int, per_device_batch_size: int, max_target_length: int, enable_nnx: bool):
198+
del enable_nnx # NNX vocab tiling supported via vocab_tiling_nnx_loss in vocabulary_tiling.py
198199
if (per_device_batch_size * max_target_length) % num_vocab_tiling != 0:
199200
raise ValueError("Per device batch size times sequence length should be divisible by the number of vocab tiles.")
200-
if num_vocab_tiling > 1 and enable_nnx: # TODO (chengnuojin) enable vocab tiling on NNX after NNX migration
201-
raise ValueError("We currently don't support vocab tiling on NNX module.")
202201

203202

204203
def validate_rampup_batch_size(batch_size_start, batch_size_end, batch_size_increment, global_rampup_samples):

src/maxtext/configs/types.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2814,8 +2814,7 @@ def calculate_global_batch_sizes(per_device_batch_size, expansion_factor, num_de
28142814
and (self.per_device_batch_size * self.max_target_length) % self.num_vocab_tiling != 0
28152815
):
28162816
raise ValueError("Per device batch size times sequence length should be divisible by the number of vocab tiles.")
2817-
if self.num_vocab_tiling > 1 and self.enable_nnx:
2818-
raise ValueError("We currently don't support vocab tiling on NNX module.")
2817+
# Vocab tiling on NNX is now supported via vocab_tiling_nnx_loss in vocabulary_tiling.py.
28192818
if self.context_parallel_size > 1 and self.context_parallel_strategy.lower() == "ring":
28202819
if "gpu" not in self.hardware:
28212820
raise ValueError(

src/maxtext/experimental/rl/grpo_trainer.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -542,29 +542,28 @@ def setup_train_loop(
542542
- eval_data_iterator: The iterator for the evaluation dataset (or None).
543543
- state: The initialized training state.
544544
"""
545+
# GRPO RL trainer is Linen-shaped end-to-end (state.params accesses below,
546+
# state_mesh_shardings.params, and the inference path through MaxEngine which is
547+
# Linen-only). Run on Linen path regardless of pure_nnx; warn the user since
548+
# NNX-format checkpoints will mismatch at restore time.
549+
if config.pure_nnx or config_inference.pure_nnx:
550+
max_logging.log(
551+
"WARNING: GRPO RL trainer does not yet support pure_nnx natively; "
552+
"running on the Linen path. NNX-format checkpoints will not load correctly here."
553+
)
545554
with maybe_record_goodput(recorder, GoodputEvent.TPU_INIT):
546555
max_logging.log("Training mesh used for the workload")
547556
num_inference_devices = config.inference_devices_per_replica * config.inference_replicas
548557
training_devices = jax.devices()[num_inference_devices:]
549-
if config.pure_nnx:
550-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
551-
else:
552-
model = mt.from_config(config, devices=training_devices)
558+
model = mt.from_config(config, devices=training_devices)
553559
mesh = model.mesh
554560
max_logging.log("Inference mesh used for the workload")
555561
inference_devices = jax.devices()[:num_inference_devices]
556-
if config_inference.pure_nnx:
557-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
558-
else:
559-
inference_model = mt.from_config(config_inference, devices=inference_devices)
562+
inference_model = mt.from_config(config_inference, devices=inference_devices)
560563
inference_mesh = inference_model.mesh
561564
init_rng = jax.random.PRNGKey(config.init_weights_seed)
562565
learning_rate_schedule, tx = train_utils.create_training_optimizer(config, model)
563-
if config.pure_nnx:
564-
# NNX has a different function to init the training state.
565-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
566-
else:
567-
init_state_fn = functools.partial(maxtext_utils.init_initial_state, model, tx, config, True, init_rng)
566+
init_state_fn = functools.partial(maxtext_utils.init_initial_state, model, tx, config, True, init_rng)
568567
checkpoint_manager = train_utils.create_checkpoint_manager(config, mesh, init_state_fn)
569568

570569
with maybe_record_goodput(recorder, GoodputEvent.TRAINING_PREPARATION):
@@ -573,14 +572,10 @@ def setup_train_loop(
573572
data_iterator, config, mesh, checkpoint_manager, init_state_fn
574573
)
575574

576-
# create inference_state_mesh_shardings from inference_mesh
577-
if config_inference.pure_nnx:
578-
# NNX has a different function to init the training state.
579-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
580-
else:
581-
init_inference_state_fn = functools.partial(
582-
maxtext_utils.init_initial_state, inference_model, tx, config_inference, False, init_rng
583-
)
575+
# create inference_state_mesh_shardings from inference_mesh (Linen path; see warning above)
576+
init_inference_state_fn = functools.partial(
577+
maxtext_utils.init_initial_state, inference_model, tx, config_inference, False, init_rng
578+
)
584579
inference_state_mesh_shardings = maxtext_utils.get_abstract_state(
585580
config_inference, inference_mesh, init_inference_state_fn, is_training=False
586581
)[2]

src/maxtext/inference/maxengine/maxengine.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@ def __init__(self, config: Any, devices: Any | None = None):
111111
devices_array = maxtext_utils.create_device_mesh(config=config, devices=devices)
112112
self._mesh = jax.sharding.Mesh(devices_array, config.mesh_axes)
113113

114-
# Model and Optimizer definition
114+
# Model and Optimizer definition.
115+
# MaxEngine uses Linen-shaped state (state.params, state_mesh_shardings.params,
116+
# state.opt_state) and serves Linen-format inference checkpoints. Use Linen path
117+
# regardless of pure_nnx — the flag affects training, not inference serving.
115118
quant = quantizations.configure_quantization(config)
116-
if config.pure_nnx:
117-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
118-
else:
119-
self.model = models.transformer_as_linen(config, mesh=self._mesh, quant=quant, model_mode=MODEL_MODE_PREFILL)
119+
self.model = models.transformer_as_linen(config, mesh=self._mesh, quant=quant, model_mode=MODEL_MODE_PREFILL)
120120
self.replicated_sharding = jax.sharding.NamedSharding(self._mesh, P(None))
121121

122122
self.abstract_params = None
@@ -232,11 +232,7 @@ def load_params(self, *args, params=None, rng: PRNGKeyType | None = None, **kwar
232232
rng1, rng2, rng3 = jax.random.split(rng, 3)
233233
if params:
234234
print("Resharding given params")
235-
if self.config.pure_nnx:
236-
# NNX has a different function to init the training state.
237-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
238-
else:
239-
init_state_fn = functools.partial(maxtext_utils.init_initial_state, self.model, None, self.config, False, rng)
235+
init_state_fn = functools.partial(maxtext_utils.init_initial_state, self.model, None, self.config, False, rng)
240236
_, self.state_mesh_annotations, state_mesh_shardings = maxtext_utils.get_abstract_state(
241237
self.config, self._mesh, init_state_fn, False
242238
)
@@ -245,11 +241,7 @@ def load_params(self, *args, params=None, rng: PRNGKeyType | None = None, **kwar
245241
state = maxtext_utils.init_decode_state(None, params)
246242
state = max_utils.unbox_logicallypartioned(state)
247243
else:
248-
if self.config.pure_nnx:
249-
# NNX has a different function to init the training state.
250-
raise NotImplementedError("Pure NNX support has not been implemented yet.")
251-
else:
252-
init_state_fn = functools.partial(maxtext_utils.init_initial_state, self.model, None, self.config, False, rng1)
244+
init_state_fn = functools.partial(maxtext_utils.init_initial_state, self.model, None, self.config, False, rng1)
253245
state, self.state_mesh_annotations = maxtext_utils.setup_decode_state(self.config, self._mesh, None, init_state_fn)
254246
# pylint: disable=isinstance-second-argument-not-valid-type
255247
self.abstract_params = jax.tree_util.tree_map(

src/maxtext/layers/attentions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,14 +525,14 @@ def __init__(
525525
elif self.is_qwen3_next:
526526
self.query_norm = Qwen3NextRMSNorm(
527527
num_features=self.config.head_dim,
528-
eps=self.config.normalization_layer_epsilon,
528+
epsilon=self.config.normalization_layer_epsilon,
529529
dtype=self.config.dtype,
530530
weight_dtype=self.config.weight_dtype,
531531
rngs=self.rngs,
532532
)
533533
self.key_norm = Qwen3NextRMSNorm(
534534
num_features=self.config.head_dim,
535-
eps=self.config.normalization_layer_epsilon,
535+
epsilon=self.config.normalization_layer_epsilon,
536536
dtype=self.config.dtype,
537537
weight_dtype=self.config.weight_dtype,
538538
rngs=self.rngs,

src/maxtext/layers/moe.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2242,8 +2242,8 @@ def __call__(
22422242
w0_kernel = jnp.asarray(self.wi_0[...], self.dtype)
22432243
w1_kernel = jnp.asarray(self.wi_1[...], self.dtype)
22442244

2245-
if self.per_expert_scale is not None:
2246-
wo_kernel = wo_kernel * jnp.asarray(self.per_expert_scale[...], self.dtype)[:, None, None]
2245+
if self.per_expert_scale is not None:
2246+
wo_kernel = wo_kernel * jnp.asarray(self.per_expert_scale[...], self.dtype)[:, None, None]
22472247

22482248
if self.wi_0_sparsity_module is not None:
22492249
_, w0_kernel = self.wi_0_sparsity_module(jnp.zeros_like(w0_kernel), w0_kernel)

src/maxtext/layers/nnx_decoders.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,16 @@ def pure_layer_fn(state_in, y_in):
543543
out = merged_layer(y_in, **kwargs)
544544
return out, nnx.state(merged_layer)
545545

546-
checkpointed_fn = jax.checkpoint(pure_layer_fn, policy=policy, prevent_cse=prevent_cse)
547-
out, new_state = checkpointed_fn(state, y)
546+
# Linen-based FP8 ops (fp8_nanoo, fp8_gpu) store scale/amax_history in Linen
547+
# mutable scope. jax.checkpoint re-traces the scan body during backward (remat),
548+
# but the Linen scope retains JAX tracers from the first trace, causing
549+
# UnexpectedTracerError. Skip checkpoint for these quantization types.
550+
uses_linen_fp8_mutable_state = self.config.quantization in ("fp8_nanoo", "fp8_gpu")
551+
if uses_linen_fp8_mutable_state:
552+
out, new_state = pure_layer_fn(state, y)
553+
else:
554+
checkpointed_fn = jax.checkpoint(pure_layer_fn, policy=policy, prevent_cse=prevent_cse)
555+
out, new_state = checkpointed_fn(state, y)
548556
nnx.update(layer, new_state)
549557

550558
return out
@@ -623,13 +631,12 @@ def layer_fn(carry, scanned_vars):
623631
return new_carry, (new_current_state, updated_kv)
624632
return new_carry, new_current_state
625633

626-
layer_fn = jax.checkpoint(layer_fn, policy=policy, prevent_cse=prevent_cse)
627-
628634
if use_kv:
629635
# If kv_caches is provided (e.g., from vLLM), we CANNOT use jax.lax.scan
630636
# because scanning requires stacking the kv_caches list, which creates a copy
631637
# and breaks the in-place memory updates required by vLLM's PagedAttention.
632638
# Therefore, we must unroll the loop statically when kv_caches is provided.
639+
layer_fn = jax.checkpoint(layer_fn, policy=policy, prevent_cse=prevent_cse)
633640

634641
# kv_caches_stacked is actually the original kv_caches list in this new flow
635642
kv_caches_list = kv_caches_stacked
@@ -651,7 +658,24 @@ def layer_fn(carry, scanned_vars):
651658
# inference with vLLM, parameters do not change and we don't need intermediates.
652659
return current_carry, layers, None
653660
else:
654-
final_carry, scanned_state = jax.lax.scan(layer_fn, x_in, (params, state))
661+
# Linen-based FP8 ops (fp8_nanoo, fp8_gpu) store scale/amax_history in Linen
662+
# mutable scope. jax.lax.scan traces the body function and Linen's setup() creates
663+
# intermediate tracer values (amax_history float32[1024]) that escape the scan scope,
664+
# causing UnexpectedTracerError. Use a Python for loop instead for these types.
665+
uses_linen_fp8_mutable_state = self.config.quantization in ("fp8_nanoo", "fp8_gpu")
666+
if uses_linen_fp8_mutable_state:
667+
carry = x_in
668+
per_layer_states = []
669+
for i in range(length):
670+
current_params = jax.tree.map(lambda x, i=i: x[i], params)
671+
current_state = jax.tree.map(lambda x, i=i: x[i], state)
672+
carry, new_state_i = layer_fn(carry, (current_params, current_state))
673+
per_layer_states.append(new_state_i)
674+
final_carry = carry
675+
scanned_state = jax.tree.map(lambda *xs: jnp.stack(list(xs)), *per_layer_states)
676+
else:
677+
layer_fn = jax.checkpoint(layer_fn, policy=policy, prevent_cse=prevent_cse)
678+
final_carry, scanned_state = jax.lax.scan(layer_fn, x_in, (params, state))
655679
returned_kv_stacked = None
656680

657681
if scan_axis != 0:

src/maxtext/layers/nnx_wrappers.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from flax.core import FrozenDict
2727
from flax.core import meta
2828
from flax.nnx import graph
29+
from flax.nnx import tracers as nnx_tracers
2930
from flax.nnx import variablelib
3031
from flax.nnx.bridge import module as bdg_module
3132
from flax.nnx.module import Module
@@ -167,6 +168,39 @@ def current_linen_module() -> linen.Module | None:
167168
return None
168169

169170

171+
def is_linen_initializing() -> bool:
172+
"""Check if the current execution context is inside a Linen init() call.
173+
174+
Returns True when called from within a ``to_linen_class`` wrapper's
175+
``init()`` path. Uses :func:`current_linen_module` to access the Linen
176+
module stack (private API already used by this module).
177+
178+
This is used by NNX pipeline modules to short-circuit the full scan
179+
during Linen init, where only the output shape/dtype is needed.
180+
"""
181+
module = current_linen_module()
182+
if module is not None and hasattr(module, "is_initializing") and callable(module.is_initializing):
183+
return module.is_initializing()
184+
return False
185+
186+
187+
def _refresh_variable_trace_state(module: Module) -> None:
188+
"""Refresh _trace_state for Variables that have stale trace state.
189+
190+
When nnx.update() is called with tracer values from a JAX transformation
191+
(e.g. jax.grad's LinearizeTracer), it uses _unsafe_bypass_check=True which
192+
updates the raw value but not _trace_state. This leaves Variables with a
193+
stale _trace_state from the outer (Python) context, causing nnx.split() to
194+
fail with "Cannot extract graph node from different trace level" errors.
195+
196+
This function resets _trace_state on any Variables whose _can_update is False
197+
so that downstream NNX operations (e.g. nnx.split in NNXPipeline) succeed.
198+
"""
199+
for _, v in nnx.graph.iter_graph(module):
200+
if isinstance(v, variablelib.Variable) and not v._can_update: # pylint: disable=protected-access
201+
object.__setattr__(v, "_trace_state", nnx_tracers.TraceState())
202+
203+
170204
class ToNNX(Module):
171205
"""A wrapper to turn any Linen module into an NNX module.
172206
@@ -464,6 +498,7 @@ def maybe_unbox(x):
464498
warnings.warn(f"Found unknown module paths in incoming state:{paths_str}")
465499

466500
nnx.update(module, new_state)
501+
_refresh_variable_trace_state(module)
467502

468503
_fix_for_qwix_quantization(module)
469504
method_fn = _get_module_method(module, nnx_method)

0 commit comments

Comments
 (0)