Skip to content

Commit 718e421

Browse files
committed
[MaxText] Implement MoE routing mismatch measurement
Calculate the mismatch rate between model-calculated routing and externally supplied forced_routed_experts. This is limited to Qwen3 and Qwen3.5 models in training mode. The mismatch rate is logged as 'learning/routing_mismatch_rate'.
1 parent df1b359 commit 718e421

9 files changed

Lines changed: 436 additions & 76 deletions

File tree

src/maxtext/integration/tunix/tunix_adapter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def __call__(
7070
attention_mask: Optional[Array], # [B, L, L] or None
7171
decoder_segment_ids: Optional[Array] = None,
7272
output_hidden_states: bool = False, # ignored
73+
forced_routed_experts: Optional[Array] = None,
7374
) -> Tuple[Array, None]:
7475
"""Forward compatible with Tunix Trainers default loss.
7576
Returns logits, None.
@@ -80,6 +81,7 @@ def __call__(
8081
decoder_input_tokens=input_tokens,
8182
decoder_positions=positions,
8283
decoder_segment_ids=decoder_segment_ids,
84+
forced_routed_experts=forced_routed_experts,
8385
)
8486
return logits, None
8587

src/maxtext/layers/decoders.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ def __call__(
794794
kv_caches: list[jax.Array] | None = None,
795795
attention_metadata=None,
796796
deepstack_visual_embeds: None | list[jnp.ndarray] = None,
797+
forced_routed_experts: jax.Array | None = None,
797798
):
798799
cfg = self.config
799800
mesh = self.mesh
@@ -1013,19 +1014,34 @@ def __call__(
10131014
current_broadcast_args = list(broadcast_args)
10141015
current_in_axes_tuple = list(in_axes_tuple)
10151016

1017+
if forced_routed_experts is not None:
1018+
# Transpose [B, L, N, E] -> [N, B, L, E] for scan
1019+
forced_routed_experts_t = jnp.transpose(forced_routed_experts, (2, 0, 1, 3))
1020+
cycle_interval = cfg.inhomogeneous_layer_cycle_interval
1021+
reshaped_forced = jnp.reshape(
1022+
forced_routed_experts_t, (scan_length, cycle_interval) + forced_routed_experts_t.shape[1:]
1023+
)
1024+
else:
1025+
reshaped_forced = None
1026+
10161027
if kv_caches is not None:
10171028
# Stack kv_caches for scan: [num_layers, ...]
10181029
stacked_kv_cache = jnp.stack(kv_caches, axis=0)
10191030

10201031
# We pass (y, stacked_kv_cache, 0) as the carry
10211032
carry = (y, stacked_kv_cache, 0)
10221033

1023-
# We don't pass kv_cache as a scanned argument anymore
1024-
10251034
# Pass None for previous_chunk, slot, kv_cache to align with __call__ signature
10261035
current_broadcast_args.extend([None, None, None, attention_metadata])
10271036
current_in_axes_tuple.extend([nn.broadcast] * 4)
10281037

1038+
if reshaped_forced is not None:
1039+
current_broadcast_args.append(reshaped_forced)
1040+
current_in_axes_tuple.append(0)
1041+
else:
1042+
current_broadcast_args.append(None)
1043+
current_in_axes_tuple.append(nn.broadcast)
1044+
10291045
max_logging.info(f"DEBUG: len(current_broadcast_args)={len(current_broadcast_args)}")
10301046
max_logging.info(f"DEBUG: current_broadcast_args={[type(a) for a in current_broadcast_args]}")
10311047

@@ -1047,8 +1063,15 @@ def __call__(
10471063
kv_caches[i] = returned_kv_cache[i]
10481064
else:
10491065
# Fallback to old behavior if kv_caches is None (not vLLM RPA)
1050-
current_broadcast_args.append(None)
1051-
current_in_axes_tuple.append(nn.broadcast)
1066+
current_broadcast_args.extend([None, None, None, None])
1067+
current_in_axes_tuple.extend([nn.broadcast] * 4)
1068+
1069+
if reshaped_forced is not None:
1070+
current_broadcast_args.append(reshaped_forced)
1071+
current_in_axes_tuple.append(0)
1072+
else:
1073+
current_broadcast_args.append(None)
1074+
current_in_axes_tuple.append(nn.broadcast)
10521075

10531076
y, _ = self.scan_decoder_layers(
10541077
cfg,
@@ -1077,6 +1100,10 @@ def __call__(
10771100
global_layer_idx = global_layer_idx_offset + index
10781101
kv_cache = kv_caches[index] if kv_caches is not None else None
10791102
input_tokens = decoder_input_tokens if cfg.engram_layers else None
1103+
extra_kwargs = {}
1104+
if layer_prefix == "moe_layers" and forced_routed_experts is not None:
1105+
extra_kwargs["forced_routed_experts"] = forced_routed_experts[:, :, index, :]
1106+
10801107
y, kv_cache = layer(
10811108
config=cfg,
10821109
mesh=mesh,
@@ -1095,6 +1122,7 @@ def __call__(
10951122
kv_cache=kv_cache,
10961123
attention_metadata=attention_metadata,
10971124
decoder_input_tokens=input_tokens,
1125+
**extra_kwargs,
10981126
)
10991127
if kv_caches is not None and kv_cache is not None:
11001128
kv_caches[index] = kv_cache
@@ -1114,6 +1142,7 @@ def __call__(
11141142
slot=slot,
11151143
)
11161144
else:
1145+
moe_lyr_idx = 0
11171146
for lyr in range(cfg.num_decoder_layers):
11181147
RemattedBlockLayer = RemattedBlockLayers[0]
11191148
layer_kwargs = {}
@@ -1150,6 +1179,26 @@ def __call__(
11501179
layer = RemattedBlockLayer(
11511180
config=cfg, mesh=mesh, name=f"layers_{lyr}", quant=self.quant, model_mode=self.model_mode, **layer_kwargs
11521181
)
1182+
1183+
is_moe = False
1184+
if cfg.decoder_block in (
1185+
DecoderBlockType.QWEN3_MOE,
1186+
DecoderBlockType.QWEN3_NEXT,
1187+
DecoderBlockType.QWEN3_5,
1188+
):
1189+
is_moe = True
1190+
1191+
current_forced_routed_experts = None
1192+
if is_moe and forced_routed_experts is not None:
1193+
current_forced_routed_experts = forced_routed_experts[:, :, moe_lyr_idx, :]
1194+
moe_lyr_idx += 1
1195+
elif is_moe:
1196+
moe_lyr_idx += 1
1197+
1198+
extra_kwargs = {}
1199+
if is_moe and current_forced_routed_experts is not None:
1200+
extra_kwargs["forced_routed_experts"] = current_forced_routed_experts
1201+
11531202
y, returned_cache = layer(
11541203
y,
11551204
decoder_segment_ids,
@@ -1160,6 +1209,7 @@ def __call__(
11601209
slot=slot,
11611210
kv_cache=kv_cache,
11621211
attention_metadata=attention_metadata,
1212+
**extra_kwargs,
11631213
**layer_call_kwargs,
11641214
)
11651215
if kv_caches is not None and returned_cache is not None:

0 commit comments

Comments
 (0)