Skip to content

Build the Qwen3-Next shared expert only when shared_experts > 0 - #4930

Open
WandLZhang wants to merge 3 commits into
AI-Hypercomputer:mainfrom
WandLZhang:qwen3next-shared-expert
Open

Build the Qwen3-Next shared expert only when shared_experts > 0#4930
WandLZhang wants to merge 3 commits into
AI-Hypercomputer:mainfrom
WandLZhang:qwen3next-shared-expert

Conversation

@WandLZhang

@WandLZhang WandLZhang commented Aug 18, 2026

Copy link
Copy Markdown

Qwen3NextSparseMoeBlock builds a shared expert and its gate in every layer. It doesn't read cfg.shared_experts, which defaults to 0 in base.yml.

A dense configuration therefore carries one unrequested MlpBlock per layer. The parameter count rises and the extra weights receive gradients.

Effect

Measured on a Qwen 3.5 dense configuration, 32 layers, emb_dim 4096, moe_mlp_dim 12288:

Parameters
Reported by MaxText 11.756B
Published for this architecture 6.924B

The 4.8B difference is one unrequested MLP per layer. At 11.756B the model doesn't fit a v6e-8: HLO temporaries reach 40.50 GB against 31.24 GB of HBM. With this change the same configuration reports 6.924B and trains on 8 chips.

Scope

The block now builds the shared expert only when cfg.shared_experts > 0, and skips it in the forward pass on the same condition. A configuration that sets the field keeps its current behaviour, so shipped Qwen3-Next MoE configurations are unaffected.

Tests

Adds tests/unit/qwen3_next_shared_expert_test.py, three cases, CPU only. With shared_experts: 0 the expert and its gate are both None. With 1, both are built. And the parameter-count difference between the two equals the shared expert plus its gate, and nothing else.

On main the third case fails with 0 != 392 and the other two error on the missing use_shared_expert.

tests/unit/qwen3_5_layers_test.py only covers TestQwen3_5MoeVisionEncoderEndToEnd, so it never reaches this code and still passes.

cc @mmcsa

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request conditionally instantiates the shared expert and its gate in the Qwen3 model based on the configuration, preventing unnecessary MLP blocks from being added. The review feedback recommends explicitly initializing these attributes to None when they are not used to prevent potential AttributeError issues and static analysis warnings.

Comment on lines +1144 to +1170
self.use_shared_expert = cfg.shared_experts > 0
if self.use_shared_expert:
self.shared_expert = MlpBlock(
config=cfg,
mesh=mesh,
in_features=cfg.emb_dim,
intermediate_dim=cfg.moe_mlp_dim,
activations=cfg.mlp_activations,
intermediate_dropout_rate=cfg.dropout_rate,
dtype=cfg.dtype,
weight_dtype=cfg.weight_dtype,
quant=self.quant,
model_mode=config.model_call_mode,
rngs=rngs,
)

# 3. Instantiate and apply the gate for the shared expert.
self.shared_expert_gate = DenseGeneral(
in_features_shape=cfg.emb_dim,
out_features_shape=1,
use_bias=False, # Qwen3-Next shared_expert_gate does not have a bias
dtype=cfg.dtype,
kernel_init=max_initializers.nd_dense_init(cfg.dense_init_scale, "fan_in", "truncated_normal"),
kernel_axes=("embed", None),
matmul_precision=cfg.matmul_precision,
rngs=rngs,
)
# 3. Instantiate and apply the gate for the shared expert.
self.shared_expert_gate = DenseGeneral(
in_features_shape=cfg.emb_dim,
out_features_shape=1,
use_bias=False, # Qwen3-Next shared_expert_gate does not have a bias
dtype=cfg.dtype,
kernel_init=max_initializers.nd_dense_init(cfg.dense_init_scale, "fan_in", "truncated_normal"),
kernel_axes=("embed", None),
matmul_precision=cfg.matmul_precision,
rngs=rngs,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Conditionally defining instance attributes can lead to AttributeErrors if they are accessed elsewhere, and it often triggers warnings or errors in static type checkers like Pytype or MyPy. It is a best practice to always initialize all instance attributes in __init__.

Consider explicitly initializing self.shared_expert and self.shared_expert_gate to None when self.use_shared_expert is False.

    self.use_shared_expert = cfg.shared_experts > 0
    if self.use_shared_expert:
      self.shared_expert = MlpBlock(
          config=cfg,
          mesh=mesh,
          in_features=cfg.emb_dim,
          intermediate_dim=cfg.moe_mlp_dim,
          activations=cfg.mlp_activations,
          intermediate_dropout_rate=cfg.dropout_rate,
          dtype=cfg.dtype,
          weight_dtype=cfg.weight_dtype,
          quant=self.quant,
          model_mode=config.model_call_mode,
          rngs=rngs,
      )

      # 3. Instantiate and apply the gate for the shared expert.
      self.shared_expert_gate = DenseGeneral(
          in_features_shape=cfg.emb_dim,
          out_features_shape=1,
          use_bias=False,  # Qwen3-Next shared_expert_gate does not have a bias
          dtype=cfg.dtype,
          kernel_init=max_initializers.nd_dense_init(cfg.dense_init_scale, "fan_in", "truncated_normal"),
          kernel_axes=("embed", None),
          matmul_precision=cfg.matmul_precision,
          rngs=rngs,
      )
    else:
      self.shared_expert = None
      self.shared_expert_gate = None

Qwen3NextSparseMoeBlock builds a shared expert and its gate in every layer. It
does not read cfg.shared_experts, which defaults to 0.

A dense configuration therefore carries one unrequested MLP per layer. The
parameter count rises and the extra weights receive gradients.

Measured on a Qwen 3.5 dense configuration with 32 layers and d_model 4096: the
model reports 11.756B parameters against a published 6.924B. The 4.8B difference
is one extra MLP per layer. At that size the model no longer fits a v6e-8,
because the HLO temporaries reach 40.50 GB against 31.24 GB of HBM.

The block now builds the shared expert only when cfg.shared_experts > 0, and
skips it in the forward pass on the same condition. A configuration that sets
the field keeps its current behaviour.
@WandLZhang
WandLZhang force-pushed the qwen3next-shared-expert branch from 145a400 to 002f851 Compare August 18, 2026 13:41
Set both to None when shared_experts is 0, so the attributes always exist.
Conditional attributes trip static type checkers and raise AttributeError if
anything reads them.

Caught in review by gemini-code-assist on AI-Hypercomputer#4930.
@WandLZhang

WandLZhang commented Aug 18, 2026

Copy link
Copy Markdown
Author

Both attributes are now set to None in an else branch.

Three cases on CPU. With shared_experts: 0 the expert and its gate are both
None. With 1, both are built. And the parameter-count difference between the two
equals the shared expert plus its gate, and nothing else, which is the property
the fix is for.

On main the third case fails with 0 != 392 and the other two error on the
missing use_shared_expert.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant