Skip to content

Commit 852bc73

Browse files
committed
Integrate TE MoE block support for DSv4
1 parent 4919941 commit 852bc73

5 files changed

Lines changed: 126 additions & 28 deletions

File tree

‎src/maxtext/configs/types.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4965,10 +4965,11 @@ def calculate_global_batch_sizes(per_device_batch_size, expansion_factor, num_de
49654965
raise ValueError("te_moe_block=True requires sparse_matmul=True.")
49664966
if self.te_moe_block and not self.prefuse_moe_weights:
49674967
raise ValueError("te_moe_block=True requires prefuse_moe_weights=True.")
4968-
if self.te_moe_block and self.routed_bias_update_rate > 0.0:
4969-
raise ValueError("te_moe_block=True does not currently support routed_bias_update_rate > 0.")
4970-
if self.te_moe_block and self.norm_topk_prob:
4971-
raise ValueError("te_moe_block=True does not currently support norm_topk_prob=True.")
4968+
if self.te_moe_block and self.norm_topk_prob and self.routed_score_func not in ["sqrtsoftplus", "sigmoid"]:
4969+
raise ValueError(
4970+
"te_moe_block=True does not currently support norm_topk_prob=True "
4971+
"when routed_score_func is not 'sqrtsoftplus' or 'sigmoid'."
4972+
)
49724973
if self.te_moe_block and self.use_random_routing:
49734974
raise ValueError("te_moe_block=True does not support use_random_routing=True.")
49744975
if self.te_moe_block and self.decoder_block == DecoderBlockType.LLAMA4:

‎src/maxtext/layers/moe.py‎

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,7 @@ def load_balance_updates_from_counts(expert_counts, num_experts, rate):
373373
total_tokens = jnp.sum(expert_counts, axis=-1, keepdims=True)
374374
average_load = total_tokens / num_experts
375375
direction = jnp.sign(average_load - expert_counts)
376-
output = direction * rate
377-
return output
376+
return direction * rate
378377

379378

380379
def finalize_deferred_bias_signal(partial_counts, config):
@@ -924,6 +923,7 @@ def __init__(
924923
else:
925924
self.quant_einsums = None
926925

926+
self.use_gate_bias = self.config.routed_bias and not self.is_hash_routing
927927
self.gate = GateLogit(
928928
in_features_shape=self.moe_expert_input_dim,
929929
out_features_shape=self.num_experts,
@@ -934,7 +934,7 @@ def __init__(
934934
quant=self.quant,
935935
kernel_init=self.kernel_init,
936936
kernel_axes=self.kernel_axes,
937-
use_bias=self.config.routed_bias and not self.is_hash_routing,
937+
use_bias=self.use_gate_bias,
938938
# tpu-inference applies the score function in the fused_moe_gmm kernel,
939939
# so we don't apply it here to avoid redundant computation.
940940
# See https://github.com/vllm-project/tpu-inference/blob/main/tpu_inference/layers/common/fused_moe_gmm.py#L58.
@@ -4398,6 +4398,8 @@ def _te_moe_block(
43984398
w0_bias: jax.Array | None,
43994399
w1_bias: jax.Array | None,
44004400
wo_bias: jax.Array | None,
4401+
routing_indices: jax.Array | None = None,
4402+
routing_weights: jax.Array | None = None,
44014403
out_sharding: NamedSharding | None = None,
44024404
) -> tuple[jax.Array, Optional[jax.Array], Optional[jax.Array]]:
44034405
"""Run TransformerEngine's fused EP MoEBlock using MaxText-owned params."""
@@ -4411,10 +4413,10 @@ def _te_moe_block(
44114413

44124414
if self.quant is None or not hasattr(self.quant, "get_moe_block_quantizer_sets"):
44134415
raise ValueError("te_moe_block=True requires TransformerEngine quantization or te_gmm_quantization=te_no_quant.")
4416+
if self.is_hash_routing and (routing_indices is None or routing_weights is None):
4417+
raise ValueError("TE MoE hash routing requires precomputed routing indices and weights.")
44144418

4415-
expert_bias = None
4416-
if self.config.routed_bias:
4417-
expert_bias = jnp.asarray(self.gate.bias[...], jnp.float32)
4419+
expert_bias = jnp.asarray(self.gate.bias[...], jnp.float32) if self.use_gate_bias else None
44184420

44194421
fsdp_size = self.mesh.shape.get("fsdp", 1)
44204422
ep_size = self.mesh.shape.get(self._expert_parallelism_name, 1)
@@ -4430,42 +4432,68 @@ def _te_moe_block(
44304432
n_expert_groups=self.num_experts,
44314433
)
44324434

4433-
output, lb_loss, total_recv_tokens = te_moe.moe(
4435+
if self.is_hash_routing:
4436+
router_info = te_moe.RoutingMapInfo(
4437+
routing_indices=routing_indices,
4438+
routing_weights=routing_weights,
4439+
)
4440+
collect_expert_counts = False
4441+
else:
4442+
router_info = te_moe.RouterComputationInfo(
4443+
gate_kernel=gate_kernel,
4444+
num_experts_per_tok=self.num_experts_per_tok,
4445+
expert_bias=expert_bias,
4446+
score_function=self.config.routed_score_func or "softmax",
4447+
use_pre_softmax=False,
4448+
num_groups=None if self.config.n_routing_groups <= 0 else self.config.n_routing_groups,
4449+
group_topk=None if self.config.topk_routing_group <= 0 else self.config.topk_routing_group,
4450+
scaling_factor=self.config.routed_scaling_factor,
4451+
aux_loss_coeff=self.config.load_balance_loss_weight,
4452+
gate_kernel_axes=self.kernel_axes,
4453+
)
4454+
collect_expert_counts = self.should_update_load_balance()
4455+
4456+
output, lb_loss, total_recv_tokens, expert_counts = te_moe.moe(
44344457
inputs,
4435-
gate_kernel,
4458+
router_info,
44364459
wi_kernel,
44374460
wo_kernel,
44384461
w0_bias,
44394462
w1_bias,
44404463
wo_bias,
4441-
expert_bias,
44424464
num_experts=self.num_experts,
4443-
num_experts_per_tok=self.num_experts_per_tok,
44444465
activation_type=self.config.mlp_activations[0],
4445-
score_function=self.config.routed_score_func or "softmax",
4446-
use_pre_softmax=False,
4447-
num_groups=None if self.config.n_routing_groups <= 0 else self.config.n_routing_groups,
4448-
group_topk=None if self.config.topk_routing_group <= 0 else self.config.topk_routing_group,
4449-
scaling_factor=self.config.routed_scaling_factor,
4450-
aux_loss_coeff=self.config.load_balance_loss_weight,
44514466
apply_topk_weights_early=True,
44524467
quantizer_sets=(fc1_quantizer_set, fc2_quantizer_set),
44534468
ep_axis=self._expert_parallelism_name,
44544469
data_parallelism_axes=("fsdp",),
44554470
input_axes=("activation_batch", "activation_norm_length", None),
4456-
gate_kernel_axes=self.kernel_axes,
44574471
wi_kernel_axes=self.wi_kernel_axes,
44584472
wo_kernel_axes=self.wo_kernel_axes,
44594473
dtype=self.dtype,
44604474
recv_capacity_per_rank=max_utils.get_te_moe_recv_capacity_per_rank(),
4475+
collect_expert_counts=collect_expert_counts,
44614476
)
44624477
recv_capacity_per_rank = max_utils.get_te_moe_recv_capacity_per_rank()
44634478
output = output.astype(self.dtype)
44644479
if lb_loss is not None:
44654480
lb_loss = lb_loss.astype(self.dtype)
44664481
if out_sharding is not None:
44674482
output = jax.lax.with_sharding_constraint(output, out_sharding)
4468-
return output, lb_loss, (total_recv_tokens, jnp.asarray(recv_capacity_per_rank, dtype=jnp.int32))
4483+
bias_updates = None
4484+
if self.should_update_load_balance():
4485+
bias_updates = load_balance_updates_from_counts(
4486+
expert_counts, self.num_experts, self.config.routed_bias_update_rate
4487+
)
4488+
return (
4489+
output,
4490+
lb_loss,
4491+
(
4492+
total_recv_tokens,
4493+
jnp.asarray(recv_capacity_per_rank, dtype=jnp.int32),
4494+
bias_updates,
4495+
),
4496+
)
44694497

44704498
def __call__(
44714499
self,
@@ -4500,6 +4528,16 @@ def __call__(
45004528
routing_inputs = inputs if gate_inputs is None else gate_inputs.astype(gate_dtype)
45014529

45024530
if cfg.te_moe_block:
4531+
routing_indices = None
4532+
routing_weights = None
4533+
if self.is_hash_routing:
4534+
gate_logits, pre_bias_logits = self.gate(routing_inputs)
4535+
routing_weights, routing_indices = self.get_topk(
4536+
gate_logits,
4537+
pre_bias_logits,
4538+
self.rngs,
4539+
input_ids=input_ids,
4540+
)
45034541
gate_kernel = jnp.asarray(self.gate.kernel[...], self.dtype)
45044542
wi_kernel = jnp.asarray(self.wi[...], self.dtype)
45054543
wo_kernel = jnp.asarray(self.wo[...], self.dtype)
@@ -4519,7 +4557,9 @@ def __call__(
45194557
w0_bias,
45204558
w1_bias,
45214559
wo_bias,
4522-
out_sharding,
4560+
routing_indices=routing_indices,
4561+
routing_weights=routing_weights,
4562+
out_sharding=out_sharding,
45234563
)
45244564

45254565
gate_logits, pre_bias_logits = self.gate(routing_inputs)

‎src/maxtext/models/deepseek.py‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,15 +250,16 @@ def post_process(self, layer_output, load_balance_loss, moe_bias_updates, kv_cac
250250
self.sow(nnx.Intermediate, "moe_lb_loss", load_balance_loss)
251251

252252
if self.config.te_moe_block and moe_bias_updates is not None:
253-
total_recv_tokens, recv_capacity_per_rank = moe_bias_updates
253+
total_recv_tokens, recv_capacity_per_rank, routed_bias_updates = moe_bias_updates
254254
self.sow(nnx.Intermediate, "te_moe_total_recv_tokens", total_recv_tokens)
255255
self.sow(nnx.Intermediate, "te_moe_recv_capacity_per_rank", recv_capacity_per_rank)
256256
self.sow(
257257
nnx.Intermediate,
258258
"te_moe_capacity_overflow",
259259
jnp.any(total_recv_tokens > recv_capacity_per_rank),
260260
)
261-
elif self.config.routed_bias and self.config.routed_bias_update_rate > 0.0 and moe_bias_updates is not None:
261+
moe_bias_updates = routed_bias_updates
262+
if self.config.routed_bias and self.config.routed_bias_update_rate > 0.0 and moe_bias_updates is not None:
262263
self.sow(nnx.Intermediate, "moe_bias_updates", moe_bias_updates)
263264

264265
if getattr(self.config, "record_internal_nn_metrics", False):
@@ -607,19 +608,27 @@ def extract_fn(x):
607608
self.DeepSeekMoeBlock_0,
608609
x=intermediate_inputs,
609610
mhc_type=HyperConnectionType.MLP_MOE,
611+
input_ids=decoder_input_tokens,
610612
)
611613
load_balance_loss = metadata["load_balance_loss"]
612614
moe_bias_updates = metadata["moe_bias_updates"]
613615
else:
614-
mlp_lnx, load_balance_loss, moe_bias_updates = self.mlp_op(hidden_states, deterministic)
616+
mlp_lnx, load_balance_loss, moe_bias_updates = self.mlp_op(
617+
hidden_states,
618+
deterministic,
619+
input_ids=decoder_input_tokens,
620+
)
615621
layer_output = mlp_lnx + intermediate_inputs
616622
layer_output = self.dropout_op(layer_output, deterministic=deterministic)
617623

618624
return self.post_process(layer_output, load_balance_loss, moe_bias_updates, kv_cache)
619625

620626
def mlp_op(self, x, deterministic, *args, **kwargs):
621627
mlp_lnx, load_balance_loss, moe_bias_updates = self.DeepSeekMoeBlock_0(
622-
x, intermediate_sharding=self.mlp_intermediate_sharding, out_sharding=self.out_sharding
628+
x,
629+
intermediate_sharding=self.mlp_intermediate_sharding,
630+
out_sharding=self.out_sharding,
631+
input_ids=kwargs.get("input_ids"),
623632
)
624633
return self.with_logical_constraint(mlp_lnx), load_balance_loss, moe_bias_updates
625634

‎tests/integration/train_tests.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,50 @@ def test_gpu_te_moe_block(self):
637637
]
638638
)
639639

640+
@pytest.mark.integration_test
641+
@pytest.mark.gpu_only
642+
def test_gpu_te_moe_block_deepseek4_hash_and_routed_bias(self):
643+
"""Runs real TE MoE kernels for DSv4 hash routing and top-k routed-bias updates."""
644+
gpu_device = jax.devices("gpu")[0]
645+
compute_capability = getattr(gpu_device, "compute_capability", None)
646+
try:
647+
if float(compute_capability) < 10.0:
648+
pytest.skip("TransformerEngine MoEBlock is only supported on sm100+!")
649+
except Exception: # pylint: disable=broad-exception-caught
650+
pytest.skip("TransformerEngine MoEBlock is only supported on sm100+!")
651+
652+
train_main(
653+
TrainTests.CONFIGS["synthetic"]
654+
+ [
655+
"model_name=deepseek4-tiny",
656+
"override_model_config=True",
657+
"base_num_decoder_layers=3",
658+
"first_num_hash_layers=1",
659+
"compress_ratios=[0,4,128]",
660+
"attention=dot_product",
661+
"quantization=te_no_quant",
662+
"base_emb_dim=32",
663+
"base_num_kv_heads=1",
664+
"head_dim=32",
665+
"num_experts=4",
666+
"num_experts_per_tok=2",
667+
"shared_experts=1",
668+
"base_moe_mlp_dim=32",
669+
"routed_bias=True",
670+
"routed_bias_update_rate=0.125",
671+
"load_balance_loss_weight=0.0",
672+
"sparse_matmul=True",
673+
"megablox=False",
674+
"prefuse_moe_weights=True",
675+
"te_moe_block=True",
676+
"te_gmm_quantization=te_no_quant",
677+
"hardware=gpu_multiprocess",
678+
"ici_fsdp_parallelism=1",
679+
"ici_expert_parallelism=-1",
680+
"enable_tensorboard=False",
681+
]
682+
)
683+
640684
@pytest.mark.integration_test
641685
@pytest.mark.tpu_only
642686
def test_tpu_dropout(self):

‎tests/unit/configs_value_test.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,11 @@ def test_te_moe_block_rejects_unsupported_options_during_config_validation(self)
126126
"override_logical_axis_rules": True,
127127
}
128128
invalid_configs = (
129-
({"norm_topk_prob": True}, "te_moe_block=True does not currently support norm_topk_prob=True."),
129+
(
130+
{"norm_topk_prob": True},
131+
"te_moe_block=True does not currently support norm_topk_prob=True "
132+
"when routed_score_func is not 'sqrtsoftplus' or 'sigmoid'.",
133+
),
130134
({"use_random_routing": True}, "te_moe_block=True does not support use_random_routing=True."),
131135
(
132136
{"decoder_block": types.DecoderBlockType.LLAMA4},

0 commit comments

Comments
 (0)