Skip to content

cuda: BATCH_INVARIANT: occupancy-independent FA KV split, PTQ1_0 mat-vec up to 8 columns - #322

Closed
professorpalmer wants to merge 1 commit into
PrismML-Eng:prismfrom
professorpalmer:claude/batch-invariant-fa
Closed

professorpalmer wants to merge 1 commit into
PrismML-Eng:prismfrom
professorpalmer:claude/batch-invariant-fa

Conversation

@professorpalmer

Copy link
Copy Markdown

One of the five pieces of #285, split per bri-prism's request on #317. Single commit on current prism (eaecb50c7), applies without conflicts. Only active under GGML_CUDA_BATCH_INVARIANT=1.

What it does

  • The flash-attention vector path's KV split no longer depends on the occupancy of the kernel instance that runs, so the same query produces the same split whether it is decoded alone or inside a verify batch.
  • PTQ1_0 batches of 5-8 columns stay on the PT mat-vec path instead of crossing to MMQ: a 5-column verify on MMQ did not match a single decode. GGML_CUDA_PTQ1_MMVQ_MAX overrides the crossover; the default of 4 is confirmed by measurement (pp5: 172 tok/s on MMQ vs 162 on the mat-vec, so 4 is where MMQ starts to win and where the verify columns of a draft of 4 still match).

Receipts

…vec up to 8 columns

Under GGML_CUDA_BATCH_INVARIANT:
- the non-stream-k flash-attention KV split is sized from a fixed blocks-per-SM value instead of the
  occupancy of the template instance that runs; the 1-query and multi-query instances differ in registers
  and shared memory, so their splits (and combine order) differed.
- PTQ1_0 batches up to MMVQ_MAX_BATCH_SIZE stay on the PT mat-vec, whose per-column arithmetic does not
  depend on the column count; a 5-column speculative verify on MMQ did not match the same tokens decoded
  alone. Measured cost: pp5 172 -> 162 tok/s, only on 5-8 column batches.
GGML_CUDA_PTQ1_MMVQ_MAX overrides the mat-vec / MMQ crossover (default 4, confirmed: MMQ wins from 5).

Not covered: the stream-k split of the MMA kernel follows the padded KV length (and the tile instance
follows the query count), so attention in a verify batch is not bit-identical to single-token decode past
~32k, or at any depth on the MMA decode route. Measured at 4k/20k/40k: the weight path matches, a few
continuations diverge at the rounding level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit dc0cd6c)

@bri-prism bri-prism 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.

Agent review: posted by the maintainer's coding agent at their request.

H200 (sm_90, CUDA 12.8), this diff applied to prism 73bfe6c, Ternary-Bonsai-2-9B PTQ1_0:

  • With GGML_CUDA_BATCH_INVARIANT=1: MUL_MAT ptq1_0 180/180, FLASH_ATTN_EXT with hsk=256 372/372.
  • Default mode (the env var unset): llama-server greedy output, 4 prompts × 128 tokens, is token-identical to stock prism; decode is within noise (+0.0% to +0.8%).

So the change stays inside the opt-in path, as described. I didn't measure 5-8-column verify throughput under BATCH_INVARIANT on this card.

bri-prism added a commit that referenced this pull request Oct 24, 2026
… -30%, MTP depth 5 x1.24 on H200; not token-identical at verify width >= 5) (#382)

On Hopper, PTQ1_0 batches of 5-8 columns stay on the PT mat-vec instead of falling back to MMQ (pp N=5 -30%, N=8 -16% on H200; MTP depth 5 x1.24).

Not token-identical to the previous MMQ path at verify widths >= 5. The KLD gate against an F16 reference is at the noise floor (27B ub8: mean KLD 3.04e-4 vs 3.06e-4, top-1 +0.04 pp).

Builds on the PT kernel from #218; related to #322.
@bri-prism

Copy link
Copy Markdown
Collaborator

Retested on an H200 against the current prism tip. With GGML_CUDA_BATCH_INVARIANT unset, nothing changes: tests pass and greedy output and speed match base. I couldn't confirm the invariance this is for, though. On the 9B PQ2_0 with the MTP head and BI on, plain and MTP-n4 output already match on base with 512-token prompts. At about 29k tokens of context they diverge at token 65 on both base and this PR, at the same token, so the occupancy-independent split doesn't close that case. Do you have a repro where base under BI diverges and this PR fixes it? Under BI the PTQ1_0 change costs about 23% decode time at 8 sequences (9B, batched-bench), which is expected, but worth stating in the description. Two small things: clamp GGML_CUDA_PTQ1_MMVQ_MAX to MMVQ_MAX_BATCH_SIZE (the MUL_MAT caller doesn't bound ne11 first, so a value above 8 would send prefill batches to the mat-vec), and document it next to the other GGML_CUDA_* knobs.

@professorpalmer

Copy link
Copy Markdown
Author

Thank you for testing this. I looked for a repro, and I did not find one. So I am closing this PR.

Since #221, prism already covers batch-invariant mode in the three places this PR touches: the vector kernel for 1-8 queries, the KV split sized as for one query tile (ntiles_dst_eff), and the PTQ1_0 PT kernel with the batch-invariant flag. On an RTX 4070 (Ada, where the default sends 5+ column PTQ1_0 batches to MMQ), prism c96baf4ab under GGML_CUDA_BATCH_INVARIANT=1 gives the same tokens with MTP drafting at n_max 4 as plain decode: Bonsai 2 27B with its MTP head, q8_0 K/V, a 600-token prompt, 256 greedy tokens. That matches your H200 result. The divergence that you saw at about 29k is in the stream-k split, which this PR does not change either.

If it is useful, I can open a small docs-only PR for GGML_CUDA_BATCH_INVARIANT (docs/build.md has no entry for it). Tell me and I will.

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