[ROCm][MLA][DCP] Support causal multi-token verification - #51705
Conversation
shen-shanshan
left a comment
There was a problem hiding this comment.
AMD CI passed (http://buildkite.com/vllm/amd-ci/builds/12452/canvas), let's run the full CI tests.
|
/ci run |
|
✅ Triggered Buildkite CI #86273 for commit |
|
/ci retry |
|
✅ Queued 1 failed job(s) for retry in Buildkite CI #86273. |
billishyahao
left a comment
There was a problem hiding this comment.
Please address the comments. Thank you!
| @@ -0,0 +1,128 @@ | |||
| # SPDX-License-Identifier: Apache-2.0 | |||
There was a problem hiding this comment.
can we fold this one segment triton kernel into common dcp specific util file? e.g. https://github.com/vllm-project/vllm/blob/main/vllm/v1/attention/ops/dcp.py
| # Non-causal DSpark block is flattened to one decode row per query token in | ||
| # forward_mqa, so no intra-block causal masking is required. | ||
| supports_non_causal_multi_token_decode: ClassVar[bool] = True | ||
| supports_non_causal_multi_token_dcp: ClassVar[bool] = True |
There was a problem hiding this comment.
Will this new flag bring unexpected regression to other platform? Please double check
| def _segmented_mla_page_size(block_size: int) -> int: | ||
| """Largest supported power-of-two subpage dividing a physical KV block.""" | ||
| assert block_size > 0 | ||
| return min(128, largest_power_of_2_divisor(block_size)) |
There was a problem hiding this comment.
Why limit this page size up to 128?
| def _get_segmented_mla_decode(): | ||
| """Load AITER's segmented MLA decode with unreduced partial output.""" | ||
| from aiter.ops.triton.attention.mla import mla_decode_fwd | ||
|
|
There was a problem hiding this comment.
Checked. In order to maintain the same format pattern, a blank row will remain after the inner import.
| @functools.lru_cache(maxsize=1) | ||
| def _get_aiter_mla_decode(): | ||
| from aiter.mla import mla_decode_fwd | ||
|
|
| kv_cache_dtype: str, | ||
| dcp_world_size: int = 1, | ||
| ) -> bool: | ||
| """Whether non-DCP multi-token verification uses Gluon.""" |
There was a problem hiding this comment.
restore function docstring:
"""
Whether a small-head multi-token verify uses native Gluon MTP.
bf16 has no gqa<16, qseqlen>1 asm kernel, so verify goes through
``mla_gluon``'s 4-D MTP entry (``q`` shaped ``[batch, qlen, nhead, dim]``)
with ``use_2d_view=False``. fp8 has one via the q-row fold and must not
come here: the MTP path hands Gluon the batch size its fp8 regime
asserts against. A predicate rather than inline in forward_mqa so the
builder sees the same answer the impl acts on.
"""
There was a problem hiding this comment.
Done. Restored and updated.
| skip_paged_kv_expand = use_segmented_dcp_verify | ||
|
|
||
| if not skip_paged_kv_expand: |
There was a problem hiding this comment.
why not use use_segmented_dcp_verify instead?
There was a problem hiding this comment.
Removed. Redundant code introduced from reconstruction.
| pages_per_block: int, | ||
| max_local_pages: int, | ||
| ) -> None: | ||
| if block_table.is_cuda: |
There was a problem hiding this comment.
Keep only one path.
|
/amd-ci run |
|
✅ Triggered Buildkite AMD CI #12473 for commit |
|
/ci run |
|
✅ Triggered Buildkite CI #86360 for commit |
|
/ci retry |
|
✅ Queued 1 failed job(s) for retry in Buildkite CI #86360. |
Co-authored-by: andyluo7 <andy.luo@amd.com> Signed-off-by: Yichao Zhu <Yichao.Zhu@amd.com>
Co-authored-by: andyluo7 <andy.luo@amd.com> Signed-off-by: Yichao Zhu <Yichao.Zhu@amd.com>
Signed-off-by: billishyahao <bill.he@amd.com>
Split the Triton MLA non-causal multi-token DCP capability into its own PR: it is the only part of this change that touches a backend shared with CUDA and XPU. This PR keeps only the ROCm AITER causal target verification path. Give every decode route a single owner. The builder decides, the decode metadata carries the decision, and the impl acts on it: - segmented DCP verify is recorded as an AiterDCPVerifyView, whose four tensors were four independent optional fields that were always set or cleared together; its presence is the routing decision, so five is-not-None assertions and the impl-side predicate all go away; - use_gluon_verify joins use_gluon_decode on the metadata instead of being recomputed in the impl from a different dtype source; - configuration support for the segmented route is one predicate evaluated once per builder, with the interleave restriction inside it rather than only in the constructor, replacing a query-length probe that passed a placeholder 2. Collapse the DCP verify page-table fill onto one implementation. It was split into a Triton kernel and a torch fallback selected by block_table.is_cuda, so unit tests exercised the fallback while production ran the kernel, and the kernel did not bound its block-table load. The torch expansion runs on both devices and states the block count invariant it relies on. Stop aliasing the query as the segmented kernel's unused output pointer, so a build that does write it fails loudly instead of scribbling over q. Build the verify row lengths through dcp_local_verify_row_lens instead of a second inline copy, and state on AiterMLAImpl that its decode LSE is natural-log: aiter mla_decode_fwd(return_lse=True) matches logsumexp on gfx950, and the segment merge converts AITER's base-2 statistics. Rename that merge to merge_mla_segments_triton, in module rocm_aiter_mla_merge, so it is not read as a collective reduce. Also revert refactors unrelated to DCP verify (the use_gluon_decode rewrite and the num_kernel_reqs rename), restore the comments and docstrings they dropped, inline skip_paged_kv_expand, and explain the segmented subpage size bound. Signed-off-by: Yichao Zhu <Yichao.Zhu@amd.com> Assisted-by: Codex
The small-head verify routing decision now lives in the metadata builder instead of AiterMLAImpl.forward_mqa, so _gluon_mla_decode_supported is probed during build(). The test patched it only around forward_mqa, so on gfx942 the builder recorded use_gluon_verify=False and forward_mqa fell through to the asm decode path, which dereferences layer._q_scale on the layer=None handed in by the test. Hoist the probe patch so the build and the forward see the same answer, and assert the metadata flag directly so the build-time contract is pinned rather than incidentally satisfied. Forcing the probe also makes the build architecture independent: use_persistent_metadata is False on gfx942 as well now, matching gfx950. Signed-off-by: Yichao Zhu <Yichao.Zhu@amd.com>
|
/ci run |
|
✅ Triggered Buildkite CI #86381 for commit |
Pin the small-head Gluon verify test to the intended 12-head metadata shape instead of relying on a partial HF config override that still leaves the builder at the full model head count. Use 64 query heads in the segmented DCP verify causal-reference test. The test still exercises DCP > 1 and qlen > 1 against causal attention, while avoiding a gfx942 Triton launch that requires 128 KiB of shared memory on hardware capped at 64 KiB. Co-authored-by: andyluo7 <andy.luo@amd.com> Signed-off-by: Yichao Zhu <Yichao.Zhu@amd.com>
|
/ci run |
|
✅ Triggered Buildkite CI #86397 for commit |
GirasoleY
left a comment
There was a problem hiding this comment.
Thanks for the iteration! LGTM
Summary
Enable ROCm AITER MLA decode context parallelism for causal multi-token target verification. Each verification token gets its correct per-rank causal KV window, and rank-local partial outputs are merged through the existing DCP attention merge.
This PR is scoped to the ROCm AITER target backend. Hybrid prefix-cache geometry and external connector integration remain separate changes.
Implementation
AiterDCPVerifyViewselects segmented DCP verification; the implementation consumes that decision without recomputing it.Validation
Focused ROCm tests
Revision:
e1843114b7c233a9c71ad44b28bf63426ad64836The suite includes an actual FP8 segmented-kernel correctness test with
DCP=2,qlen=3, and 96 gathered query heads. It builds each rank's causal row view throughAiterMLAMetadataBuilder, computes both rank-local partials throughAiterMLAImpl.forward_mqa(), merges them using their returned LSE, and compares the result against global causal attention.It also covers:
Negative control: the causal-reference test was rerun after changing only the row-boundary calculation back to the previous committed-prefix-only form. The fixed implementation passed. The old calculation failed deterministically with 2,671/147,456 output elements outside tolerance and a maximum absolute difference of 0.1311. This test-only mutation was not committed.
Full-model regression
Configuration:
vllm/vllm-openai-rocm:nightlye1843114b7c233a9c71ad44b28bf63426ad648362c7d7dd64a2eaba0feedf42cab2f527486d7479cFull lm-eval GSM8K, 5-shot, 1,319 samples, concurrency 64, default generation length (
max_length=2047):The server selected
ROCM_AITER_MLA, completed both PIECEWISE and FULL graph capture, exposed 27,012,229 KV-cache tokens, and remained healthy after the evaluation. The staged runtime files were checked against a SHA256 manifest in the container, and the server log recorded the exact PR revision above.This target-only run validates the DCP8/FP8-KV full-model accuracy baseline. The causal multi-token target path is covered directly by the focused causal-reference test.
Stacked target-verification run
PR 54546 at
8474084088b12b7bc3ca35cf6d74f87cb7161f92was cleanly stacked on this PR head to enable Triton MLA for the non-causal draft group. The target retained the same TP8/DCP8, FP8-KV, 1M, FULL graph, and maximum-batch-32 configuration. The speculative configuration used two draft tokens, probabilistic draft sampling, block rejection sampling, and Triton MLA for the draft.This run exercises the ROCm AITER segmented causal target-verification path from this PR together with PR 54546's non-causal draft capability. The server completed FULL graph capture and remained healthy after evaluation.
AI assistance
This change was prepared with AI assistance (Codex). The human submitter has reviewed every changed line and is responsible for defending it.