Skip to content

Add QNN (Qualcomm Hexagon HTP) execution-provider profile - #370

Merged
kunal-vaishnavi merged 1 commit into
mainfrom
add-qnn-ep-profile
Jun 22, 2026
Merged

Add QNN (Qualcomm Hexagon HTP) execution-provider profile#370
kunal-vaishnavi merged 1 commit into
mainfrom
add-qnn-ep-profile

Conversation

@shreyshah-microsoft

@shreyshah-microsoft shreyshah-microsoft commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

Registers a qnn execution-provider profile so the ORT-GenAI builder can target the Qualcomm Hexagon NPU (HTP backend, via the onnxruntime-qnn QAIRT plugin):

  • _execution_providers.py: new EpCapabilities(name="qnn", ...).
  • integrations/ort_genai/ep_config.py: map qnnQNN for ORT-GenAI provider dispatch.

Why

Bringing up gemma-4-12B (gemma4_unified) on Qualcomm Copilot+ devices (Snapdragon X Elite / X2 Elite). The HTP runs a statically-shaped, QDQ-quantized graph compiled to a QNN context binary and has no kernels for ORT contrib fused ops, so the build must emit standard ONNX primitives the QNN op builders can lower.

How

The profile reuses the existing rewrite infrastructure rather than adding new passes:

  • supports_fused_rope=FalseSeparateRoPE / UnpackQKV
  • supports_skip_layer_norm=False → skip-norm inlined to standard ops
  • supports_packed_multi_head_attention=False → PackedMHA expanded
  • gqa_dtypes=frozenset() → no GroupQueryAttention (the gemma-4 multimodal decoder forgoes GQA anyway — bidirectional-vision overlay); standard Attention is emitted and static-shaped downstream
  • supports_past_present_share_buffer=False (standard-Attention KV concat can't alias a shared buffer)
  • provider_options = onnxruntime-qnn HTP launch defaults; soc_model + EP-context binary path are device-specific, set at build/validation time.

Validation

Building gemma-4-12B with execution_provider="qnn" emits pure ai.onnx (zero com.microsoft contrib ops).

@kunal-vaishnavi
kunal-vaishnavi enabled auto-merge (squash) June 22, 2026 18:23
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing c4d460f11719ae

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 22, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing c4d460f11719ae

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.

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

Registers a new qnn execution-provider profile in Mobius so builds can target Qualcomm Hexagon HTP via the ONNX Runtime QNN EP, and wires the ORT-GenAI integration to dispatch the provider name correctly.

Changes:

  • Added a qnn EpCapabilities entry (disabling fused/contrib ops so graphs lower to standard ONNX primitives).
  • Added ORT-GenAI provider-name mapping for qnnQNN.
Show a summary per file
File Description
src/mobius/_execution_providers.py Registers the new qnn EP capability profile and its default provider options.
src/mobius/integrations/ort_genai/ep_config.py Maps qnn to ORT-GenAI’s provider identifier (QNN) for provider_options emission.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread src/mobius/integrations/ort_genai/ep_config.py Outdated
Comment thread src/mobius/integrations/ort_genai/ep_config.py
Registers an EpCapabilities entry for the "qnn" execution provider so the
ORT-GenAI builder can target the Qualcomm Hexagon NPU (HTP backend, via the
onnxruntime-qnn QAIRT plugin), and maps "qnn" -> "QNN" in the ORT-GenAI
provider-name table.

The HTP runs a statically-shaped, QDQ-quantized graph compiled to a QNN
context binary and has no kernels for ORT contrib fused ops, so the profile
forces decomposition to the standard ONNX primitives the QNN op builders can
lower: no fused RoPE, no Skip[Simplified]LayerNorm, no PackedMHA, and no
GroupQueryAttention (empty gqa_dtypes) -- the gemma4 multimodal decoder
forgoes GQA anyway (bidirectional-vision overlay). past_present_share_buffer
is disabled since standard-Attention KV concat cannot alias a shared buffer;
fixed prefill-bucket / fixed-max-KV static shaping is applied as a separate
downstream pass before context-binary generation.

Validated by building gemma-4-12B (gemma4_unified) with
execution_provider="qnn": the graph emits pure ai.onnx (zero com.microsoft
contrib ops).
@kunal-vaishnavi
kunal-vaishnavi merged commit bab4068 into main Jun 22, 2026
3 checks passed
@kunal-vaishnavi
kunal-vaishnavi deleted the add-qnn-ep-profile branch June 22, 2026 20:21
kunal-vaishnavi pushed a commit that referenced this pull request Jun 25, 2026
## 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.
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