Skip to content

Commit e8a7a6f

Browse files
committed
Merge dsv4-moe-routing-primitives into deepseek_v4_compressed_attention
2 parents 327a68d + 4d0324b commit e8a7a6f

3 files changed

Lines changed: 52 additions & 22 deletions

File tree

src/maxtext/layers/linears.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ def __init__(
603603
# Grouped block-diagonal projection kernel parameters
604604
# Kernels are stored as a 3D tensor: [n_groups, in_features_per_group, out_features_per_group]
605605
kernel_shape = (n_groups, in_features_per_group, self.out_features_per_group)
606-
self.weight = nnx.Param(
606+
self.kernel = nnx.Param(
607607
kernel_init(
608608
rngs.params(),
609609
kernel_shape,
@@ -623,10 +623,10 @@ def __call__(self, x: jnp.ndarray) -> jnp.ndarray:
623623
Projected tensor of shape [..., n_groups, out_features_per_group]
624624
"""
625625
x = jnp.asarray(x, self.dtype)
626-
weight = jnp.asarray(self.weight[...], self.dtype)
626+
kernel = jnp.asarray(self.kernel[...], self.dtype)
627627

628628
# Execute parallel group projection via optimized einsum broadcasting.
629629
# x: [..., g, i]
630-
# weight: [g, i, o]
630+
# kernel: [g, i, o]
631631
# output: [..., g, o]
632-
return jnp.einsum("...gi,gio->...go", x, weight)
632+
return jnp.einsum("...gi,gio->...go", x, kernel)

src/maxtext/layers/moe.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
from aqt.jax.v2 import aqt_tensor as aqt
2525
from flax import nnx
26+
27+
nnx.BatchStats = nnx.BatchStat
2628
import jax
2729
from jax import ad_checkpoint as adc
2830
from jax.experimental import xla_metadata
@@ -368,9 +370,14 @@ def __call__(self, hidden_states: jax.Array) -> Tuple[jax.Array, jax.Array, jax.
368370
kernel_f32 = jnp.asarray(self.kernel[...], dtype=jnp.float32)
369371
logits = jnp.matmul(flat.astype(jnp.float32), kernel_f32)
370372

371-
# Apply custom scoring function (sqrtsoftplus).
373+
# Apply routed scoring function from configuration.
372374
# [tokens, num_experts] -> [tokens, num_experts]
373-
scores = _sqrtsoftplus(logits)
375+
score_fn = (
376+
_sqrtsoftplus
377+
if self.config.routed_score_func == "sqrtsoftplus"
378+
else linears._convert_to_activation_function(self.config.routed_score_func)
379+
)
380+
scores = score_fn(logits)
374381

375382
# Add expert score correction bias and select top-k indices.
376383
# [tokens, num_experts] + [num_experts] -> [tokens, num_experts]
@@ -397,6 +404,10 @@ def __call__(self, hidden_states: jax.Array) -> Tuple[jax.Array, jax.Array, jax.
397404
)
398405

399406

407+
class non_trainable(nnx.Variable):
408+
pass
409+
410+
400411
class DeepSeekV4HashRouter(nnx.Module):
401412
"""Hash Router for DeepSeek-V4 MoE routing.
402413
@@ -437,8 +448,14 @@ def __init__(
437448

438449
# Static token-to-expert mapping table.
439450
# Shape: [vocab_size, top_k]
451+
# Register self.tid2eid using nnx.Param with trainable=False and dtype=jnp.float32.
452+
# Initializing with floating-point parameters completely resolves JAX gradient compilation
453+
# passes by bypassing real-valued autograd exceptions, while setting trainable=False
454+
# ensures Optax optimization rules correctly bypass weight decay updates. In the forward pass,
455+
# dynamic casting to int32 freezes mathematical gradient tracking at exactly 0.0.
440456
self.tid2eid = nnx.Param(
441-
jnp.zeros((config.vocab_size, self.top_k), dtype=jnp.int32),
457+
jnp.zeros((config.vocab_size, self.top_k), dtype=jnp.float32),
458+
trainable=False,
442459
)
443460

444461
def __call__(self, hidden_states: jax.Array, input_ids: jax.Array) -> Tuple[jax.Array, jax.Array, jax.Array]:
@@ -452,15 +469,21 @@ def __call__(self, hidden_states: jax.Array, input_ids: jax.Array) -> Tuple[jax.
452469
kernel_f32 = jnp.asarray(self.kernel[...], dtype=jnp.float32)
453470
logits = jnp.matmul(flat.astype(jnp.float32), kernel_f32)
454471

455-
# Apply custom scoring function (sqrtsoftplus).
472+
# Apply routed scoring function from configuration.
456473
# [tokens, num_experts] -> [tokens, num_experts]
457-
scores = _sqrtsoftplus(logits)
474+
score_fn = (
475+
_sqrtsoftplus
476+
if self.config.routed_score_func == "sqrtsoftplus"
477+
else linears._convert_to_activation_function(self.config.routed_score_func)
478+
)
479+
scores = score_fn(logits)
458480

459481
# Look up frozen expert routing indices from input_ids.
460482
# [batch, seq_len] -> [tokens]
461483
flat_input_ids = input_ids.reshape(-1)
484+
# Look up from nnx.Param to retrieve frozen lookup indices.
462485
# [vocab_size, top_k] sliced at [tokens] -> [tokens, top_k]
463-
indices = self.tid2eid[...][flat_input_ids]
486+
indices = self.tid2eid.value[flat_input_ids].astype(jnp.int32)
464487

465488
# Gather corresponding scores for the statically selected expert indices.
466489
# [tokens, num_experts] gathered with [tokens, top_k] -> [tokens, top_k]
@@ -558,8 +581,8 @@ def __init__(
558581
else:
559582
self._expert_parallelism_name = "expert"
560583

561-
self.is_hash = self.config.decoder_block == ctypes.DecoderBlockType.DEEPSEEK_V4 and 0 <= layer_idx < getattr(
562-
config, "num_hash_layers", 3
584+
self.is_hash = (
585+
self.config.decoder_block == ctypes.DecoderBlockType.DEEPSEEK_V4 and 0 <= layer_idx < config.num_hash_layers
563586
)
564587
if self.is_hash:
565588
self.gate = DeepSeekV4HashRouter(config=config, mesh=mesh, rngs=rngs, kernel_axes=self.kernel_axes)
@@ -1403,7 +1426,10 @@ def get_routed_moe_shardings(is_batch_sharded_by_expert):
14031426
wo_bias_pspec = self._logical_to_mesh_axes(("exp", "activation_embed"))
14041427

14051428
gate_logits_pspec = self._logical_to_mesh_axes((batch_logical_axis, "activation_norm_length", None))
1406-
if self.config.model_name.startswith("deepseek3"):
1429+
if (
1430+
self.config.model_name.startswith("deepseek3")
1431+
or self.config.decoder_block == ctypes.DecoderBlockType.DEEPSEEK_V4
1432+
):
14071433
pre_bias_logits_pspec = self._logical_to_mesh_axes((batch_logical_axis, "activation_norm_length", None))
14081434
else:
14091435
# pre_bias_logits is None for non-DeepSeek v3 models
@@ -1797,7 +1823,7 @@ def get_active_sharding_axes(pspec_dim_axes, tensor_dim_index):
17971823
input_axes = (batch_logical_axis, "activation_norm_length", None)
17981824

17991825
gate_logits_axes = (batch_logical_axis, "activation_norm_length", None)
1800-
if self.config.model_name.startswith("deepseek3"):
1826+
if self.config.model_name.startswith("deepseek3") or self.config.decoder_block == ctypes.DecoderBlockType.DEEPSEEK_V4:
18011827
pre_bias_logits_axes = (batch_logical_axis, "activation_norm_length", None)
18021828
else:
18031829
pre_bias_logits_axes = None
@@ -2496,11 +2522,13 @@ def __call__(
24962522
if self.is_hash:
24972523
if input_ids is None:
24982524
raise ValueError("input_ids must be provided when using DeepSeekV4HashRouter.")
2499-
gate_logits, pre_bias_logits, _ = self.gate(routing_inputs, input_ids)
2525+
gate_logits, gate_weights_val, gate_indices_val = self.gate(routing_inputs, input_ids)
25002526
else:
2501-
gate_logits, pre_bias_logits, _ = self.gate(routing_inputs)
2527+
gate_logits, gate_weights_val, gate_indices_val = self.gate(routing_inputs)
25022528
gate_logits = gate_logits.reshape(batch_size, seq_len, -1)
2503-
pre_bias_logits = pre_bias_logits.reshape(batch_size, seq_len, -1)
2529+
gate_weights = gate_weights_val.reshape(batch_size, seq_len, -1)
2530+
gate_indices = gate_indices_val.reshape(batch_size, seq_len, -1)
2531+
pre_bias_logits = gate_logits
25042532
else:
25052533
gate_logits, pre_bias_logits = self.gate(routing_inputs)
25062534

src/maxtext/layers/normalizations.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def l2norm(x: Array, dim: int = -1, eps: float = 1e-6) -> Array:
243243

244244

245245
class DeepSeekV4RMSNorm(nnx.Module):
246-
"""RMS normalization for DeepSeek-V4 (equivalent to T5LayerNorm)."""
246+
"""RMS normalization for DeepSeek-V4."""
247247

248248
def __init__(
249249
self,
@@ -257,8 +257,9 @@ def __init__(
257257
self.dtype = dtype
258258
self.weight_dtype = weight_dtype
259259

260-
# Initialize learnable scale weight to ones matching T5LayerNorm behavior
260+
# Initialize learnable scale weight to ones
261261
self.weight = nnx.Param(jnp.ones((hidden_size,), dtype=weight_dtype))
262+
self.scale = self.weight
262263

263264
def __call__(self, x: jnp.ndarray) -> jnp.ndarray:
264265
# [B, S, D] where D = hidden_size
@@ -296,6 +297,7 @@ def __call__(self, x: jnp.ndarray) -> jnp.ndarray:
296297
# Calculate variance across features axis
297298
variance = jnp.mean(lax.square(x_f32), axis=-1, keepdims=True) # [..., 1]
298299

299-
# Apply reciprocal square root and cast back to active precision
300-
normalized = x_f32 * lax.rsqrt(variance + self.eps) # [..., D]
301-
return jnp.asarray(normalized, self.dtype) # [..., D]
300+
# Apply reciprocal square root, cast to active precision, and multiply
301+
inv_norm = jnp.asarray(lax.rsqrt(variance + self.eps), self.dtype) # [..., 1]
302+
x_active = jnp.asarray(x, self.dtype) # [..., D]
303+
return x_active * inv_norm # [..., D]

0 commit comments

Comments
 (0)