Skip to content

Commit a97e92a

Browse files
committed
Integrate TE MoE block support for DSv4-related changes
1 parent 2c16460 commit a97e92a

5 files changed

Lines changed: 165 additions & 33 deletions

File tree

‎src/maxtext/configs/types.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5697,10 +5697,11 @@ def calculate_global_batch_sizes(per_device_batch_size, expansion_factor, num_de
56975697
raise ValueError("te_moe_quantize_before_fsdp_all_gather=True does not support 2D FSDP weight sharding.")
56985698
if self.te_moe_block and not self.prefuse_moe_weights:
56995699
raise ValueError("te_moe_block=True requires prefuse_moe_weights=True.")
5700-
if self.te_moe_block and self.routed_bias_update_rate > 0.0:
5701-
raise ValueError("te_moe_block=True does not currently support routed_bias_update_rate > 0.")
5702-
if self.te_moe_block and self.norm_topk_prob:
5703-
raise ValueError("te_moe_block=True does not currently support norm_topk_prob=True.")
5700+
if self.te_moe_block and self.norm_topk_prob and self.routed_score_func not in ["sqrtsoftplus", "sigmoid"]:
5701+
raise ValueError(
5702+
"te_moe_block=True does not currently support norm_topk_prob=True "
5703+
"when routed_score_func is not 'sqrtsoftplus' or 'sigmoid'."
5704+
)
57045705
if self.te_moe_block and self.use_random_routing:
57055706
raise ValueError("te_moe_block=True does not support use_random_routing=True.")
57065707
if self.te_moe_block and self.decoder_block == DecoderBlockType.LLAMA4:

‎src/maxtext/layers/moe.py‎

Lines changed: 98 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,7 @@ def load_balance_updates_from_counts(expert_counts, num_experts, rate):
401401
total_tokens = jnp.sum(expert_counts, axis=-1, keepdims=True)
402402
average_load = total_tokens / num_experts
403403
direction = jnp.sign(average_load - expert_counts)
404-
output = direction * rate
405-
return output
404+
return direction * rate
406405

407406

408407
def finalize_deferred_bias_signal(partial_counts, config):
@@ -1022,6 +1021,7 @@ def __init__(
10221021
self.quant_einsums = None
10231022

10241023
gate_weight_dtype = ctypes.get_weight_dtype(self.config, "gate")
1024+
self.use_gate_bias = self.config.routed_bias and not self.is_hash_routing
10251025
self.gate = GateLogit(
10261026
in_features_shape=self.moe_expert_input_dim,
10271027
out_features_shape=self.num_experts,
@@ -1032,7 +1032,7 @@ def __init__(
10321032
quant=ctypes.get_quant(self.config, "gate", self.quant),
10331033
kernel_init=self.kernel_init,
10341034
kernel_axes=self.kernel_axes,
1035-
use_bias=self.config.routed_bias and not self.is_hash_routing,
1035+
use_bias=self.use_gate_bias,
10361036
bias_dtype=jnp.float32 if self.config.float32_gate_logits else gate_weight_dtype,
10371037
# tpu-inference applies the score function in the fused_moe_gmm kernel,
10381038
# so we don't apply it here to avoid redundant computation.
@@ -4867,6 +4867,8 @@ def _te_moe_block(
48674867
w0_bias: jax.Array | None,
48684868
w1_bias: jax.Array | None,
48694869
wo_bias: jax.Array | None,
4870+
routing_indices: jax.Array | None = None,
4871+
routing_weights: jax.Array | None = None,
48704872
out_sharding: NamedSharding | None = None,
48714873
) -> tuple[jax.Array, Optional[jax.Array], Optional[jax.Array]]:
48724874
"""Run TransformerEngine's fused EP MoEBlock using MaxText-owned params."""
@@ -4881,10 +4883,10 @@ def _te_moe_block(
48814883

48824884
if self.quant is None or not hasattr(self.quant, "get_moe_block_quantizer_sets"):
48834885
raise ValueError("te_moe_block=True requires TransformerEngine quantization or te_gmm_quantization=te_no_quant.")
4886+
if self.is_hash_routing and (routing_indices is None or routing_weights is None):
4887+
raise ValueError("TE MoE hash routing requires precomputed routing indices and weights.")
48844888

4885-
expert_bias = None
4886-
if self.config.routed_bias:
4887-
expert_bias = jnp.asarray(self.gate.bias[...], jnp.float32)
4889+
expert_bias = jnp.asarray(self.gate.bias[...], jnp.float32) if self.use_gate_bias else None
48884890

48894891
fsdp_size = self.mesh.shape.get("fsdp", 1)
48904892
ep_size = self.mesh.shape.get(self._expert_parallelism_name, 1)
@@ -4908,44 +4910,104 @@ def _te_moe_block(
49084910
checkpoint_kwargs = _get_te_moe_checkpoint_kwargs(te_moe.moe)
49094911
cudnn_gmm_fusion_kwargs = _get_te_moe_cudnn_gmm_fusion_kwargs(self.config.te_moe_disable_cudnn_gmm_fusion)
49104912

4911-
output, lb_loss, total_recv_tokens = te_moe.moe(
4913+
# Older TE versions take the router arguments directly and do not return expert counts.
4914+
supports_router_info = hasattr(te_moe, "RouterComputationInfo")
4915+
if not supports_router_info:
4916+
if self.is_hash_routing:
4917+
raise ImportError(
4918+
"te_moe_block=True with hash routing requires TransformerEngine with "
4919+
"moe.RoutingMapInfo support. Please upgrade TransformerEngine."
4920+
)
4921+
if self.should_update_load_balance():
4922+
raise ImportError(
4923+
"te_moe_block=True with routed_bias_update_rate > 0 requires TransformerEngine with "
4924+
"MoE expert-count support. Please upgrade TransformerEngine."
4925+
)
4926+
router_kwargs = {
4927+
"gate_kernel": gate_kernel,
4928+
"num_experts_per_tok": self.num_experts_per_tok,
4929+
"expert_bias": expert_bias,
4930+
"score_function": self.config.routed_score_func or "softmax",
4931+
"use_pre_softmax": False,
4932+
"num_groups": None if self.config.n_routing_groups <= 0 else self.config.n_routing_groups,
4933+
"group_topk": None if self.config.topk_routing_group <= 0 else self.config.topk_routing_group,
4934+
"scaling_factor": self.config.routed_scaling_factor,
4935+
"aux_loss_coeff": self.config.load_balance_loss_weight,
4936+
"gate_kernel_axes": self.kernel_axes,
4937+
}
4938+
elif self.is_hash_routing:
4939+
router_kwargs = {
4940+
"router_info": te_moe.RoutingMapInfo(
4941+
routing_indices=routing_indices,
4942+
routing_weights=routing_weights,
4943+
),
4944+
"collect_expert_counts": False,
4945+
}
4946+
else:
4947+
router_kwargs = {
4948+
"router_info": te_moe.RouterComputationInfo(
4949+
gate_kernel=gate_kernel,
4950+
num_experts_per_tok=self.num_experts_per_tok,
4951+
expert_bias=expert_bias,
4952+
score_function=self.config.routed_score_func or "softmax",
4953+
use_pre_softmax=False,
4954+
num_groups=None if self.config.n_routing_groups <= 0 else self.config.n_routing_groups,
4955+
group_topk=None if self.config.topk_routing_group <= 0 else self.config.topk_routing_group,
4956+
scaling_factor=self.config.routed_scaling_factor,
4957+
aux_loss_coeff=self.config.load_balance_loss_weight,
4958+
gate_kernel_axes=self.kernel_axes,
4959+
),
4960+
"collect_expert_counts": self.should_update_load_balance(),
4961+
}
4962+
4963+
# Pass the weights by keyword: the older interface has gate_kernel in the second positional slot.
4964+
te_moe_outputs = te_moe.moe(
49124965
inputs,
4913-
gate_kernel,
4914-
wi_kernel,
4915-
wo_kernel,
4916-
w0_bias,
4917-
w1_bias,
4918-
wo_bias,
4919-
expert_bias,
4966+
wi=wi_kernel,
4967+
wo=wo_kernel,
4968+
wi_0_bias=w0_bias,
4969+
wi_1_bias=w1_bias,
4970+
wo_bias=wo_bias,
49204971
num_experts=self.num_experts,
4921-
num_experts_per_tok=self.num_experts_per_tok,
49224972
activation_type=self.config.mlp_activations[0],
4923-
score_function=self.config.routed_score_func or "softmax",
4924-
use_pre_softmax=False,
4925-
num_groups=None if self.config.n_routing_groups <= 0 else self.config.n_routing_groups,
4926-
group_topk=None if self.config.topk_routing_group <= 0 else self.config.topk_routing_group,
4927-
scaling_factor=self.config.routed_scaling_factor,
4928-
aux_loss_coeff=self.config.load_balance_loss_weight,
49294973
apply_topk_weights_early=True,
49304974
quantizer_sets=(fc1_quantizer_set, fc2_quantizer_set),
49314975
mesh_resource=MeshResource(dp_resource="data", fsdp_resource="fsdp", ep_resource=self._expert_parallelism_name),
49324976
input_axes=("activation_batch", "activation_norm_length", None),
4933-
gate_kernel_axes=self.kernel_axes,
49344977
wi_kernel_axes=self.wi_kernel_axes,
49354978
wo_kernel_axes=self.wo_kernel_axes,
49364979
dtype=self.dtype,
49374980
recv_capacity_per_rank=max_utils.get_te_moe_recv_capacity_per_rank(),
49384981
quant_before_fsdp_ag=self.config.te_moe_quantize_before_fsdp_all_gather,
4982+
**router_kwargs,
49394983
**cudnn_gmm_fusion_kwargs,
49404984
**checkpoint_kwargs,
49414985
)
4986+
if supports_router_info:
4987+
output, lb_loss, total_recv_tokens, expert_counts = te_moe_outputs
4988+
else:
4989+
output, lb_loss, total_recv_tokens = te_moe_outputs
4990+
expert_counts = None
49424991
recv_capacity_per_rank = max_utils.get_te_moe_recv_capacity_per_rank()
49434992
output = output.astype(self.dtype)
49444993
if lb_loss is not None:
49454994
lb_loss = lb_loss.astype(self.dtype)
49464995
if out_sharding is not None:
49474996
output = jax.lax.with_sharding_constraint(output, out_sharding)
4948-
return output, lb_loss, (total_recv_tokens, jnp.asarray(recv_capacity_per_rank, dtype=jnp.int32))
4997+
bias_updates = None
4998+
if self.should_update_load_balance():
4999+
bias_updates = load_balance_updates_from_counts(
5000+
expert_counts, self.num_experts, self.config.routed_bias_update_rate
5001+
)
5002+
return (
5003+
output,
5004+
lb_loss,
5005+
(
5006+
total_recv_tokens,
5007+
jnp.asarray(recv_capacity_per_rank, dtype=jnp.int32),
5008+
bias_updates,
5009+
),
5010+
)
49495011

49505012
def __call__(
49515013
self,
@@ -4980,6 +5042,16 @@ def __call__(
49805042
routing_inputs = inputs if gate_inputs is None else gate_inputs.astype(gate_dtype)
49815043

49825044
if cfg.te_moe_block:
5045+
routing_indices = None
5046+
routing_weights = None
5047+
if self.is_hash_routing:
5048+
gate_logits, pre_bias_logits = self.gate(routing_inputs)
5049+
routing_weights, routing_indices = self.get_topk(
5050+
gate_logits,
5051+
pre_bias_logits,
5052+
self.rngs,
5053+
input_ids=input_ids,
5054+
)
49835055
gate_kernel = jnp.asarray(self.gate.kernel[...], self.dtype)
49845056
wi_kernel = jnp.asarray(self.wi[...], self.dtype)
49855057
wo_kernel = jnp.asarray(self.wo[...], self.dtype)
@@ -4999,7 +5071,9 @@ def __call__(
49995071
w0_bias,
50005072
w1_bias,
50015073
wo_bias,
5002-
out_sharding,
5074+
routing_indices=routing_indices,
5075+
routing_weights=routing_weights,
5076+
out_sharding=out_sharding,
50035077
)
50045078

50055079
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
@@ -249,15 +249,16 @@ def post_process(self, layer_output, load_balance_loss, moe_bias_updates, kv_cac
249249
self.sow(nnx.Intermediate, "moe_lb_loss", load_balance_loss)
250250

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

263264
if getattr(self.config, "record_internal_nn_metrics", False):
@@ -600,18 +601,26 @@ def extract_fn(x):
600601
self.DeepSeekMoeBlock_0,
601602
x=intermediate_inputs,
602603
mhc_type=HyperConnectionType.MLP_MOE,
604+
input_ids=decoder_input_tokens,
603605
)
604606
load_balance_loss = metadata["load_balance_loss"]
605607
moe_bias_updates = metadata["moe_bias_updates"]
606608
else:
607-
mlp_lnx, load_balance_loss, moe_bias_updates = self.mlp_op(hidden_states, deterministic)
609+
mlp_lnx, load_balance_loss, moe_bias_updates = self.mlp_op(
610+
hidden_states,
611+
deterministic,
612+
input_ids=decoder_input_tokens,
613+
)
608614
layer_output = mlp_lnx + intermediate_inputs
609615
layer_output = self.dropout_op(layer_output, deterministic=deterministic)
610616

611617
return self.post_process(layer_output, load_balance_loss, moe_bias_updates, kv_cache)
612618

613619
def mlp_op(self, x, deterministic, *args, **kwargs):
614620
mlp_lnx, load_balance_loss, moe_bias_updates = self.DeepSeekMoeBlock_0(
615-
x, intermediate_sharding=self.mlp_intermediate_sharding, out_sharding=self.out_sharding
621+
x,
622+
intermediate_sharding=self.mlp_intermediate_sharding,
623+
out_sharding=self.out_sharding,
624+
input_ids=kwargs.get("input_ids"),
616625
)
617626
return self.with_logical_constraint(mlp_lnx), load_balance_loss, moe_bias_updates

‎tests/integration/train_tests.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,50 @@ def test_gpu_te_moe_block(self):
737737
]
738738
)
739739

740+
@pytest.mark.integration_test
741+
@pytest.mark.gpu_only
742+
def test_gpu_te_moe_block_deepseek4_hash_and_routed_bias(self):
743+
"""Runs real TE MoE kernels for DSv4 hash routing and top-k routed-bias updates."""
744+
gpu_device = jax.devices("gpu")[0]
745+
compute_capability = getattr(gpu_device, "compute_capability", None)
746+
try:
747+
if float(compute_capability) < 10.0:
748+
pytest.skip("TransformerEngine MoEBlock is only supported on sm100+!")
749+
except Exception: # pylint: disable=broad-exception-caught
750+
pytest.skip("TransformerEngine MoEBlock is only supported on sm100+!")
751+
752+
train_main(
753+
TrainTests.CONFIGS["synthetic"]
754+
+ [
755+
"model_name=deepseek4-tiny",
756+
"override_model_config=True",
757+
"base_num_decoder_layers=3",
758+
"first_num_hash_layers=1",
759+
"compress_ratios=[0,4,128]",
760+
"attention=dot_product",
761+
"quantization=te_no_quant",
762+
"base_emb_dim=32",
763+
"base_num_kv_heads=1",
764+
"head_dim=32",
765+
"num_experts=4",
766+
"num_experts_per_tok=2",
767+
"shared_experts=1",
768+
"base_moe_mlp_dim=32",
769+
"routed_bias=True",
770+
"routed_bias_update_rate=0.125",
771+
"load_balance_loss_weight=0.0",
772+
"sparse_matmul=True",
773+
"megablox=False",
774+
"prefuse_moe_weights=True",
775+
"te_moe_block=True",
776+
"te_gmm_quantization=te_no_quant",
777+
"hardware=gpu_multiprocess",
778+
"ici_fsdp_parallelism=1",
779+
"ici_expert_parallelism=-1",
780+
"enable_tensorboard=False",
781+
]
782+
)
783+
740784
@pytest.mark.integration_test
741785
@pytest.mark.tpu_only
742786
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
@@ -181,7 +181,11 @@ def test_te_moe_block_rejects_unsupported_options_during_config_validation(self)
181181
"override_logical_axis_rules": True,
182182
}
183183
invalid_configs = (
184-
({"norm_topk_prob": True}, "te_moe_block=True does not currently support norm_topk_prob=True."),
184+
(
185+
{"norm_topk_prob": True},
186+
"te_moe_block=True does not currently support norm_topk_prob=True "
187+
"when routed_score_func is not 'sqrtsoftplus' or 'sigmoid'.",
188+
),
185189
({"use_random_routing": True}, "te_moe_block=True does not support use_random_routing=True."),
186190
(
187191
{"decoder_block": types.DecoderBlockType.LLAMA4},

0 commit comments

Comments
 (0)