Skip to content

Fix SkipSimplifiedLayerNormalization fallback output arity - #387

Merged
justinchuby merged 1 commit into
mainfrom
fix/skip-simplified-layer-norm-output-arity
Jul 1, 2026
Merged

Fix SkipSimplifiedLayerNormalization fallback output arity#387
justinchuby merged 1 commit into
mainfrom
fix/skip-simplified-layer-norm-output-arity

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Problem

The standard-ONNX fallback body for com.microsoft::SkipSimplifiedLayerNormalization (functions/skip_layer_normalization.py) returned only two outputs, [norm_out, add_out], placing the residual sum (input_skip_bias_sum) at index 1.

Per the operator spec the four positional outputs are:

idx output
0 output (normalized)
1 mean (optional, training)
2 inv_std_var (optional, training)
3 input_skip_bias_sum (residual)

The fusion rule in rewrite_rules/_skip_norm.py is already spec-correct — it emits a 4-output node and reads outputs[3] for the residual. But when InlinePass expands that 4-output node using the 2-output fallback, onnx_ir.convenience.replace_all_uses_with raises:

ValueError: The number of values and replacements must match.

because the residual at index 3 has no replacement value. Even without that guard, the sum would silently land in the optional mean slot at index 1.

Reachability

Fusion is gated on supports_skip_layer_norm (_optimizations.py), so EPs that would inline this op (onnx-standard, qnn, trt-rtx) don't create the node in a normal single-EP pass — the crash only surfaces on a fuse-then-re-optimize path. It is nonetheless a real spec-conformance bug in the fallback body.

Fix

Emit four outputs with the residual sum at index 3. mean and inv_std_var are training-only outputs that the simplified (RMS) variant does not compute and that single-output RMSNormalization cannot supply, so they are emitted as unused Constant placeholders and pruned by RemoveUnusedNodesPass immediately after the function is inlined.

Verification

  • InlinePass on a tiny Qwen2 model (onnx-standard EP) now expands all fused ops with no dangling values; the placeholder Constants are DCE'd.
  • Inlined fallback produces byte-identical logits to the native op (max abs diff 0.0).
  • New functions/skip_layer_normalization_test.py (output-arity + inline-residual regression tests).
  • Existing skip-norm and EP-optimization suites pass; lint clean.

The standard-ONNX fallback body for com.microsoft::SkipSimplifiedLayer-
Normalization returned only two outputs, [norm_out, add_out], placing the
residual sum (input_skip_bias_sum) at index 1. Per the operator spec the
four positional outputs are output(0), mean(1), inv_std_var(2) and
input_skip_bias_sum(3), and the fusion rule in rewrite_rules/_skip_norm.py
emits a 4-output node and reads outputs[3] for the residual.

When InlinePass expands that 4-output node using the 2-output fallback,
onnx_ir's replace_all_uses_with raises ValueError ("number of values and
replacements must match") because the residual at index 3 has no
replacement; even without the guard, the sum would wrongly land in the
optional mean slot at index 1.

Emit four outputs with the sum at index 3. mean and inv_std_var are
training-only outputs that the RMS (simplified) variant does not compute
and that single-output RMSNormalization cannot supply, so they are unused
Constant placeholders (pruned by RemoveUnusedNodesPass right after inline).

Verified: onnx-standard inlining now succeeds with no dangling values and
produces byte-identical logits to the native op (max abs diff 0.0) on a
tiny Qwen2 model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <11205048+justinchuby@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 1f04e6b73c8b8e

Model Sub-model Changes Status

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 1f04e6b73c8b8e

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 60 60 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 68 68 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 107 107 +0.0%
gpt2 model_size_bytes 388 KB 388 KB +0.0%
gpt2 num_nodes 54 54 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 62 62 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 58 58 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 98 98 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 60 60 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 56 56 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 62 62 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 58 58 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 275 275 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 129 129 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 413 413 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 166 166 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

@justinchuby
justinchuby merged commit 095d1e5 into main Jul 1, 2026
20 of 21 checks passed
@justinchuby
justinchuby deleted the fix/skip-simplified-layer-norm-output-arity branch July 1, 2026 23:19
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.15385% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
.../mobius/functions/skip_layer_normalization_test.py 95.74% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a spec-conformance bug in the standard-ONNX fallback for com.microsoft::SkipSimplifiedLayerNormalization so that inlining a fused 4-output node correctly reconnects the residual output (input_skip_bias_sum) at positional output index 3.

Changes:

  • Update skip_simplified_layer_normalization() fallback body to return 4 outputs in spec order, using placeholders for the training-only outputs (mean / inv_std_var).
  • Add regression tests to assert correct output arity/order and to ensure InlinePass can inline fused skip-norm nodes without leaving dangling graph references.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/mobius/functions/skip_layer_normalization.py Adjusts the SkipSimplified fallback function signature to 4 outputs and documents the positional output contract.
src/mobius/functions/skip_layer_normalization_test.py Adds unit/regression coverage for output arity/order and InlinePass inlining behavior (residual output wiring).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants