Skip to content

Support vLLM paged KV cache layouts on the gfx950 attention kernel - #1056

Open
akii96 wants to merge 5 commits into
ROCm:mainfrom
akii96:feat/gfx950-paged-attention-vllm-kv-layouts
Open

Support vLLM paged KV cache layouts on the gfx950 attention kernel#1056
akii96 wants to merge 5 commits into
ROCm:mainfrom
akii96:feat/gfx950-paged-attention-vllm-kv-layouts

Conversation

@akii96

@akii96 akii96 commented Aug 21, 2026

Copy link
Copy Markdown

Body updated to reflect the review comments, with every kernel level table re-measured.

Motivation

vLLM cannot use the gfx950 attention kernel today. The kernel and the cache disagree about layout in three ways:

  • Page size. The kernel assumes a page holds exactly one 64 token tile. vLLM serves MiniMax-M3 at page_size 128, which is two.
  • Stride. The kernel assumes K and V are separate contiguous tensors and derives each row's address from the shape. vLLM hands it two strided views into one interleaved allocation, so the shape no longer tells you the stride.
  • Head stride. A cache with one KV head must space its heads exactly D apart. With one head there is nothing to space, so the check rejects valid layouts.

Any one of them forces the caller to repack the cache before every call: a full copy, and it loses the K/V interleave, which is most of the reason to call this kernel.

Technical Details

The kernel now addresses a vLLM cache in place, along three axes.

Page size. A page may hold any multiple of BLOCK_N tokens. The staged block table is indexed per tile rather than per page, so a larger page expands to the tiles it covers. The vectorized layout hard codes one tile per page in its address arithmetic and rejects multi tile pages rather than reading the wrong rows.

Stride. The KV row stride is read off the tensor rather than inferred from head count and head dim, so two strided views of one interleaved buffer are addressable as they are. The pitch is bounded on the host, since a page descriptor spans page_size * row_stride elements as a signed int32 and wraps beyond it. A cache too wide to address is copied instead.

Head stride. A single KV head no longer has to sit exactly D from the next, a dimension the kernel never indexes.

Separately, causal q-blocks issue heaviest first. Work grows with the q-block index, so the natural order leaves the longest until last and everything waits on it. The reversal is a permutation, so output is bit identical. It only pays when one sequence dominates the dispatch, since varlen sizes grid_dim.y from the batch wide max seqlen, so causal_lpt defaults to auto: on while one sequence spans twice the batch's sequence count in q-blocks, off past that. True and False force. Dense and split-K keep it on.

Test Plan

15 new tests in tests/kernels/test_flash_attn_fwd.py, 27 cases after parametrization, gated on gfx950 like the rest of the file. Collected count goes 93 to 121.

area covers
paged equivalence page_size 64, 128 and 256 against the dense reference, the contiguous reference, and the two strided views of one buffer vLLM actually passes
rejections vectorized layout refuses a multi tile page, linear refuses a sub tile one
pitch bound the exact 2**24 boundary and past it, a dense cache still reporting dense, and the copy a refused pitch falls back to
causal LPT bit identical rather than within tolerance, inert when not causal, honored on the split-K and varlen routes, and how auto resolves
plumbing every cached dualwave builder takes causal_lpt, and every call site passes it

Two of these are pinned by mutation rather than assertion. LPT is a permutation and the builders default it on, so a route that ignores the flag still returns identical bytes and ordinary off/on tests pass. Refusing a pitch likewise says nothing about the copy behind it. Deleting the call site argument fails only the builder cache test, and removing the fallback copy fails only the copy path test.

Separately, a standalone harness times this kernel against the AITER unified attention kernel vLLM uses today, on the shapes a MiniMax-M3 server produces at TP=4 (16 q-heads, 1 KV head, head dim 128, page_size 128, bf16). Both arms are checked against a float32 per sequence reference before anything is timed.

Test Result

