Fix Gemma4 scale-free V norm FP16 overflow on CUDA - #253
Conversation
Gemma4's parameterless V normalization (v / sqrt(mean(v²) + ε)) squared FP16 values directly via op.Mul(v, v). V projection outputs can reach ~888, and 888² = 788,544 overflows FP16 max (65504), producing inf → mean(inf) → sqrt(inf) → v/inf = 0. Fix: Cast to FP32 before squaring, compute RMSNorm in FP32, CastLike back. Applied to _Gemma4ScaleFreeRMSNorm and both inline V norms. Result: F16 CUDA 151.5 tok/s on H200 (was NaN). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Justin Chu <justinchu@microsoft.com>
🏗️ Architecture Diff
gemma4 (gemma4) / decoder — 9 change(s)Op summary: 125 → 127 nodes --- base
+++ head
@@ -47,13 +47,14 @@
MatMul
Constant
Reshape
+Cast
Mul
ReduceMean
Constant
-CastLike
Add
Sqrt
Div
+CastLike
Reshape
Attention
Transpose
@@ -90,13 +91,14 @@
MatMul
Constant
Reshape
+Cast
Mul
ReduceMean
Constant
-CastLike
Add
Sqrt
Div
+CastLike
Reshape
Attention
TransposeAdded nodes:
Removed nodes:
Connectivity changes:
gemma4 (gemma4) / vision_encoder — 74 change(s)Op summary: 202 → 190 nodes --- base
+++ head
@@ -50,14 +50,8 @@
Reshape
RMSNormalization
RMSNormalization
-Mul
-Constant
-ReduceMean
-Constant
CastLike
-Add
-Sqrt
-Div
+RMSNormalization
Constant
Gather
Constant
@@ -181,21 +175,15 @@
Mul
Add
Constant
+CastLike
OneHot
-CastLike
Transpose
MatMul
Constant
CastLike
Mul
-Mul
-Constant
-ReduceMean
-Constant
CastLike
-Add
-Sqrt
-Div
+RMSNormalization
Transpose
MatMul
ConstantAdded nodes:
Removed nodes:
Modified attributes:
Connectivity changes:
Initializer changes:
gemma4_text / model — 9 change(s)Op summary: 127 → 129 nodes --- base
+++ head
@@ -49,13 +49,14 @@
MatMul
Constant
Reshape
+Cast
Mul
ReduceMean
Constant
-CastLike
Add
Sqrt
Div
+CastLike
Reshape
Attention
Transpose
@@ -92,13 +93,14 @@
MatMul
Constant
Reshape
+Cast
Mul
ReduceMean
Constant
-CastLike
Add
Sqrt
Div
+CastLike
Reshape
Attention
TransposeAdded nodes:
Removed nodes:
Connectivity changes:
Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed) |
Performance Comparison
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
titaiwangms
left a comment
There was a problem hiding this comment.
Is there a way to avoid copilot doing this again?
|
It's concerning especially mobius would be heavily relying on ai if we don't have a good steering. |
|
I think we can have unit tests to test the overflow behavior to harden this. What do you think? |
Sure. But also I think it would be good to have some ort knowledge in mobius as well. ORT is the major backend. |
Replace manual Cast+Mul+ReduceMean+Sqrt+Div decomposition with the proper RMSNormalization op using stash_type=1 (float32 accumulation). This handles FP16 overflow natively without manual F32 casts. The constant all-ones weight is initialized with const_value so no external weight file is needed. CastLike ensures type compatibility. Add unit test verifying V norm handles input=888 (888²=788K > FP16 max 65504) without producing NaN/Inf. 6 gemma4 unit tests pass, 13 build tests pass. Signed-off-by: Justin Chu <justinchu@microsoft.com>
Maybe have it as a skill? Or we can use onnx-doctor to detect unsupported dtypes? What do you think? |
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Maybe it's sufficient that we point it to https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/providers/cuda/cuda_execution_provider.cc for registered kernels (as well as contrib op and cpu ones). Just for copilot to understand what options it has when it's building models. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Justin Chu <justinchu@microsoft.com>
532acfe to
e228380
Compare
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>
Gemma4's parameterless V normalization (
v / sqrt(mean(v²) + ε)) squared FP16 values directly. V projection outputs reach ~888, and 888² overflows FP16 max (65504) → inf → 0. This caused all-zero V outputs on CUDA (CPU uses FP32 internally).Fix: Cast to FP32 before squaring, compute full RMSNorm in FP32, CastLike back.
Result: F16 CUDA 151.5 tok/s on H200 (was NaN).