[2/N][warmup][DSv4] Migrate sequence and DCP kernels (#53564)
Signed-off-by: LopezCastroRoberto <rocastro@redhat.com> Co-authored-by: Codex <codex@openai.com> Co-authored-by: OpenAI Codex <noreply@openai.com> Co-authored-by: Lucas Wilkinson <LucasWilkinson@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Roberto L. Castro committed
Sep 7, 2026 at 15:35 UTC
1713b9866ae5c380d97da5796217064ac7ab57b8
12 files changed
+1461
-583
.buildkite/test_areas/model_executor.yaml
+4
@@ -13,6 +13,8 @@ steps:
13
- vllm/model_executor/warmup
14
- tests/model_executor
15
- tests/model_executor/test_jit_warmup.py
16
+ - tests/model_executor/test_jit_warmup_cutedsl_launcher.py
17
+ - tests/model_executor/test_jit_warmup_triton_launcher.py
18
- tests/entrypoints/openai/completion/test_tensorizer_entrypoint.py
19
commands:
20
- apt-get update && apt-get install -y curl libsodium23
@@ -41,6 +43,8 @@ steps:
43
- vllm/model_executor/warmup
44
- tests/model_executor
45
- tests/model_executor/test_jit_warmup.py
46
+ - tests/model_executor/test_jit_warmup_cutedsl_launcher.py
47
+ - tests/model_executor/test_jit_warmup_triton_launcher.py
48
- tests/entrypoints/openai/completion/test_tensorizer_entrypoint.py
49
- vllm/_aiter_ops.py
50
- vllm/platforms/rocm.py
tests/model_executor/test_jit_warmup.py
+3
-1
@@ -532,10 +532,12 @@ def test_runtime_cache_miss_compiles_and_caches_executor() -> None:
532
def test_registry_records_only_inside_model_setup_context() -> None:
533
registry = JitWarmupRegistry(_config())
534
kernel = RecordingToyKernel()
535
+ config = _config()
536
537
kernel.register_warmup(3, _config())
538
with registry.activate():
538
- kernel.register_warmup(3, _config())
539
+ kernel.register_warmup(3, config)
540
+ kernel.register_warmup(3, config)
541
542
assert len(registry) == 1
543
assert kernel.compiled == []
tests/model_executor/test_jit_warmup_cutedsl_launcher.py
new
+76
@@ -0,0 +1,76 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from dataclasses import dataclass
5
+from typing import Any
6
+
7
+from vllm.model_executor.warmup import jit_warmup_cutedsl_helper
8
+from vllm.model_executor.warmup.jit_warmup_cutedsl_helper import (
9
+ CuTeDSLLaunchSpec,
10
+ VllmCuTeDSLJitKernel,
11
+ cutedsl_kernel_launcher,
12
+)
13
+
14
+
15
+class _TestCuTeDSLKernel(VllmCuTeDSLJitKernel["_TestCuTeDSLKernel.CompileKey"]):
16
+ @dataclass(frozen=True)
17
+ class CompileKey:
18
+ variant: int
19
+
20
+ @staticmethod
21
+ def kernel(compile_key: CompileKey) -> tuple[str, int]:
22
+ return "entry", compile_key.variant
23
+
24
+ def dispatch(self, *, variant: int) -> CompileKey:
25
+ return self.CompileKey(variant=variant)
26
+
27
+ def get_warmup_keys(self) -> list[CompileKey]:
28
+ return self._trace_dispatch(self.dispatch)(variant=(1, 2))
29
+
30
+ def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
31
+ return (f"fake-{compile_key.variant}",)
32
+
33
+ @cutedsl_kernel_launcher
34
+ def __call__(
35
+ self,
36
+ payload: str,
37
+ *,
38
+ variant: int,
39
+ ) -> CuTeDSLLaunchSpec[CompileKey]:
40
+ compile_key = self.dispatch(variant=variant)
41
+ return compile_key, (payload,), {"variant": variant}
42
+
43
+
44
+def test_cutedsl_launcher_reuses_compiled_executor(monkeypatch) -> None:
45
+ compile_calls: list[tuple[Any, tuple[Any, ...]]] = []
46
+ launch_calls: list[tuple[int, tuple[Any, ...]]] = []
47
+
48
+ def compile_cutedsl(entry: tuple[str, int], *args: Any) -> Any:
49
+ compile_calls.append((entry, args))
50
+ variant = entry[1]
51
+
52
+ def executor(*runtime_args: Any) -> tuple[int, tuple[Any, ...]]:
53
+ launch_calls.append((variant, runtime_args))
54
+ return variant, runtime_args
55
+
56
+ return executor
57
+
58
+ monkeypatch.setattr(
59
+ jit_warmup_cutedsl_helper,
60
+ "compile_cutedsl",
61
+ compile_cutedsl,
62
+ )
63
+ kernel = _TestCuTeDSLKernel()
64
+ compile_key = kernel.CompileKey(variant=1)
65
+
66
+ kernel.compile(compile_key)
67
+ kernel.compile(compile_key)
68
+ assert compile_calls == [(("entry", 1), ("fake-1",))]
69
+
70
+ assert kernel("runtime", variant=1) == (1, ("runtime",))
71
+ assert kernel("other", variant=2) == (2, ("other",))
72
+ assert compile_calls == [
73
+ (("entry", 1), ("fake-1",)),
74
+ (("entry", 2), ("fake-2",)),
75
+ ]
76
+ assert launch_calls == [(1, ("runtime",)), (2, ("other",))]
tests/model_executor/test_jit_warmup_triton_launcher.py
new
+128
@@ -0,0 +1,128 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from dataclasses import dataclass
5
+from typing import Any
6
+
7
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
8
+ LaunchSpec,
9
+ VllmTritonJitKernel,
10
+ kernel_launcher,
11
+)
12
+
13
+
14
+class _FakeTritonKernel:
15
+ arg_names = ("first", "second", "CONST")
16
+
17
+ def __init__(self) -> None:
18
+ self.warmup_calls: list[dict[str, Any]] = []
19
+
20
+ def warmup(self, **kwargs: Any) -> None:
21
+ self.warmup_calls.append(kwargs)
22
+
23
+
24
+class _TestTritonKernel(VllmTritonJitKernel["_TestTritonKernel.CompileKey"]):
25
+ kernel = _FakeTritonKernel()
26
+
27
+ @dataclass(frozen=True)
28
+ class CompileKey:
29
+ value: int
30
+
31
+ def dispatch(self, *, value: int) -> CompileKey:
32
+ return self.CompileKey(value=value)
33
+
34
+ def get_warmup_keys(self) -> list[CompileKey]:
35
+ return self._trace_dispatch(self.dispatch)(value=1)
36
+
37
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
38
+ return dict(first="warmup", second=compile_key.value, runtime_launcher=None)
39
+
40
+ @kernel_launcher
41
+ def __call__(
42
+ self,
43
+ first: str,
44
+ second: int,
45
+ runtime_launcher: Any,
46
+ ) -> LaunchSpec:
47
+ return (2,), dict(
48
+ CONST=7,
49
+ _runtime_launcher=runtime_launcher,
50
+ _runtime_launcher_arg_count=2,
51
+ )
52
+
53
+
54
+def test_triton_launcher_supports_compile_and_runtime_adapters() -> None:
55
+ owner = _TestTritonKernel()
56
+ owner.kernel.warmup_calls.clear()
57
+
58
+ owner.compile(owner.CompileKey(value=1))
59
+ assert owner.kernel.warmup_calls == [
60
+ {"grid": (1,), "first": "warmup", "second": 1, "CONST": 7}
61
+ ]
62
+
63
+ runtime_calls: list[tuple[Any, ...]] = []
64
+
65
+ def runtime_launcher(kernel: Any, grid: Any, *args: Any, **kwargs: Any) -> None:
66
+ runtime_calls.append((kernel, grid, args, kwargs))
67
+
68
+ owner("runtime", 2, runtime_launcher)
69
+ assert runtime_calls == [(owner.kernel, (2,), ("runtime", 2), {"CONST": 7})]
70
+
71
+
72
+def test_triton_launcher_supports_cpu_function_wrappers() -> None:
73
+ calls: list[tuple[Any, ...]] = []
74
+
75
+ def kernel(first: str, second: int, CONST: int) -> None:
76
+ calls.append((first, second, CONST))
77
+
78
+ class FuncWrapper:
79
+ def __init__(self) -> None:
80
+ self.func = kernel
81
+
82
+ def __getitem__(self, _grid: Any) -> Any:
83
+ return self.func
84
+
85
+ class TestCpuKernel(_TestTritonKernel):
86
+ kernel: Any = FuncWrapper()
87
+
88
+ owner = TestCpuKernel()
89
+
90
+ owner("runtime", 2, None)
91
+ assert calls == [("runtime", 2, 7)]
92
+
93
+
94
+def test_compute_slot_mapping_uses_named_launcher_inputs(monkeypatch) -> None:
95
+ from vllm.v1.attention.backends.utils import PAD_SLOT_ID
96
+ from vllm.v1.worker.block_table import ComputeSlotMappingKernel
97
+
98
+ owner = ComputeSlotMappingKernel()
99
+ compile_key = owner.CompileKey(
100
+ kv_cache_block_size=16,
101
+ blocks_per_kv_block=1,
102
+ total_cp_world_size=2,
103
+ total_cp_rank=1,
104
+ cp_kv_cache_interleave_size=1,
105
+ block_table_stride=128,
106
+ block_size=16,
107
+ )
108
+ launches: list[tuple[Any, ...]] = []
109
+
110
+ def launch(grid: Any, inputs: Any, **kwargs: Any) -> None:
111
+ launches.append((grid, inputs, kwargs))
112
+
113
+ monkeypatch.setattr(owner, "launch", launch)
114
+ owner.compile(compile_key)
115
+
116
+ grid, inputs, kwargs = launches[0]
117
+ assert grid == (2,)
118
+ assert inputs["num_tokens"] == 2
119
+ assert inputs["block_table_stride"] == 128
120
+ assert kwargs == {
121
+ "KV_CACHE_BLOCK_SIZE": 16,
122
+ "BLOCKS_PER_KV_BLOCK": 1,
123
+ "TOTAL_CP_WORLD_SIZE": 2,
124
+ "TOTAL_CP_RANK": 1,
125
+ "CP_KV_CACHE_INTERLEAVE_SIZE": 1,
126
+ "PAD_ID": PAD_SLOT_ID,
127
+ "BLOCK_SIZE": owner.triton_block_size,
128
+ }
vllm/model_executor/kernels/attention/dsa/dcp_indexer_cutedsl.py
+501
-328
@@ -1,7 +1,8 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
-from functools import cache
4
+from dataclasses import dataclass
5
+from typing import Any
6
7
import cutlass
8
import cutlass.cute as cute
@@ -11,6 +12,17 @@ from cutlass import Float32, Int32, Uint32, Uint64
12
from quack.compile_utils import make_fake_tensor
13
14
from vllm.cute_utils import recast_val
15
+from vllm.model_executor.warmup.jit_warmup_cutedsl_helper import (
16
+ CuTeDSLLaunchSpec,
17
+ VllmCuTeDSLJitKernel,
18
+ cutedsl_kernel_launcher,
19
+)
20
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
21
+ LaunchSpec,
22
+ TritonWarmupTensor,
23
+ VllmTritonJitKernel,
24
+ kernel_launcher,
25
+)
26
from vllm.triton_utils import tl, triton
27
28
@@ -25,9 +37,7 @@ def stable_topk_from_gathered_candidates_cutedsl(
37
dtype=torch.int32,
38
device=gathered.device,
39
)
28
- StableTopKFromGatheredCandidatesKernel.compile(topk, gathered.shape[1])(
29
- gathered, out
30
- )
40
+ _STABLE_TOPK_FROM_GATHERED_CANDIDATES_KERNEL(gathered, out, topk=topk)
41
return out
42
43
@@ -41,380 +51,543 @@ def pack_dcp_topk_candidates_cutedsl(
51
row_starts: torch.Tensor | None,
52
) -> None:
53
topk = topk_indices.shape[1]
44
- grid = (topk_indices.shape[0], triton.cdiv(topk, 512))
54
row_starts_arg = row_starts if row_starts is not None else topk_indices
46
- _pack_dcp_topk_candidates_triton_kernel[grid](
55
+ _PACK_DCP_TOPK_CANDIDATES_KERNEL(
56
logits,
57
topk_indices,
58
packed,
59
row_starts_arg,
51
- logits.stride(0),
52
- logits.stride(1),
53
- topk_indices.stride(0),
54
- topk_indices.stride(1),
55
- packed.stride(0),
56
- packed.stride(1),
57
- packed.stride(2),
58
- logits.shape[1],
59
- DCP_RANK=dcp_rank,
60
- DCP_WORLD_SIZE=dcp_world_size,
61
- CP_INTERLEAVE=cp_interleave,
62
- HAS_ROW_STARTS=row_starts is not None,
63
- TOPK=topk,
64
- BLOCK_SIZE=512,
65
- num_warps=8,
60
+ logits_stride0=logits.stride(0),
61
+ logits_stride1=logits.stride(1),
62
+ topk_stride0=topk_indices.stride(0),
63
+ topk_stride1=topk_indices.stride(1),
64
+ packed_stride0=packed.stride(0),
65
+ packed_stride1=packed.stride(1),
66
+ packed_stride2=packed.stride(2),
67
+ num_cols=logits.shape[1],
68
+ dcp_rank=dcp_rank,
69
+ dcp_world_size=dcp_world_size,
70
+ cp_interleave=cp_interleave,
71
+ has_row_starts=row_starts is not None,
72
+ topk=topk,
73
+ block_size=512,
74
)
75
76
69
-@triton.jit
70
-def _pack_dcp_topk_candidates_triton_kernel(
71
- logits,
72
- topk_indices,
73
- packed,
74
- row_starts,
75
- logits_stride0: tl.constexpr,
76
- logits_stride1: tl.constexpr,
77
- topk_stride0: tl.constexpr,
78
- topk_stride1: tl.constexpr,
79
- packed_stride0: tl.constexpr,
80
- packed_stride1: tl.constexpr,
81
- packed_stride2: tl.constexpr,
82
- num_cols,
83
- DCP_RANK: tl.constexpr,
84
- DCP_WORLD_SIZE: tl.constexpr,
85
- CP_INTERLEAVE: tl.constexpr,
86
- HAS_ROW_STARTS: tl.constexpr,
87
- TOPK: tl.constexpr,
88
- BLOCK_SIZE: tl.constexpr,
77
+class PackDCPTopkCandidatesKernel(
78
+ VllmTritonJitKernel["PackDCPTopkCandidatesKernel.CompileKey"]
79
):
90
- row = tl.program_id(0)
91
- tile = tl.program_id(1)
92
- cols = tile * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
93
- mask = cols < TOPK
94
-
95
- local_idx = tl.load(
96
- topk_indices + row * topk_stride0 + cols * topk_stride1,
97
- mask=mask,
98
- other=-1,
99
- )
100
- valid = local_idx >= 0
101
- safe_local_idx = tl.maximum(local_idx, 0)
102
-
103
- row_start = 0
104
- if HAS_ROW_STARTS:
105
- row_start = tl.load(row_starts + row)
106
-
107
- score_col = safe_local_idx + row_start
108
- score_col = tl.minimum(score_col, tl.maximum(num_cols - 1, 0))
109
- score = tl.load(
110
- logits + row * logits_stride0 + score_col * logits_stride1,
111
- mask=mask & valid,
112
- other=-float("inf"),
80
+ @dataclass(frozen=True)
81
+ class CompileKey:
82
+ dcp_rank: int
83
+ dcp_world_size: int
84
+ cp_interleave: int
85
+ has_row_starts: bool
86
+ topk: int
87
+ block_size: int
88
+
89
+ # These scalars only describe runtime layouts and bounds; the constexpr
90
+ # fields below own the launch geometry and algorithmic specialization.
91
+ @staticmethod
92
+ @triton.jit(
93
+ do_not_specialize=[
94
+ "logits_stride0",
95
+ "logits_stride1",
96
+ "topk_stride0",
97
+ "topk_stride1",
98
+ "packed_stride0",
99
+ "packed_stride1",
100
+ "packed_stride2",
101
+ "num_cols",
102
+ ]
103
)
104
+ def kernel(
105
+ logits,
106
+ topk_indices,
107
+ packed,
108
+ row_starts,
109
+ logits_stride0,
110
+ logits_stride1,
111
+ topk_stride0,
112
+ topk_stride1,
113
+ packed_stride0,
114
+ packed_stride1,
115
+ packed_stride2,
116
+ num_cols,
117
+ DCP_RANK: tl.constexpr,
118
+ DCP_WORLD_SIZE: tl.constexpr,
119
+ CP_INTERLEAVE: tl.constexpr,
120
+ HAS_ROW_STARTS: tl.constexpr,
121
+ TOPK: tl.constexpr,
122
+ BLOCK_SIZE: tl.constexpr,
123
+ ):
124
+ row = tl.program_id(0)
125
+ tile = tl.program_id(1)
126
+ cols = tile * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
127
+ mask = cols < TOPK
128
+
129
+ local_idx = tl.load(
130
+ topk_indices + row * topk_stride0 + cols * topk_stride1,
131
+ mask=mask,
132
+ other=-1,
133
+ )
134
+ valid = local_idx >= 0
135
+ safe_local_idx = tl.maximum(local_idx, 0)
136
+
137
+ row_start = 0
138
+ if HAS_ROW_STARTS:
139
+ row_start = tl.load(row_starts + row)
140
+
141
+ score_col = safe_local_idx + row_start
142
+ score_col = tl.minimum(score_col, tl.maximum(num_cols - 1, 0))
143
+ score = tl.load(
144
+ logits + row * logits_stride0 + score_col * logits_stride1,
145
+ mask=mask & valid,
146
+ other=-float("inf"),
147
+ )
148
115
- global_id = (
116
- (safe_local_idx // CP_INTERLEAVE) * (DCP_WORLD_SIZE * CP_INTERLEAVE)
117
- + DCP_RANK * CP_INTERLEAVE
118
- + safe_local_idx % CP_INTERLEAVE
119
- )
120
- global_id = tl.where(valid, global_id, -1)
121
-
122
- packed_base = packed + row * packed_stride0 + cols * packed_stride1
123
- tl.store(packed_base, score, mask=mask)
124
- tl.store(packed_base + packed_stride2, global_id.to(tl.float32), mask=mask)
125
-
126
-
127
-@cute.jit
128
-def _warp_scan_inclusive_i32(val: Int32, lane: Int32) -> Int32:
129
- for i in cutlass.range_constexpr(cute.arch.WARP_SIZE.bit_length() - 1):
130
- offset = 1 << i
131
- partial = cute.arch.shuffle_sync_up(val, offset=offset, mask_and_clamp=0)
132
- if lane >= offset:
133
- val += partial
134
- return val
135
-
136
-
137
-@cute.jit
138
-def _block_scan_inclusive_i32(
139
- val: Int32,
140
- lane: Int32,
141
- warp_id: Int32,
142
- warp_scratch: cute.Tensor,
143
- warps_per_block: int,
144
-) -> Int32:
145
- prefix = _warp_scan_inclusive_i32(val, lane)
146
- if lane == Int32(cute.arch.WARP_SIZE - 1):
147
- warp_scratch[0, warp_id] = prefix
148
- cute.arch.sync_threads()
149
-
150
- if warp_id == Int32(0):
151
- warp_total = Int32(0)
152
- if lane < Int32(warps_per_block):
153
- warp_total = warp_scratch[0, lane]
154
- warp_prefix = _warp_scan_inclusive_i32(warp_total, lane)
155
- if lane < Int32(warps_per_block):
156
- warp_scratch[0, lane] = warp_prefix - warp_total
157
- cute.arch.sync_threads()
158
-
159
- return prefix + warp_scratch[0, warp_id]
160
-
161
-
162
-class StableTopKFromGatheredCandidatesKernel:
149
+ global_id = (
150
+ (safe_local_idx // CP_INTERLEAVE) * (DCP_WORLD_SIZE * CP_INTERLEAVE)
151
+ + DCP_RANK * CP_INTERLEAVE
152
+ + safe_local_idx % CP_INTERLEAVE
153
+ )
154
+ global_id = tl.where(valid, global_id, -1)
155
+
156
+ packed_base = packed + row * packed_stride0 + cols * packed_stride1
157
+ tl.store(packed_base, score, mask=mask)
158
+ tl.store(packed_base + packed_stride2, global_id.to(tl.float32), mask=mask)
159
+
160
+ def dispatch( # type: ignore[override]
161
+ self,
162
+ *,
163
+ has_row_starts: bool,
164
+ **compile_key_fields: int,
165
+ ) -> CompileKey:
166
+ return self.CompileKey(
167
+ **compile_key_fields,
168
+ has_row_starts=has_row_starts,
169
+ )
170
+
171
+ def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
172
+ dcp_world_size = vllm_config.parallel_config.decode_context_parallel_size
173
+ if dcp_world_size <= 1:
174
+ return []
175
+ cp_interleave = vllm_config.parallel_config.cp_kv_cache_interleave_size
176
+ topk = vllm_config.model_config.hf_config.index_topk
177
+ if topk <= 0:
178
+ return []
179
+
180
+ try:
181
+ from vllm.distributed.parallel_state import get_dcp_group
182
+
183
+ dcp_rank = get_dcp_group().rank_in_group
184
+ except Exception:
185
+ dcp_rank = 0
186
+
187
+ return self._trace_dispatch(self.dispatch)(
188
+ dcp_rank=dcp_rank,
189
+ dcp_world_size=dcp_world_size,
190
+ cp_interleave=cp_interleave,
191
+ has_row_starts=(False, True),
192
+ topk=topk,
193
+ block_size=512,
194
+ )
195
+
196
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
197
+ fp32_ptr = TritonWarmupTensor(torch.float32)
198
+ int32_ptr = TritonWarmupTensor(torch.int32)
199
+ return dict(
200
+ logits=fp32_ptr,
201
+ topk_indices=TritonWarmupTensor(torch.int32, shape=(1, compile_key.topk)),
202
+ packed=fp32_ptr,
203
+ row_starts_arg=int32_ptr,
204
+ logits_stride0=1,
205
+ logits_stride1=1,
206
+ topk_stride0=1,
207
+ topk_stride1=1,
208
+ packed_stride0=1,
209
+ packed_stride1=1,
210
+ packed_stride2=1,
211
+ num_cols=1,
212
+ dcp_rank=compile_key.dcp_rank,
213
+ dcp_world_size=compile_key.dcp_world_size,
214
+ cp_interleave=compile_key.cp_interleave,
215
+ has_row_starts=compile_key.has_row_starts,
216
+ topk=compile_key.topk,
217
+ block_size=compile_key.block_size,
218
+ )
219
+
220
+ @kernel_launcher
221
+ def __call__(
222
+ self,
223
+ logits: torch.Tensor,
224
+ topk_indices: torch.Tensor,
225
+ packed: torch.Tensor,
226
+ row_starts_arg: torch.Tensor,
227
+ *,
228
+ logits_stride0: int,
229
+ logits_stride1: int,
230
+ topk_stride0: int,
231
+ topk_stride1: int,
232
+ packed_stride0: int,
233
+ packed_stride1: int,
234
+ packed_stride2: int,
235
+ num_cols: int,
236
+ dcp_rank: int,
237
+ dcp_world_size: int,
238
+ cp_interleave: int,
239
+ has_row_starts: bool,
240
+ topk: int,
241
+ block_size: int,
242
+ ) -> LaunchSpec:
243
+ grid = (topk_indices.shape[0], triton.cdiv(topk, block_size))
244
+ return grid, dict(
245
+ row_starts=row_starts_arg,
246
+ DCP_RANK=dcp_rank,
247
+ DCP_WORLD_SIZE=dcp_world_size,
248
+ CP_INTERLEAVE=cp_interleave,
249
+ HAS_ROW_STARTS=has_row_starts,
250
+ TOPK=topk,
251
+ BLOCK_SIZE=block_size,
252
+ num_warps=8,
253
+ )
254
+
255
+
256
+class StableTopKFromGatheredCandidatesKernel(
257
+ VllmCuTeDSLJitKernel["StableTopKFromGatheredCandidatesKernel.CompileKey"]
258
+):
259
tb_size = 512
260
hist_bins = 2048
261
radix_bits = (hist_bins - 1).bit_length()
166
- assert hist_bins == 1 << radix_bits
262
key_bits = Uint64.width
263
radix_passes = (key_bits + radix_bits - 1) // radix_bits
264
final_radix_bits = key_bits - radix_bits * (radix_passes - 1)
265
hist_chunks = (hist_bins + tb_size - 1) // tb_size
266
warps_per_block = tb_size // cute.arch.WARP_SIZE
267
173
- def __init__(self, topk: int, num_candidates: int):
174
- assert num_candidates % self.tb_size == 0, (
268
+ @dataclass(frozen=True)
269
+ class CompileKey:
270
+ topk: int
271
+ num_candidates: int
272
+
273
+ @staticmethod
274
+ def kernel(compile_key: CompileKey) -> Any:
275
+ tb_size = StableTopKFromGatheredCandidatesKernel.tb_size
276
+ hist_bins = StableTopKFromGatheredCandidatesKernel.hist_bins
277
+ radix_bits = StableTopKFromGatheredCandidatesKernel.radix_bits
278
+ key_bits = StableTopKFromGatheredCandidatesKernel.key_bits
279
+ radix_passes = StableTopKFromGatheredCandidatesKernel.radix_passes
280
+ final_radix_bits = StableTopKFromGatheredCandidatesKernel.final_radix_bits
281
+ hist_chunks = StableTopKFromGatheredCandidatesKernel.hist_chunks
282
+ warps_per_block = StableTopKFromGatheredCandidatesKernel.warps_per_block
283
+ topk = compile_key.topk
284
+ assert hist_bins == 1 << radix_bits
285
+ assert compile_key.num_candidates % tb_size == 0, (
286
"StableTopKFromGatheredCandidatesKernel requires candidate count "
176
- f"to be a multiple of {self.tb_size}, got {num_candidates}"
287
+ f"to be a multiple of {tb_size}, got {compile_key.num_candidates}"
288
)
178
- self.topk = topk
179
- self.keys_per_thread = num_candidates // self.tb_size
289
+ keys_per_thread = compile_key.num_candidates // tb_size
290
291
@cute.struct
292
class SharedStorage:
183
- hist: cute.struct.MemRange[Int32, self.hist_bins]
293
+ hist: cute.struct.MemRange[Int32, hist_bins]
294
committed_count: cute.struct.MemRange[Int32, 1]
295
running_count: cute.struct.MemRange[Int32, 1]
296
threshold_bin: cute.struct.MemRange[Int32, 1]
297
threshold_found: cute.struct.MemRange[Int32, 1]
298
include_threshold_bin: cute.struct.MemRange[Int32, 1]
299
prefix_s: cute.struct.Align[cute.struct.MemRange[Uint64, 1], 8]
190
- warp_totals: cute.struct.MemRange[Int32, self.warps_per_block]
300
+ warp_totals: cute.struct.MemRange[Int32, warps_per_block]
301
192
- self.shared_storage = SharedStorage
302
+ shared_storage = SharedStorage
303
194
- @cute.jit
195
- def __call__(
196
- self,
197
- gathered: cute.Tensor,
198
- out: cute.Tensor,
199
- stream: CUstream,
200
- ):
201
- grid = (gathered.shape[0], 1, 1)
202
- self.kernel(gathered, out).launch(
203
- grid=grid,
204
- block=(self.tb_size, 1, 1),
205
- stream=stream,
206
- )
304
+ @cute.jit
305
+ def warp_scan_inclusive_i32(val: Int32, lane: Int32) -> Int32:
306
+ for i in cutlass.range_constexpr(cute.arch.WARP_SIZE.bit_length() - 1):
307
+ offset = 1 << i
308
+ partial = cute.arch.shuffle_sync_up(
309
+ val, offset=offset, mask_and_clamp=0
310
+ )
311
+ if lane >= offset:
312
+ val += partial
313
+ return val
314
+
315
+ @cute.jit
316
+ def block_scan_inclusive_i32(
317
+ val: Int32,
318
+ lane: Int32,
319
+ warp_id: Int32,
320
+ warp_scratch: cute.Tensor,
321
+ warps_per_block: int,
322
+ ) -> Int32:
323
+ prefix = warp_scan_inclusive_i32(val, lane)
324
+ if lane == Int32(cute.arch.WARP_SIZE - 1):
325
+ warp_scratch[0, warp_id] = prefix
326
+ cute.arch.sync_threads()
327
208
- @cute.jit
209
- def _stable_key(self, score: Float32, token_id: Int32) -> Uint64:
210
- bits = recast_val(score, Uint32)
211
- mask = Uint32(0x80000000)
212
- if (bits & Uint32(0x80000000)) != Uint32(0):
213
- mask = Uint32(0xFFFFFFFF)
214
- score_key = Uint64(bits ^ mask) << Uint64(32)
215
- id_key = Uint64(~Uint32(token_id))
216
- key = score_key | id_key
217
- if token_id < Int32(0):
218
- key = Uint64(0)
219
- return key
220
-
221
- @cute.jit
222
- def _prefix_matches(
223
- self,
224
- key: Uint64,
225
- prefix: Uint64,
226
- prefix_bits: Int32,
227
- ):
228
- matches = prefix_bits == Int32(0)
229
- if prefix_bits != Int32(0):
230
- shift = Int32(self.key_bits) - prefix_bits
231
- matches = (key >> Uint64(shift)) == (prefix >> Uint64(shift))
232
- return matches
233
-
234
- @cute.jit
235
- def _radix_pass(
236
- self,
237
- keys: cute.Tensor,
238
- output: cute.Tensor,
239
- storage,
240
- tid: Int32,
241
- step: Int32,
242
- bits: int,
243
- is_final_pass: bool,
244
- ):
245
- hist_smem = storage.hist.get_tensor(cute.make_layout((self.hist_bins,)))
246
- committed_count_smem = storage.committed_count.data_ptr()
247
- running_count_smem = storage.running_count.data_ptr()
248
- threshold_bin_smem = storage.threshold_bin.data_ptr()
249
- threshold_found_smem = storage.threshold_found.data_ptr()
250
- include_threshold_bin_smem = storage.include_threshold_bin.data_ptr()
251
- prefix_smem = storage.prefix_s.data_ptr()
252
- warp_totals_smem = storage.warp_totals.get_tensor(
253
- cute.make_layout((1, self.warps_per_block))
254
- )
328
+ if warp_id == Int32(0):
329
+ warp_total = Int32(0)
330
+ if lane < Int32(warps_per_block):
331
+ warp_total = warp_scratch[0, lane]
332
+ warp_prefix = warp_scan_inclusive_i32(warp_total, lane)
333
+ if lane < Int32(warps_per_block):
334
+ warp_scratch[0, lane] = warp_prefix - warp_total
335
+ cute.arch.sync_threads()
336
256
- prefix_bits = step * Int32(self.radix_bits)
257
- num_bins = 1 << bits
258
- block_scan_iterations = (num_bins + self.tb_size - 1) // self.tb_size
259
- shift = Int32(self.key_bits) - prefix_bits - Int32(bits)
260
- bin_mask = Uint64(num_bins - 1)
261
- prefix = prefix_smem.load()
262
-
263
- for chunk in cutlass.range_constexpr(self.hist_chunks):
264
- hist_smem[tid + Int32(chunk * self.tb_size)] = Int32(0)
265
- if tid == Int32(0):
266
- running_count_smem.store(committed_count_smem.load())
267
- include_threshold_bin_smem.store(Int32(0))
268
- threshold_found_smem.store(Int32(0))
269
- cute.arch.sync_threads()
270
-
271
- for key_idx in cutlass.range_constexpr(self.keys_per_thread):
272
- key = keys[key_idx]
273
- if self._prefix_matches(key, prefix, prefix_bits):
274
- bin_idx = Int32((key >> Uint64(shift)) & bin_mask)
275
- cute.arch.atomic_add(
276
- hist_smem.iterator + bin_idx,
277
- Int32(1),
278
- sem="relaxed",
279
- scope="cta",
280
- )
281
- cute.arch.sync_threads()
282
-
283
- lane = cute.arch.lane_idx()
284
- warp_id = cute.arch.warp_idx()
285
- # Each iteration scans one tb_size-wide slice of bins, high to low.
286
- iter = Int32(0)
287
- threshold_found = threshold_found_smem.load()
288
- while threshold_found == Int32(0) and iter < Int32(block_scan_iterations):
289
- bin_idx = Int32(num_bins - 1) - (iter * Int32(self.tb_size) + tid)
290
- count = hist_smem[bin_idx]
291
- chunk_inclusive = _block_scan_inclusive_i32(
292
- count,
293
- lane,
294
- warp_id,
295
- warp_totals_smem,
296
- self.warps_per_block,
337
+ return prefix + warp_scratch[0, warp_id]
338
+
339
+ @cute.jit
340
+ def stable_key(score: Float32, token_id: Int32) -> Uint64:
341
+ bits = recast_val(score, Uint32)
342
+ mask = Uint32(0x80000000)
343
+ if (bits & Uint32(0x80000000)) != Uint32(0):
344
+ mask = Uint32(0xFFFFFFFF)
345
+ score_key = Uint64(bits ^ mask) << Uint64(32)
346
+ id_key = Uint64(~Uint32(token_id))
347
+ key = score_key | id_key
348
+ if token_id < Int32(0):
349
+ key = Uint64(0)
350
+ return key
351
+
352
+ @cute.jit
353
+ def prefix_matches(
354
+ key: Uint64,
355
+ prefix: Uint64,
356
+ prefix_bits: Int32,
357
+ ):
358
+ matches = prefix_bits == Int32(0)
359
+ if prefix_bits != Int32(0):
360
+ shift = Int32(key_bits) - prefix_bits
361
+ matches = (key >> Uint64(shift)) == (prefix >> Uint64(shift))
362
+ return matches
363
+
364
+ @cute.jit
365
+ def radix_pass(
366
+ keys: cute.Tensor,
367
+ output: cute.Tensor,
368
+ storage,
369
+ tid: Int32,
370
+ step: Int32,
371
+ bits: int,
372
+ is_final_pass: bool,
373
+ ):
374
+ hist_smem = storage.hist.get_tensor(cute.make_layout((hist_bins,)))
375
+ committed_count_smem = storage.committed_count.data_ptr()
376
+ running_count_smem = storage.running_count.data_ptr()
377
+ threshold_bin_smem = storage.threshold_bin.data_ptr()
378
+ threshold_found_smem = storage.threshold_found.data_ptr()
379
+ include_threshold_bin_smem = storage.include_threshold_bin.data_ptr()
380
+ prefix_smem = storage.prefix_s.data_ptr()
381
+ warp_totals_smem = storage.warp_totals.get_tensor(
382
+ cute.make_layout((1, warps_per_block))
383
)
298
- running_count = running_count_smem.load()
299
- prior_in_scan_slice = chunk_inclusive - count
300
- remaining = Int32(self.topk) - running_count - prior_in_scan_slice
301
- if count > Int32(0) and remaining > Int32(0) and remaining <= count:
302
- threshold_bin_smem.store(bin_idx)
303
- if count <= remaining or cutlass.const_expr(is_final_pass):
304
- include_threshold_bin_smem.store(Int32(1))
305
- threshold_found_smem.store(Int32(1))
306
- # Barrier: every thread must finish reading running_count for this
307
- # slice before tb_size-1 advances it, else a warp racing ahead to
308
- # the store makes a lagging thread double-count the slice total
309
- # (-> remaining too small -> threshold too high -> under-fill).
310
- cute.arch.sync_threads()
311
- if tid == Int32(self.tb_size - 1):
312
- running_count_smem.store(running_count + chunk_inclusive)
384
+
385
+ prefix_bits = step * Int32(radix_bits)
386
+ num_bins = 1 << bits
387
+ block_scan_iterations = (num_bins + tb_size - 1) // tb_size
388
+ shift = Int32(key_bits) - prefix_bits - Int32(bits)
389
+ bin_mask = Uint64(num_bins - 1)
390
+ prefix = prefix_smem.load()
391
+
392
+ for chunk in cutlass.range_constexpr(hist_chunks):
393
+ hist_smem[tid + Int32(chunk * tb_size)] = Int32(0)
394
+ if tid == Int32(0):
395
+ running_count_smem.store(committed_count_smem.load())
396
+ include_threshold_bin_smem.store(Int32(0))
397
+ threshold_found_smem.store(Int32(0))
398
cute.arch.sync_threads()
399
315
- threshold_found = threshold_found_smem.load()
316
- iter += Int32(1)
317
-
318
- threshold = threshold_bin_smem.load()
319
- should_include_threshold = include_threshold_bin_smem.load() != Int32(0)
320
- for key_idx in cutlass.range_constexpr(self.keys_per_thread):
321
- key = keys[key_idx]
322
- if self._prefix_matches(key, prefix, prefix_bits):
323
- bin_idx = Int32((key >> Uint64(shift)) & bin_mask)
324
- selected = bin_idx > threshold
325
- if should_include_threshold:
326
- selected = selected or bin_idx == threshold
327
- if selected:
328
- dst = cute.arch.atomic_add(
329
- committed_count_smem,
400
+ for key_idx in cutlass.range_constexpr(keys_per_thread):
401
+ key = keys[key_idx]
402
+ if prefix_matches(key, prefix, prefix_bits):
403
+ bin_idx = Int32((key >> Uint64(shift)) & bin_mask)
404
+ cute.arch.atomic_add(
405
+ hist_smem.iterator + bin_idx,
406
Int32(1),
407
sem="relaxed",
408
scope="cta",
409
)
334
- if dst < Int32(self.topk):
335
- output[dst] = recast_val(~Uint32(key), Int32)
336
- cute.arch.sync_threads()
410
+ cute.arch.sync_threads()
411
338
- pass_finished = include_threshold_bin_smem.load()
339
- if tid == Int32(0) and pass_finished == Int32(0):
340
- prefix_smem.store(prefix | (Uint64(threshold) << Uint64(shift)))
341
- cute.arch.sync_threads()
342
- return pass_finished
412
+ lane = cute.arch.lane_idx()
413
+ warp_id = cute.arch.warp_idx()
414
+ # Each iteration scans one tb_size-wide slice of bins, high to low.
415
+ iter = Int32(0)
416
+ threshold_found = threshold_found_smem.load()
417
+ while threshold_found == Int32(0) and iter < Int32(block_scan_iterations):
418
+ bin_idx = Int32(num_bins - 1) - (iter * Int32(tb_size) + tid)
419
+ count = hist_smem[bin_idx]
420
+ chunk_inclusive = block_scan_inclusive_i32(
421
+ count,
422
+ lane,
423
+ warp_id,
424
+ warp_totals_smem,
425
+ warps_per_block,
426
+ )
427
+ running_count = running_count_smem.load()
428
+ prior_in_scan_slice = chunk_inclusive - count
429
+ remaining = Int32(topk) - running_count - prior_in_scan_slice
430
+ if count > Int32(0) and remaining > Int32(0) and remaining <= count:
431
+ threshold_bin_smem.store(bin_idx)
432
+ if count <= remaining or cutlass.const_expr(is_final_pass):
433
+ include_threshold_bin_smem.store(Int32(1))
434
+ threshold_found_smem.store(Int32(1))
435
+ # Barrier: every thread must finish reading running_count for this
436
+ # slice before tb_size-1 advances it, else a warp racing ahead to
437
+ # the store makes a lagging thread double-count the slice total
438
+ # (-> remaining too small -> threshold too high -> under-fill).
439
+ cute.arch.sync_threads()
440
+ if tid == Int32(tb_size - 1):
441
+ running_count_smem.store(running_count + chunk_inclusive)
442
+ cute.arch.sync_threads()
443
+
444
+ threshold_found = threshold_found_smem.load()
445
+ iter += Int32(1)
446
+
447
+ threshold = threshold_bin_smem.load()
448
+ should_include_threshold = include_threshold_bin_smem.load() != Int32(0)
449
+ for key_idx in cutlass.range_constexpr(keys_per_thread):
450
+ key = keys[key_idx]
451
+ if prefix_matches(key, prefix, prefix_bits):
452
+ bin_idx = Int32((key >> Uint64(shift)) & bin_mask)
453
+ selected = bin_idx > threshold
454
+ if should_include_threshold:
455
+ selected = selected or bin_idx == threshold
456
+ if selected:
457
+ dst = cute.arch.atomic_add(
458
+ committed_count_smem,
459
+ Int32(1),
460
+ sem="relaxed",
461
+ scope="cta",
462
+ )
463
+ if dst < Int32(topk):
464
+ output[dst] = recast_val(~Uint32(key), Int32)
465
+ cute.arch.sync_threads()
466
344
- @cute.kernel
345
- def kernel(
346
- self,
347
- input: cute.Tensor,
348
- out: cute.Tensor,
349
- ):
350
- row, _, _ = cute.arch.block_idx()
351
- tid, _, _ = cute.arch.thread_idx()
352
- input_row = input[row, None, None]
353
- output_row = out[row, None]
354
- keys = cute.make_rmem_tensor((self.keys_per_thread,), Uint64)
355
-
356
- smem = cutlass.utils.SmemAllocator()
357
- storage = smem.allocate(self.shared_storage, 8)
358
- committed_count_smem = storage.committed_count.data_ptr()
359
- prefix_smem = storage.prefix_s.data_ptr()
360
- for i in range(tid, self.topk, self.tb_size):
361
- output_row[i] = Int32(-1)
362
-
363
- for key_idx in cutlass.range_constexpr(self.keys_per_thread):
364
- col = tid + Int32(key_idx * self.tb_size)
365
- score = Float32(input_row[col, 0])
366
- token_id = Int32(input_row[col, 1])
367
- keys[key_idx] = self._stable_key(score, token_id)
368
-
369
- if tid == Int32(0):
370
- committed_count_smem.store(Int32(0))
371
- prefix_smem.store(Uint64(0))
372
- cute.arch.sync_threads()
373
-
374
- step = Int32(0)
375
- finished = Int32(0)
376
- while finished == Int32(0) and step < Int32(self.radix_passes - 1):
377
- finished = self._radix_pass(
378
- keys,
379
- output_row,
380
- storage,
381
- tid,
382
- step,
383
- self.radix_bits,
384
- False,
385
- )
386
- step += Int32(1)
387
-
388
- if finished == Int32(0):
389
- self._radix_pass(
390
- keys,
391
- output_row,
392
- storage,
393
- tid,
394
- Int32(self.radix_passes - 1),
395
- self.final_radix_bits,
396
- True,
467
+ pass_finished = include_threshold_bin_smem.load()
468
+ if tid == Int32(0) and pass_finished == Int32(0):
469
+ prefix_smem.store(prefix | (Uint64(threshold) << Uint64(shift)))
470
+ cute.arch.sync_threads()
471
+ return pass_finished
472
+
473
+ @cute.kernel
474
+ def device_kernel(input: cute.Tensor, out: cute.Tensor):
475
+ row, _, _ = cute.arch.block_idx()
476
+ tid, _, _ = cute.arch.thread_idx()
477
+ input_row = input[row, None, None]
478
+ output_row = out[row, None]
479
+ keys = cute.make_rmem_tensor((keys_per_thread,), Uint64)
480
+
481
+ smem = cutlass.utils.SmemAllocator()
482
+ storage = smem.allocate(shared_storage, 8)
483
+ committed_count_smem = storage.committed_count.data_ptr()
484
+ prefix_smem = storage.prefix_s.data_ptr()
485
+ for i in range(tid, topk, tb_size):
486
+ output_row[i] = Int32(-1)
487
+
488
+ for key_idx in cutlass.range_constexpr(keys_per_thread):
489
+ col = tid + Int32(key_idx * tb_size)
490
+ score = Float32(input_row[col, 0])
491
+ token_id = Int32(input_row[col, 1])
492
+ keys[key_idx] = stable_key(score, token_id)
493
+
494
+ if tid == Int32(0):
495
+ committed_count_smem.store(Int32(0))
496
+ prefix_smem.store(Uint64(0))
497
+ cute.arch.sync_threads()
498
+
499
+ step = Int32(0)
500
+ finished = Int32(0)
501
+ while finished == Int32(0) and step < Int32(radix_passes - 1):
502
+ finished = radix_pass(
503
+ keys,
504
+ output_row,
505
+ storage,
506
+ tid,
507
+ step,
508
+ radix_bits,
509
+ False,
510
+ )
511
+ step += Int32(1)
512
+
513
+ if finished == Int32(0):
514
+ radix_pass(
515
+ keys,
516
+ output_row,
517
+ storage,
518
+ tid,
519
+ Int32(radix_passes - 1),
520
+ final_radix_bits,
521
+ True,
522
+ )
523
+
524
+ @cute.jit
525
+ def host_entrypoint(
526
+ gathered: cute.Tensor,
527
+ out: cute.Tensor,
528
+ stream: CUstream,
529
+ ):
530
+ grid = (gathered.shape[0], 1, 1)
531
+ device_kernel(gathered, out).launch(
532
+ grid=grid,
533
+ block=(tb_size, 1, 1),
534
+ stream=stream,
535
)
536
399
- @cache
400
- @staticmethod
401
- def compile(topk: int, num_candidates: int):
402
- num_rows = cute.sym_int()
537
+ return host_entrypoint
538
539
+ def dispatch( # type: ignore[override]
540
+ self,
541
+ **compile_key_fields: int,
542
+ ) -> CompileKey:
543
+ return self.CompileKey(**compile_key_fields)
544
+
545
+ def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
546
+ dcp_world_size = vllm_config.parallel_config.decode_context_parallel_size
547
+ if dcp_world_size <= 1:
548
+ return []
549
+ topk = vllm_config.model_config.hf_config.index_topk
550
+ if topk <= 0:
551
+ return []
552
+ return self._trace_dispatch(self.dispatch)(
553
+ topk=topk,
554
+ num_candidates=topk * dcp_world_size,
555
+ )
556
+
557
+ def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
558
+ num_rows = cute.sym_int()
559
gathered = cute.runtime.make_fake_tensor(
560
Float32,
406
- (num_rows, num_candidates, 2),
561
+ (num_rows, compile_key.num_candidates, 2),
562
stride=(cute.sym_int64(divisibility=2), 2, 1),
563
assumed_align=8,
564
)
410
- out = make_fake_tensor(Int32, (num_rows, topk), divisibility=1)
411
-
412
- kernel = StableTopKFromGatheredCandidatesKernel(topk, num_candidates)
413
- stream = cute.runtime.make_fake_stream(use_tvm_ffi_env_stream=True)
414
- return cute.compile(
415
- kernel,
416
- gathered,
417
- out,
418
- stream,
419
- options="--enable-tvm-ffi",
565
+ out = make_fake_tensor(
566
+ Int32,
567
+ (num_rows, compile_key.topk),
568
+ divisibility=1,
569
+ )
570
+ return gathered, out
571
+
572
+ @cutedsl_kernel_launcher
573
+ def __call__(
574
+ self,
575
+ gathered: torch.Tensor,
576
+ out: torch.Tensor,
577
+ *,
578
+ topk: int,
579
+ ) -> CuTeDSLLaunchSpec[CompileKey]:
580
+ compile_key = self.dispatch(topk=topk, num_candidates=gathered.shape[1])
581
+ return (
582
+ compile_key,
583
+ (gathered, out),
584
+ {
585
+ "gathered_shape": tuple(gathered.shape),
586
+ "out_shape": tuple(out.shape),
587
+ "topk": topk,
588
+ },
589
)
590
+
591
+
592
+_PACK_DCP_TOPK_CANDIDATES_KERNEL = PackDCPTopkCandidatesKernel()
593
+_STABLE_TOPK_FROM_GATHERED_CANDIDATES_KERNEL = StableTopKFromGatheredCandidatesKernel()
vllm/model_executor/layers/sparse_attn_indexer.py
+24
-1
@@ -768,7 +768,8 @@ class SparseAttnIndexer(CustomOp):
768
# DCP scalars are constant for the run; resolve them here (config is set
769
# during model construction) and pass them into the custom op, rather
770
# than threading them through per-step metadata.
771
- parallel_config = get_current_vllm_config().parallel_config
771
+ vllm_config = get_current_vllm_config()
772
+ parallel_config = vllm_config.parallel_config
773
self._parallel_config = parallel_config
774
self.dcp_world_size = parallel_config.decode_context_parallel_size
775
self.dcp_rank = get_dcp_group().rank_in_group if self.dcp_world_size > 1 else 0
@@ -780,6 +781,28 @@ class SparseAttnIndexer(CustomOp):
781
"the current vLLM environment."
782
)
783
784
+ if vllm_config.kernel_config.enable_jit_warmup:
785
+ from vllm.v1.attention.ops.common import (
786
+ _PACK_SEQ_TRITON_KERNEL,
787
+ _UNPACK_SEQ_TRITON_KERNEL,
788
+ )
789
+
790
+ pack_dtype = torch.uint8 if use_fp4_cache else current_platform.fp8_dtype()
791
+ _PACK_SEQ_TRITON_KERNEL.register_warmup(
792
+ dtype=pack_dtype,
793
+ pad_value=0 if use_fp4_cache else -float("inf"),
794
+ )
795
+ _UNPACK_SEQ_TRITON_KERNEL.register_warmup()
796
+
797
+ if self.dcp_world_size > 1 and current_platform.is_cuda() and has_cutedsl():
798
+ from vllm.model_executor.kernels.attention.dsa.dcp_indexer_cutedsl import ( # noqa: E501
799
+ _PACK_DCP_TOPK_CANDIDATES_KERNEL,
800
+ _STABLE_TOPK_FROM_GATHERED_CANDIDATES_KERNEL,
801
+ )
802
+
803
+ _PACK_DCP_TOPK_CANDIDATES_KERNEL.register_warmup()
804
+ _STABLE_TOPK_FROM_GATHERED_CANDIDATES_KERNEL.register_warmup()
805
+
806
@property
807
def cp_kv_cache_interleave_size(self) -> int:
808
"""With PD+DCP, the real value isn't known until block_size is finalized,
vllm/model_executor/warmup/jit_warmup.py
+37
-7
@@ -670,6 +670,34 @@ class VllmJitKernel(Generic[CompileKeyT], ABC):
670
self.compile(compile_key)
671
672
673
+def _same_value(left: Any, right: Any) -> bool:
674
+ """True if two registration values are the same object or compare equal.
675
+
676
+ Identity is checked first so shared singletons (e.g. ``vllm_config``, torch
677
+ dtypes) short-circuit before any potentially deep or non-boolean ``__eq__``.
678
+ """
679
+ if left is right:
680
+ return True
681
+ try:
682
+ return bool(left == right)
683
+ except (TypeError, ValueError, RuntimeError):
684
+ return False
685
+
686
+
687
+def _same_registration(
688
+ left: tuple[tuple[Any, ...], dict[str, Any]],
689
+ right: tuple[tuple[Any, ...], dict[str, Any]],
690
+) -> bool:
691
+ """True if two ``(args, kwargs)`` registrations are equivalent."""
692
+ left_args, left_kwargs = left
693
+ right_args, right_kwargs = right
694
+ if len(left_args) != len(right_args) or left_kwargs.keys() != right_kwargs.keys():
695
+ return False
696
+ return all(
697
+ _same_value(x, y) for x, y in zip(left_args, right_args, strict=True)
698
+ ) and all(_same_value(left_kwargs[k], right_kwargs[k]) for k in left_kwargs)
699
+
700
+
701
class JitWarmupRegistry:
702
"""Collect and compile JIT kernels selected during runner setup."""
703
@@ -713,13 +741,15 @@ class JitWarmupRegistry:
741
kwargs: dict[str, Any],
742
) -> None:
743
registrations = self._registrations.setdefault(kernel, [])
716
- if (
717
- not args
718
- and not kwargs
719
- and any(
720
- not registered_args and not registered_kwargs
721
- for registered_args, registered_kwargs in registrations
722
- )
744
+ # Every layer of a deep model appends an identical (args, kwargs) here
745
+ # (e.g. a 61-layer DSA model registers the same pack (dtype, pad_value)
746
+ # 61 times); each expands to the same compile keys, so tracing
747
+ # get_warmup_keys once per distinct registration is sufficient. Dedup on
748
+ # identity-or-equality -- identity short-circuits shared singletons like
749
+ # vllm_config before any deep __eq__.
750
+ if any(
751
+ _same_registration(registered, (args, kwargs))
752
+ for registered in registrations
753
):
754
return
755
registrations.append((args, kwargs))
vllm/model_executor/warmup/jit_warmup_cutedsl_helper.py
new
+80
@@ -0,0 +1,80 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from abc import abstractmethod
5
+from collections.abc import Callable, Mapping
6
+from functools import wraps
7
+from typing import Any, ClassVar, Generic, TypeAlias, TypeVar
8
+
9
+from vllm.model_executor.warmup.jit_warmup import VllmJitKernel
10
+
11
+DEFAULT_CUTEDSL_COMPILE_OPTIONS = "--enable-tvm-ffi"
12
+
13
+CompileKeyT = TypeVar("CompileKeyT")
14
+CuTeDSLLaunchSpec: TypeAlias = tuple[
15
+ CompileKeyT,
16
+ tuple[Any, ...],
17
+ Mapping[str, Any] | None,
18
+]
19
+
20
+
21
+def cutedsl_fake_stream(*, use_tvm_ffi_env_stream: bool = True) -> Any:
22
+ import cutlass.cute as cute
23
+
24
+ return cute.runtime.make_fake_stream(use_tvm_ffi_env_stream=use_tvm_ffi_env_stream)
25
+
26
+
27
+def compile_cutedsl(
28
+ entry: Callable[..., Any],
29
+ *args: Any,
30
+ options: str = DEFAULT_CUTEDSL_COMPILE_OPTIONS,
31
+ use_tvm_ffi_env_stream: bool = True,
32
+) -> Any:
33
+ import cutlass.cute as cute
34
+
35
+ return cute.compile(
36
+ entry,
37
+ *args,
38
+ cutedsl_fake_stream(use_tvm_ffi_env_stream=use_tvm_ffi_env_stream),
39
+ options=options,
40
+ )
41
+
42
+
43
+class VllmCuTeDSLJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
44
+ """CuTeDSL owner whose compiled executor is shared by warmup and runtime."""
45
+
46
+ kernel: ClassVar[Any]
47
+
48
+ @abstractmethod
49
+ def warmup_inputs(self, compile_key: CompileKeyT) -> tuple[Any, ...]:
50
+ """Return fake arguments that compile one executor specialization."""
51
+ raise NotImplementedError
52
+
53
+ def compile(self, compile_key: CompileKeyT) -> None:
54
+ if compile_key in self._compiled_cache:
55
+ return
56
+ self._compiled_cache[compile_key] = compile_cutedsl(
57
+ self.kernel(compile_key),
58
+ *self.warmup_inputs(compile_key),
59
+ )
60
+
61
+
62
+def cutedsl_kernel_launcher(
63
+ call_fn: Callable[..., CuTeDSLLaunchSpec[CompileKeyT]],
64
+) -> Callable[..., Any]:
65
+ """Invoke a cached CuTeDSL executor from a declarative ``__call__``."""
66
+
67
+ @wraps(call_fn)
68
+ def wrapper(
69
+ self: VllmCuTeDSLJitKernel[CompileKeyT],
70
+ *args: Any,
71
+ **kwargs: Any,
72
+ ) -> Any:
73
+ compile_key, launch_args, runtime_context = call_fn(self, *args, **kwargs)
74
+ executor = self._get_or_compile(
75
+ compile_key,
76
+ runtime_context=runtime_context,
77
+ )
78
+ return executor(*launch_args)
79
+
80
+ return wrapper
vllm/model_executor/warmup/jit_warmup_triton_helper.py
+20
-4
@@ -106,8 +106,16 @@ class VllmTritonJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
106
self._warming = False
107
108
@cached_property
109
- def _kernel_param_names(self) -> frozenset[str]:
110
- return frozenset(self.kernel.arg_names)
109
+ def _kernel_arg_names(self) -> tuple[str, ...]:
110
+ arg_names = getattr(self.kernel, "arg_names", None)
111
+ if arg_names is not None:
112
+ return tuple(arg_names)
113
+ wrapped = getattr(self.kernel, "func", None)
114
+ if wrapped is not None:
115
+ return tuple(inspect.signature(wrapped).parameters)
116
+ raise TypeError(
117
+ f"Cannot inspect kernel parameters for {type(self.kernel).__name__}"
118
+ )
119
120
def launch(
121
self,
@@ -116,14 +124,22 @@ class VllmTritonJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
124
/,
125
**kwargs: Any,
126
) -> Any:
127
+ runtime_launcher = kwargs.pop("_runtime_launcher", None)
128
+ runtime_launcher_arg_count = kwargs.pop("_runtime_launcher_arg_count", 0)
129
for name, value in inputs.items():
120
- target = name if name in self._kernel_param_names else f"{name}_ptr"
121
- if target in self._kernel_param_names and target not in kwargs:
130
+ target = name if name in self._kernel_arg_names else f"{name}_ptr"
131
+ if target in self._kernel_arg_names and target not in kwargs:
132
kwargs[target] = value
133
if self._warming:
134
warmup = getattr(self.kernel, "warmup", None)
135
assert warmup is not None
136
return warmup(grid=(1,), **kwargs)
137
+ if runtime_launcher is not None:
138
+ regular_args = [
139
+ kwargs.pop(name)
140
+ for name in self._kernel_arg_names[:runtime_launcher_arg_count]
141
+ ]
142
+ return runtime_launcher(self.kernel, grid, *regular_args, **kwargs)
143
return self.kernel[grid](**kwargs)
144
145
vllm/v1/attention/ops/common.py
+271
-115
@@ -1,63 +1,157 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from dataclasses import dataclass
4
+from typing import Any
5
+
6
import torch
7
8
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
9
+ LaunchSpec,
10
+ TritonWarmupTensor,
11
+ VllmTritonJitKernel,
12
+ kernel_launcher,
13
+)
14
from vllm.triton_utils import tl, triton
15
16
8
-@triton.jit
9
-def _pack_seq_kernel(
10
- x_ptr, # [N, D]
11
- out_ptr, # [B, Lmax, D]
12
- lengths_ptr, # *i32, [B]
13
- N: tl.constexpr,
14
- D: tl.constexpr,
15
- Lmax: tl.constexpr,
16
- PAD_VALUE: tl.constexpr,
17
- PAD_IS_UINT8: tl.constexpr,
18
- BLOCK_T: tl.constexpr, # timesteps per program
19
- BLOCK_D: tl.constexpr, # features per program
20
-):
21
- pid_b = tl.program_id(0) # batch id
22
- pid_t = tl.program_id(1) # block over time dimension
23
- pid_d = tl.program_id(2) # block over feature dimension
24
- off_t = pid_t * BLOCK_T + tl.arange(0, BLOCK_T) # [BLOCK_T]
25
- off_d = pid_d * BLOCK_D + tl.arange(0, BLOCK_D) # [BLOCK_D]
26
-
27
- # Compute start index and sequence length from cumulative lengths
28
- in_start = 0
29
- for i in range(pid_b):
30
- in_start += tl.load(lengths_ptr + i)
31
- seq_len = tl.load(lengths_ptr + pid_b)
32
-
33
- # valid time positions for this block
34
- t_mask = off_t < Lmax
35
-
36
- # compute input row indices for valid (b, t)
37
- in_row = in_start + off_t
38
- valid_row = (off_t < seq_len) & t_mask
39
-
40
- # Pointers
41
- # x_ptr: row-major [N, D]
42
- x_row_ptr = x_ptr + in_row[:, None] * D + off_d[None, :]
43
-
44
- # out_ptr: row-major [B, Lmax, D]
45
- out_row_ptr = out_ptr + (pid_b * Lmax + off_t)[:, None] * D + off_d[None, :]
46
-
47
- # Initialize with PAD. PAD_IS_UINT8 selects the pad tensor's dtype so
48
- # integer-typed outputs (e.g. MXFP4 packed nibbles, ue8m0 scale bytes)
49
- # get an exact-byte pad rather than going through an fp32→uint8 cast
50
- # that's implementation-defined outside of value 0.
51
- d_mask = off_d[None, :] < D
52
- if PAD_IS_UINT8:
53
- pad_vals = tl.full([BLOCK_T, BLOCK_D], PAD_VALUE, tl.uint8)
54
- else:
55
- pad_vals = tl.full([BLOCK_T, BLOCK_D], PAD_VALUE, tl.float32)
56
- tl.store(out_row_ptr, pad_vals, mask=t_mask[:, None] & d_mask)
17
+class PackSeqTritonKernel(VllmTritonJitKernel["PackSeqTritonKernel.CompileKey"]):
18
+ @dataclass(frozen=True)
19
+ class CompileKey:
20
+ dtype: torch.dtype
21
+ pad_value: float | int
22
+ pad_is_uint8: bool
23
+ block_t: int
24
+ block_d: int
25
+
26
+ # N is unused; D and Lmax only affect runtime addressing and masks. The
27
+ # compile-time tile shape remains controlled by BLOCK_T and BLOCK_D.
28
+ @staticmethod
29
+ @triton.jit(do_not_specialize=["N", "D", "Lmax"])
30
+ def kernel(
31
+ x_ptr, # [N, D]
32
+ out_ptr, # [B, Lmax, D]
33
+ lengths_ptr, # *i32, [B]
34
+ N,
35
+ D,
36
+ Lmax,
37
+ PAD_VALUE: tl.constexpr,
38
+ PAD_IS_UINT8: tl.constexpr,
39
+ BLOCK_T: tl.constexpr, # timesteps per program
40
+ BLOCK_D: tl.constexpr, # features per program
41
+ ):
42
+ pid_b = tl.program_id(0) # batch id
43
+ pid_t = tl.program_id(1) # block over time dimension
44
+ pid_d = tl.program_id(2) # block over feature dimension
45
+ off_t = pid_t * BLOCK_T + tl.arange(0, BLOCK_T) # [BLOCK_T]
46
+ off_d = pid_d * BLOCK_D + tl.arange(0, BLOCK_D) # [BLOCK_D]
47
+
48
+ # Compute start index and sequence length from cumulative lengths
49
+ in_start = 0
50
+ for i in range(pid_b):
51
+ in_start += tl.load(lengths_ptr + i)
52
+ seq_len = tl.load(lengths_ptr + pid_b)
53
+
54
+ # valid time positions for this block
55
+ t_mask = off_t < Lmax
56
+
57
+ # compute input row indices for valid (b, t)
58
+ in_row = in_start + off_t
59
+ valid_row = (off_t < seq_len) & t_mask
60
+
61
+ # Pointers
62
+ # x_ptr: row-major [N, D]
63
+ x_row_ptr = x_ptr + in_row[:, None] * D + off_d[None, :]
64
+
65
+ # out_ptr: row-major [B, Lmax, D]
66
+ out_row_ptr = out_ptr + (pid_b * Lmax + off_t)[:, None] * D + off_d[None, :]
67
+
68
+ # Initialize with PAD. PAD_IS_UINT8 selects the pad tensor's dtype so
69
+ # integer-typed outputs (e.g. MXFP4 packed nibbles, ue8m0 scale bytes)
70
+ # get an exact-byte pad rather than going through an fp32→uint8 cast
71
+ # that's implementation-defined outside of value 0.
72
+ d_mask = off_d[None, :] < D
73
+ if PAD_IS_UINT8:
74
+ pad_vals = tl.full([BLOCK_T, BLOCK_D], PAD_VALUE, tl.uint8)
75
+ else:
76
+ pad_vals = tl.full([BLOCK_T, BLOCK_D], PAD_VALUE, tl.float32)
77
+ tl.store(out_row_ptr, pad_vals, mask=t_mask[:, None] & d_mask)
78
+
79
+ # Load & write only where within seq_len
80
+ x_vals = tl.load(x_row_ptr, mask=valid_row[:, None] & d_mask)
81
+ tl.store(out_row_ptr, x_vals, mask=valid_row[:, None] & d_mask)
82
+
83
+ def dispatch( # type: ignore[override]
84
+ self,
85
+ *,
86
+ dtype: torch.dtype,
87
+ pad_value: float | int,
88
+ **compile_key_fields: int,
89
+ ) -> CompileKey:
90
+ is_uint8 = dtype == torch.uint8
91
+ return self.CompileKey(
92
+ **compile_key_fields,
93
+ dtype=dtype,
94
+ pad_value=int(pad_value) if is_uint8 else float(pad_value),
95
+ pad_is_uint8=is_uint8,
96
+ )
97
58
- # Load & write only where within seq_len
59
- x_vals = tl.load(x_row_ptr, mask=valid_row[:, None] & d_mask)
60
- tl.store(out_row_ptr, x_vals, mask=valid_row[:, None] & d_mask)
98
+ def get_warmup_keys(
99
+ self,
100
+ *,
101
+ dtype: torch.dtype,
102
+ pad_value: float | int,
103
+ ) -> list[CompileKey]:
104
+ return self._trace_dispatch(self.dispatch)(
105
+ dtype=dtype,
106
+ pad_value=pad_value,
107
+ block_t=64,
108
+ block_d=64,
109
+ )
110
+
111
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
112
+ return dict(
113
+ x_reshaped=TritonWarmupTensor(compile_key.dtype),
114
+ out=TritonWarmupTensor(compile_key.dtype),
115
+ lengths=TritonWarmupTensor(torch.int32),
116
+ N=1,
117
+ D=1,
118
+ Lmax=1,
119
+ pad_value=compile_key.pad_value,
120
+ pad_is_uint8=compile_key.pad_is_uint8,
121
+ block_t=compile_key.block_t,
122
+ block_d=compile_key.block_d,
123
+ )
124
+
125
+ @kernel_launcher
126
+ def __call__(
127
+ self,
128
+ x_reshaped: torch.Tensor,
129
+ out: torch.Tensor,
130
+ lengths: torch.Tensor,
131
+ *,
132
+ N: int,
133
+ D: int,
134
+ Lmax: int,
135
+ pad_value: float | int,
136
+ pad_is_uint8: bool,
137
+ block_t: int,
138
+ block_d: int,
139
+ ) -> LaunchSpec:
140
+ grid = (
141
+ lengths.shape[0],
142
+ triton.cdiv(Lmax, block_t),
143
+ triton.cdiv(D, block_d),
144
+ )
145
+ return grid, dict(
146
+ x_ptr=x_reshaped,
147
+ lengths_ptr=lengths,
148
+ PAD_VALUE=pad_value,
149
+ PAD_IS_UINT8=pad_is_uint8,
150
+ BLOCK_T=block_t,
151
+ BLOCK_D=block_d,
152
+ num_warps=4,
153
+ num_stages=2,
154
+ )
155
156
157
def pack_seq_triton(
@@ -109,20 +203,18 @@ def pack_seq_triton(
203
204
out = torch.empty((B, Lmax, D), device=x.device, dtype=x.dtype)
205
112
- grid = (B, triton.cdiv(Lmax, block_t), triton.cdiv(D, block_d))
113
- _pack_seq_kernel[grid](
206
+ lengths = lengths.int()
207
+ _PACK_SEQ_TRITON_KERNEL(
208
x_reshaped,
209
out,
116
- lengths.int(),
117
- N,
118
- D,
119
- Lmax,
120
- PAD_VALUE=pad_constexpr,
121
- PAD_IS_UINT8=is_uint8,
122
- BLOCK_T=block_t,
123
- BLOCK_D=block_d,
124
- num_warps=4,
125
- num_stages=2,
210
+ lengths,
211
+ N=N,
212
+ D=D,
213
+ Lmax=Lmax,
214
+ pad_value=pad_constexpr,
215
+ pad_is_uint8=is_uint8,
216
+ block_t=block_t,
217
+ block_d=block_d,
218
)
219
220
if len(original_shape) > 2:
@@ -131,47 +223,109 @@ def pack_seq_triton(
223
return out
224
225
134
-@triton.jit
135
-def _unpack_seq_triton_kernel(
136
- packed_ptr, # [B, Lmax, D]
137
- out_ptr, # [N, D]
138
- lengths_ptr, # *i32, [B]
139
- B: tl.constexpr,
140
- Lmax: tl.constexpr,
141
- D: tl.constexpr,
142
- BLOCK_T: tl.constexpr, # timesteps per program
143
- BLOCK_D: tl.constexpr, # features per program
144
-):
145
- pid_b = tl.program_id(0) # batch id
146
- pid_t = tl.program_id(1) # block over time dimension
147
- pid_d = tl.program_id(2) # block over feature dimension
148
- off_t = pid_t * BLOCK_T + tl.arange(0, BLOCK_T) # [BLOCK_T]
149
- off_d = pid_d * BLOCK_D + tl.arange(0, BLOCK_D) # [BLOCK_D]
150
-
151
- # bounds: compute start from cumulative lengths
152
- in_start = 0
153
- for i in range(pid_b):
154
- in_start += tl.load(lengths_ptr + i)
155
- seq_len = tl.load(lengths_ptr + pid_b)
156
-
157
- # valid time positions for this block
158
- t_mask = off_t < Lmax
159
- valid_row = (off_t < seq_len) & t_mask
160
-
161
- # compute output row indices for valid (b, t)
162
- out_row = in_start + off_t
163
-
164
- # Pointers
165
- # packed_ptr: row-major [B, Lmax, D]
166
- packed_row_ptr = packed_ptr + (pid_b * Lmax + off_t)[:, None] * D + off_d[None, :]
167
-
168
- # out_ptr: row-major [N, D]
169
- out_row_ptr = out_ptr + out_row[:, None] * D + off_d[None, :]
170
-
171
- # Load from packed tensor and store to output
172
- d_mask = off_d[None, :] < D
173
- packed_vals = tl.load(packed_row_ptr, mask=valid_row[:, None] & d_mask)
174
- tl.store(out_row_ptr, packed_vals, mask=valid_row[:, None] & d_mask)
226
+class UnpackSeqTritonKernel(VllmTritonJitKernel["UnpackSeqTritonKernel.CompileKey"]):
227
+ @dataclass(frozen=True)
228
+ class CompileKey:
229
+ dtype: torch.dtype
230
+ block_t: int
231
+ block_d: int
232
+
233
+ # B, Lmax, and D only affect runtime addressing and masks. The compile-time
234
+ # tile shape remains controlled by BLOCK_T and BLOCK_D.
235
+ @staticmethod
236
+ @triton.jit(do_not_specialize=["B", "Lmax", "D"])
237
+ def kernel(
238
+ packed_ptr, # [B, Lmax, D]
239
+ out_ptr, # [N, D]
240
+ lengths_ptr, # *i32, [B]
241
+ B,
242
+ Lmax,
243
+ D,
244
+ BLOCK_T: tl.constexpr, # timesteps per program
245
+ BLOCK_D: tl.constexpr, # features per program
246
+ ):
247
+ pid_b = tl.program_id(0) # batch id
248
+ pid_t = tl.program_id(1) # block over time dimension
249
+ pid_d = tl.program_id(2) # block over feature dimension
250
+ off_t = pid_t * BLOCK_T + tl.arange(0, BLOCK_T) # [BLOCK_T]
251
+ off_d = pid_d * BLOCK_D + tl.arange(0, BLOCK_D) # [BLOCK_D]
252
+
253
+ # bounds: compute start from cumulative lengths
254
+ in_start = 0
255
+ for i in range(pid_b):
256
+ in_start += tl.load(lengths_ptr + i)
257
+ seq_len = tl.load(lengths_ptr + pid_b)
258
+
259
+ # valid time positions for this block
260
+ t_mask = off_t < Lmax
261
+ valid_row = (off_t < seq_len) & t_mask
262
+
263
+ # compute output row indices for valid (b, t)
264
+ out_row = in_start + off_t
265
+
266
+ # Pointers
267
+ # packed_ptr: row-major [B, Lmax, D]
268
+ packed_row_ptr = (
269
+ packed_ptr + (pid_b * Lmax + off_t)[:, None] * D + off_d[None, :]
270
+ )
271
+
272
+ # out_ptr: row-major [N, D]
273
+ out_row_ptr = out_ptr + out_row[:, None] * D + off_d[None, :]
274
+
275
+ # Load from packed tensor and store to output
276
+ d_mask = off_d[None, :] < D
277
+ packed_vals = tl.load(packed_row_ptr, mask=valid_row[:, None] & d_mask)
278
+ tl.store(out_row_ptr, packed_vals, mask=valid_row[:, None] & d_mask)
279
+
280
+ def dispatch( # type: ignore[override]
281
+ self,
282
+ *,
283
+ dtype: torch.dtype,
284
+ **compile_key_fields: int,
285
+ ) -> CompileKey:
286
+ return self.CompileKey(**compile_key_fields, dtype=dtype)
287
+
288
+ def get_warmup_keys(self) -> list[CompileKey]:
289
+ return self._trace_dispatch(self.dispatch)(
290
+ dtype=torch.int32,
291
+ block_t=64,
292
+ block_d=64,
293
+ )
294
+
295
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
296
+ return dict(
297
+ packed_reshaped=TritonWarmupTensor(compile_key.dtype),
298
+ out=TritonWarmupTensor(compile_key.dtype),
299
+ lengths=TritonWarmupTensor(torch.int32),
300
+ B=1,
301
+ Lmax=1,
302
+ D=1,
303
+ block_t=compile_key.block_t,
304
+ block_d=compile_key.block_d,
305
+ )
306
+
307
+ @kernel_launcher
308
+ def __call__(
309
+ self,
310
+ packed_reshaped: torch.Tensor,
311
+ out: torch.Tensor,
312
+ lengths: torch.Tensor,
313
+ *,
314
+ B: int,
315
+ Lmax: int,
316
+ D: int,
317
+ block_t: int,
318
+ block_d: int,
319
+ ) -> LaunchSpec:
320
+ grid = (B, triton.cdiv(Lmax, block_t), triton.cdiv(D, block_d))
321
+ return grid, dict(
322
+ packed_ptr=packed_reshaped,
323
+ lengths_ptr=lengths,
324
+ BLOCK_T=block_t,
325
+ BLOCK_D=block_d,
326
+ num_warps=4,
327
+ num_stages=2,
328
+ )
329
330
331
def unpack_seq_triton(
@@ -209,18 +363,16 @@ def unpack_seq_triton(
363
364
out = torch.empty((N, D), device=packed_tensor.device, dtype=packed_tensor.dtype)
365
212
- grid = (B, triton.cdiv(Lmax, block_t), triton.cdiv(D, block_d))
213
- _unpack_seq_triton_kernel[grid](
366
+ lengths = lengths.int()
367
+ _UNPACK_SEQ_TRITON_KERNEL(
368
packed_reshaped,
369
out,
216
- lengths.int(),
217
- B,
218
- Lmax,
219
- D,
220
- BLOCK_T=block_t,
221
- BLOCK_D=block_d,
222
- num_warps=4,
223
- num_stages=2,
370
+ lengths,
371
+ B=B,
372
+ Lmax=Lmax,
373
+ D=D,
374
+ block_t=block_t,
375
+ block_d=block_d,
376
)
377
378
# Reshape output back to original dimensions (except first dimension)
@@ -229,3 +381,7 @@ def unpack_seq_triton(
381
out = out.reshape(output_shape)
382
383
return out
384
+
385
+
386
+_PACK_SEQ_TRITON_KERNEL = PackSeqTritonKernel()
387
+_UNPACK_SEQ_TRITON_KERNEL = UnpackSeqTritonKernel()
vllm/v1/attention/ops/dcp.py
+271
-99
@@ -6,7 +6,8 @@ from __future__ import annotations
6
7
import functools
8
from collections.abc import Callable
9
-from typing import TYPE_CHECKING, Protocol
9
+from dataclasses import dataclass
10
+from typing import TYPE_CHECKING, Any, Protocol
11
12
import torch
13
import torch.distributed as dist
@@ -15,6 +16,16 @@ import vllm.envs as envs
16
from vllm.config import VllmConfig
17
from vllm.distributed import get_dcp_group
18
from vllm.logger import init_logger
19
+from vllm.model_executor.warmup.jit_warmup import (
20
+ WarmupIntRange,
21
+)
22
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
23
+ LaunchSpec,
24
+ TritonWarmupTensor,
25
+ VllmTritonJitKernel,
26
+ kernel_launcher,
27
+ triton_scalar_specialization_rep,
28
+)
29
from vllm.triton_utils import tl, triton
30
from vllm.v1.attention.ops.cp_common import (
31
DirectCPWorkspace,
@@ -65,93 +76,254 @@ def mask_dcp_empty_shards_(
76
# AG + RS/AR implementation
77
78
68
-@triton.jit
69
-def _correct_attn_cp_out_kernel(
70
- outputs_ptr,
71
- new_output_ptr,
72
- lses_ptr,
73
- vlse_ptr,
74
- outputs_stride_B,
75
- outputs_stride_H,
76
- outputs_stride_D,
77
- lses_stride_N,
78
- lses_stride_B,
79
- lses_stride_H,
80
- lse_idx,
81
- HEAD_DIM: tl.constexpr,
82
- N_ROUNDED: tl.constexpr,
83
- IS_BASE_E: tl.constexpr,
84
-):
85
- """
86
- Apply the all-gathered lses to correct each local rank's attention
87
- output. we still need perform a cross-rank reduction to obtain the
88
- final attention output.
79
+class CorrectAttnCPOutKernel(VllmTritonJitKernel["CorrectAttnCPOutKernel.CompileKey"]):
80
+ @dataclass(frozen=True)
81
+ class CompileKey:
82
+ output_dtype: torch.dtype
83
+ lse_dtype: torch.dtype
84
+ outputs_stride_b: int
85
+ outputs_stride_h: int
86
+ outputs_stride_d: int
87
+ lses_stride_n: int
88
+ lses_stride_b: int
89
+ lses_stride_h: int
90
+ lse_idx: int
91
+ head_dim: int
92
+ n_rounded: int
93
+ is_base_e: bool
94
+
95
+ @staticmethod
96
+ @triton.jit
97
+ def kernel(
98
+ outputs_ptr,
99
+ new_output_ptr,
100
+ lses_ptr,
101
+ vlse_ptr,
102
+ outputs_stride_B,
103
+ outputs_stride_H,
104
+ outputs_stride_D,
105
+ lses_stride_N,
106
+ lses_stride_B,
107
+ lses_stride_H,
108
+ lse_idx,
109
+ HEAD_DIM: tl.constexpr,
110
+ N_ROUNDED: tl.constexpr,
111
+ IS_BASE_E: tl.constexpr,
112
+ ):
113
+ """
114
+ Apply the all-gathered lses to correct each local rank's attention
115
+ output. we still need perform a cross-rank reduction to obtain the
116
+ final attention output.
117
+
118
+ Args:
119
+ outputs_ptr (triton.PointerType):
120
+ Pointer to input tensor of shape [ B, H, D ]
121
+ lses_ptr (triton.PointerType):
122
+ Pointer to input tensor of shape [ N, B, H ]
123
+ new_output_ptr (triton.PointerType):
124
+ Pointer to output tensor of shape [ B, H, D ]
125
+ vlse_ptr (triton.PointerType):
126
+ Pointer to output tensor of shape [ B, H ]
127
+ """
128
+ batch_idx = tl.program_id(axis=0).to(tl.int64)
129
+ head_idx = tl.program_id(axis=1).to(tl.int64)
130
+ d_offsets = tl.arange(0, HEAD_DIM)
131
+ num_n_offsets = tl.arange(0, N_ROUNDED)
132
+
133
+ # shape = [N]
134
+ lse_offsets = (
135
+ num_n_offsets * lses_stride_N
136
+ + batch_idx * lses_stride_B
137
+ + head_idx * lses_stride_H
138
+ )
139
90
- Args:
91
- outputs_ptr (triton.PointerType):
92
- Pointer to input tensor of shape [ B, H, D ]
93
- lses_ptr (triton.PointerType):
94
- Pointer to input tensor of shape [ N, B, H ]
95
- new_output_ptr (triton.PointerType):
96
- Pointer to output tensor of shape [ B, H, D ]
97
- vlse_ptr (triton.PointerType):
98
- Pointer to output tensor of shape [ B, H ]
99
- """
100
- batch_idx = tl.program_id(axis=0).to(tl.int64)
101
- head_idx = tl.program_id(axis=1).to(tl.int64)
102
- d_offsets = tl.arange(0, HEAD_DIM)
103
- num_n_offsets = tl.arange(0, N_ROUNDED)
140
+ # calc final lse
141
+ lse = tl.load(lses_ptr + lse_offsets).to(tl.float32)
142
+ lse = tl.where((lse != lse) | (lse == float("inf")), -float("inf"), lse)
143
+ lse_max = tl.max(lse, axis=0)
144
+ lse_max = tl.where(lse_max == -float("inf"), 0, lse_max)
145
+ lse -= lse_max
146
+ if IS_BASE_E:
147
+ lse_exp = tl.exp(lse)
148
+ lse_acc = tl.sum(lse_exp, axis=0)
149
+ lse = tl.log(lse_acc)
150
+ else:
151
+ lse_exp = tl.exp2(lse)
152
+ lse_acc = tl.sum(lse_exp, axis=0)
153
+ lse = tl.log2(lse_acc)
154
+ lse += lse_max
155
+
156
+ lse_offsets = batch_idx * lses_stride_B + head_idx * lses_stride_H
157
+ tl.store(vlse_ptr + lse_offsets, lse)
158
+
159
+ # shape = [D]
160
+ output_offsets = (
161
+ batch_idx * outputs_stride_B
162
+ + head_idx * outputs_stride_H
163
+ + d_offsets * outputs_stride_D
164
+ )
165
105
- # shape = [N]
106
- lse_offsets = (
107
- num_n_offsets * lses_stride_N
108
- + batch_idx * lses_stride_B
109
- + head_idx * lses_stride_H
110
- )
166
+ # correct output
167
+ lse_offset = (
168
+ lse_idx * lses_stride_N
169
+ + batch_idx * lses_stride_B
170
+ + head_idx * lses_stride_H
171
+ )
172
+ lse_tmp = tl.load(lses_ptr + lse_offset).to(tl.float32)
173
+ lse_finally = lse_tmp - lse
174
+ lse_finally = tl.where(
175
+ (lse_finally != lse_finally) | (lse_finally == float("inf")),
176
+ -float("inf"),
177
+ lse_finally,
178
+ )
179
+ factor = tl.exp(lse_finally) if IS_BASE_E else tl.exp2(lse_finally)
180
+ output = tl.load(outputs_ptr + output_offsets)
181
+ output = output * factor
182
+ output = tl.where(factor == 0.0, 0.0, output)
183
112
- # calc final lse
113
- lse = tl.load(lses_ptr + lse_offsets).to(tl.float32)
114
- lse = tl.where((lse != lse) | (lse == float("inf")), -float("inf"), lse)
115
- lse_max = tl.max(lse, axis=0)
116
- lse_max = tl.where(lse_max == -float("inf"), 0, lse_max)
117
- lse -= lse_max
118
- if IS_BASE_E:
119
- lse_exp = tl.exp(lse)
120
- lse_acc = tl.sum(lse_exp, axis=0)
121
- lse = tl.log(lse_acc)
122
- else:
123
- lse_exp = tl.exp2(lse)
124
- lse_acc = tl.sum(lse_exp, axis=0)
125
- lse = tl.log2(lse_acc)
126
- lse += lse_max
127
-
128
- lse_offsets = batch_idx * lses_stride_B + head_idx * lses_stride_H
129
- tl.store(vlse_ptr + lse_offsets, lse)
130
-
131
- # shape = [D]
132
- output_offsets = (
133
- batch_idx * outputs_stride_B
134
- + head_idx * outputs_stride_H
135
- + d_offsets * outputs_stride_D
136
- )
184
+ tl.store(new_output_ptr + output_offsets, output)
185
138
- # correct output
139
- lse_offset = (
140
- lse_idx * lses_stride_N + batch_idx * lses_stride_B + head_idx * lses_stride_H
141
- )
142
- lse_tmp = tl.load(lses_ptr + lse_offset).to(tl.float32)
143
- lse_finally = lse_tmp - lse
144
- lse_finally = tl.where(
145
- (lse_finally != lse_finally) | (lse_finally == float("inf")),
146
- -float("inf"),
147
- lse_finally,
148
- )
149
- factor = tl.exp(lse_finally) if IS_BASE_E else tl.exp2(lse_finally)
150
- output = tl.load(outputs_ptr + output_offsets)
151
- output = output * factor
152
- output = tl.where(factor == 0.0, 0.0, output)
186
+ def dispatch( # type: ignore[override]
187
+ self,
188
+ *,
189
+ output_dtype: torch.dtype,
190
+ lse_dtype: torch.dtype,
191
+ num_tokens: int,
192
+ num_heads: int,
193
+ head_dim: int,
194
+ n_rounded: int,
195
+ lse_idx: int,
196
+ is_base_e: bool,
197
+ ) -> CompileKey:
198
+ # Warm the contiguous [B, H, D] output and [N, B, H] LSE layouts used
199
+ # by the production collective path. Runtime still forwards real
200
+ # strides so unsupported layouts remain correct, but may compile on
201
+ # first use under the JIT monitor.
202
+ outputs_stride_d = 1
203
+ outputs_stride_h = head_dim
204
+ outputs_stride_b = num_heads * head_dim
205
+ lses_stride_h = 1
206
+ lses_stride_b = num_heads
207
+ lses_stride_n = num_tokens * num_heads
208
+ return self.CompileKey(
209
+ output_dtype=output_dtype,
210
+ lse_dtype=lse_dtype,
211
+ outputs_stride_b=triton_scalar_specialization_rep(outputs_stride_b),
212
+ outputs_stride_h=triton_scalar_specialization_rep(outputs_stride_h),
213
+ outputs_stride_d=triton_scalar_specialization_rep(outputs_stride_d),
214
+ lses_stride_n=triton_scalar_specialization_rep(lses_stride_n),
215
+ lses_stride_b=triton_scalar_specialization_rep(lses_stride_b),
216
+ lses_stride_h=triton_scalar_specialization_rep(lses_stride_h),
217
+ lse_idx=triton_scalar_specialization_rep(lse_idx),
218
+ head_dim=head_dim,
219
+ n_rounded=n_rounded,
220
+ is_base_e=is_base_e,
221
+ )
222
+
223
+ def get_warmup_keys(
224
+ self,
225
+ vllm_config: Any,
226
+ *,
227
+ output_dtype: torch.dtype | None = None,
228
+ num_heads: int | None = None,
229
+ head_dim: int | None = None,
230
+ is_base_e: bool | tuple[bool, ...] = (False, True),
231
+ ) -> list[CompileKey]:
232
+ from vllm.model_executor.layers.attention.mla_attention import (
233
+ get_mla_dims,
234
+ )
235
+
236
+ dcp_world_size = vllm_config.parallel_config.decode_context_parallel_size
237
+ max_tokens = min(
238
+ 16,
239
+ vllm_config.scheduler_config.max_num_batched_tokens,
240
+ )
241
+ hf_config = vllm_config.model_config.hf_config
242
+ if num_heads is None:
243
+ num_heads = (
244
+ hf_config.num_attention_heads
245
+ // vllm_config.parallel_config.tensor_parallel_size
246
+ )
247
+ if head_dim is None:
248
+ head_dim = get_mla_dims(vllm_config.model_config).v_head_dim
249
+ if output_dtype is None:
250
+ output_dtype = vllm_config.model_config.dtype
251
+ if dcp_world_size <= 1 or max_tokens <= 0 or num_heads <= 0:
252
+ return []
253
+ return self._trace_dispatch(self.dispatch)(
254
+ output_dtype=output_dtype,
255
+ lse_dtype=torch.float32,
256
+ num_tokens=WarmupIntRange(1, max_tokens + 1),
257
+ num_heads=num_heads,
258
+ head_dim=head_dim,
259
+ n_rounded=dcp_world_size,
260
+ lse_idx=WarmupIntRange(0, dcp_world_size),
261
+ is_base_e=is_base_e,
262
+ )
263
+
264
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
265
+ output_ptr = TritonWarmupTensor(
266
+ compile_key.output_dtype,
267
+ shape=(1, 1, compile_key.head_dim),
268
+ strides=(
269
+ compile_key.outputs_stride_b,
270
+ compile_key.outputs_stride_h,
271
+ compile_key.outputs_stride_d,
272
+ ),
273
+ )
274
+ lse_ptr = TritonWarmupTensor(
275
+ compile_key.lse_dtype,
276
+ shape=(compile_key.n_rounded, 1, 1),
277
+ strides=(
278
+ compile_key.lses_stride_n,
279
+ compile_key.lses_stride_b,
280
+ compile_key.lses_stride_h,
281
+ ),
282
+ )
283
+ return dict(
284
+ outputs=output_ptr,
285
+ new_output=output_ptr,
286
+ lses=lse_ptr,
287
+ vlse=lse_ptr,
288
+ lse_idx=compile_key.lse_idx,
289
+ ctx=None,
290
+ is_base_e=compile_key.is_base_e,
291
+ )
292
154
- tl.store(new_output_ptr + output_offsets, output)
293
+ @kernel_launcher
294
+ def __call__(
295
+ self,
296
+ outputs: torch.Tensor,
297
+ new_output: torch.Tensor,
298
+ lses: torch.Tensor,
299
+ vlse: torch.Tensor,
300
+ lse_idx: int,
301
+ ctx: Any,
302
+ *,
303
+ is_base_e: bool,
304
+ ) -> LaunchSpec:
305
+ num_tokens, num_heads, head_dim = outputs.shape
306
+ n_rounded = lses.shape[0]
307
+ outputs_stride_b, outputs_stride_h, outputs_stride_d = outputs.stride()
308
+ lses_stride_n, lses_stride_b, lses_stride_h = lses.stride()
309
+ grid = (num_tokens, num_heads, 1)
310
+ return grid, dict(
311
+ outputs_stride_B=outputs_stride_b,
312
+ outputs_stride_H=outputs_stride_h,
313
+ outputs_stride_D=outputs_stride_d,
314
+ lses_stride_N=lses_stride_n,
315
+ lses_stride_B=lses_stride_b,
316
+ lses_stride_H=lses_stride_h,
317
+ HEAD_DIM=head_dim,
318
+ N_ROUNDED=n_rounded,
319
+ IS_BASE_E=is_base_e,
320
+ _runtime_launcher=None if self._warming else ctx.call_kernel,
321
+ # CPTritonContext caches the non-constexpr positional prefix; derive
322
+ # its length so adding/reordering a kernel arg cannot silently
323
+ # misalign the cached replay path.
324
+ _runtime_launcher_arg_count=len(self.kernel.arg_names)
325
+ - len(self.kernel.constexprs),
326
+ )
327
328
329
class CPTritonContext:
@@ -202,14 +374,12 @@ def correct_attn_out(
374
f"got {tuple(lses.shape)}"
375
)
376
205
- B, H, D = out.shape
206
- N = lses.shape[0]
377
+ B, H, _ = out.shape
378
379
# Strides after we normalized shapes to 3-D views. The kernel computes
380
# offsets for `vlse_ptr` using lses_stride_B/H, so the output buffer must
381
# have the same B/H stride layout as a slice of `lses`.
211
- o_sB, o_sH, o_sD = out.stride()
212
- l_sN, l_sB, l_sH = lses.stride()
382
+ _, l_sB, l_sH = lses.stride()
383
384
# Allocate LSE with the same B/H strides as `lses` so writes land correctly
385
# even when `lses` is a non-contiguous view (e.g., 4-D to 3-D squeeze).
@@ -217,24 +387,15 @@ def correct_attn_out(
387
(B, H), (l_sB, l_sH), device=lses.device, dtype=lses.dtype
388
)
389
220
- # Kernel launch config
221
- grid = (B, H, 1)
222
-
223
- regular_args = (
390
+ _CORRECT_ATTN_CP_OUT_KERNEL(
391
out,
392
out,
393
lses,
394
lse,
228
- o_sB,
229
- o_sH,
230
- o_sD,
231
- l_sN,
232
- l_sB,
233
- l_sH,
395
cp_rank,
396
+ ctx,
397
+ is_base_e=is_lse_base_on_e,
398
)
236
- const_args = {"HEAD_DIM": D, "N_ROUNDED": N, "IS_BASE_E": is_lse_base_on_e}
237
- ctx.call_kernel(_correct_attn_cp_out_kernel, grid, *regular_args, **const_args)
399
return out, lse
400
401
@@ -1250,6 +1411,14 @@ class MLADCPManager:
1411
is_lse_base_on_e,
1412
use_pcp,
1413
)
1414
+ if vllm_config.kernel_config.enable_jit_warmup and not self.use_a2a:
1415
+ _CORRECT_ATTN_CP_OUT_KERNEL.register_warmup(
1416
+ vllm_config,
1417
+ output_dtype=output_dtype,
1418
+ num_heads=num_heads,
1419
+ head_dim=output_head_dim,
1420
+ is_base_e=is_lse_base_on_e,
1421
+ )
1422
self.query_gather = (
1423
None
1424
if use_pcp
@@ -1411,3 +1580,6 @@ class MLADCPManager:
1580
local_kv: torch.Tensor,
1581
) -> object:
1582
return self._kv_gather(gathered_kv, local_kv)
1583
+
1584
+
1585
+_CORRECT_ATTN_CP_OUT_KERNEL = CorrectAttnCPOutKernel()
vllm/v1/worker/block_table.py
+46
-28
@@ -11,11 +11,11 @@ import torch
11
12
from vllm.distributed import get_dcp_group, get_pcp_group
13
from vllm.logger import init_logger
14
-from vllm.model_executor.warmup.jit_warmup import (
15
- VllmJitKernel,
16
-)
14
from vllm.model_executor.warmup.jit_warmup_triton_helper import (
15
+ LaunchSpec,
16
TritonWarmupTensor,
17
+ VllmTritonJitKernel,
18
+ kernel_launcher,
19
triton_scalar_specialization_rep,
20
)
21
from vllm.triton_utils import tl, triton
@@ -394,7 +394,9 @@ class MultiGroupBlockTable:
394
return self.block_tables[idx]
395
396
397
-class ComputeSlotMappingKernel(VllmJitKernel["ComputeSlotMappingKernel.CompileKey"]):
397
+class ComputeSlotMappingKernel(
398
+ VllmTritonJitKernel["ComputeSlotMappingKernel.CompileKey"]
399
+):
400
triton_block_size = 1024
401
402
@dataclass(frozen=True)
@@ -490,37 +492,53 @@ class ComputeSlotMappingKernel(VllmJitKernel["ComputeSlotMappingKernel.CompileKe
492
def get_warmup_keys(self, **dispatch_kwargs: int) -> list[CompileKey]:
493
return self._trace_dispatch(self.dispatch)(**dispatch_kwargs)
494
493
- def compile(self, compile_key: CompileKey) -> None:
494
- warmup = getattr(self.kernel, "warmup", None)
495
- assert warmup is not None
495
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
496
int32_ptr = TritonWarmupTensor(torch.int32)
497
int64_ptr = TritonWarmupTensor(torch.int64)
498
- warmup(
499
- 2, # arbitrary, num_tokens in do_not_specialize
500
- 2, # arbitrary, max_num_tokens in do_not_specialize
501
- int32_ptr,
502
- int64_ptr,
503
- int32_ptr,
504
- compile_key.block_table_stride,
505
- compile_key.block_size,
506
- int64_ptr,
507
- KV_CACHE_BLOCK_SIZE=compile_key.kv_cache_block_size,
508
- BLOCKS_PER_KV_BLOCK=compile_key.blocks_per_kv_block,
509
- TOTAL_CP_WORLD_SIZE=compile_key.total_cp_world_size,
510
- TOTAL_CP_RANK=compile_key.total_cp_rank,
511
- CP_KV_CACHE_INTERLEAVE_SIZE=compile_key.cp_kv_cache_interleave_size,
512
- PAD_ID=PAD_SLOT_ID,
513
- BLOCK_SIZE=self.triton_block_size,
514
- grid=(2,),
498
+ return dict(
499
+ num_reqs=1,
500
+ num_tokens=2, # arbitrary, in do_not_specialize
501
+ max_num_tokens=2, # arbitrary, in do_not_specialize
502
+ query_start_loc=int32_ptr,
503
+ positions=int64_ptr,
504
+ block_table=TritonWarmupTensor(
505
+ torch.int32,
506
+ shape=(1, compile_key.block_table_stride),
507
+ ),
508
+ block_table_stride=compile_key.block_table_stride,
509
+ block_size=compile_key.block_size,
510
+ slot_mapping=int64_ptr,
511
+ kv_cache_block_size=compile_key.kv_cache_block_size,
512
+ blocks_per_kv_block=compile_key.blocks_per_kv_block,
513
+ total_cp_world_size=compile_key.total_cp_world_size,
514
+ total_cp_rank=compile_key.total_cp_rank,
515
+ cp_kv_cache_interleave_size=compile_key.cp_kv_cache_interleave_size,
516
)
517
518
+ @kernel_launcher
519
def __call__(
520
self,
521
num_reqs: int,
520
- *args: Any,
521
- ) -> None:
522
- self.kernel[(num_reqs + 1,)](
523
- *args,
522
+ num_tokens: int,
523
+ max_num_tokens: int,
524
+ query_start_loc: torch.Tensor,
525
+ positions: torch.Tensor,
526
+ block_table: torch.Tensor,
527
+ block_table_stride: int,
528
+ block_size: int,
529
+ slot_mapping: torch.Tensor,
530
+ kv_cache_block_size: int,
531
+ blocks_per_kv_block: int,
532
+ total_cp_world_size: int,
533
+ total_cp_rank: int,
534
+ cp_kv_cache_interleave_size: int,
535
+ ) -> LaunchSpec:
536
+ return (num_reqs + 1,), dict(
537
+ KV_CACHE_BLOCK_SIZE=kv_cache_block_size,
538
+ BLOCKS_PER_KV_BLOCK=blocks_per_kv_block,
539
+ TOTAL_CP_WORLD_SIZE=total_cp_world_size,
540
+ TOTAL_CP_RANK=total_cp_rank,
541
+ CP_KV_CACHE_INTERLEAVE_SIZE=cp_kv_cache_interleave_size,
542
PAD_ID=PAD_SLOT_ID,
543
BLOCK_SIZE=self.triton_block_size,
544
)