Skip to content

Commit a7698c0

Browse files
authored
Merge pull request #5 from GirasoleY/fix/pr48180-dcp-prefill-no-trtllm
[Bugfix] Gate DCP prefill and varlen spec decode on CP-aware paths (for #48180)
2 parents 2a0a363 + 1adb793 commit a7698c0

4 files changed

Lines changed: 104 additions & 25 deletions

File tree

tests/kernels/attention/test_use_trtllm_attention.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,17 @@ def test_use_dcp_fallback(_mock):
154154
assert _call(dcp_world_size=2) is False
155155

156156

157+
@patch("vllm.utils.flashinfer.supports_trtllm_attention", return_value=True)
158+
def test_use_dcp_fallback_prefill(_mock):
159+
# TRTLLM prefill has no cross-rank LSE combine for DCP-sharded KV.
160+
assert _call(dcp_world_size=2, is_prefill=True) is False
161+
162+
163+
@patch("vllm.utils.flashinfer.supports_trtllm_attention", return_value=True)
164+
def test_use_dcp_fallback_prefill_force_on_still_false(_mock):
165+
assert _call(dcp_world_size=2, is_prefill=True, force_use_trtllm=True) is False
166+
167+
157168
@patch("vllm.utils.flashinfer.supports_trtllm_attention", return_value=False)
158169
def test_use_platform_unsupported(_mock):
159170
assert _call() is False
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
"""FlashInfer GQA builder: reorder threshold under DCP with spec decode."""
4+
5+
import pytest
6+
import torch
7+
8+
from tests.v1.attention.utils import create_vllm_config
9+
from vllm.config import SpeculativeConfig
10+
from vllm.platforms import current_platform
11+
from vllm.v1.attention.backends import flashinfer as flashinfer_backend
12+
from vllm.v1.attention.backends.flashinfer import (
13+
FlashInferDecodeKernel,
14+
FlashInferMetadataBuilder,
15+
)
16+
from vllm.v1.attention.backends.utils import PerLayerParameters
17+
from vllm.v1.kv_cache_interface import FullAttentionSpec
18+
19+
if not current_platform.is_cuda():
20+
pytest.skip("FlashInfer backend requires a CUDA platform.", allow_module_level=True)
21+
22+
23+
def test_flashinfer_gqa_dcp_spec_decode_clamps_reorder_threshold(monkeypatch):
24+
"""trtllm-gen decode receives no cp_rank/global-seq-len information, so its
25+
end-aligned causal mask is wrong for q_len > 1 over the DCP-interleaved
26+
local KV shard. The builder must keep reorder_batch_threshold at 1 under
27+
DCP so spec queries take the (DCP-aware) prefill path instead.
28+
"""
29+
vllm_config = create_vllm_config(max_model_len=1024)
30+
vllm_config.parallel_config.decode_context_parallel_size = 2
31+
vllm_config.speculative_config = SpeculativeConfig(
32+
method="ngram", num_speculative_tokens=3
33+
)
34+
35+
monkeypatch.setattr(
36+
flashinfer_backend, "can_use_trtllm_attention", lambda *args, **kwargs: True
37+
)
38+
monkeypatch.setattr(
39+
FlashInferMetadataBuilder,
40+
"_get_flashinfer_trtllm_api_decode_kernel",
41+
staticmethod(lambda: FlashInferDecodeKernel.TRTLLM_GEN),
42+
)
43+
monkeypatch.setattr(
44+
flashinfer_backend,
45+
"get_per_layer_parameters",
46+
lambda *args, **kwargs: {
47+
"layer.0": PerLayerParameters(
48+
window_left=-1, logits_soft_cap=None, sm_scale=0.1, has_sinks=False
49+
)
50+
},
51+
)
52+
53+
kv_cache_spec = FullAttentionSpec(
54+
block_size=16,
55+
num_kv_heads=vllm_config.model_config.get_num_kv_heads(
56+
vllm_config.parallel_config
57+
),
58+
head_size=vllm_config.model_config.get_head_size(),
59+
dtype=vllm_config.model_config.dtype,
60+
)
61+
builder = FlashInferMetadataBuilder(
62+
kv_cache_spec,
63+
["layer.0"],
64+
vllm_config,
65+
torch.device("cpu"),
66+
)
67+
68+
# Guard against passing vacuously with the kernel disabled.
69+
assert (
70+
builder.flashinfer_trtllm_api_decode_kernel == FlashInferDecodeKernel.TRTLLM_GEN
71+
)
72+
assert builder.reorder_batch_threshold == 1

vllm/utils/flashinfer.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,16 @@ def use_trtllm_attention(
439439
if force_use_trtllm is not None and not force_use_trtllm:
440440
return False
441441

442+
# TRTLLM prefill attends only the DCP-local KV shard and has no
443+
# cross-rank LSE combine, so it cannot be used with DCP; fall back to
444+
# FlashInfer's DCP prefill path. TRTLLM decode under DCP is selected
445+
# separately (all-gathered query heads + LSE combine in forward).
446+
if dcp_world_size > 1:
447+
logger.warning_once(
448+
"TRTLLM prefill does not support DCP, reverting to FlashInfer"
449+
)
450+
return False
451+
442452
# The platform is not supported
443453
if not supports_trtllm_attention(is_prefill=is_prefill):
444454
if force_use_trtllm:

vllm/v1/attention/backends/flashinfer.py

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,14 @@ def __init__(
752752
self._init_reorder_batch_threshold(
753753
1,
754754
supports_spec_as_decode=supports_spec_as_decode,
755-
supports_dcp_with_varlen=supports_spec_as_decode,
755+
# trtllm-gen decode receives no cp_rank/global-seq-len information,
756+
# so its end-aligned causal mask is wrong for q_len > 1 over the
757+
# DCP-interleaved local KV shard (spec token i misses up to
758+
# (dcp_world_size - 1) * (q_len - 1 - i) KV entries, including its
759+
# own). Keep the threshold at 1 under DCP so spec queries take the
760+
# DCP-aware prefill path, until the kernel is CP-aware (compare
761+
# flash_attn_varlen_func's cp_world_size/cp_rank/cp_tot_seqused_k).
762+
supports_dcp_with_varlen=False,
756763
)
757764

758765
self._cascade_wrapper = None # Wrapper for cascade attention
@@ -1122,23 +1129,6 @@ def build(
11221129
has_sinks=self.has_sinks,
11231130
has_spec=uses_spec_reorder,
11241131
)
1125-
if (
1126-
causal
1127-
and self.use_dcp
1128-
and not prefill_use_trtllm
1129-
and self.flashinfer_trtllm_api_decode_kernel
1130-
== FlashInferDecodeKernel.TRTLLM_GEN
1131-
and can_use_trtllm_attention(
1132-
self.num_qo_heads,
1133-
self.num_kv_heads,
1134-
is_prefill=True,
1135-
)
1136-
):
1137-
logger.info_once(
1138-
"Using TRTLLM prefill attention with DCP because trtllm-gen "
1139-
"attention is available."
1140-
)
1141-
prefill_use_trtllm = True
11421132
decode_with_flashinfer_trtllm_api = causal and self.use_trtllm_decode_attention
11431133

11441134
if not causal and self.use_dcp:
@@ -1322,6 +1312,9 @@ def build(
13221312
assert qo_indptr_prefill_cpu.shape[0] == num_prefills + 1
13231313

13241314
if prefill_use_trtllm:
1315+
# TRTLLM prefill has no cross-rank combine for DCP-sharded KV;
1316+
# use_trtllm_attention never selects it when DCP is enabled.
1317+
assert not self.use_dcp
13251318
# Create GPU versions
13261319
qo_indptr_prefill_gpu = (
13271320
qo_indptr[prefill_start:] - qo_indptr[prefill_start]
@@ -1330,13 +1323,6 @@ def build(
13301323
# This is the cumulative sum of the number of KV cache
13311324
# blocks per prefill request.
13321325
prefill_seq_lens = seq_lens[prefill_start:]
1333-
if self.use_dcp:
1334-
prefill_seq_lens = get_dcp_local_seq_lens(
1335-
prefill_seq_lens,
1336-
self.dcp_world_size,
1337-
self.dcp_rank,
1338-
self.dcp_kv_cache_interleave_size,
1339-
)
13401326
num_blocks_per_req = (prefill_seq_lens + page_size - 1) // page_size
13411327
paged_kv_indptr_prefill_gpu = self.paged_kv_indptr.gpu[
13421328
prefill_start : num_reqs + 1

0 commit comments

Comments
 (0)