Skip to content

perf(cuda): gate int4 f16 GEMV pipe vs plain by launch occupancy - #1501

Merged
justinchuby merged 2 commits into
mainfrom
squad/int4-gemv-latency
Aug 19, 2026
Merged

justinchuby merged 2 commits into
mainfrom
squad/int4-gemv-latency

Conversation

@justinchuby

Copy link
Copy Markdown
Owner

Lever: occupancy-gated pipe-vs-plain int4 MatMulNBits GEMV

The block-32 scales-fp16 int4 MatMulNBits decode GEMV has two occupancy regimes. The prefetch-pipelined (pipe) entry uses more registers to hide the Long-Scoreboard weight-load latency — a win on grid-starved (warp-capped) projections, but a loss on launches that already fill the SMs many waves over, where its lower occupancy dominates. This routes well-occupied launches to the lower-register plain entry and keeps the pipe entry for grid-starved ones.

Both entries are byte-identical (same lane→nibble mapping, same fp16 accumulation order) — a pure occupancy/register trade, not a numeric change.

Before/after ncu (H200, 132 SMs, isolated, --graph-profiling node)

shape grid (CTAs/SM) pipe plain landed
LM head N=248320 31040 (~235/SM) 98.8µs 85.0µs (−14%) → plain
projection N=4096 512 (~3.9/SM) 4.66µs 4.79µs → pipe (kept)

Gate: route to plain when ceil(N/columns_per_block) >= mp_count*32 CTAs.

End-to-end tok/s (qwen3.5-0.8b-fp16io, H200)

Paired A/B (interleaved, env-toggled on the same binary to cancel shared-box noise): gate-ON beats the old all-pipe path by +1.3 to +1.6 tok/s every round (~242 → ~243.5 tok/s). The within-round paired delta is stable even as absolute tok/s drifts 239–244.

Byte-identity / golden lock

  • Greedy tokens byte-identical gate-on vs gate-off (string-equal check: YES).
  • qwen35_0_8b_text_decode_lock golden lock PASSES (ok).
  • Unit tests pass: scales_f16_pipeline_is_bit_identical_to_scalar, fp16_gemv_variant_selection_is_structural, fp16_gemv_matches_dequant_reference, plus new scales_f16_pipe_well_occupied_routes_lm_head_to_plain.

Properties

  • General — keys only on N, launch width, live SM count; no hardcoded shapes/head-dim/layer-count; works for arbitrary MatMulNBits dims and both fp16/fp32 accumulation paths. Not special-cased to qwen3.5.
  • Capture-safe — launch-time constant, stable across CUDA-graph replays; no host syncs or per-step alloc.
  • Opt-out — ONNX_GENAI_GEMV_PIPE_WELLOCC=0 restores the pre-gate all-pipe behavior.

Honest floor

