Skip to content

Add rank-4 RMSNormalization reshape rule for QNN HTP - #372

Merged
kunal-vaishnavi merged 4 commits into
mainfrom
add-rank4-rmsnorm-reshape
Jun 25, 2026
Merged

Add rank-4 RMSNormalization reshape rule for QNN HTP#372
kunal-vaishnavi merged 4 commits into
mainfrom
add-rank4-rmsnorm-reshape

Conversation

@shreyshah-microsoft

Copy link
Copy Markdown
Contributor

What

A rewrite rule that reshapes rank-4 RMSNormalization (query/key norm) to rank-3 for the QNN HTP, which miscomputes RMSNormalization over the last axis of a rank-4 tensor.

Why

Gemma-4 (and Qwen3) apply RMSNormalization over the head dimension after reshaping q/k to (batch, seq, heads, head_dim), so the norm runs on a rank-4 tensor. On the Hexagon HTP this computes incorrectly -- the graph finalizes but the q/k-norm output is numerically wrong (uncorrelated with CPU); rank-3 RMSNormalization runs correctly. Pairs with the QNN EP profile (#370): that targets the HTP, this makes the q/k-norm graph compute correctly there.

How

ReshapeRank4RMSNorm reshapes (B, S, H, Dh) -> (B, S*H, Dh), applies the identical RMSNormalization over the last axis, and reshapes back. Both reshapes use constant shapes ([0, -1, head_dim], [0, -1, heads, head_dim]) so no Shape op is introduced and the graph stays static for the HTP. Numerically exact -- the normalized axis is unchanged. Gated on a new supports_rank4_rmsnorm capability (default True; False only for qnn), applied in the lowering stage alongside SeparateRoPE.

Test

_reshape_rmsnorm_test.py: the rule reshapes a rank-4 RMSNorm to rank-3 (2 Reshapes added) and the rewritten graph matches the original on CPU (where rank-4 RMSNorm is correct); a rank-3 RMSNorm is left untouched.

The QNN HTP miscomputes RMSNormalization over the last axis of a rank-4
tensor (query/key norm, shaped (batch, seq, heads, head_dim)) -- the result
is numerically wrong even though the graph finalizes. Rank-3 RMSNormalization
(over the last axis of a rank-3 tensor) runs correctly.

ReshapeRank4RMSNorm reshapes the rank-4 input to (batch, seq*heads, head_dim),
applies the identical RMSNormalization over the last axis, and reshapes back.
Both reshapes use constant shapes ([0, -1, head_dim] and [0, -1, heads,
head_dim]) so no Shape op is introduced; the transform is numerically exact.
Gated on a new supports_rank4_rmsnorm capability, set False only for the qnn
EP, applied in the lowering stage.
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 1ad416092e5638

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 Jun 23, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 1ad416092e5638

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.

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

The author of this PR, shreyshah-microsoft, is not an activated member of this organization on Codecov.
Please activate this user on Codecov to display this PR comment.
Coverage data is still being uploaded to Codecov.io for purposes of overall coverage calculations.
Please don't hesitate to email us at support@codecov.io with any questions.

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

Adds an EP-gated lowering rewrite to make query/key RMSNormalization numerically correct on Qualcomm Hexagon HTP by reshaping rank-4 RMSNorm inputs to rank-3 and back during the optimization pipeline.

Changes:

  • Introduces Rank4RMSNormToRank3 rewrite rule (reshape_rank4_rmsnorm_rules) and exports it via mobius.rewrite_rules.
  • Wires the rule into the lowering stage in optimize_model() when EpCapabilities.supports_rank4_rmsnorm is False.
  • Adds a unit test validating the rewrite adds exactly 2 reshapes and is numerically exact on CPU.
Show a summary per file
File Description
src/mobius/rewrite_rules/_reshape_rmsnorm.py New rewrite rule: rank-4 RMSNorm → reshape-to-rank-3 RMSNorm → reshape-back.
src/mobius/rewrite_rules/_reshape_rmsnorm_test.py New test for structural + numerical equivalence of the rewrite.
src/mobius/rewrite_rules/__init__.py Exports reshape_rank4_rmsnorm_rules.
src/mobius/_optimizations.py Adds ReshapeRank4RMSNorm lowering stage gated by EP capability.
src/mobius/_execution_providers.py Adds supports_rank4_rmsnorm capability and disables it for qnn.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 1

Comment thread src/mobius/rewrite_rules/_reshape_rmsnorm_test.py Outdated
Comment thread src/mobius/_execution_providers.py
Build the fixture model via ir.Value/ir.Graph/GraphBuilder and serialize
with ir.serde.serialize_model for ORT, per the repo's no-explicit-protobuf
guideline (review feedback). Behavior unchanged: rewrite adds exactly two
Reshapes and stays numerically exact on CPU.
@kunal-vaishnavi
kunal-vaishnavi enabled auto-merge (squash) June 25, 2026 19:05
Comment thread src/mobius/rewrite_rules/_htp_rank4_rmsnorm.py
This is a Qualcomm Hexagon HTP hardware workaround, not a generic transform,
so make the names say so: HtpRank4RMSNormToRank3 / htp_rank4_rmsnorm_rules,
the _htp_rank4_rmsnorm module, and the HtpRank4RMSNorm lowering stage. The
supports_rank4_rmsnorm EP capability stays generic (it is per-EP by design;
only the QNN HTP sets it False).
@kunal-vaishnavi
kunal-vaishnavi merged commit 98b49e8 into main Jun 25, 2026
21 of 23 checks passed
@kunal-vaishnavi
kunal-vaishnavi deleted the add-rank4-rmsnorm-reshape branch June 25, 2026 20:57
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.

4 participants