Re-measured on MI355X, flydsl 0.3.2, AITER f93e6cf, page_size 128, 16 q-heads, 1 KV head, D=128, bf16. Both arms are checked against a float32 reference before timing, and host enqueue is amortised over 20 calls: flydsl_flash_attn_func spends about 0.13ms per call on the host against AITER's 0.03ms, so one event pair per call charges that idle GPU time to the kernel and understates this one by 25 to 35%. pre e5ed3c5 in parentheses.

Prefill, this kernel vs AITER UA:

chunk ctx 8k ctx 16k ctx 32k ctx 60k
2048 1.58x (1.48x) 1.63x (1.62x) 1.66x (1.64x) 1.65x (1.64x)
8192 1.67x (1.78x) 1.49x (1.47x) 1.44x (1.49x) 1.43x (1.44x)
16384 1.62x (1.59x) 1.52x (1.48x) 1.60x (1.47x)
32768 1.65x (1.54x) 1.64x (1.49x)

Ragged and mixed batches at ctx 8k, which is what chunked prefill actually schedules. Times in ms:

batch aiter flydsl speedup pre e5ed3c5
2 x 4096 0.617 0.391 1.58x 1.56x
4 x 2048 0.671 0.467 1.44x 1.56x
8 x 1024 0.682 0.488 1.40x 1.46x
8192 + 1 decode row 0.493 0.428 1.15x 1.45x
8192 + 4 decode rows 0.494 0.431 1.15x 1.36x

Decode is the other side of that. BLOCK_M is 256, so a single token row fills 1/256 of its tile while still streaming that row's whole KV:

batch ctx 8k ctx 32k
16 x 1 token 0.27x (0.26x) 0.07x (0.08x)
64 x 1 token 0.07x (0.08x) 0.06x (0.06x)
128 x 1 token 0.06x (0.07x) 0.06x (0.05x)

This is a prefill kernel, and the gap widens with both batch size and context. A caller should route decode to AITER and keep this kernel for prefill shaped work. The same effect sets the floor on the prefill side: batches with many decode rows mixed in fall below 1.0x, measured 0.85x for a 4096 chunk sharing a batch with 16 decode rows. Below about 0.15ms of GPU work the host path dominates instead, since flydsl_flash_attn_func spends about 0.13ms per call enqueueing, which is why a full 1024 token prefill measures 0.28x and only crosses 1.0x at ctx 4096.

The decode mixed rows are the one place the two runs disagree beyond noise, 1.15x against 1.45x, so a routing threshold should be fitted on them rather than on the pure prefill rows.

Full attention suite passes, 121 tests including the 27 new ones, from a cold JIT cache.

End to end vLLM serving on amd/MiniMax-M3-MXFP4, TP=4, 133k max model len, against a server running only AITER UA, measured at 943152d with caller side routing from vllm-project/vllm#53335. Not re-run: the kernel level numbers above hold up after the review changes, so re-evaluating the vLLM side is deferred until this lands, and these uplifts are expected to hold at a similar level. Output token throughput, mean TPOT, P99 TTFT:

ISL / OSL conc tok/s aiter tok/s flydsl tok/s TPOT TTFT
8k / 1k 8 740.5 740.3 flat flat flat
8k / 1k 32 1723.1 1723.3 flat flat flat
32k / 1k 8 449.0 535.9 +19.3% -19.0% -7.0%
32k / 1k 32 771.9 914.4 +18.5% -20.7% +6.5%
60k / 600 8 264.0 305.5 +15.7% -13.6% -13.6%
60k / 600 32 331.6 397.2 +19.8% -19.1% -8.7%

The 8k rows are flat because the caller keeps this kernel out of those batches: at 8k a scheduler step is one chunk plus more decode rows than this kernel handles well.

gsm8k, full set, 8 shot, same commit:

flexible strict
aiter 0.9469 0.9477
flydsl 0.9325 0.9333

@akii96
akii96 requested a review from aghamari August 21, 2026 23:47
@akii96
akii96 force-pushed the feat/gfx950-paged-attention-vllm-kv-layouts branch from 635108b to 943152d Compare August 22, 2026 01:22
@akii96
akii96 marked this pull request as ready for review August 22, 2026 05:59

@jhinpan jhinpan 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.

The page-128 addressing change holds up on an exact-base runtime: the 10 focused cases, full attention file (103/103), and additional partial-page, runtime-pitch, split-K, and ragged-varlen probes pass on gfx950. Causal LPT was bit-identical and 1.039x faster at B=1, Sq=8192, Skv=32768, H=16, Hkv=1 in 30 interleaved samples. Requesting changes for the remaining host-boundary, public-flag, and test-coverage defects below; the PR body's end-to-end gsm8k/TTFT TODOs remain unverified here.

return None
block_stride, row_stride, head_stride, elem_stride = (int(s) for s in k.stride())
dense_row = num_kv_heads * head_dim
if elem_stride != 1 or row_stride < dense_row or block_stride != page_size * row_stride:

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.

[blocking] The direct-stride gate accepts pitches that the generated layout and buffer descriptor cannot represent. A synthetic valid stride tuple with page_size=128 and row_stride=2**24 is accepted here; downstream that becomes page_elems=2**31 in fx.Int32(...) and a 2**32-byte page descriptor. Please bound the runtime pitch before launch (signed-int32 element extent and unsigned-32-bit descriptor byte span, both exclusive), falling back to a copy or rejecting it, and add boundary tests.

@akii96 akii96 Aug 28, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, good catch, and your arithmetic was right. Fixed in e5ed3c5.

page_elems = page_size * row_stride
if page_elems >= 2**31 or page_elems * k.element_size() >= 2**32:
    return None

None means dense, which routes into the .contiguous() copy that already existed, so it falls back rather than rejects. Boundary tests at 2**24 - 1, 2**24 and 2**25.

The byte bound can't fire today since this path is bf16/f16 only, but kept for a future 1 or 4 byte dtype.

It also tests the copy itself: removing both .contiguous() calls left every pitch test green, so the fallback was untested.

# Reverse the causal q-block grid axis so the heaviest blocks issue first,
# shortening the makespan tail. Bit-identical, being a permutation of
# workgroup -> q-block. See flash_attn_utils._init_causal_lpt_order.
causal_lpt: bool = True,

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.

[blocking] causal_lpt=False is not honored by two dualwave routes: _build_splitk and long _build_varlen neither accept nor forward this flag, so the underlying builder default silently re-enables LPT. That breaks the public override and prevents a trustworthy A/B for mixed varlen workloads, where reversing global grid_dim.y can place a short/decode sequence's only active block at the end. Thread the flag through both cached builder signatures/calls and add route-specific off/on tests; given the reported TTFT TODO, gate mixed varlen unless its default-on result is measured.

@akii96 akii96 Aug 28, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, both fixed in e5ed3c5. _build_splitk and _build_varlen now take and forward the flag, and both call sites pass it.

You were right to ask for the measurement, it does regress. Sweeping B as a fraction of one sequence's q-block count put the sign change at 0.75 to 0.875 of it, at 8k/16k/32k alike. causal_lpt now defaults to auto, on while one sequence spans at least twice the batch's sequence count in q-blocks. True and False still force. The factor of two matters: admitting the whole range up to 1.0 still includes a 0.985x row.

Not the mechanism you suggested, though: I tried a per-sequence reversal so a short row keeps q-block 0, and timings were identical. It tracks sequence count against q-block count.

Route-specific off/on tests are there too, but they can't catch this on their own, since a route that ignores the flag returns identical bytes. It's pinned instead by asserting the two settings land on distinct lru_cache entries.

return flydsl_flash_attn_func(q, k, v, causal=False, dualwave_swp_xcd_swizzle=flag).clone()


@_requires_gfx950

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.

[blocking] Inserting the new test here ends test_xcd_swizzle_is_bit_identical immediately after its local run definition. Its previous off/on launches, synchronization, and equality assertion are gone, so all four parametrized XCD cases pass without launching a kernel; I reproduced launch_calls=[] by instrumenting the test. Restore those three lines before this decorator while keeping the new LPT test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, and confirmed exactly as you described. Restored in e5ed3c5, with the LPT test kept after it.

Two things while in there: two definitions of test_causal_lpt_splitk_off_on_bit_identical had accumulated and Python kept only the last, now deduped. And I swept the file for tests with no assertion at all; none.

_PAGED_PAGE_SIZE = 64
# KV tile length the dualwave pipeline consumes per step; a page holds one or more.
_PAGED_TILE = 64
_PAGED_PAGE_SIZES = (64, 128)

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.

The PR body and commit describe any positive BLOCK_N multiple, while the public entry point accepts only 64 and 128 even though the traits support larger multiples. Please either cover and expose at least a >2-tile page, or narrow the external claim to the implemented 64/128 contract.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Went with exposing it, in e5ed3c5. _PAGED_PAGE_SIZES is gone and the check is just a multiple-of-64 test now. The three paged equivalence tests run 64/128/256, so a 4-tile page is covered against the dense, contiguous and vLLM-view references. Body updated to match.

Also added a real vectorized rejection test. The old one built a linear cache at page_size 32, so that path was never hit.

@akii96
akii96 force-pushed the feat/gfx950-paged-attention-vllm-kv-layouts branch from bce2084 to e5ed3c5 Compare August 28, 2026 07:15
@akii96

akii96 commented Aug 28, 2026

Copy link
Copy Markdown
Author

Thanks so much @jhinpan, genuinely useful review!

e5ed3c5 addresses all four items. PR body is updated throughout. Every table re-measured on MI355X, the full chunk x ctx grid restored, and the end-to-end numbers labelled with the commit they came from, since re-running those depends on vllm-project/vllm#53335 and is deferred until this lands.

One number worth flagging is that mixed prefill plus decode batches measure 1.15x here against 1.45x before, so that row is where a caller's routing threshold should be fitted.

Happy to iterate further on any of it!

akii96 added 3 commits August 28, 2026 11:07
Causal work grows with the q-block index, so the natural order issues the
heaviest block last. The reversal already existed but was reachable only
from the fp8 builder; this shares it and enables it for bf16 behind a
CAUSAL_LPT trait. Measured 2.0-8.9% on causal paged prefill at 32k,
bit-identical since it is a permutation.

The shared helper reverses over grid_dim.y instead of recomputing from
seq_len, which is a bijection on dispatched indices by construction.

Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com>
… layouts

The paged path assumed a page holds exactly one KV tile and that the cache is
contiguous in the layout the kernel derives from its shape. Both hold for the
configurations swept so far and neither holds for a vLLM KV cache serving
MiniMax-M3, which uses page_size 128 against a 64-token tile and hands the
kernel two strided views of one interleaved allocation.

Three changes make that cache addressable without repacking it:

* page_size may be any multiple of BLOCK_N. The staged block table is indexed
  per KV tile rather than per page, so the LDS window check counts tiles; a
  page spanning several tiles expands to its constituent tiles on stage.

* The KV row stride is read off the tensor instead of inferred from the head
  count and head dim. A cache whose K and V are strided views of one buffer is
  passed through as-is; calling .contiguous() on it would cost a cache-sized
  copy and drop the K/V interleave.

* A single-KV-head cache whose heads are not exactly D apart is accepted. With
  Hkv == 1 the head stride is unobservable, so requiring it to equal D rejected
  valid layouts for a dimension the kernel never indexes.

The vectorized KV layout keeps the one-tile-per-page assumption in its address
arithmetic, so it now rejects multi-tile pages explicitly rather than reading
the wrong rows.

Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com>
…CD test

Addresses review feedback on the paged-KV and causal-LPT commits.