Deep prefetch (PF>2) was evaluated and reverted: the grid-starved int4 projections here are all K=1024 → only 4 weight loads/warp, too little per-warp work to hide latency, and occupancy is warp-capped (one warp per output column). Raising it would require split-K, which reorders the fp32 partial reduction → not byte-identical (barred). Those projections are at their byte-identical latency floor (already ~4.3–4.7µs/call, beating ORT's cited 4733ns/call). The one clean, free, byte-identical win is de-pipelining the well-occupied LM head.

Working as Gaff (CUDA-kernel specialist).

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

justinchuby and others added 2 commits August 19, 2026 21:05
The block-32 scales-fp16 int4 MatMulNBits decode GEMV has two occupancy
regimes. The prefetch-pipelined ("pipe") entry uses more registers to hide
the Long-Scoreboard weight-load latency, which is a net win on grid-starved
(warp-capped) projections but a net LOSS on launches that already fill the
SMs many waves over, where its lower occupancy dominates.

Measured on qwen3.5-0.8b-fp16io (H200, 132 SMs):
- Wide LM head (N=248320, grid=31040 ~235 CTAs/SM): plain 85.0us vs
  pipe 98.8us (-14% on that kernel).
- Grid-starved projections (N=4096, grid=512 ~3.9 CTAs/SM): pipe 4.66us vs
  plain 4.79us (pipe still wins, kept).

Route well-occupied launches (ceil(N/cols) >= mp_count*32 CTAs) to the
lower-register plain entry; keep the pipe entry for grid-starved ones. Both
entries are BYTE-IDENTICAL (same lane->nibble mapping, same fp16 accumulation
order) so this is a pure occupancy/register trade, not a numeric change.

End-to-end paired A/B (interleaved, env-toggled on the same binary):
gate-ON beats the old all-pipe path by +1.3 to +1.6 tok/s every round
(~242 -> ~243.5 tok/s). Greedy tokens byte-identical (gate on == off), and
the qwen35_0_8b_text_decode_lock golden lock PASSES.

General (no hardcoded shapes: keys only on N, launch width, live SM count),
capture-safe (launch-time constant, stable across CUDA-graph replays), and
opt-out via ONNX_GENAI_GEMV_PIPE_WELLOCC=0 (forces the pre-gate all-pipe
behavior). Deep-prefetch (PF>2) was evaluated and reverted: short-K (K=1024)
int4 GEMV has too little per-warp work to hide latency and split-K would
reorder the fp32 reduction (not byte-identical) -- the grid-starved
projections are at their byte-identical latency floor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby
justinchuby merged commit 2fb1658 into main Aug 19, 2026
3 checks passed
@justinchuby
justinchuby deleted the squad/int4-gemv-latency branch August 19, 2026 21:11
justinchuby added a commit that referenced this pull request Aug 19, 2026
…1502)

Scribe consolidation after PR #1501 merge. Appends the int4 MatMulNBits
GEMV occupancy-gate decision to decisions.md and a #1501 entry to gaff
history. State files only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 19, 2026
Consolidates the LinearAttn warp-coop (#1503) decision + gaff history,
and sweeps three inbox drops whose content is already in decisions.md
(#1495/#1501/#1503). State files only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 20, 2026
#1519)

Consolidation-only state PR (no code). Persists the campaign brain +
decisions log to main after PR #1516 (int8 GEMV split-K, the 6th clean
token-identical win) merged.

## What's in here
- **`.squad/decisions.md`** — merges Deckard's corrected captured-replay
attribution + Gaff's #1516 GEMV drop (22,963 → 25,954 bytes, under
gate).
- **`.squad/identity/now.md`** — new top verdict block: 6-win arc
(#1486/#1495/#1496/#1501/#1503/#1516), fair gap 1.24× → **~1.057×
ORT-ahead**, data-shuffle retired as a dead lever, honest ceiling
~260–270 tok/s reached → kernel-chasing stopped.
- **`.squad/agents/{gaff,deckard}/history.md`** — appended.
- **Removes** `.squad/decisions/inbox/gaff-gemv-bias-fusion.md` — it
rode onto main via #1516's squash (tracked-inbox anti-pattern); already
consolidated into decisions.md.

## Campaign standing
Native qwen3.5-0.8b fp16io decode: **~263.8 tok/s** vs ORT eager
~279–290 = **~1.057× ORT-ahead** (from 1.24× at session start). Residual
is a batch=1 launch/latency floor, not a buildable kernel lever. No
manufactured levers remain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented Aug 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.25%. Comparing base (4a9f4ec) to head (40b588a).
⚠️ Report is 79 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main    #1501       +/-   ##
===========================================
- Coverage   82.10%   80.25%    -1.85%     
===========================================
  Files          12      377      +365     
  Lines        5471   166289   +160818     
  Branches     5471   166289   +160818     
===========================================
+ Hits         4492   133462   +128970     
- Misses        780    27994    +27214     
- Partials      199     4833     +4634     
Flag Coverage Δ
cli-ort-linux 82.60% <ø> (?)
cli-ort-windows 82.19% <ø> (+0.09%) ⬆️
offline 80.17% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 365 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

justinchuby added a commit that referenced this pull request Aug 20, 2026
- decisions.md: append #1501 compact entry (18,208→19,182 bytes; archive gate not triggered)
- agents/gaff/history.md: #1501 landed entry + 4-win arc note
- orchestration-log and log dirs are .gitignored; files written to disk only

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant