Skip to content

Commit 58edb88

Browse files
justinchubyCopilot
andcommitted
Gemma 4 MoE: re-enable fused com.microsoft::MoE op
ORT main now plumbs the SwiGLU schema attributes (`swiglu_fusion`, `activation_alpha`, `activation_beta`, `swiglu_limit`) through to the kernel via microsoft/onnxruntime#28467 (QMoE CUDA EP + MoE GEMM Refactor), so the fused MoE op now correctly implements standard SwiGLU (`y = silu(gate) * up`) rather than only GPT-OSS-style SwiGLU. This was the original blocker tracked in microsoft/onnxruntime-genai#2062. Switch Gemma 4's MoE block back to `com.microsoft::MoE` when the EP advertises `supports_fused_moe`, with the explicit attribute set required by standard SwiGLU: activation_type = 'swiglu' activation_alpha = 1.0 (no GPT-OSS 1.702 multiplier) activation_beta = 0.0 (no GPT-OSS "+1" bias on the up branch) swiglu_limit = inf (no clipping) swiglu_fusion = 1 (interleaved) normalize_routing_weights = 1 k = top_k The CPU MoE kernel still only supports interleaved layout (`contrib_ops/cpu/moe/moe_cpu.cc:27`), and the new CUDA kernel accepts either, so emit interleaved (`swiglu_fusion=1`) for maximum portability. HuggingFace stores `experts.gate_up_proj` chunked as `[E, 2*inter, H]` (first `inter` rows = gate, next `inter` = up). Convert at graph-emit time via Reshape→Transpose→Reshape on the initializer; ORT folds the chain to a single static tensor at session load. The static-unroll `_dispatch_moe_fallback` is kept verbatim for EPs that don't expose the fused op. Validation on H200 with ORT 1.27.0.dev20260511001 (which contains #28467): * fp16 build of google/gemma-4-26b-a4b-it: 30 MoE nodes emitted, all with the expected attribute set. * `InferenceSession` on CUDAExecutionProvider loads in 12.3s (vs 959s with the previous fully-unrolled fallback, a ~78x speedup on session creation alone). * Prefill (B=1, S=4) runs in 0.65s; logits are well-behaved (no NaN or Inf, top-k token IDs land in the valid Gemma 4 vocab range). All 15 `gemma4` graph-construction tests pass, lintrunner clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Justin Chu <justinchu@microsoft.com>
1 parent 395f022 commit 58edb88

1 file changed

Lines changed: 70 additions & 20 deletions

File tree

src/mobius/models/gemma4.py

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,8 @@ def __init__(self, config: Gemma4Config, layer_idx: int):
10991099

11001100
self._top_k = config.num_experts_per_tok
11011101
self._num_experts = config.num_local_experts
1102+
self._moe_intermediate_size = config.moe_intermediate_size
1103+
self._hidden_size = config.hidden_size
11021104
moe_inter = config.moe_intermediate_size
11031105

