Skip to content

Commit 8700af6

Browse files
justinchubyCopilot
andcommitted
Fix audio encoder SkipSimplifiedLayerNormalization CUDA crash
ORT fuses Add(output_proj.bias) + RMSNormalization into SkipSimplifiedLayerNormalization, placing the 1D bias as the 'skip' input. ORT's CUDA kernel rejects 1D skip (requires 2D+), while the CPU kernel accepts it. This fusion was enabled by PR #253 which changed _Gemma4ScaleFreeRMSNorm from manual primitive ops to op.RMSNormalization(stash_type=1). The RMSNormalization op is recognized by ORT's SkipLayerNorm fusion pattern. Fix: inline manual RMSNorm ops in _Gemma4AudioEncoderModel.forward() for the pre_projection_norm, preventing ORT from recognizing the fusion pattern. This preserves the FP32 accumulation for numerical stability. Tested: audio encoder runs on CUDA with correct output (standalone ORT and GenAI at 117 tok/s). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Justin Chu <justinchu@microsoft.com>
1 parent 90385c6 commit 8700af6

2 files changed

Lines changed: 21 additions & 7 deletions

File tree

src/mobius/components/_gemma4_audio.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -784,10 +784,12 @@ def __init__(
784784
]
785785
)
786786
# HF uses nn.Linear(..., bias=True) for the output projection.
787-
# The bias would normally cause ORT to fuse Add(1D bias) + LayerNorm into
788-
# SkipSimplifiedLayerNorm (with 1D skip, which ORT rejects). This is avoided
789-
# because _Gemma4ScaleFreeRMSNorm uses manual primitive ops rather than
790-
# op.RMSNormalization, preventing ORT from recognizing the fusion pattern.
787+
# ORT fuses Add(1D bias) + RMSNormalization into
788+
# SkipSimplifiedLayerNormalization, placing the 1D bias in the
789+
# "skip" input position. The CUDA kernel rejects 1D skip.
790+
# Keep bias=True here; the caller's pre_projection_norm must use
791+
# manual primitive ops (not op.RMSNormalization) to prevent this
792+
# fusion pattern.
791793
self.output_proj = Linear(hidden_size, output_proj_dims, bias=True)
792794

793795
def forward(

src/mobius/models/gemma4.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,7 +1970,10 @@ def __init__(self, config: Gemma4Config):
19701970
)
19711971
# Scale-free RMSNorm applied before the projection (HF embed_audio.embedding_pre_projection_norm).
19721972
# with_scale=False in HF → no learnable weight → no checkpoint key, no ONNX initializer.
1973-
self.pre_projection_norm = _Gemma4ScaleFreeRMSNorm(output_proj_dims, eps=rms_norm_eps)
1973+
# NOTE: We inline the RMSNorm in forward() using manual ops to prevent
1974+
# ORT from fusing Add(output_proj.bias) + RMSNormalization into
1975+
# SkipSimplifiedLayerNormalization (CUDA rejects 1D skip).
1976+
self._rms_norm_eps = rms_norm_eps
19741977
# Learned projection from encoder output space → text hidden size.
19751978
# Corresponds to HF's embed_audio.embedding_projection (no bias).
19761979
self.projector = Linear(output_proj_dims, config.hidden_size, bias=False)
@@ -1985,8 +1988,17 @@ def forward(
19851988
audio_features, downsampled_mask = self.encoder(
19861989
op, input_features, input_features_mask=input_features_mask
19871990
)
1988-
# Scale-free RMSNorm before projection (HF embed_audio.embedding_pre_projection_norm)
1989-
audio_features = self.pre_projection_norm(op, audio_features)
1991+
# Scale-free RMSNorm before projection (HF embed_audio.embedding_pre_projection_norm).
1992+
# Use manual primitive ops instead of op.RMSNormalization to prevent
1993+
# ORT from fusing Add(output_proj.bias) + RMSNorm into
1994+
# SkipSimplifiedLayerNormalization with a 1D bias as skip input
1995+
# (CUDA kernel rejects 1D skip, CPU kernel accepts it).
1996+
x_f32 = op.Cast(audio_features, to=ir.DataType.FLOAT)
1997+
sq = op.Mul(x_f32, x_f32)
1998+
mean_sq = op.ReduceMean(sq, op.Constant(value_ints=[-1]), keepdims=1)
1999+
eps = op.Constant(value_float=self._rms_norm_eps)
2000+
rms = op.Sqrt(op.Add(mean_sq, eps))
2001+
audio_features = op.CastLike(op.Div(x_f32, rms), audio_features)
19902002
# → projector → [B, T//4, text_hidden_size]
19912003
return self.projector(op, audio_features), downsampled_mask
19922004

0 commit comments

Comments
 (0)