The direct-stride gate accepted pitches the generated layout cannot represent.
A page descriptor spans page_size * row_stride elements, emitted as a signed
int32 extent and a 32-bit byte span, so page_size 128 with row_stride 2**24
reached exactly 2**31 and wrapped, reading the wrong rows with no error.
_paged_kv_row_stride now rejects such a pitch on the host and reports the cache
as dense, which routes it through the pre-existing .contiguous() copy.

causal_lpt was not honored on two routes. _build_splitk and _build_varlen
neither accepted nor forwarded it, so the builder default silently re-enabled
LPT and the public override was a no-op there. Both now take and forward the
flag.

The reviewer asked for mixed varlen to be gated unless default-on was measured.
Measuring it showed the concern was justified: varlen sizes grid_dim.y from the
batch-wide max seqlen, and once the batch holds enough sequences relative to one
sequence's q-block count, reversing that axis fights the scheduler rather than
shortening the causal tail. causal_lpt now defaults to None, meaning auto, with
True/False still forcing either way so an A/B stays trustworthy.

The gate admits a batch only while one sequence spans at least twice as many
q-blocks as the batch has sequences. The factor of two is not slack: sweeping B
as a fraction of the q-block count at max_seqlen_q 8k/16k/32k puts the crossover
at 0.75-0.875 of it, consistently across all three, so admitting the whole range
would keep LPT on over a band measuring 0.974-0.998x. Half is the widest round
threshold whose entire range is a win, measured 1.006-1.075x. Measured with an
interleaved off/on/off harness carrying an off-vs-off control arm, so 1-2%
effects sit above a 0.1-0.5% noise floor rather than inside it.

Dense and split-K keep LPT on by default, where it measured neutral to positive
across B and S, so only varlen consults the gate.

A new test had been inserted directly after the local run() definition in
test_xcd_swizzle_is_bit_identical, absorbing its three closing lines. All four
parametrized cases were passing without launching a kernel. Restored.

page_size is widened to any multiple of the 64-token KV tile, which the traits
layer already supported, so the implementation now matches the documented
contract instead of narrowing the text to 64/128.

Tests: 121 pass cold-cache on gfx950, up from 103. New coverage for the pitch
bounds at the exact 2**24 boundary, causal_lpt on the split-K and long-varlen
routes, the auto-gate resolution, page_size 256 against the dense, contiguous
and vLLM-view references, and the vectorized layout's multi-tile rejection,
which the PR body claimed was covered but was not: that test built a linear
cache at a sub-tile page_size.

Two of those tests are written against mutations that the obvious version
misses. Asserting that every cached dualwave builder takes causal_lpt does not
catch a call site dropping the argument, since both builders default it to True
and LPT is a permutation, so outputs are unchanged; the builders are lru_cached
on the flag, so the surviving signal is that two calls differing only in it must
produce two cache entries. Likewise the pitch bound is unit-tested on stride
tuples that no real cache can reach, leaving the consequence untested, so the
fallback is exercised by stubbing _paged_kv_row_stride to refuse and requiring a
real packed vLLM-view cache to still match both references. Removing the
.contiguous() calls fails that and nothing else.

Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com>
@akii96
akii96 force-pushed the feat/gfx950-paged-attention-vllm-kv-layouts branch from e5ed3c5 to b83922b Compare August 28, 2026 08:07

@jhinpan jhinpan 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.

Reviewed b83922b in three passes: prior-finding closure; paged addressing/API/cache-key audit; and tests/CI. On a locked MI355X (gfx950), 34 focused paged/LPT/XCD cases passed, the full attention file passed 145/145, and additional page_size=192/320 partial-page probes passed for both single-split and split-K=3 (max abs error <= 5.13e-4 versus the fp32 reference). check_repo and Python style also pass. One descriptor-boundary hole remains: dense layouts return before the new page-span check, so the newly advertised unrestricted 64-multiple page_size can still create a >=231-element / >=232-byte page descriptor and silently wrap. Requesting changes until the dense effective pitch is bounded (or oversized pages are rejected) with a regression test.