11041106
self.router = _Gemma4MoeRouter(
@@ -1175,26 +1177,74 @@ def forward(
11751177
# Norm residual before experts
11761178
normed_flat = self.pre_feedforward_layernorm_2(op, residual_flat) # [B*S, H]
11771179

1178-
# NOTE: We intentionally do NOT use the fused com.microsoft::MoE
1179-
# op here, even when ``ep_capabilities().supports_fused_moe`` is
1180-
# True. The ORT MoE kernel for ``activation_type="swiglu"`` is
1181-
# hardcoded for GPT-OSS-style SwiGLU:
1182-
# * CUDA kernel (ft_moe/moe_kernel.cu) hardcodes
1183-
# ``alpha=1.702, limit=7.0`` and an interleaved gate/up
1184-
# layout — neither matches Gemma 4's standard SwiGLU
1185-
# (alpha=1.0, no limit, concatenated layout).
1186-
# * CPU kernel (moe_cpu.cc) refuses to load unless
1187-
# ``swiglu_fusion=1`` (interleaved).
1188-
# ``activation_type="silu"`` would also be wrong because
1189-
# ``fc1_experts_weights`` packs gate+up along dim 1
1190-
# ([E, 2*inter, hidden]), causing a shape mismatch against
1191-
# the kernel's expected ``[E, inter, hidden]`` for silu.
1192-
#
1193-
# Until ORT exposes a Gemma-4-compatible SwiGLU mode (standard
1194-
# alpha=1.0, concatenated layout) we always take the static
1195-
# unroll fallback. See microsoft/onnxruntime-genai#2062 for
1196-
# the upstream report.
1197-
moe_out_flat = self._dispatch_moe_fallback(op, normed_flat, router_probs)
1180+
caps = ep_capabilities()
1181+
if caps.supports_fused_moe:
1182+
# Fused com.microsoft::MoE op handles top-k selection +
1183+
# expert dispatch internally. Requires ORT main (post
1184+
# microsoft/onnxruntime#28467, MoE GEMM Refactor) which
1185+
# plumbs the SwiGLU schema attributes to the kernel.
1186+
#
1187+
# Gemma 4 SwiGLU semantics (vs GPT-OSS):
1188+
# activation_alpha=1.0 — silu(gate) (no GPT-OSS alpha=1.702)
1189+
# activation_beta=0.0 — linear * gate (no GPT-OSS "+1" bias)
1190+
# swiglu_limit=inf — no clipping (≤0 disables the clamp)
1191+
# swiglu_fusion=1 — interleaved layout
1192+
# [g_0, u_0, g_1, u_1, ...].
1193+
#
1194+
# mobius stores ``fc1_experts_weights`` chunked as
1195+
# ``[E, 2*inter, H]`` (first ``inter`` rows = gate,
1196+
# next ``inter`` = up) because that matches HuggingFace
1197+
# ``experts.gate_up_proj``. The fused op needs the
1198+
# interleaved layout, so reshape ``[E, 2, inter, H]`` →
1199+
# transpose to ``[E, inter, 2, H]`` → flatten back to
1200+
# ``[E, 2*inter, H]``. The whole chain operates on a
1201+
# constant initializer so ORT folds it into a single
1202+
# static tensor at session load.
1203+
#
1204+
# ``swiglu_fusion=1`` is required because the CPU MoE
1205+
# kernel still only supports the interleaved layout
1206+
# (``contrib_ops/cpu/moe/moe_cpu.cc:27``); the new CUDA
1207+
# kernel accepts either.
1208+
#
1209+
# CastLike restores the input dtype because op.MoE is a
1210+
# custom op with type=None on its output; without the
1211+
# cast downstream type inference cannot share scalar
1212+
# initializers in bf16/fp16 graphs.
1213+
e_dim = self._num_experts
1214+
inter = self._moe_intermediate_size
1215+
hidden = self._hidden_size
1216+
fc1_interleaved = op.Reshape(
1217+
op.Transpose(
1218+
op.Reshape(
1219+
self.fc1_experts_weights,
1220+
op.Constant(value_ints=[e_dim, 2, inter, hidden]),
1221+
),
1222+
perm=[0, 2, 1, 3],
1223+
),
1224+
op.Constant(value_ints=[e_dim, 2 * inter, hidden]),
1225+
) # [E, 2*inter, H] interleaved
1226+
moe_out_flat = op.CastLike(
1227+
op.MoE( # type: ignore[attr-defined]
1228+
normed_flat,
1229+
router_probs,
1230+
fc1_interleaved,
1231+
None, # fc1_experts_bias (slot 3, optional)
1232+
self.fc2_experts_weights,
1233+
activation_type="swiglu",
1234+
k=self._top_k,
1235+
normalize_routing_weights=1,
1236+
activation_alpha=1.0,
1237+
activation_beta=0.0,
1238+
swiglu_limit=float("inf"),
1239+
swiglu_fusion=1,
1240+
_domain="com.microsoft",
1241+
),
1242+
normed_flat, # match input dtype (bf16/fp16/fp32)
1243+
) # [B*S, H]
1244+
else:
1245+
# EPs without fused MoE support fall back to a static
1246+
# per-expert unroll.
1247+
moe_out_flat = self._dispatch_moe_fallback(op, normed_flat, router_probs)
11981248

11991249
moe_out = op.Reshape(moe_out_flat, op.Shape(residual)) # [B, S, H]
12001250
moe_out = self.post_feedforward_layernorm_2(op, moe_out)

0 commit comments

Comments
 (0)