Skip to content

feat(lowering): optionally run multi-query attention against the unrepeated key/value head - #4921

Merged
lanluo-nvidia merged 2 commits into
pytorch:mainfrom
shoumikhin:fold-repeated-kv-matmul
Oct 28, 2026
Merged

lanluo-nvidia merged 2 commits into
pytorch:mainfrom
shoumikhin:fold-repeated-kv-matmul

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What is wrong today

In multi-query attention, all query heads share one key head and one value head (Gemma 2B, PaliGemma, pi0, pi0.5). Hugging Face code first copies the shared head once per query head (repeat_kv), then runs two batched matrix multiplications over the copies. TensorRT copies the repeated keys and values and runs its fused attention kernel, which is slow at head size 256 on some GPUs, such as A100 and H100.

What this change does

A new setting, fold_shared_kv_attention (default False), turns on a lowering pass. It folds the query heads into the rows and multiplies against the single head:

before: q [b, h, s, d] @ repeat(k [b, 1, n, d]).transpose  ->  [b, h, s, n]
after:  (q.reshape(b, 1, h*s, d) @ k.transpose).reshape(b, h, s, n)
trt = torch_tensorrt.dynamo.compile(program, arg_inputs=inputs, fold_shared_kv_attention=True)

Things to know:

  • The math is the same, but plain matrix multiplications can be less accurate in 16-bit floating point (FP16). At head size 256 the largest error against 32-bit floating point (FP32) was 1.2 to 7 times that of the unfolded attention. use_fp32_acc=True brought it to 0.9 to 2.3 times.
  • It needs exactly one key/value head and static shapes. Scaled dot product attention (SDPA) needs decompose_attention=True.
  • It does not apply under torch.compile with use_distributed_mode_trace.
  • If nothing matched, the pass says so in the log, once per graph.

It is off by default because a Jetson Orin runs the fused kernel faster.

The first commit is a small fix it builds on: replace_fused_rms_norm now sets the fake value (meta["val"]) on the nodes it creates, so a later pass can read shapes after an RMSNorm (root mean square normalization) layer.

Performance

8 query heads, 1 key/value head, head size 256, kernel time:

GPU queries x keys before after
A100 968 x 968 235 us 109 us
A100 50 x 1018 140 us 44 us
H100 968 x 968 86 us 44 us
H100 50 x 1018 75 us 24 us

pi0.5 on an A100: 59.5 to 47.0 ms per action chunk. On a Jetson Orin Nano a denoising step got slower, 14.4 to 15.4 ms.

What was tested

The tests run the lowering pipeline and check the folded shapes, also after an RMSNorm. They cover decomposed SDPA (plain, use_fp32_acc, enable_gqa) and attention compiled with torch_tensorrt.dynamo.compile and with torch.compile, compared against PyTorch. They also check graphs that must stay unchanged: several key/value heads, dynamic lengths, and ten graphs that look like repeat_kv but must not be folded. Removing any one check of the pass makes at least one test fail.

They pass on a Jetson AGX Thor and on x86-64 with an H100 (43 passed), both with TensorRT 11.4 and a PyTorch 2.16 nightly.

@meta-cla meta-cla Bot added the cla signed label Oct 22, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation component: tests Issues re: Tests component: lowering Issues re: The lowering / preprocessing passes component: core Issues re: The core compiler component: api [Python] Issues re: Python API component: dynamo Issues relating to the `torch.compile` or `torch._dynamo.export` paths labels Oct 22, 2026
@github-actions
github-actions Bot requested a review from zewenli98 October 22, 2026 22:59
@shoumikhin
shoumikhin force-pushed the fold-repeated-kv-matmul branch 4 times, most recently from 2b85550 to d9fd8e9 Compare October 25, 2026 23:19
…eates

replace_fused_rms_norm swaps aten._fused_rms_norm for plain mul, mean, add
and rsqrt nodes, but left those replacement nodes without a fake value
(meta["val"]). A later lowering pass that reads shapes from meta["val"]
finds no shape on them, so such a pass had to run before this one.

Each new node now gets meta["val"] computed from its inputs' fake values,
when all of its inputs have one.
…peated key/value head

Hugging Face models with one key/value head repeat it once per query head
before attention (repeat_kv). TensorRT then copies the repeated tensors and
runs its fused attention kernel, which is slow at head size 256 on some GPUs
(A100 and H100).

The new fold_shared_kv_attention setting runs such attention as two plain
matmuls against the single key/value head, with the query heads folded into
the rows. The math is the same, but float16 results round differently;
use_fp32_acc accumulates each matmul in float32. Only static shapes are
rewritten, and scaled_dot_product_attention only with decompose_attention.
Under torch.compile with use_distributed_mode_trace, matmuls are lowered to
bmm first, so nothing is rewritten there. When nothing matches, the pass says
so in the log. It is off by default because other GPUs (Jetson Orin) run the
fused kernel faster.
@shoumikhin
shoumikhin force-pushed the fold-repeated-kv-matmul branch from d9fd8e9 to a2273a6 Compare October 26, 2026 18:01
@lanluo-nvidia lanluo-nvidia added this to the v2.15.0 milestone Oct 26, 2026

@zewenli98 zewenli98 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's a great addition. LGTM

@lanluo-nvidia
lanluo-nvidia merged commit bacbc42 into pytorch:main Oct 28, 2026
285 of 302 checks passed
lanluo-nvidia pushed a commit that referenced this pull request Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci: full cla signed component: api [Python] Issues re: Python API component: core Issues re: The core compiler component: dynamo Issues relating to the `torch.compile` or `torch._dynamo.export` paths component: lowering Issues re: The lowering / preprocessing passes component: tests Issues re: Tests documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants