main
py 708 lines 25.9 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # ruff: noqa: E501
4
5 from dataclasses import dataclass
6 from typing import Any
7
8 import cutlass
9 import cutlass.cute as cute
10 import torch
11 from cuda.bindings.driver import CUstream
12 from cutlass import BFloat16, Float32, Int64, Uint8, Uint32, const_expr
13 from quack.compile_utils import make_fake_tensor
14
15 from vllm.cute_utils import (
16 _bf16x2_abs,
17 _bf16x2_max,
18 cvt,
19 recast_val,
20 torch_to_cute_dtype,
21 )
22 from vllm.model_executor.warmup.jit_warmup import kernel_launcher
23 from vllm.model_executor.warmup.jit_warmup_cutedsl_helper import (
24 CuTeDSLLaunchSpec,
25 VllmCuTeDSLJitKernel,
26 )
27 from vllm.vllm_flash_attn.cute import utils as cute_utils
28
29 # MXFP4: 32 elements per block, packed 2 nibbles per byte, ue8m0 block scale.
30 MXFP4_BLOCK_SIZE = 32
31
32
33 @cute.jit
34 def _load_q_and_rope(
35 positions: cute.Tensor,
36 q: cute.Tensor,
37 cos_sin_cache: cute.Tensor,
38 num_heads: cutlass.Constexpr,
39 coarsen: cutlass.Constexpr,
40 tb_size: cutlass.Constexpr,
41 subwarp_size: cutlass.Constexpr,
42 threads_per_token: cutlass.Constexpr,
43 nope_dim: cutlass.Constexpr,
44 rope_dim: cutlass.Constexpr,
45 cos_sin_dtype: cutlass.Constexpr,
46 ):
47 """Compute thread indices, load Q (BF16), and apply interleaved RoPE.
48
49 Returns a tuple
50 (q_bf16x2, tid, global_tid, sublane, token_id, head_tile_id,
51 head_start, in_bounds, num_token_heads)
52 where ``q_bf16x2`` is a (coarsen, 8) rmem tile of Uint32 packed
53 bf16x2 pairs covering the 16 BF16 lanes owned by this thread for
54 each of ``coarsen`` heads. RoPE is applied in place to the
55 trailing ``rope_dim`` lanes; the leading nope lanes pass through.
56 """
57 block_id, _, _ = cute.arch.block_idx()
58 tid, _, _ = cute.arch.thread_idx()
59
60 num_tokens = q.shape[0]
61 num_token_heads = num_tokens * num_heads
62 global_tid = block_id * tb_size + tid
63
64 global_subwarp_id = global_tid // subwarp_size
65 sublane = tid % subwarp_size
66
67 token_id = global_subwarp_id // (num_heads // coarsen)
68 head_tile_id = global_subwarp_id % (num_heads // coarsen)
69 head_start = head_tile_id * coarsen
70
71 # NOTE: token_id may exceed bounds, hence we need to add load/store guards
72 # we can't do early exit because CuteDSL doesn't support it. and we also need
73 # all threads in a warp to be active since we utilize warp shuffle later.
74 # must_in_bounds is constexpr, True when a token spans a whole number of
75 # threadblocks. Then num_tokens * threads_per_token is always a multiple of
76 # tb_size, so the trailing block never straddles num_tokens and the compiler
77 # removes the bounds check. Otherwise the guard has to stay: e.g. 32 heads
78 # with coarsen=4 needs 64 threads per token, so an odd num_tokens leaves half
79 # of the last block addressing token num_tokens.
80 must_in_bounds = cutlass.const_expr(threads_per_token % tb_size == 0)
81 in_bounds = must_in_bounds or (token_id < num_tokens)
82
83 cp_op = cute.nvgpu.CopyUniversalOp()
84
85 _layout = cute.make_layout((coarsen, 8), stride=(8, 1))
86 q_bf16x2 = cute.make_rmem_tensor(_layout, Uint32)
87
88 if in_bounds:
89 # we can't do cute.copy() on the whole 2D tile directly because
90 # cute.copy() wants the 1st mode to be covered by the copy atom,
91 # and other modes as for loop. there is no fast way to
92 # "transpose" the tensor view.
93 q_tile = cute.local_tile(
94 q[token_id, None, None],
95 tiler=(coarsen, 16),
96 coord=(head_tile_id, sublane),
97 )
98 cp_u32x8 = cute.make_copy_atom(cp_op, Uint32, num_bits_per_copy=256)
99 for i in cutlass.range_constexpr(coarsen):
100 src = cute.recast_tensor(q_tile[i, None], Uint32)
101 cute.copy(cp_u32x8, src, q_bf16x2[i, None])
102
103 # RoPE applies only to the trailing rope_dim values. We keep the rounded
104 # BF16 result in q_bits so the later amax and quantization see BF16.
105 # cos_sin_cache layout: [max_pos, rope_dim]
106 if in_bounds and sublane * 16 >= nope_dim:
107 cos_vals = cute.make_rmem_tensor((8,), Float32)
108 sin_vals = cute.make_rmem_tensor((8,), Float32)
109
110 pos = positions[token_id]
111
112 # select 8 elems from cos and sin
113 cos_id = sublane - nope_dim // 16
114 sin_id = cos_id + rope_dim // 16
115 cos_src = cute.local_tile(cos_sin_cache[pos, None], tiler=(8,), coord=(cos_id,))
116 sin_src = cute.local_tile(cos_sin_cache[pos, None], tiler=(8,), coord=(sin_id,))
117
118 cp_f32x8 = cute.make_copy_atom(cp_op, Float32, num_bits_per_copy=256)
119 cp_u32x4 = cute.make_copy_atom(cp_op, Uint32, num_bits_per_copy=128)
120
121 if const_expr(cos_sin_dtype is Float32):
122 cute.copy(cp_f32x8, cos_src, cos_vals)
123 cute.copy(cp_f32x8, sin_src, sin_vals)
124 else:
125 cos_bf16x2 = cute.make_rmem_tensor((4,), Uint32)
126 sin_bf16x2 = cute.make_rmem_tensor((4,), Uint32)
127 cute.copy(cp_u32x4, cute.recast_tensor(cos_src, Uint32), cos_bf16x2)
128 cute.copy(cp_u32x4, cute.recast_tensor(sin_src, Uint32), sin_bf16x2)
129
130 for i in cutlass.range_constexpr(4):
131 cos0, cos1 = cvt.bf16x2_to_fp32x2(cos_bf16x2[i])
132 sin0, sin1 = cvt.bf16x2_to_fp32x2(sin_bf16x2[i])
133 cos_vals[i * 2] = cos0
134 cos_vals[i * 2 + 1] = cos1
135 sin_vals[i * 2] = sin0
136 sin_vals[i * 2 + 1] = sin1
137
138 for i in cutlass.range_constexpr(coarsen):
139 for j in cutlass.range_constexpr(8):
140 q0, q1 = cvt.bf16x2_to_fp32x2(q_bf16x2[i, j])
141 rot0 = q0 * cos_vals[j] - q1 * sin_vals[j]
142 rot1 = q0 * sin_vals[j] + q1 * cos_vals[j]
143 # convert back to BF16 to match numerics
144 q_bf16x2[i, j] = cvt.fp32x2_to_bf16x2(rot0, rot1)
145
146 return (
147 q_bf16x2,
148 tid,
149 global_tid,
150 sublane,
151 token_id,
152 head_tile_id,
153 head_start,
154 in_bounds,
155 num_token_heads,
156 )
157
158
159 class IndexerQMxFp4Kernel(VllmCuTeDSLJitKernel["IndexerQMxFp4Kernel.CompileKey"]):
160 tb_size = 128
161
162 @dataclass(frozen=True)
163 class CompileKey:
164 head_dim: int
165 rope_dim: int
166 num_heads: int
167 cos_sin_dtype: type[cutlass.Numeric]
168 weights_out_dtype: type[cutlass.Numeric]
169 coarsen: int
170
171 @staticmethod
172 def kernel(compile_key: CompileKey) -> Any:
173 head_dim = compile_key.head_dim
174 rope_dim = compile_key.rope_dim
175 nope_dim = head_dim - rope_dim
176 num_heads = compile_key.num_heads
177 cos_sin_dtype = compile_key.cos_sin_dtype
178 # process multiple heads at the same time to armotize RoPE load costs
179 coarsen = compile_key.coarsen
180 tb_size = IndexerQMxFp4Kernel.tb_size
181 weights_out_dtype = compile_key.weights_out_dtype
182 assert num_heads % coarsen == 0
183
184 # later we will use 32B load = 16 BF16 elems
185 # thus, head_dim=128 requires 8 threads to handle.
186 # let's call subwarp = 8 threads.
187 subwarp_size = head_dim // 16
188 threads_per_token = (num_heads // coarsen) * subwarp_size
189
190 @cute.kernel
191 def device_kernel(
192 positions: cute.Tensor,
193 q: cute.Tensor,
194 cos_sin_cache: cute.Tensor,
195 weights: cute.Tensor,
196 q_quant: cute.Tensor,
197 q_scale: cute.Tensor,
198 weights_out: cute.Tensor,
199 scale: Float32,
200 ):
201 (
202 q_bf16x2,
203 tid,
204 global_tid,
205 sublane,
206 token_id,
207 head_tile_id,
208 head_start,
209 in_bounds,
210 num_token_heads,
211 ) = _load_q_and_rope(
212 positions,
213 q,
214 cos_sin_cache,
215 num_heads,
216 coarsen,
217 tb_size,
218 subwarp_size,
219 threads_per_token,
220 nope_dim,
221 rope_dim,
222 cos_sin_dtype,
223 )
224
225 cp_op = cute.nvgpu.CopyUniversalOp()
226
227 # layout: [coarsen, 8]
228 q_fp4_tile = cute.local_tile(
229 q_quant[token_id, None, None],
230 tiler=(coarsen, 8),
231 coord=(head_tile_id, sublane),
232 )
233
234 for i in cutlass.range_constexpr(coarsen):
235 # compute amax in packed bf16x2 to save instructions
236 # Each thread holds 16 elems. Two adjacent threads form one 32-elem
237 # MXFP4 block, so a width-2 shuffle gives the block amax.
238 amax_bf16x2 = _bf16x2_abs(q_bf16x2[i, 0])
239 for j in cutlass.range_constexpr(1, 8):
240 amax_bf16x2 = _bf16x2_max(amax_bf16x2, _bf16x2_abs(q_bf16x2[i, j]))
241 amax_bf16x2 = cute_utils.warp_reduce(
242 amax_bf16x2,
243 _bf16x2_max,
244 width=MXFP4_BLOCK_SIZE // 16,
245 )
246 amax_pair = cvt.bf16x2_to_fp32x2(amax_bf16x2)
247 amax = cute_utils.fmax(amax_pair[0], amax_pair[1])
248
249 if in_bounds:
250 # compute block scale with bit manipulation
251 # UE8M0 stores ceil(log2(fp4_scale)) + 127. Adding the mantissa mask
252 # increments the exponent whenever fp4_scale is not exactly a power of 2
253 eps = cutlass.const_expr(float.fromhex("0x6p-126"))
254 fp4_scale = cute_utils.fmax(amax, eps) * Float32(1.0 / 6.0)
255 bits = recast_val(fp4_scale, Uint32)
256 ue8m0 = cute_utils.shr_u32(
257 bits + Uint32(0x7FFFFF), Uint32(23)
258 ) & Uint32(0xFF)
259
260 # Only one of the two threads in an MXFP4 block writes the shared scale.
261 if tid % 2 == 0:
262 mx_block = sublane // 2
263 q_scale[token_id, head_start + i, mx_block] = Uint8(ue8m0)
264
265 # If scale = 2^A and ue8m0 = A + 127, then inverse scale has exponent
266 # -A + 127 = 254 - ue8m0.
267 inv_scale_bits = (Uint32(254) - ue8m0) << Uint32(23)
268 inv_fp4_scale = recast_val(inv_scale_bits, Float32)
269
270 vals = cute.make_rmem_tensor(16, Float32)
271 for j in cutlass.range_constexpr(8):
272 q0, q1 = cvt.bf16x2_to_fp32x2(q_bf16x2[i, j])
273 vals[j * 2] = q0 * inv_fp4_scale
274 vals[j * 2 + 1] = q1 * inv_fp4_scale
275
276 # pack to FP4
277 packed = cute.make_rmem_tensor((2,), Uint32)
278 packed[0] = cvt.fp32x8_to_fp4x8(vals, 0)
279 packed[1] = cvt.fp32x8_to_fp4x8(vals, 8)
280
281 dst = q_fp4_tile[i, None]
282 cp_u32x2 = cute.make_copy_atom(cp_op, Uint32, num_bits_per_copy=64)
283 cute.copy(cp_u32x2, packed, cute.recast_tensor(dst, Uint32))
284
285 # Weight scaling is independent of the Q subwarp work. The first
286 # num_tokens * num_heads logical threads cover one weight each.
287 if global_tid < num_token_heads:
288 weight_token_id = global_tid // num_heads
289 weight_head_id = global_tid % num_heads
290 weights_out[weight_token_id, weight_head_id] = (
291 weights[weight_token_id, weight_head_id].to(Float32) * scale
292 ).to(weights_out_dtype)
293
294 @cute.jit
295 def host_entrypoint(
296 positions: cute.Tensor,
297 q: cute.Tensor,
298 cos_sin_cache: cute.Tensor,
299 weights: cute.Tensor,
300 q_quant: cute.Tensor,
301 q_scale: cute.Tensor,
302 weights_out: cute.Tensor,
303 scale: Float32,
304 stream: CUstream,
305 ):
306 total_threads = q.shape[0] * threads_per_token
307 grid = (cute.ceil_div(total_threads, tb_size), 1, 1)
308 device_kernel(
309 positions,
310 q,
311 cos_sin_cache,
312 weights,
313 q_quant,
314 q_scale,
315 weights_out,
316 scale,
317 ).launch(grid=grid, block=(tb_size, 1, 1), stream=stream)
318
319 return host_entrypoint
320
321 def dispatch( # type: ignore[override]
322 self,
323 *,
324 cos_sin_dtype: type[cutlass.Numeric],
325 weights_out_dtype: type[cutlass.Numeric],
326 **compile_key_fields: int,
327 ) -> CompileKey:
328 return self.CompileKey(
329 **compile_key_fields,
330 cos_sin_dtype=cos_sin_dtype,
331 weights_out_dtype=weights_out_dtype,
332 )
333
334 def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
335 from vllm.v1.attention.backends.mla.indexer import dsa_indexer_uses_fp4
336
337 hf_config = vllm_config.model_config.hf_config
338 if hf_config is None or not dsa_indexer_uses_fp4(vllm_config):
339 return []
340
341 num_heads = int(getattr(hf_config, "index_n_heads", 0) or 0)
342 head_dim = int(getattr(hf_config, "index_head_dim", 0) or 0)
343 rope_dim = int(getattr(hf_config, "qk_rope_head_dim", 0) or 0)
344 if num_heads <= 0 or head_dim <= 0 or rope_dim <= 0:
345 return []
346
347 # The DeepSeek V4.1 sparse-logits indexer takes bf16 weights; the
348 # dense scoring kernels take fp32.
349 weights_out_dtypes: tuple[type[cutlass.Numeric], ...] = (Float32,)
350 if vllm_config.attention_config.indexer_sparse_logits:
351 weights_out_dtypes = (Float32, BFloat16)
352 return self._trace_dispatch(self.dispatch)(
353 head_dim=head_dim,
354 rope_dim=rope_dim,
355 num_heads=num_heads,
356 cos_sin_dtype=Float32,
357 weights_out_dtype=weights_out_dtypes,
358 coarsen=(1, 4),
359 )
360
361 def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
362 num_tokens = cute.sym_int()
363 max_pos = cute.sym_int()
364 q = make_fake_tensor(
365 BFloat16,
366 (num_tokens, compile_key.num_heads, compile_key.head_dim),
367 divisibility=16,
368 )
369 positions = make_fake_tensor(Int64, (num_tokens,), divisibility=1)
370 cos_sin_cache = make_fake_tensor(
371 compile_key.cos_sin_dtype,
372 (max_pos, compile_key.rope_dim),
373 divisibility=8,
374 )
375 weights = make_fake_tensor(
376 BFloat16, (num_tokens, compile_key.num_heads), divisibility=8
377 )
378 q_packed = make_fake_tensor(
379 Uint8,
380 (num_tokens, compile_key.num_heads, compile_key.head_dim // 2),
381 divisibility=16,
382 )
383 q_scale = make_fake_tensor(
384 Uint8,
385 (
386 num_tokens,
387 compile_key.num_heads,
388 compile_key.head_dim // MXFP4_BLOCK_SIZE,
389 ),
390 divisibility=4,
391 )
392 weights_out = make_fake_tensor(
393 compile_key.weights_out_dtype,
394 (num_tokens, compile_key.num_heads),
395 divisibility=4,
396 )
397 return (
398 positions,
399 q,
400 cos_sin_cache,
401 weights,
402 q_packed,
403 q_scale,
404 weights_out,
405 Float32(0.0),
406 )
407
408 @kernel_launcher
409 def __call__(
410 self,
411 *,
412 positions: torch.Tensor,
413 q: torch.Tensor,
414 cos_sin_cache: torch.Tensor,
415 weights: torch.Tensor,
416 weights_softmax_scale: float,
417 weights_head_scale: float,
418 q_packed: torch.Tensor,
419 q_scale: torch.Tensor,
420 weights_out: torch.Tensor,
421 ) -> CuTeDSLLaunchSpec["IndexerQMxFp4Kernel.CompileKey"]:
422 num_tokens, num_heads, head_dim = q.shape
423 compile_key = self.dispatch(
424 head_dim=head_dim,
425 rope_dim=cos_sin_cache.shape[-1],
426 num_heads=num_heads,
427 cos_sin_dtype=torch_to_cute_dtype(cos_sin_cache.dtype),
428 weights_out_dtype=torch_to_cute_dtype(weights_out.dtype),
429 coarsen=1 if num_tokens < 512 else 4,
430 )
431 launch_args = (
432 positions,
433 q,
434 cos_sin_cache,
435 weights,
436 q_packed,
437 q_scale,
438 weights_out,
439 float(weights_softmax_scale * weights_head_scale),
440 )
441 return compile_key, launch_args
442
443
444 class IndexerQFp8Kernel(VllmCuTeDSLJitKernel["IndexerQFp8Kernel.CompileKey"]):
445 tb_size = 128
446
447 @dataclass(frozen=True)
448 class CompileKey:
449 head_dim: int
450 rope_dim: int
451 num_heads: int
452 cos_sin_dtype: type[cutlass.Numeric]
453 coarsen: int
454
455 @staticmethod
456 def kernel(compile_key: CompileKey) -> Any:
457 head_dim = compile_key.head_dim
458 rope_dim = compile_key.rope_dim
459 nope_dim = head_dim - rope_dim
460 num_heads = compile_key.num_heads
461 cos_sin_dtype = compile_key.cos_sin_dtype
462 # process multiple heads at the same time to armotize RoPE load costs
463 coarsen = compile_key.coarsen
464 tb_size = IndexerQFp8Kernel.tb_size
465 assert num_heads % coarsen == 0
466
467 # later we will use 32B load = 16 BF16 elems
468 # thus, head_dim=128 requires 8 threads to handle.
469 # let's call subwarp = 8 threads.
470 subwarp_size = head_dim // 16
471 threads_per_token = (num_heads // coarsen) * subwarp_size
472 # Each subwarp owns `coarsen` heads; we use the first `coarsen`
473 # threads of the subwarp to write the per-head weights using the
474 # fp8 scale computed in the matching loop iteration.
475 assert coarsen <= subwarp_size, (
476 f"FP8 kernel requires coarsen ({coarsen}) <= "
477 f"subwarp_size ({subwarp_size}) for the weight-fold step"
478 )
479
480 @cute.kernel
481 def device_kernel(
482 positions: cute.Tensor,
483 q: cute.Tensor,
484 cos_sin_cache: cute.Tensor,
485 weights: cute.Tensor,
486 q_fp8: cute.Tensor,
487 weights_out: cute.Tensor,
488 scale: Float32,
489 ):
490 (
491 q_bf16x2,
492 _tid,
493 _global_tid,
494 sublane,
495 token_id,
496 head_tile_id,
497 head_start,
498 in_bounds,
499 _num_token_heads,
500 ) = _load_q_and_rope(
501 positions,
502 q,
503 cos_sin_cache,
504 num_heads,
505 coarsen,
506 tb_size,
507 subwarp_size,
508 threads_per_token,
509 nope_dim,
510 rope_dim,
511 cos_sin_dtype,
512 )
513
514 cp_op = cute.nvgpu.CopyUniversalOp()
515
516 # layout: [coarsen, 16] bytes (one e4m3fn per element).
517 q_fp8_tile = cute.local_tile(
518 q_fp8[token_id, None, None],
519 tiler=(coarsen, 16),
520 coord=(head_tile_id, sublane),
521 )
522
523 for i in cutlass.range_constexpr(coarsen):
524 # Reduce amax across the full head_dim: each thread already holds
525 # the max over its 16 lanes; a width=subwarp_size warp shuffle
526 # spreads the head-wide max to every lane in the subwarp.
527 amax_bf16x2 = _bf16x2_abs(q_bf16x2[i, 0])
528 for j in cutlass.range_constexpr(1, 8):
529 amax_bf16x2 = _bf16x2_max(amax_bf16x2, _bf16x2_abs(q_bf16x2[i, j]))
530 amax_bf16x2 = cute_utils.warp_reduce(
531 amax_bf16x2,
532 _bf16x2_max,
533 width=subwarp_size,
534 )
535 amax_pair = cvt.bf16x2_to_fp32x2(amax_bf16x2)
536 amax = cute_utils.fmax(amax_pair[0], amax_pair[1])
537
538 # scale = max(amax, eps) / fp8_max, then rounded UP to the next
539 # power of two. Adding the mantissa mask before shifting out the
540 # mantissa bumps the exponent whenever s isn't a pure pow2.
541 fp32_scale = cute_utils.fmax(amax, Float32(1e-4)) * Float32(1.0 / 448.0)
542 bits = recast_val(fp32_scale, Uint32)
543 scale_exp = cute_utils.shr_u32(
544 bits + Uint32(0x7FFFFF), Uint32(23)
545 ) & Uint32(0xFF)
546
547 # rounded scale = 2^(scale_exp - 127); bit pattern is scale_exp << 23
548 fp8_scale_bits = scale_exp << Uint32(23)
549 fp8_scale = recast_val(fp8_scale_bits, Float32)
550 # inverse = 2^-(scale_exp - 127); bit pattern is (254 - scale_exp) << 23
551 inv_scale_bits = (Uint32(254) - scale_exp) << Uint32(23)
552 inv_fp8_scale = recast_val(inv_scale_bits, Float32)
553
554 # Weight fold: weights_out = weights * q_scale * scale_combined.
555 # All threads in the subwarp share the same fp8_scale after the
556 # warp_reduce above, so we let thread `sublane == i` write the
557 # weight for head `head_start + i`.
558 if in_bounds and sublane == i:
559 head_id = head_start + i
560 weights_out[token_id, head_id] = (
561 weights[token_id, head_id].to(Float32) * scale * fp8_scale
562 )
563
564 if in_bounds:
565 # 16 BF16 → 16 e4m3 bytes per thread, packed into 4 b32s
566 # (one cp.async-shaped 128-bit store per row).
567 packed = cute.make_rmem_tensor((4,), Uint32)
568 for j in cutlass.range_constexpr(4):
569 q0, q1 = cvt.bf16x2_to_fp32x2(q_bf16x2[i, j * 2])
570 q2, q3 = cvt.bf16x2_to_fp32x2(q_bf16x2[i, j * 2 + 1])
571 packed[j] = cvt.fp32x4_to_fp8x4(
572 q0 * inv_fp8_scale,
573 q1 * inv_fp8_scale,
574 q2 * inv_fp8_scale,
575 q3 * inv_fp8_scale,
576 )
577
578 dst = q_fp8_tile[i, None]
579 cp_u32x4 = cute.make_copy_atom(cp_op, Uint32, num_bits_per_copy=128)
580 cute.copy(cp_u32x4, packed, cute.recast_tensor(dst, Uint32))
581
582 @cute.jit
583 def host_entrypoint(
584 positions: cute.Tensor,
585 q: cute.Tensor,
586 cos_sin_cache: cute.Tensor,
587 weights: cute.Tensor,
588 q_fp8: cute.Tensor,
589 weights_out: cute.Tensor,
590 scale: Float32,
591 stream: CUstream,
592 ):
593 total_threads = q.shape[0] * threads_per_token
594 grid = (cute.ceil_div(total_threads, tb_size), 1, 1)
595 device_kernel(
596 positions,
597 q,
598 cos_sin_cache,
599 weights,
600 q_fp8,
601 weights_out,
602 scale,
603 ).launch(grid=grid, block=(tb_size, 1, 1), stream=stream)
604
605 return host_entrypoint
606
607 def dispatch( # type: ignore[override]
608 self,
609 *,
610 cos_sin_dtype: type[cutlass.Numeric],
611 **compile_key_fields: int,
612 ) -> CompileKey:
613 return self.CompileKey(
614 **compile_key_fields,
615 cos_sin_dtype=cos_sin_dtype,
616 )
617
618 def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
619 from vllm.v1.attention.backends.mla.indexer import dsa_indexer_uses_fp4
620
621 hf_config = vllm_config.model_config.hf_config
622 if hf_config is None or dsa_indexer_uses_fp4(vllm_config):
623 return []
624
625 num_heads = int(getattr(hf_config, "index_n_heads", 0) or 0)
626 head_dim = int(getattr(hf_config, "index_head_dim", 0) or 0)
627 rope_dim = int(getattr(hf_config, "qk_rope_head_dim", 0) or 0)
628 if num_heads <= 0 or head_dim <= 0 or rope_dim <= 0:
629 return []
630
631 return self._trace_dispatch(self.dispatch)(
632 head_dim=head_dim,
633 rope_dim=rope_dim,
634 num_heads=num_heads,
635 cos_sin_dtype=Float32,
636 coarsen=(1, 4),
637 )
638
639 def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
640 num_tokens = cute.sym_int()
641 max_pos = cute.sym_int()
642 q = make_fake_tensor(
643 BFloat16,
644 (num_tokens, compile_key.num_heads, compile_key.head_dim),
645 divisibility=16,
646 )
647 positions = make_fake_tensor(Int64, (num_tokens,), divisibility=1)
648 cos_sin_cache = make_fake_tensor(
649 compile_key.cos_sin_dtype,
650 (max_pos, compile_key.rope_dim),
651 divisibility=8,
652 )
653 weights = make_fake_tensor(
654 BFloat16, (num_tokens, compile_key.num_heads), divisibility=8
655 )
656 q_fp8 = make_fake_tensor(
657 Uint8,
658 (num_tokens, compile_key.num_heads, compile_key.head_dim),
659 divisibility=16,
660 )
661 weights_out = make_fake_tensor(
662 Float32, (num_tokens, compile_key.num_heads), divisibility=4
663 )
664 return (
665 positions,
666 q,
667 cos_sin_cache,
668 weights,
669 q_fp8,
670 weights_out,
671 Float32(0.0),
672 )
673
674 @kernel_launcher
675 def __call__(
676 self,
677 *,
678 positions: torch.Tensor,
679 q: torch.Tensor,
680 cos_sin_cache: torch.Tensor,
681 weights: torch.Tensor,
682 weights_softmax_scale: float,
683 weights_head_scale: float,
684 q_fp8: torch.Tensor,
685 weights_out: torch.Tensor,
686 ) -> CuTeDSLLaunchSpec["IndexerQFp8Kernel.CompileKey"]:
687 num_tokens, num_heads, head_dim = q.shape
688 compile_key = self.dispatch(
689 head_dim=head_dim,
690 rope_dim=cos_sin_cache.shape[-1],
691 num_heads=num_heads,
692 cos_sin_dtype=torch_to_cute_dtype(cos_sin_cache.dtype),
693 coarsen=1 if num_tokens < 512 else 4,
694 )
695 launch_args = (
696 positions,
697 q,
698 cos_sin_cache,
699 weights,
700 q_fp8.view(torch.uint8),
701 weights_out,
702 float(weights_softmax_scale * weights_head_scale),
703 )
704 return compile_key, launch_args
705
706
707 _INDEXER_Q_MXFP4_KERNEL = IndexerQMxFp4Kernel()
708 _INDEXER_Q_FP8_KERNEL = IndexerQFp8Kernel()