return None
if num_kv_heads > 1 and head_stride != head_dim:
return None
if row_stride == dense_row:

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.

[blocking] This early return bypasses the descriptor-span check below for dense caches. For example, Hkv=64, D=128, page_size=262144 gives dense_row=8192 and page_elems=231 (232 bytes); this helper returns None here, the caller treats that as the dense/default stride, and k.contiguous() is a no-op for an already-contiguous cache, so init_descriptors still casts the overflowing page extent and builds the wrapped descriptor. This became reachable when the PR widened page_size to any multiple of 64. Please validate the effective dense pitch before this return (and reject when even a dense copy cannot fit), with a dense-boundary test in addition to the strided cases.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks @jhinpan , confirmed, and your example lands on exactly 2**31. Fixed in 05d6f49.

Checked at the entry point rather than before this return, since None here means "dense" and the helper can't refuse. Rejecting is right anyway: dense is already the narrowest pitch, so a copy has nothing to shrink into.

dense_page_elems = page_size * num_kv_heads * D
if not _paged_page_descriptor_fits(dense_page_elems, k.element_size()):
    raise NotImplementedError(...)

Both bounds share that predicate now. It has to be the resolved num_kv_heads, not k.shape[2]: DEFAULT_STRIDE_KV_N comes from the former, so bounding the cache's count leaves the same wrap reachable by asking for more KV heads than it holds.

Boundary test on the predicate, plus a launch-path one for an oversized page and head count.

The page-span check sat after the dense early return, so it only ever saw a pitch
read off the tensor. Harmless while page_size was 64 or 128, but widening it left
the dense pitch unbounded past 2**31 elements / 2**32 bytes, wrapping silently.

Both bounds now share one predicate, consulted at the entry point too. Dense
rejects rather than falling back, having no narrower layout to copy into, and uses
the resolved num_kv_heads, which is what DEFAULT_STRIDE_KV_N is built from.

152 pass cold-cache on gfx950, up from 145.

Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderfeli

Copy link
Copy Markdown
Collaborator

@yanguahe take a look?

@coderfeli

coderfeli commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

aiter | 0.9469 | 0.9477
flydsl | 0.9325 | 0.9333

accuracy drop 1%?

@yanguahe yanguahe 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.

The paged addressing and the review follow-ups on pitch bounds, causal_lpt plumbing, and page_size look in good shape. The remaining ask is to pin down the gsm8k gap and to narrow the in-place layout claim.

gsm8k (−1.4pp)
The table is from an older e2e point (943152d + vllm-project/vllm#53335). Please re-run gsm8k on the kernel after the pitch / LPT / page_size review fixes, not that earlier commit.

Before treating the 1.4pp as a kernel issue, please also show:

  1. Routing. On the 8-shot gsm8k prompts, how many steps actually entered FlyDSL vs fell through to AITER? If almost none hit FlyDSL, this drop is not this kernel.
  2. Layer type. Full softmax attention layers may use dualwave; MiniMax sparse layers must stay on the existing AITER/sparse path. A sparse layer sent to dualwave would drop accuracy and would be independent of the paged layout work.
  3. Repeatability. Re-run the same gsm8k setup 2–3 times and check whether the gap stays above ~1pp; or run once more with dualwave_swp_lazy_rescale=False only.

A single run at ~0.6pp binomial stderr is not enough to decide. The near-tie token note in #53335 is useful, but it does not replace a re-run on current HEAD.

PR description: in-place KV contract
Please do not describe this as in-place for an arbitrary vLLM cache. The kernel can consume the cache without a copy only when:

  • layout is linear and page_size is a multiple of 64 (vectorized remains page_size=64 only);
  • and either Hkv == 1, or the heads are already packed NHD so head_stride == D.

GQA + HND still goes through .contiguous(). That is fine for MiniMax-M3 TP=4 (Hkv=1); it is not a general vLLM zero-copy promise. Please state that bound in the PR body (and the flydsl_flash_attn_func docstring if you touch it).

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