Skip to content

Emit MatMulNBits accuracy_level=4 (int8 path) — 2.3x CPU decode - #402

Merged
justinchuby merged 3 commits into
mainfrom
perf/matmulnbits-accuracy-level
Jul 14, 2026
Merged

Emit MatMulNBits accuracy_level=4 (int8 path) — 2.3x CPU decode#402
justinchuby merged 3 commits into
mainfrom
perf/matmulnbits-accuracy-level

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Root cause of onnx-genai being ~3.6x slower than llama.cpp on CPU: MatMulNBits was emitted with no accuracy_level, so ORT's MLAS kernel ran the fp32 dequant+GEMM path instead of the int8 dynamic-quant + int8 dot-product path (ARM SDOT / x86 AVX-VNNI) — the same class of kernel llama.cpp uses. ORT's parity claim holds; we just weren't opting in.

default_int4_accuracy_level=4 already existed on the cpu/webgpu EpCapabilities but was dead config. This plumbs it via a new _accuracy_level_attrs() helper into both MatMulNBits emission sites in components/_quantized_linear.py (Q/K/V/O, MLP, tied + non-tied head). Emits when >0, omits at 0 (portable default preserved).

Measured (Qwen2.5-0.5B Q4, CPU EP, decode tok/s, all coherent)

config tok/s
baseline (accuracy_level missing) 39.3
accuracy_level=4 (int8) 91.8 (2.33x)
quantized head + acc4 194.7
ref: LM Studio CPU 157

fp16/bf16 levels regress on M1 (no native GEMM). End-to-end verified: --ep cpu stamps all 168 nodes with accuracy_level=4; --ep default omits it.

lintrunner clean; pytest 518 passed (+2 tests).

MatMulNBits was emitted without accuracy_level, so ORT's MLAS CPU kernel ran the
slow fp32 dequant+GEMM path instead of the int8 dynamic-quant + int8 dot-product
path (ARM SDOT / x86 VNNI) that makes ORT competitive with llama.cpp.
default_int4_accuracy_level=4 already existed on cpu/webgpu EpCapabilities but was
dead config. Plumb it via _accuracy_level_attrs() into both MatMulNBits emission
sites (Q/K/V/O, MLP, tied + non-tied head); emit when >0, omit at 0 (portable
default preserved).

Measured (Qwen2.5-0.5B Q4, CPU, decode tok/s, coherent): baseline 39.3 ->
accuracy_level=4 91.8 (2.33x); quantized-head + acc4 = 194.7, exceeding LM Studio
CPU (157). fp16/bf16 levels regress on M1 (no native GEMM).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bd351e74-2569-444d-a7da-6456f3b5df82
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 57177b73ca7c36

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

This PR plumbs EpCapabilities.default_int4_accuracy_level into emitted com.microsoft::MatMulNBits nodes by adding an _accuracy_level_attrs() helper, enabling ORT’s faster int8 compute path for INT4 weight-only quantized models (notably improving CPU decode throughput).

Changes:

  • Add _accuracy_level_attrs() and apply it to both MatMulNBits emission sites (standard quantized linear + tied quantized LM head).
  • Add unit tests to assert accuracy_level is omitted without a build context and emitted as 4 under a CPU EP build context.

Reviewed changes

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

File Description
src/mobius/components/_quantized_linear.py Adds EP-context-driven accuracy_level attribute emission for MatMulNBits.
src/mobius/components/_quantized_linear_test.py Adds coverage for attribute presence/absence based on build context / CPU EP.

Comment thread src/mobius/components/_quantized_linear.py Outdated
Comment thread src/mobius/components/_quantized_linear.py Outdated
Comment thread src/mobius/components/_quantized_linear_test.py
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.59184% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/mobius/components/_quantized_linear_test.py 76.74% 4 Missing and 6 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 57177b73ca7c36

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 0
gemma4_text model 0
gpt2 model 0
llama model 0
llama (static-cache) model 0
mamba (ssm-text-generation) model 0
phi3 model 0
phi3 (static-cache) model 0
qwen model 0
qwen (static-cache) model 0
qwen2 model 0
qwen2 (static-cache) model 0
qwen2_moe model 0
qwen2_moe (static-cache) model 0
qwen3 model 0
qwen3 (static-cache) model 0
qwen3_5_moe (hybrid-text-generation) model 0
qwen3_5_text (hybrid-text-generation) model 0
qwen3_5_vl (hybrid-qwen-vl) decoder 0
qwen3_5_vl (hybrid-qwen-vl) embedding 0
qwen3_5_vl (hybrid-qwen-vl) vision_encoder 0
qwen3_moe model 0
qwen3_moe (static-cache) model 0
qwen3_next (hybrid-text-generation) model 0
t5 (seq2seq) decoder 0
t5 (seq2seq) encoder 0
whisper (speech-to-text) decoder 0
whisper (speech-to-text) encoder 0

No architecture changes detected.


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

@justinchuby
justinchuby requested a review from jambayk July 13, 2026 14:26
@justinchuby

Copy link
Copy Markdown
Member Author

@jambayk could you review? Thanks!

justinchuby and others added 2 commits July 13, 2026 08:25
…e guard

Addresses PR #402 review: accuracy_level (from default_int4_accuracy_level) was
emitted on ALL MatMulNBits regardless of bits. Its int8-accumulation semantics
are INT4-specific, so 2/8-bit models could change behavior or hit undefined
paths. _accuracy_level_attrs(bits) now returns {} unless bits==4; both call sites
pass self._bits. Added test_cpu_ep_omits_accuracy_level_for_non_int4 (8-bit under
CPU ctx omits it) and a for/else fail guard to test_matmulnbits_attributes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bd351e74-2569-444d-a7da-6456f3b5df82
@justinchuby
justinchuby merged commit 1ab1661 into main Jul 14, 2026
19 of 23 checks passed
@justinchuby
justinchuby deleted the perf/matmulnbits-accuracy-level branch July 14, 2026 17:46
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