main
py 2,503 lines 98.3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """CuTe DSL sparse-attention compressor for DeepSeek V4.
4
5 The public wrappers provide the C4 fused and C128 split kernels.
6 """
7
8 from __future__ import annotations
9
10 from dataclasses import dataclass
11 from typing import Any
12
13 import cutlass
14 import cutlass.cute as cute
15 import torch
16 from cuda.bindings.driver import CUstream
17 from cutlass import Float32, Int32, Int64, Uint8, Uint16, Uint32, const_expr
18 from cutlass._mlir.dialects import llvm
19 from cutlass.cutlass_dsl import T, dsl_user_op
20 from quack.compile_utils import make_fake_tensor
21
22 from vllm.cute_utils import torch_to_cute_dtype
23 from vllm.model_executor.warmup.jit_warmup import kernel_launcher
24 from vllm.model_executor.warmup.jit_warmup_cutedsl_helper import (
25 CuTeDSLLaunchSpec,
26 VllmCuTeDSLJitKernel,
27 )
28 from vllm.utils.math_utils import round_up
29
30
31 @dsl_user_op
32 def _recast_val(x, dtype, *, loc=None, ip=None):
33 return dtype(llvm.bitcast(dtype.mlir_type, x.ir_value(loc=loc, ip=ip)))
34
35
36 @dsl_user_op
37 def _fp32x2_to_bf16x2(a: Float32, b: Float32, *, loc=None, ip=None) -> Uint32:
38 out = llvm.inline_asm(
39 T.i32(),
40 [a.ir_value(loc=loc, ip=ip), b.ir_value(loc=loc, ip=ip)],
41 "cvt.rn.bf16x2.f32 $0, $2, $1;",
42 "=r,f,f",
43 has_side_effects=False,
44 is_align_stack=False,
45 )
46 return Uint32(out)
47
48
49 @dsl_user_op
50 def _bf16x2_to_fp32(data: Uint32, *, loc=None, ip=None) -> tuple[Float32, Float32]:
51 out = llvm.inline_asm(
52 llvm.StructType.get_literal([T.f32(), T.f32()]),
53 [data.ir_value(loc=loc, ip=ip)],
54 "shl.b32 $0, $2, 16;\n\tand.b32 $1, $2, 0xFFFF0000;\n",
55 "=f,=f,r",
56 has_side_effects=False,
57 is_align_stack=False,
58 )
59 return (
60 Float32(llvm.extractvalue(T.f32(), out, [0], loc=loc, ip=ip)),
61 Float32(llvm.extractvalue(T.f32(), out, [1], loc=loc, ip=ip)),
62 )
63
64
65 @dsl_user_op
66 def _fp32x2_to_fp8e4m3x2(a: Float32, b: Float32, *, loc=None, ip=None) -> Uint16:
67 out = llvm.inline_asm(
68 T.i16(),
69 [a.ir_value(loc=loc, ip=ip), b.ir_value(loc=loc, ip=ip)],
70 "cvt.rn.satfinite.e4m3x2.f32 $0, $2, $1;",
71 "=h,f,f",
72 has_side_effects=False,
73 is_align_stack=False,
74 )
75 return Uint16(out)
76
77
78 class SparseAttnCompressNormRopeStoreC4Kernel(
79 VllmCuTeDSLJitKernel["SparseAttnCompressNormRopeStoreC4Kernel.CompileKey"]
80 ):
81 min_scale = 1.0e-4
82 rcp_ln2 = 1.4426950408889634
83
84 @dataclass(frozen=True)
85 class CompileKey:
86 head_size: int
87 state_width: int
88 rope_head_dim: int
89 fp8_max: float
90 quant_block: int
91 token_stride: int
92 scale_dim: int
93 compress_ratio: int
94 overlap: bool
95 norm_weight_dtype: type[cutlass.Numeric]
96
97 @staticmethod
98 def kernel(compile_key: CompileKey) -> Any:
99 min_scale = SparseAttnCompressNormRopeStoreC4Kernel.min_scale
100 rcp_ln2 = SparseAttnCompressNormRopeStoreC4Kernel.rcp_ln2
101 head_dim = compile_key.head_size
102 state_width = compile_key.state_width
103 rope_dim = compile_key.rope_head_dim
104 nope_dim = head_dim - rope_dim
105 fp8_max = compile_key.fp8_max
106 quant_block = compile_key.quant_block
107 token_stride = compile_key.token_stride
108 scale_dim = compile_key.scale_dim
109 elems_per_lane = 8
110 copy_elems = 4
111 copy_chunks = elems_per_lane // copy_elems
112 lanes_per_group = quant_block // elems_per_lane
113 groups_per_warp = 32 // lanes_per_group
114 scale_reduce_steps = lanes_per_group.bit_length() - 1
115 scale_reduce_offset = lanes_per_group // 2
116 num_warps = (head_dim // quant_block) // groups_per_warp
117 nope_blocks = nope_dim // quant_block
118 tb_size = num_warps * 32
119 compress_ratio = compile_key.compress_ratio
120 overlap = compile_key.overlap
121 window = (1 + int(overlap)) * compress_ratio
122
123 @cute.kernel
124 def device_kernel(
125 state_cache: cute.Tensor,
126 token_to_req_indices: cute.Tensor,
127 positions: cute.Tensor,
128 slot_mapping: cute.Tensor,
129 block_table: cute.Tensor,
130 block_size: Int64,
131 rms_norm_weight: cute.Tensor,
132 rms_norm_eps: Float32,
133 cos_sin_cache: cute.Tensor,
134 k_cache: cute.Tensor,
135 kv_slot_mapping: cute.Tensor,
136 kv_cache_block_size: Int64,
137 ):
138 token_idx, _, _ = cute.arch.block_idx()
139 tid, _, _ = cute.arch.thread_idx()
140 warp_id = cute.arch.make_warp_uniform(tid // 32)
141 lane_id = tid % 32
142 group_lane = lane_id % lanes_per_group
143 group_idx = warp_id * groups_per_warp + lane_id // lanes_per_group
144 elem_base = group_idx * quant_block + group_lane * elems_per_lane
145
146 slot_id = slot_mapping[token_idx]
147 has_position = token_idx < positions.shape[0]
148 position = Int64(0)
149 if has_position:
150 position = positions[token_idx]
151 boundary = has_position and (
152 (position + Int64(1)) % Int64(compress_ratio) == Int64(0)
153 )
154 has_req_idx = token_idx < token_to_req_indices.shape[0]
155 has_kv_slot_idx = token_idx < kv_slot_mapping.shape[0]
156 kv_slot_idx = Int64(-1)
157 if has_kv_slot_idx:
158 kv_slot_idx = kv_slot_mapping[token_idx]
159 active = (
160 slot_id >= Int64(0)
161 and has_req_idx
162 and boundary
163 and kv_slot_idx >= Int64(0)
164 )
165
166 if active:
167 req_idx = token_to_req_indices[token_idx]
168 start = position - Int64(window - 1)
169
170 smem = cutlass.utils.SmemAllocator()
171 s_block_numbers = smem.allocate_tensor(
172 Int32, cute.make_layout((window,)), byte_alignment=4
173 )
174 partial_sums = smem.allocate_tensor(
175 Float32, cute.make_layout((num_warps,)), byte_alignment=4
176 )
177 rrms_shared = smem.allocate_tensor(
178 Float32, cute.make_layout((1,)), byte_alignment=4
179 )
180
181 for row in cutlass.range_constexpr(window):
182 pos = start + Int64(row)
183 if tid == row:
184 block_number_i32 = Int32(0)
185 if pos >= Int64(0):
186 block_index = pos // block_size
187 block_number_i32 = block_table[req_idx, block_index]
188 s_block_numbers[row] = block_number_i32
189 cute.arch.sync_threads()
190
191 local_max = cute.make_rmem_tensor((elems_per_lane,), Float32)
192 local_sum = cute.make_rmem_tensor((elems_per_lane,), Float32)
193 local_product = cute.make_rmem_tensor((elems_per_lane,), Float32)
194
195 for e in cutlass.range_constexpr(elems_per_lane):
196 local_max[e] = -Float32.inf
197 local_sum[e] = Float32(0.0)
198 local_product[e] = Float32(0.0)
199
200 cp_f32x4 = cute.make_copy_atom(
201 cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128
202 )
203 copy_layout = cute.make_layout(
204 (copy_chunks, copy_elems),
205 stride=(copy_elems, 1),
206 )
207 kv_vals = cute.make_rmem_tensor(copy_layout, Float32)
208 score_vals = cute.make_rmem_tensor(copy_layout, Float32)
209
210 for row in cutlass.range_constexpr(window):
211 pos = start + Int64(row)
212 if pos >= Int64(0):
213 block_index = pos // block_size
214 block_offset = pos - block_index * block_size
215 block_number = s_block_numbers[row].to(Int64)
216 head_offset = Int64((row // compress_ratio) * head_dim)
217 row_tensor = state_cache[block_number, block_offset, None]
218 for chunk in cutlass.range_constexpr(copy_chunks):
219 copy_elem = const_expr(chunk * copy_elems)
220 col_tile = (
221 head_offset + (elem_base + Int32(copy_elem)).to(Int64)
222 ) // Int64(copy_elems)
223 kv_src = cute.local_tile(
224 row_tensor,
225 tiler=(copy_elems,),
226 coord=(col_tile,),
227 )
228 score_src = cute.local_tile(
229 row_tensor,
230 tiler=(copy_elems,),
231 coord=(col_tile + Int64(state_width // copy_elems),),
232 )
233 cute.copy(cp_f32x4, kv_src, kv_vals[chunk, None])
234 cute.copy(cp_f32x4, score_src, score_vals[chunk, None])
235
236 for e in cutlass.range_constexpr(elems_per_lane):
237 chunk = const_expr(e // copy_elems)
238 copy_elem = const_expr(e % copy_elems)
239 score = score_vals[chunk, copy_elem]
240 kv = kv_vals[chunk, copy_elem]
241 new_max = cute.arch.fmax(local_max[e], score)
242 old_scale = cute.math.exp2(
243 (local_max[e] - new_max) * Float32(rcp_ln2),
244 fastmath=True,
245 )
246 new_scale = cute.math.exp2(
247 (score - new_max) * Float32(rcp_ln2),
248 fastmath=True,
249 )
250 local_sum[e] = local_sum[e] * old_scale + new_scale
251 local_product[e] = (
252 local_product[e] * old_scale + kv * new_scale
253 )
254 local_max[e] = new_max
255
256 x = cute.make_rmem_tensor((elems_per_lane,), Float32)
257 local_sumsq = Float32(0.0)
258 for e in cutlass.range_constexpr(elems_per_lane):
259 x[e] = local_product[e] / local_sum[e]
260 local_sumsq += x[e] * x[e]
261
262 warp_sum = local_sumsq
263 for step in cutlass.range_constexpr(5):
264 offset = const_expr(16 >> step)
265 warp_sum += cute.arch.shuffle_sync_bfly(warp_sum, offset)
266
267 if lane_id == 0:
268 partial_sums[warp_id] = warp_sum
269 cute.arch.sync_threads()
270 if tid == 0:
271 total = Float32(0.0)
272 for i in cutlass.range_constexpr(num_warps):
273 total += partial_sums[i]
274 rrms_shared[0] = cute.math.rsqrt(
275 total / Float32(head_dim) + rms_norm_eps, fastmath=True
276 )
277 cute.arch.sync_threads()
278
279 rrms = rrms_shared[0]
280 for e in cutlass.range_constexpr(elems_per_lane):
281 elem = elem_base + e
282 x[e] = x[e] * rrms * rms_norm_weight[elem].to(Float32)
283
284 k_cache_u16 = cute.recast_tensor(k_cache, Uint16)
285 k_cache_u32 = cute.recast_tensor(k_cache, Uint32)
286 page = kv_slot_idx // kv_cache_block_size
287 kv_offset = kv_slot_idx - page * kv_cache_block_size
288 value_base = page * k_cache.stride[0] + kv_offset * Int64(token_stride)
289 scale_base = (
290 page * k_cache.stride[0]
291 + kv_cache_block_size * Int64(token_stride)
292 + kv_offset * Int64(scale_dim)
293 )
294
295 if group_idx == nope_blocks:
296 compressed_pos = (position // Int64(compress_ratio)) * Int64(
297 compress_ratio
298 )
299 for pair in cutlass.range_constexpr(elems_per_lane // 2):
300 elem = const_expr(pair * 2)
301 pair_idx = (elem_base - nope_dim) // 2 + Int32(pair)
302 cos_v = cos_sin_cache[compressed_pos, pair_idx]
303 sin_v = cos_sin_cache[
304 compressed_pos, pair_idx + Int32(rope_dim // 2)
305 ]
306 real = x[elem] * cos_v - x[elem + 1] * sin_v
307 imag = x[elem] * sin_v + x[elem + 1] * cos_v
308 packed = _fp32x2_to_bf16x2(real, imag)
309 out_base = (
310 value_base
311 + Int64(nope_dim)
312 + ((elem_base - nope_dim + Int32(elem)) * 2).to(Int64)
313 )
314 k_cache_u32.iterator[out_base // Int64(4)] = packed
315 else:
316 q = cute.make_rmem_tensor((elems_per_lane,), Float32)
317 local_absmax = Float32(0.0)
318 for pair in cutlass.range_constexpr(elems_per_lane // 2):
319 elem = const_expr(pair * 2)
320 q_packed = _fp32x2_to_bf16x2(x[elem], x[elem + 1])
321 q0, q1 = _bf16x2_to_fp32(q_packed)
322 q[elem] = q0
323 q[elem + 1] = q1
324 local_absmax = cute.arch.fmax(
325 local_absmax,
326 cute.arch.fmax(cute.math.absf(q0), cute.math.absf(q1)),
327 )
328 absmax = local_absmax
329 group_mask_and_clamp = const_expr(
330 (cute.arch.WARP_SIZE - lanes_per_group) << 8
331 | (cute.arch.WARP_SIZE - 1)
332 )
333 for step in cutlass.range_constexpr(scale_reduce_steps):
334 offset = const_expr(scale_reduce_offset >> step)
335 absmax = cute.arch.fmax(
336 absmax,
337 cute.arch.shuffle_sync_bfly(
338 absmax,
339 offset=offset,
340 mask_and_clamp=group_mask_and_clamp,
341 ),
342 )
343 scale_raw = cute.arch.fmax(
344 Float32(min_scale),
345 absmax / Float32(fp8_max),
346 )
347 bits = _recast_val(scale_raw, Uint32)
348 ue8m0 = ((bits + Uint32(0x7FFFFF)) >> Uint32(23)) & Uint32(0xFF)
349 inv_scale = _recast_val(
350 (Uint32(254) - ue8m0) << Uint32(23), Float32
351 )
352 for pair in cutlass.range_constexpr(elems_per_lane // 2):
353 elem = const_expr(pair * 2)
354 y0 = cutlass.min(
355 cute.arch.fmax(q[elem] * inv_scale, Float32(-fp8_max)),
356 Float32(fp8_max),
357 )
358 y1 = cutlass.min(
359 cute.arch.fmax(q[elem + 1] * inv_scale, Float32(-fp8_max)),
360 Float32(fp8_max),
361 )
362 packed_fp8 = _fp32x2_to_fp8e4m3x2(y0, y1)
363 out_base = value_base + (elem_base + Int32(elem)).to(Int64)
364 k_cache_u16.iterator[out_base // Int64(2)] = packed_fp8
365 if group_lane == 0:
366 k_cache.iterator[scale_base + group_idx.to(Int64)] = ue8m0.to(
367 Uint8
368 )
369 if group_idx == 0:
370 k_cache.iterator[scale_base + Int64(nope_blocks)] = Uint8(0)
371
372 @cute.jit
373 def host_entrypoint(
374 state_cache: cute.Tensor,
375 token_to_req_indices: cute.Tensor,
376 positions: cute.Tensor,
377 slot_mapping: cute.Tensor,
378 block_table: cute.Tensor,
379 block_size: Int64,
380 rms_norm_weight: cute.Tensor,
381 rms_norm_eps: Float32,
382 cos_sin_cache: cute.Tensor,
383 k_cache: cute.Tensor,
384 kv_slot_mapping: cute.Tensor,
385 kv_cache_block_size: Int64,
386 stream: CUstream,
387 ):
388 grid = (slot_mapping.shape[0], 1, 1)
389 device_kernel(
390 state_cache,
391 token_to_req_indices,
392 positions,
393 slot_mapping,
394 block_table,
395 block_size,
396 rms_norm_weight,
397 rms_norm_eps,
398 cos_sin_cache,
399 k_cache,
400 kv_slot_mapping,
401 kv_cache_block_size,
402 ).launch(grid=grid, block=(tb_size, 1, 1), stream=stream)
403
404 return host_entrypoint
405
406 def dispatch( # type: ignore[override]
407 self,
408 *,
409 compress_ratio: int,
410 norm_weight_dtype: type[cutlass.Numeric],
411 head_size: int,
412 rope_head_dim: int,
413 ) -> CompileKey:
414 overlap = compress_ratio == 4
415 state_width = head_size * (2 if overlap else 1)
416 return self.CompileKey(
417 head_size=head_size,
418 state_width=state_width,
419 rope_head_dim=rope_head_dim,
420 fp8_max=448.0,
421 quant_block=64,
422 token_stride=576,
423 scale_dim=8,
424 compress_ratio=compress_ratio,
425 overlap=overlap,
426 norm_weight_dtype=norm_weight_dtype,
427 )
428
429 def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
430 hf_config = vllm_config.model_config.hf_config
431 cache_config = vllm_config.cache_config
432 return self._trace_dispatch(self.dispatch)(
433 compress_ratio=4,
434 norm_weight_dtype=torch_to_cute_dtype(vllm_config.model_config.dtype),
435 head_size=hf_config.head_dim,
436 rope_head_dim=hf_config.qk_rope_head_dim,
437 enabled=(
438 cache_config.block_size > 0
439 and 4 in hf_config.compress_ratios
440 and cache_config.cache_dtype == "fp8_ds_mla"
441 ),
442 _when=lambda *, enabled: enabled,
443 )
444
445 def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
446 head_size = compile_key.head_size
447 state_width = compile_key.state_width
448 rope_head_dim = compile_key.rope_head_dim
449 quant_block = compile_key.quant_block
450 token_stride = compile_key.token_stride
451 scale_dim = compile_key.scale_dim
452 compress_ratio = compile_key.compress_ratio
453 overlap = compile_key.overlap
454 norm_weight_dtype = compile_key.norm_weight_dtype
455 if compress_ratio != 4 or not overlap:
456 raise ValueError("CuTe DSL C4 fused sparse-attn requires C4 overlap.")
457 if head_size != 512:
458 raise ValueError(
459 "CuTe DSL C4 fused sparse-attn currently requires head_size=512.",
460 )
461 if state_width != 2 * head_size:
462 raise ValueError(
463 "CuTe DSL C4 fused sparse-attn requires state_width=2*head_size.",
464 )
465 if quant_block != 64:
466 raise ValueError(
467 "CuTe DSL C4 fused sparse-attn currently requires quant_block=64.",
468 )
469 if rope_head_dim != 64:
470 raise ValueError(
471 "CuTe DSL C4 fused sparse-attn currently requires rope_head_dim=64.",
472 )
473 if token_stride < head_size + rope_head_dim:
474 raise ValueError("token_stride is too small for the packed FP8/BF16 row.")
475 expected_scale_dim = (head_size - rope_head_dim) // quant_block + 1
476 if scale_dim < expected_scale_dim:
477 raise ValueError("scale_dim is too small for the UE8M0 scale row.")
478 num_positions = cute.sym_int()
479 num_slots = cute.sym_int()
480 num_req_indices = cute.sym_int()
481 num_kv_slots = cute.sym_int()
482 num_state_blocks = cute.sym_int()
483 num_kv_blocks = cute.sym_int()
484 state_cache_block_size = cute.sym_int()
485 block_table_width = cute.sym_int()
486 max_pos = cute.sym_int()
487 state_cache_width = state_width * 2
488 state_cache = cute.runtime.make_fake_tensor(
489 Float32,
490 (num_state_blocks, state_cache_block_size, state_cache_width),
491 stride=(
492 cute.sym_int64(divisibility=16),
493 cute.sym_int64(divisibility=16),
494 1,
495 ),
496 assumed_align=16,
497 )
498 token_to_req_indices = make_fake_tensor(
499 Int32,
500 (num_req_indices,),
501 divisibility=4,
502 )
503 positions = make_fake_tensor(Int64, (num_positions,), divisibility=8)
504 slot_mapping = make_fake_tensor(Int64, (num_slots,), divisibility=8)
505 block_table = make_fake_tensor(
506 Int32,
507 (cute.sym_int(), block_table_width),
508 divisibility=1,
509 )
510 rms_norm_weight = make_fake_tensor(
511 norm_weight_dtype,
512 (head_size,),
513 divisibility=4,
514 )
515 cos_sin_cache = cute.runtime.make_fake_tensor(
516 Float32,
517 (max_pos, rope_head_dim),
518 stride=(cute.sym_int64(divisibility=4), 1),
519 assumed_align=4,
520 )
521 k_cache = cute.runtime.make_fake_tensor(
522 Uint8,
523 (num_kv_blocks, cute.sym_int(), cute.sym_int()),
524 stride=(cute.sym_int64(divisibility=16), cute.sym_int64(divisibility=8), 1),
525 assumed_align=16,
526 )
527 kv_slot_mapping = make_fake_tensor(Int64, (num_kv_slots,), divisibility=8)
528
529 return (
530 state_cache,
531 token_to_req_indices,
532 positions,
533 slot_mapping,
534 block_table,
535 Int64(0),
536 rms_norm_weight,
537 Float32(0.0),
538 cos_sin_cache,
539 k_cache,
540 kv_slot_mapping,
541 Int64(0),
542 )
543
544 @kernel_launcher
545 def __call__(
546 self,
547 *,
548 state_cache: torch.Tensor,
549 token_to_req_indices: torch.Tensor,
550 positions: torch.Tensor,
551 slot_mapping: torch.Tensor,
552 block_table: torch.Tensor,
553 block_size: int,
554 rms_norm_weight: torch.Tensor,
555 rms_norm_eps: float,
556 cos_sin_cache: torch.Tensor,
557 kv_cache: torch.Tensor,
558 kv_slot_mapping: torch.Tensor,
559 compress_ratio: int,
560 head_dim: int,
561 rope_head_dim: int,
562 ) -> CuTeDSLLaunchSpec[SparseAttnCompressNormRopeStoreC4Kernel.CompileKey]:
563 compile_key = self.dispatch(
564 compress_ratio=compress_ratio,
565 norm_weight_dtype=torch_to_cute_dtype(rms_norm_weight.dtype),
566 head_size=head_dim,
567 rope_head_dim=rope_head_dim,
568 )
569 launch_args = (
570 state_cache,
571 token_to_req_indices,
572 positions,
573 slot_mapping,
574 block_table,
575 block_size,
576 rms_norm_weight,
577 rms_norm_eps,
578 cos_sin_cache,
579 kv_cache,
580 kv_slot_mapping,
581 kv_cache.shape[1],
582 )
583 return compile_key, launch_args
584
585
586 class SparseAttnCompressNormRopeStoreFullC4Kernel(
587 VllmCuTeDSLJitKernel["SparseAttnCompressNormRopeStoreFullC4Kernel.CompileKey"]
588 ):
589 rcp_ln2 = 1.4426950408889634
590
591 @dataclass(frozen=True)
592 class CompileKey:
593 head_size: int
594 state_width: int
595 rope_head_dim: int
596 fp8_max: float
597 quant_block: int
598 compress_ratio: int
599 overlap: bool
600 store_full_fp8: bool
601 norm_weight_dtype: type[cutlass.Numeric]
602
603 @staticmethod
604 def kernel(compile_key: CompileKey) -> Any:
605 rcp_ln2 = SparseAttnCompressNormRopeStoreFullC4Kernel.rcp_ln2
606 head_dim = compile_key.head_size
607 state_width = compile_key.state_width
608 rope_dim = compile_key.rope_head_dim
609 nope_dim = head_dim - rope_dim
610 fp8_max = compile_key.fp8_max
611 quant_block = compile_key.quant_block
612 elems_per_lane = 8
613 copy_elems = 4
614 copy_chunks = elems_per_lane // copy_elems
615 lanes_per_group = quant_block // elems_per_lane
616 groups_per_warp = 32 // lanes_per_group
617 num_warps = (head_dim // quant_block) // groups_per_warp
618 nope_blocks = nope_dim // quant_block
619 tb_size = num_warps * 32
620 compress_ratio = compile_key.compress_ratio
621 overlap = compile_key.overlap
622 window = (1 + int(overlap)) * compress_ratio
623 store_full_fp8 = compile_key.store_full_fp8
624
625 @cute.kernel
626 def device_kernel(
627 state_cache: cute.Tensor,
628 token_to_req_indices: cute.Tensor,
629 positions: cute.Tensor,
630 slot_mapping: cute.Tensor,
631 block_table: cute.Tensor,
632 block_size: Int64,
633 rms_norm_weight: cute.Tensor,
634 rms_norm_eps: Float32,
635 cos_sin_cache: cute.Tensor,
636 k_cache: cute.Tensor,
637 kv_slot_mapping: cute.Tensor,
638 kv_cache_block_size: Int64,
639 fp8_scale: cute.Tensor,
640 ):
641 token_idx, _, _ = cute.arch.block_idx()
642 tid, _, _ = cute.arch.thread_idx()
643 warp_id = cute.arch.make_warp_uniform(tid // 32)
644 lane_id = tid % 32
645 group_lane = lane_id % lanes_per_group
646 group_idx = warp_id * groups_per_warp + lane_id // lanes_per_group
647 elem_base = group_idx * quant_block + group_lane * elems_per_lane
648
649 slot_id = slot_mapping[token_idx]
650 has_position = token_idx < positions.shape[0]
651 position = Int64(0)
652 if has_position:
653 position = positions[token_idx]
654 boundary = has_position and (
655 (position + Int64(1)) % Int64(compress_ratio) == Int64(0)
656 )
657 has_req_idx = token_idx < token_to_req_indices.shape[0]
658 has_kv_slot_idx = token_idx < kv_slot_mapping.shape[0]
659 kv_slot_idx = Int64(-1)
660 if has_kv_slot_idx:
661 kv_slot_idx = kv_slot_mapping[token_idx]
662 active = (
663 slot_id >= Int64(0)
664 and has_req_idx
665 and boundary
666 and kv_slot_idx >= Int64(0)
667 )
668
669 if active:
670 req_idx = token_to_req_indices[token_idx]
671 start = position - Int64(window - 1)
672
673 smem = cutlass.utils.SmemAllocator()
674 s_block_numbers = smem.allocate_tensor(
675 Int32, cute.make_layout((window,)), byte_alignment=4
676 )
677 partial_sums = smem.allocate_tensor(
678 Float32, cute.make_layout((num_warps,)), byte_alignment=4
679 )
680 rrms_shared = smem.allocate_tensor(
681 Float32, cute.make_layout((1,)), byte_alignment=4
682 )
683
684 for row in cutlass.range_constexpr(window):
685 pos = start + Int64(row)
686 if tid == row:
687 block_number_i32 = Int32(0)
688 if pos >= Int64(0):
689 block_index = pos // block_size
690 block_number_i32 = block_table[req_idx, block_index]
691 s_block_numbers[row] = block_number_i32
692 cute.arch.sync_threads()
693
694 local_max = cute.make_rmem_tensor((elems_per_lane,), Float32)
695 local_sum = cute.make_rmem_tensor((elems_per_lane,), Float32)
696 local_product = cute.make_rmem_tensor((elems_per_lane,), Float32)
697
698 for e in cutlass.range_constexpr(elems_per_lane):
699 local_max[e] = -Float32.inf
700 local_sum[e] = Float32(0.0)
701 local_product[e] = Float32(0.0)
702
703 cp_f32x4 = cute.make_copy_atom(
704 cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128
705 )
706 copy_layout = cute.make_layout(
707 (copy_chunks, copy_elems),
708 stride=(copy_elems, 1),
709 )
710 kv_vals = cute.make_rmem_tensor(copy_layout, Float32)
711 score_vals = cute.make_rmem_tensor(copy_layout, Float32)
712
713 for row in cutlass.range_constexpr(window):
714 pos = start + Int64(row)
715 if pos >= Int64(0):
716 block_index = pos // block_size
717 block_offset = pos - block_index * block_size
718 block_number = s_block_numbers[row].to(Int64)
719 head_offset = Int64((row // compress_ratio) * head_dim)
720 row_tensor = state_cache[block_number, block_offset, None]
721 for chunk in cutlass.range_constexpr(copy_chunks):
722 copy_elem = const_expr(chunk * copy_elems)
723 col_tile = (
724 head_offset + (elem_base + Int32(copy_elem)).to(Int64)
725 ) // Int64(copy_elems)
726 kv_src = cute.local_tile(
727 row_tensor,
728 tiler=(copy_elems,),
729 coord=(col_tile,),
730 )
731 score_src = cute.local_tile(
732 row_tensor,
733 tiler=(copy_elems,),
734 coord=(col_tile + Int64(state_width // copy_elems),),
735 )
736 cute.copy(cp_f32x4, kv_src, kv_vals[chunk, None])
737 cute.copy(cp_f32x4, score_src, score_vals[chunk, None])
738
739 for e in cutlass.range_constexpr(elems_per_lane):
740 chunk = const_expr(e // copy_elems)
741 copy_elem = const_expr(e % copy_elems)
742 score = score_vals[chunk, copy_elem]
743 kv = kv_vals[chunk, copy_elem]
744 new_max = cute.arch.fmax(local_max[e], score)
745 old_scale = cute.math.exp2(
746 (local_max[e] - new_max) * Float32(rcp_ln2),
747 fastmath=True,
748 )
749 new_scale = cute.math.exp2(
750 (score - new_max) * Float32(rcp_ln2),
751 fastmath=True,
752 )
753 local_sum[e] = local_sum[e] * old_scale + new_scale
754 local_product[e] = (
755 local_product[e] * old_scale + kv * new_scale
756 )
757 local_max[e] = new_max
758
759 x = cute.make_rmem_tensor((elems_per_lane,), Float32)
760 local_sumsq = Float32(0.0)
761 for e in cutlass.range_constexpr(elems_per_lane):
762 x[e] = local_product[e] / local_sum[e]
763 local_sumsq += x[e] * x[e]
764
765 warp_sum = local_sumsq
766 for step in cutlass.range_constexpr(5):
767 offset = const_expr(16 >> step)
768 warp_sum += cute.arch.shuffle_sync_bfly(warp_sum, offset)
769
770 if lane_id == 0:
771 partial_sums[warp_id] = warp_sum
772 cute.arch.sync_threads()
773 if tid == 0:
774 total = Float32(0.0)
775 for i in cutlass.range_constexpr(num_warps):
776 total += partial_sums[i]
777 rrms_shared[0] = cute.math.rsqrt(
778 total / Float32(head_dim) + rms_norm_eps, fastmath=True
779 )
780 cute.arch.sync_threads()
781
782 rrms = rrms_shared[0]
783 for e in cutlass.range_constexpr(elems_per_lane):
784 elem = elem_base + e
785 x[e] = x[e] * rrms * rms_norm_weight[elem].to(Float32)
786
787 page = kv_slot_idx // kv_cache_block_size
788 kv_offset = kv_slot_idx - page * kv_cache_block_size
789 value_base = page * k_cache.stride[0] + kv_offset * k_cache.stride[1]
790
791 if const_expr(store_full_fp8):
792 k_cache_u16 = cute.recast_tensor(k_cache, Uint16)
793 inv_fp8 = Float32(1.0) / fp8_scale[0]
794 if group_idx == nope_blocks:
795 compressed_pos = (position // Int64(compress_ratio)) * Int64(
796 compress_ratio
797 )
798 for pair in cutlass.range_constexpr(elems_per_lane // 2):
799 elem = const_expr(pair * 2)
800 pair_idx = (elem_base - nope_dim) // 2 + Int32(pair)
801 cos_v = cos_sin_cache[compressed_pos, pair_idx]
802 sin_v = cos_sin_cache[
803 compressed_pos, pair_idx + Int32(rope_dim // 2)
804 ]
805 real = x[elem] * cos_v - x[elem + 1] * sin_v
806 imag = x[elem] * sin_v + x[elem + 1] * cos_v
807 packed_bf16 = _fp32x2_to_bf16x2(real, imag)
808 b0, b1 = _bf16x2_to_fp32(packed_bf16)
809 y0 = cutlass.min(
810 cutlass.max(b0 * inv_fp8, Float32(-fp8_max)),
811 Float32(fp8_max),
812 )
813 y1 = cutlass.min(
814 cutlass.max(b1 * inv_fp8, Float32(-fp8_max)),
815 Float32(fp8_max),
816 )
817 packed_fp8 = _fp32x2_to_fp8e4m3x2(y0, y1)
818 out_base = value_base + (elem_base + Int32(elem)).to(Int64)
819 k_cache_u16.iterator[out_base // Int64(2)] = packed_fp8
820 else:
821 for pair in cutlass.range_constexpr(elems_per_lane // 2):
822 elem = const_expr(pair * 2)
823 packed_bf16 = _fp32x2_to_bf16x2(x[elem], x[elem + 1])
824 b0, b1 = _bf16x2_to_fp32(packed_bf16)
825 y0 = cutlass.min(
826 cutlass.max(b0 * inv_fp8, Float32(-fp8_max)),
827 Float32(fp8_max),
828 )
829 y1 = cutlass.min(
830 cutlass.max(b1 * inv_fp8, Float32(-fp8_max)),
831 Float32(fp8_max),
832 )
833 packed_fp8 = _fp32x2_to_fp8e4m3x2(y0, y1)
834 out_base = value_base + (elem_base + Int32(elem)).to(Int64)
835 k_cache_u16.iterator[out_base // Int64(2)] = packed_fp8
836 else:
837 k_cache_u32 = cute.recast_tensor(k_cache, Uint32)
838 if group_idx == nope_blocks:
839 compressed_pos = (position // Int64(compress_ratio)) * Int64(
840 compress_ratio
841 )
842 for pair in cutlass.range_constexpr(elems_per_lane // 2):
843 elem = const_expr(pair * 2)
844 pair_idx = (elem_base - nope_dim) // 2 + Int32(pair)
845 cos_v = cos_sin_cache[compressed_pos, pair_idx]
846 sin_v = cos_sin_cache[
847 compressed_pos, pair_idx + Int32(rope_dim // 2)
848 ]
849 real = x[elem] * cos_v - x[elem + 1] * sin_v
850 imag = x[elem] * sin_v + x[elem + 1] * cos_v
851 packed_bf16 = _fp32x2_to_bf16x2(real, imag)
852 out_base = value_base + ((elem_base + Int32(elem)) * 2).to(
853 Int64
854 )
855 k_cache_u32.iterator[out_base // Int64(4)] = packed_bf16
856 else:
857 for pair in cutlass.range_constexpr(elems_per_lane // 2):
858 elem = const_expr(pair * 2)
859 packed_bf16 = _fp32x2_to_bf16x2(x[elem], x[elem + 1])
860 out_base = value_base + ((elem_base + Int32(elem)) * 2).to(
861 Int64
862 )
863 k_cache_u32.iterator[out_base // Int64(4)] = packed_bf16
864
865 @cute.jit
866 def host_entrypoint(
867 state_cache: cute.Tensor,
868 token_to_req_indices: cute.Tensor,
869 positions: cute.Tensor,
870 slot_mapping: cute.Tensor,
871 block_table: cute.Tensor,
872 block_size: Int64,
873 rms_norm_weight: cute.Tensor,
874 rms_norm_eps: Float32,
875 cos_sin_cache: cute.Tensor,
876 k_cache: cute.Tensor,
877 kv_slot_mapping: cute.Tensor,
878 kv_cache_block_size: Int64,
879 fp8_scale: cute.Tensor,
880 stream: CUstream,
881 ):
882 grid = (slot_mapping.shape[0], 1, 1)
883 device_kernel(
884 state_cache,
885 token_to_req_indices,
886 positions,
887 slot_mapping,
888 block_table,
889 block_size,
890 rms_norm_weight,
891 rms_norm_eps,
892 cos_sin_cache,
893 k_cache,
894 kv_slot_mapping,
895 kv_cache_block_size,
896 fp8_scale,
897 ).launch(grid=grid, block=(tb_size, 1, 1), stream=stream)
898
899 return host_entrypoint
900
901 def dispatch( # type: ignore[override]
902 self,
903 *,
904 compress_ratio: int,
905 store_full_fp8: bool,
906 norm_weight_dtype: type[cutlass.Numeric],
907 head_size: int,
908 rope_head_dim: int,
909 ) -> CompileKey:
910 overlap = compress_ratio == 4
911 return self.CompileKey(
912 head_size=head_size,
913 state_width=head_size * (2 if overlap else 1),
914 rope_head_dim=rope_head_dim,
915 fp8_max=448.0,
916 quant_block=64,
917 compress_ratio=compress_ratio,
918 overlap=overlap,
919 store_full_fp8=store_full_fp8,
920 norm_weight_dtype=norm_weight_dtype,
921 )
922
923 def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
924 hf_config = vllm_config.model_config.hf_config
925 cache_config = vllm_config.cache_config
926 return self._trace_dispatch(self.dispatch)(
927 compress_ratio=4,
928 store_full_fp8=(
929 cache_config.cache_dtype.startswith("fp8")
930 and cache_config.cache_dtype != "fp8_ds_mla"
931 ),
932 norm_weight_dtype=torch_to_cute_dtype(vllm_config.model_config.dtype),
933 head_size=hf_config.head_dim,
934 rope_head_dim=hf_config.qk_rope_head_dim,
935 enabled=(
936 cache_config.block_size > 0
937 and 4 in hf_config.compress_ratios
938 and cache_config.cache_dtype != "fp8_ds_mla"
939 ),
940 _when=lambda *, enabled: enabled,
941 )
942
943 def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
944 head_size = compile_key.head_size
945 state_width = compile_key.state_width
946 rope_head_dim = compile_key.rope_head_dim
947 quant_block = compile_key.quant_block
948 compress_ratio = compile_key.compress_ratio
949 overlap = compile_key.overlap
950 norm_weight_dtype = compile_key.norm_weight_dtype
951 if compress_ratio != 4 or not overlap:
952 raise ValueError("CuTe DSL C4 fused sparse-attn requires C4 overlap.")
953 if head_size != 512:
954 raise ValueError(
955 "CuTe DSL C4 fused sparse-attn currently requires head_size=512.",
956 )
957 if state_width != 2 * head_size:
958 raise ValueError(
959 "CuTe DSL C4 fused sparse-attn requires state_width=2*head_size.",
960 )
961 if quant_block != 64:
962 raise ValueError(
963 "CuTe DSL C4 fused sparse-attn currently requires quant_block=64.",
964 )
965 if rope_head_dim != 64:
966 raise ValueError(
967 "CuTe DSL C4 fused sparse-attn currently requires rope_head_dim=64.",
968 )
969 num_positions = cute.sym_int()
970 num_slots = cute.sym_int()
971 num_req_indices = cute.sym_int()
972 num_kv_slots = cute.sym_int()
973 num_state_blocks = cute.sym_int()
974 num_kv_blocks = cute.sym_int()
975 state_cache_block_size = cute.sym_int()
976 block_table_width = cute.sym_int()
977 max_pos = cute.sym_int()
978 state_cache_width = state_width * 2
979 state_cache = cute.runtime.make_fake_tensor(
980 Float32,
981 (num_state_blocks, state_cache_block_size, state_cache_width),
982 stride=(
983 cute.sym_int64(divisibility=16),
984 cute.sym_int64(divisibility=16),
985 1,
986 ),
987 assumed_align=16,
988 )
989 token_to_req_indices = make_fake_tensor(
990 Int32,
991 (num_req_indices,),
992 divisibility=4,
993 )
994 positions = make_fake_tensor(Int64, (num_positions,), divisibility=8)
995 slot_mapping = make_fake_tensor(Int64, (num_slots,), divisibility=8)
996 block_table = make_fake_tensor(
997 Int32,
998 (cute.sym_int(), block_table_width),
999 divisibility=1,
1000 )
1001 rms_norm_weight = make_fake_tensor(
1002 norm_weight_dtype,
1003 (head_size,),
1004 divisibility=4,
1005 )
1006 cos_sin_cache = cute.runtime.make_fake_tensor(
1007 Float32,
1008 (max_pos, rope_head_dim),
1009 stride=(cute.sym_int64(divisibility=4), 1),
1010 assumed_align=4,
1011 )
1012 k_cache = cute.runtime.make_fake_tensor(
1013 Uint8,
1014 (num_kv_blocks, cute.sym_int(), cute.sym_int()),
1015 stride=(cute.sym_int64(divisibility=16), cute.sym_int64(divisibility=8), 1),
1016 assumed_align=16,
1017 )
1018 kv_slot_mapping = make_fake_tensor(Int64, (num_kv_slots,), divisibility=8)
1019 fp8_scale = make_fake_tensor(Float32, (1,), divisibility=1)
1020 return (
1021 state_cache,
1022 token_to_req_indices,
1023 positions,
1024 slot_mapping,
1025 block_table,
1026 Int64(0),
1027 rms_norm_weight,
1028 Float32(0.0),
1029 cos_sin_cache,
1030 k_cache,
1031 kv_slot_mapping,
1032 Int64(0),
1033 fp8_scale,
1034 )
1035
1036 @kernel_launcher
1037 def __call__(
1038 self,
1039 *,
1040 state_cache: torch.Tensor,
1041 token_to_req_indices: torch.Tensor,
1042 positions: torch.Tensor,
1043 slot_mapping: torch.Tensor,
1044 block_table: torch.Tensor,
1045 block_size: int,
1046 rms_norm_weight: torch.Tensor,
1047 rms_norm_eps: float,
1048 cos_sin_cache: torch.Tensor,
1049 kv_cache: torch.Tensor,
1050 kv_slot_mapping: torch.Tensor,
1051 compress_ratio: int,
1052 store_full_fp8: bool,
1053 fp8_scale: torch.Tensor | None,
1054 head_dim: int,
1055 rope_head_dim: int,
1056 ) -> CuTeDSLLaunchSpec[SparseAttnCompressNormRopeStoreFullC4Kernel.CompileKey]:
1057 compile_key = self.dispatch(
1058 compress_ratio=compress_ratio,
1059 store_full_fp8=store_full_fp8,
1060 norm_weight_dtype=torch_to_cute_dtype(rms_norm_weight.dtype),
1061 head_size=head_dim,
1062 rope_head_dim=rope_head_dim,
1063 )
1064 if fp8_scale is None:
1065 fp8_scale = torch.ones(1, dtype=torch.float32, device=kv_cache.device)
1066 launch_args = (
1067 state_cache,
1068 token_to_req_indices,
1069 positions,
1070 slot_mapping,
1071 block_table,
1072 block_size,
1073 rms_norm_weight,
1074 rms_norm_eps,
1075 cos_sin_cache,
1076 kv_cache.view(torch.uint8),
1077 kv_slot_mapping,
1078 kv_cache.shape[1],
1079 fp8_scale,
1080 )
1081 return compile_key, launch_args
1082
1083
1084 class SparseAttnCompressC128Block8Kernel(
1085 VllmCuTeDSLJitKernel["SparseAttnCompressC128Block8Kernel.CompileKey"]
1086 ):
1087 head_tile = 64
1088 rows_per_warp = 16
1089 elems_per_lane = 2
1090 lanes_per_row = head_tile // elems_per_lane
1091 num_warps = 8
1092 stats_lane_stride = lanes_per_row + 1
1093 final_reduce_steps = 3
1094 final_reduce_initial_offset = 4
1095 tb_size = num_warps * 32
1096 compress_ratio = 128
1097 state_block_size = 8
1098 rcp_ln2 = 1.4426950408889634
1099
1100 @dataclass(frozen=True)
1101 class CompileKey:
1102 head_size: int
1103 state_width: int
1104
1105 @staticmethod
1106 def kernel(compile_key: CompileKey) -> Any:
1107 head_tile = SparseAttnCompressC128Block8Kernel.head_tile
1108 rows_per_warp = SparseAttnCompressC128Block8Kernel.rows_per_warp
1109 elems_per_lane = SparseAttnCompressC128Block8Kernel.elems_per_lane
1110 lanes_per_row = SparseAttnCompressC128Block8Kernel.lanes_per_row
1111 num_warps = SparseAttnCompressC128Block8Kernel.num_warps
1112 stats_lane_stride = SparseAttnCompressC128Block8Kernel.stats_lane_stride
1113 final_reduce_steps = SparseAttnCompressC128Block8Kernel.final_reduce_steps
1114 final_reduce_initial_offset = (
1115 SparseAttnCompressC128Block8Kernel.final_reduce_initial_offset
1116 )
1117 tb_size = SparseAttnCompressC128Block8Kernel.tb_size
1118 compress_ratio = SparseAttnCompressC128Block8Kernel.compress_ratio
1119 state_block_size = SparseAttnCompressC128Block8Kernel.state_block_size
1120 rcp_ln2 = SparseAttnCompressC128Block8Kernel.rcp_ln2
1121 head_dim = compile_key.head_size
1122 num_splits = head_dim // head_tile
1123 state_width = compile_key.state_width
1124
1125 @cute.kernel
1126 def device_kernel(
1127 state_cache: cute.Tensor,
1128 token_to_req_indices: cute.Tensor,
1129 positions: cute.Tensor,
1130 slot_mapping: cute.Tensor,
1131 block_table: cute.Tensor,
1132 compressed_kv: cute.Tensor,
1133 ):
1134 block_id, _, _ = cute.arch.block_idx()
1135 tid, _, _ = cute.arch.thread_idx()
1136 warp_id = cute.arch.make_warp_uniform(tid // 32)
1137 lane_id = tid % 32
1138 col_group = lane_id % lanes_per_row
1139
1140 token_idx = block_id // num_splits
1141 split_idx = block_id - token_idx * num_splits
1142 col_base = split_idx * head_tile + col_group * elems_per_lane
1143
1144 position = Int64(0)
1145 req_idx = Int32(0)
1146 slot_id = Int64(-1)
1147 has_position = token_idx < positions.shape[0]
1148 has_req_idx = token_idx < token_to_req_indices.shape[0]
1149 if lane_id == 0:
1150 slot_id = slot_mapping[token_idx]
1151 if lane_id == 0 and has_position:
1152 position = positions[token_idx]
1153 if lane_id == 0 and has_req_idx:
1154 req_idx = token_to_req_indices[token_idx]
1155 slot_id = cute.arch.shuffle_sync(slot_id, offset=0)
1156 position = cute.arch.shuffle_sync(position, offset=0)
1157 req_idx = cute.arch.shuffle_sync(req_idx, offset=0)
1158 boundary = has_position and (
1159 (position + Int64(1)) % Int64(compress_ratio) == Int64(0)
1160 )
1161 start = position - Int64(compress_ratio - 1)
1162 active = slot_id >= Int64(0) and has_req_idx and boundary
1163
1164 if active:
1165 smem = cutlass.utils.SmemAllocator()
1166 s_max = smem.allocate_tensor(
1167 Float32,
1168 cute.make_layout(
1169 (
1170 num_warps,
1171 lanes_per_row,
1172 elems_per_lane,
1173 ),
1174 stride=(
1175 stats_lane_stride * elems_per_lane,
1176 elems_per_lane,
1177 1,
1178 ),
1179 ),
1180 byte_alignment=4,
1181 )
1182 s_sum = smem.allocate_tensor(
1183 Float32,
1184 cute.make_layout(
1185 (
1186 num_warps,
1187 lanes_per_row,
1188 elems_per_lane,
1189 ),
1190 stride=(
1191 stats_lane_stride * elems_per_lane,
1192 elems_per_lane,
1193 1,
1194 ),
1195 ),
1196 byte_alignment=4,
1197 )
1198 s_product = smem.allocate_tensor(
1199 Float32,
1200 cute.make_layout(
1201 (
1202 num_warps,
1203 lanes_per_row,
1204 elems_per_lane,
1205 ),
1206 stride=(
1207 stats_lane_stride * elems_per_lane,
1208 elems_per_lane,
1209 1,
1210 ),
1211 ),
1212 byte_alignment=4,
1213 )
1214
1215 row_layout = cute.make_layout(
1216 (rows_per_warp, elems_per_lane),
1217 stride=(elems_per_lane, 1),
1218 )
1219 kv_vals = cute.make_rmem_tensor(row_layout, Float32)
1220 score_vals = cute.make_rmem_tensor(row_layout, Float32)
1221 local_max = cute.make_rmem_tensor((elems_per_lane,), Float32)
1222 local_sum = cute.make_rmem_tensor((elems_per_lane,), Float32)
1223 local_product = cute.make_rmem_tensor((elems_per_lane,), Float32)
1224
1225 for e in cutlass.range_constexpr(elems_per_lane):
1226 local_max[e] = -Float32.inf
1227 local_sum[e] = Float32(0.0)
1228 local_product[e] = Float32(0.0)
1229
1230 first_block_index = start // Int64(state_block_size)
1231 warp_block_index = first_block_index + (warp_id * 2).to(Int64)
1232 block0_i32 = Int32(0)
1233 block1_i32 = Int32(0)
1234 if lane_id == 0:
1235 block0_i32 = block_table[req_idx, warp_block_index]
1236 block1_i32 = block_table[req_idx, warp_block_index + Int64(1)]
1237 block0_i32 = cute.arch.shuffle_sync(block0_i32, offset=0)
1238 block1_i32 = cute.arch.shuffle_sync(block1_i32, offset=0)
1239
1240 cp_f32x2 = cute.make_copy_atom(
1241 cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=64
1242 )
1243 final_mask_and_clamp = const_expr(
1244 (cute.arch.WARP_SIZE - num_warps) << 8 | (cute.arch.WARP_SIZE - 1)
1245 )
1246 col_tile = col_base.to(Int64) // Int64(elems_per_lane)
1247 score_col_tile = col_tile + Int64(state_width // elems_per_lane)
1248
1249 for i in cutlass.range_constexpr(rows_per_warp):
1250 block_number_i32 = block0_i32
1251 block_offset = Int64(i)
1252 if const_expr(i >= state_block_size):
1253 block_number_i32 = block1_i32
1254 block_offset = Int64(i - state_block_size)
1255 row_tensor = state_cache[
1256 block_number_i32.to(Int64), block_offset, None
1257 ]
1258 kv_src = cute.local_tile(
1259 row_tensor,
1260 tiler=(elems_per_lane,),
1261 coord=(col_tile,),
1262 )
1263 score_src = cute.local_tile(
1264 row_tensor,
1265 tiler=(elems_per_lane,),
1266 coord=(score_col_tile,),
1267 )
1268 cute.copy(cp_f32x2, kv_src, kv_vals[i, None])
1269 cute.copy(cp_f32x2, score_src, score_vals[i, None])
1270
1271 for e in cutlass.range_constexpr(elems_per_lane):
1272 local_max[e] = cute.arch.fmax(local_max[e], score_vals[i, e])
1273
1274 for e in cutlass.range_constexpr(elems_per_lane):
1275 for i in cutlass.range_constexpr(rows_per_warp):
1276 exp_score = cute.math.exp2(
1277 (score_vals[i, e] - local_max[e]) * Float32(rcp_ln2),
1278 fastmath=True,
1279 )
1280 local_sum[e] += exp_score
1281 local_product[e] += kv_vals[i, e] * exp_score
1282
1283 for e in cutlass.range_constexpr(elems_per_lane):
1284 s_max[warp_id, col_group, e] = local_max[e]
1285 s_sum[warp_id, col_group, e] = local_sum[e]
1286 s_product[warp_id, col_group, e] = local_product[e]
1287 cute.arch.sync_threads()
1288
1289 out_group = tid // num_warps
1290 final_lane = tid % num_warps
1291 final_groups_per_pass = const_expr(tb_size // num_warps)
1292 for pass_idx in cutlass.range_constexpr(
1293 head_tile // final_groups_per_pass
1294 ):
1295 out_idx = pass_idx * final_groups_per_pass + out_group
1296 out_lane = out_idx // elems_per_lane
1297 out_elem = out_idx % elems_per_lane
1298
1299 local_warp_max = s_max[final_lane, out_lane, out_elem]
1300 global_max = local_warp_max
1301 for step in cutlass.range_constexpr(final_reduce_steps):
1302 offset = const_expr(final_reduce_initial_offset >> step)
1303 global_max = cute.arch.fmax(
1304 global_max,
1305 cute.arch.shuffle_sync_bfly(
1306 global_max,
1307 offset=offset,
1308 mask_and_clamp=final_mask_and_clamp,
1309 ),
1310 )
1311
1312 scale = cute.math.exp2(
1313 (local_warp_max - global_max) * Float32(rcp_ln2),
1314 fastmath=True,
1315 )
1316 global_sum = s_sum[final_lane, out_lane, out_elem] * scale
1317 global_product = s_product[final_lane, out_lane, out_elem] * scale
1318 for step in cutlass.range_constexpr(final_reduce_steps):
1319 offset = const_expr(final_reduce_initial_offset >> step)
1320 global_sum += cute.arch.shuffle_sync_bfly(
1321 global_sum,
1322 offset=offset,
1323 mask_and_clamp=final_mask_and_clamp,
1324 )
1325 global_product += cute.arch.shuffle_sync_bfly(
1326 global_product,
1327 offset=offset,
1328 mask_and_clamp=final_mask_and_clamp,
1329 )
1330
1331 if final_lane == 0:
1332 compressed_kv.iterator[
1333 token_idx.to(Int64) * compressed_kv.stride[0]
1334 + (split_idx * head_tile + out_idx).to(Int64)
1335 ] = global_product / global_sum
1336
1337 @cute.jit
1338 def host_entrypoint(
1339 state_cache: cute.Tensor,
1340 token_to_req_indices: cute.Tensor,
1341 positions: cute.Tensor,
1342 slot_mapping: cute.Tensor,
1343 block_table: cute.Tensor,
1344 compressed_kv: cute.Tensor,
1345 stream: CUstream,
1346 ):
1347 grid = (slot_mapping.shape[0] * num_splits, 1, 1)
1348 device_kernel(
1349 state_cache,
1350 token_to_req_indices,
1351 positions,
1352 slot_mapping,
1353 block_table,
1354 compressed_kv,
1355 ).launch(grid=grid, block=(tb_size, 1, 1), stream=stream)
1356
1357 return host_entrypoint
1358
1359 def dispatch( # type: ignore[override]
1360 self, *, head_size: int, state_width: int
1361 ) -> CompileKey:
1362 return self.CompileKey(head_size=head_size, state_width=state_width)
1363
1364 def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
1365 hf_config = vllm_config.model_config.hf_config
1366 return self._trace_dispatch(self.dispatch)(
1367 head_size=hf_config.head_dim,
1368 state_width=hf_config.head_dim,
1369 enabled=128 in hf_config.compress_ratios,
1370 _when=lambda *, enabled: enabled,
1371 )
1372
1373 def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
1374 head_size = compile_key.head_size
1375 state_width = compile_key.state_width
1376 if head_size % SparseAttnCompressC128Block8Kernel.head_tile != 0:
1377 raise ValueError("head_size must be divisible by the 64-wide head tile.")
1378 num_positions = cute.sym_int()
1379 num_slots = cute.sym_int()
1380 num_req_indices = cute.sym_int()
1381 num_blocks = cute.sym_int()
1382 state_cache_block_size = cute.sym_int()
1383 block_table_width = cute.sym_int()
1384 state_cache_width = state_width * 2
1385 state_cache = cute.runtime.make_fake_tensor(
1386 Float32,
1387 (num_blocks, state_cache_block_size, state_cache_width),
1388 stride=(
1389 cute.sym_int64(divisibility=16),
1390 cute.sym_int64(divisibility=16),
1391 1,
1392 ),
1393 assumed_align=16,
1394 )
1395 token_to_req_indices = make_fake_tensor(
1396 Int32,
1397 (num_req_indices,),
1398 divisibility=4,
1399 )
1400 positions = make_fake_tensor(Int64, (num_positions,), divisibility=8)
1401 slot_mapping = make_fake_tensor(Int64, (num_slots,), divisibility=8)
1402 block_table = make_fake_tensor(
1403 Int32,
1404 (cute.sym_int(), block_table_width),
1405 divisibility=1,
1406 )
1407 compressed_kv = cute.runtime.make_fake_tensor(
1408 Float32,
1409 (num_slots, head_size),
1410 stride=(head_size, 1),
1411 assumed_align=4,
1412 )
1413 return (
1414 state_cache,
1415 token_to_req_indices,
1416 positions,
1417 slot_mapping,
1418 block_table,
1419 compressed_kv,
1420 )
1421
1422 @kernel_launcher
1423 def __call__(
1424 self,
1425 *,
1426 state_cache: torch.Tensor,
1427 num_actual: int,
1428 token_to_req_indices: torch.Tensor,
1429 positions: torch.Tensor,
1430 slot_mapping: torch.Tensor,
1431 block_table: torch.Tensor,
1432 head_dim: int,
1433 compressed_kv: torch.Tensor | None = None,
1434 ) -> CuTeDSLLaunchSpec[SparseAttnCompressC128Block8Kernel.CompileKey]:
1435 compile_key = self.dispatch(head_size=head_dim, state_width=head_dim)
1436 if compressed_kv is None:
1437 compressed_kv = torch.empty(
1438 (num_actual, head_dim),
1439 dtype=torch.float32,
1440 device=state_cache.device,
1441 )
1442 launch_args = (
1443 state_cache,
1444 token_to_req_indices,
1445 positions,
1446 slot_mapping,
1447 block_table,
1448 compressed_kv,
1449 )
1450 return compile_key, launch_args, compressed_kv
1451
1452
1453 class SparseAttnNormRopeStoreKernel(
1454 VllmCuTeDSLJitKernel["SparseAttnNormRopeStoreKernel.CompileKey"]
1455 ):
1456 min_scale = 1.0e-4
1457
1458 @dataclass(frozen=True)
1459 class CompileKey:
1460 head_size: int
1461 rope_head_dim: int
1462 fp8_max: float
1463 quant_block: int
1464 token_stride: int
1465 scale_dim: int
1466 kv_block_stride: int
1467 compress_ratio: int
1468 norm_weight_dtype: type[cutlass.Numeric]
1469 kv_cache_block_size: int
1470
1471 @staticmethod
1472 def kernel(compile_key: CompileKey) -> Any:
1473 min_scale = SparseAttnNormRopeStoreKernel.min_scale
1474 head_dim = compile_key.head_size
1475 rope_dim = compile_key.rope_head_dim
1476 nope_dim = head_dim - rope_dim
1477 fp8_max = compile_key.fp8_max
1478 quant_block = compile_key.quant_block
1479 token_stride = compile_key.token_stride
1480 scale_dim = compile_key.scale_dim
1481 num_warps = head_dim // quant_block
1482 nope_blocks = nope_dim // quant_block
1483 tb_size = head_dim // 2
1484 compress_ratio = compile_key.compress_ratio
1485 static_kv_cache_block_size = compile_key.kv_cache_block_size
1486
1487 @cute.kernel
1488 def device_kernel(
1489 compressed_kv: cute.Tensor,
1490 positions: cute.Tensor,
1491 slot_mapping: cute.Tensor,
1492 rms_norm_weight: cute.Tensor,
1493 rms_norm_eps: Float32,
1494 cos_sin_cache: cute.Tensor,
1495 k_cache: cute.Tensor,
1496 kv_slot_mapping: cute.Tensor,
1497 ):
1498 token_idx, _, _ = cute.arch.block_idx()
1499 tid, _, _ = cute.arch.thread_idx()
1500 warp_id = cute.arch.make_warp_uniform(tid // 32)
1501 lane_id = tid % 32
1502 elem0 = tid * 2
1503
1504 position = Int64(0)
1505 kv_slot_idx = Int64(-1)
1506 has_position = token_idx < positions.shape[0]
1507 slot_id = Int64(-1)
1508 if lane_id == 0:
1509 slot_id = slot_mapping[token_idx]
1510 if lane_id == 0 and has_position:
1511 position = positions[token_idx]
1512 has_kv_slot_idx = token_idx < kv_slot_mapping.shape[0]
1513 if lane_id == 0 and has_kv_slot_idx:
1514 kv_slot_idx = kv_slot_mapping[token_idx]
1515 slot_id = cute.arch.shuffle_sync(slot_id, offset=0)
1516 position = cute.arch.shuffle_sync(position, offset=0)
1517 kv_slot_idx = cute.arch.shuffle_sync(kv_slot_idx, offset=0)
1518 boundary = has_position and (
1519 (position + Int64(1)) % Int64(compress_ratio) == Int64(0)
1520 )
1521 active = slot_id >= Int64(0) and boundary and kv_slot_idx >= Int64(0)
1522
1523 if active:
1524 k_cache_u16 = cute.recast_tensor(k_cache, Uint16)
1525 k_cache_u32 = cute.recast_tensor(k_cache, Uint32)
1526 static_block_size = Int64(static_kv_cache_block_size)
1527 page = kv_slot_idx // static_block_size
1528 kv_offset = kv_slot_idx - page * static_block_size
1529 scale_row_offset = static_block_size * Int64(token_stride)
1530 value_base = page * k_cache.stride[0] + kv_offset * Int64(token_stride)
1531 scale_base = (
1532 page * k_cache.stride[0]
1533 + scale_row_offset
1534 + kv_offset * Int64(scale_dim)
1535 )
1536 weight0 = rms_norm_weight[elem0].to(Float32)
1537 weight1 = rms_norm_weight[elem0 + 1].to(Float32)
1538
1539 base = token_idx.to(Int64) * compressed_kv.stride[0] + elem0.to(Int64)
1540 x0 = compressed_kv.iterator[base]
1541 x1 = compressed_kv.iterator[base + Int64(1)]
1542
1543 local_sumsq = x0 * x0 + x1 * x1
1544 warp_sum = local_sumsq
1545 for step in cutlass.range_constexpr(5):
1546 offset = const_expr(16 >> step)
1547 warp_sum += cute.arch.shuffle_sync_bfly(warp_sum, offset)
1548
1549 smem = cutlass.utils.SmemAllocator()
1550 partial_sums = smem.allocate_tensor(
1551 Float32, cute.make_layout((num_warps,)), byte_alignment=4
1552 )
1553
1554 if lane_id == 0:
1555 partial_sums[warp_id] = warp_sum
1556 cute.arch.sync_threads()
1557
1558 total = partial_sums[lane_id % num_warps]
1559 sum_mask_and_clamp = const_expr(
1560 (cute.arch.WARP_SIZE - num_warps) << 8 | (cute.arch.WARP_SIZE - 1)
1561 )
1562 for step in cutlass.range_constexpr(3):
1563 offset = const_expr(4 >> step)
1564 total += cute.arch.shuffle_sync_bfly(
1565 total,
1566 offset,
1567 mask_and_clamp=sum_mask_and_clamp,
1568 )
1569
1570 rrms = cute.math.rsqrt(
1571 total / Float32(head_dim) + rms_norm_eps, fastmath=True
1572 )
1573 x0 = x0 * rrms * weight0
1574 x1 = x1 * rrms * weight1
1575
1576 if warp_id == nope_blocks:
1577 pair_idx = lane_id
1578 compressed_pos = position - Int64(compress_ratio - 1)
1579 cs_base = compressed_pos * cos_sin_cache.stride[0] + pair_idx.to(
1580 Int64
1581 )
1582 cos_v = cos_sin_cache.iterator[cs_base]
1583 sin_v = cos_sin_cache.iterator[cs_base + Int64(rope_dim // 2)]
1584 real = x0 * cos_v - x1 * sin_v
1585 imag = x0 * sin_v + x1 * cos_v
1586 packed = _fp32x2_to_bf16x2(real, imag)
1587 out_base = value_base + Int64(nope_dim) + (lane_id * 4).to(Int64)
1588 k_cache_u32.iterator[out_base // Int64(4)] = packed
1589 else:
1590 q_packed = _fp32x2_to_bf16x2(x0, x1)
1591 q0, q1 = _bf16x2_to_fp32(q_packed)
1592 abs0 = cute.math.absf(q0)
1593 abs1 = cute.math.absf(q1)
1594 local_absmax = cute.arch.fmax(abs0, abs1)
1595 absmax = local_absmax
1596 for step in cutlass.range_constexpr(5):
1597 offset = const_expr(16 >> step)
1598 absmax = cute.arch.fmax(
1599 absmax, cute.arch.shuffle_sync_bfly(absmax, offset)
1600 )
1601 scale_raw = cute.arch.fmax(
1602 Float32(min_scale),
1603 absmax / Float32(fp8_max),
1604 )
1605 bits = _recast_val(scale_raw, Uint32)
1606 ue8m0 = ((bits + Uint32(0x7FFFFF)) >> Uint32(23)) & Uint32(0xFF)
1607 inv_scale = _recast_val(
1608 (Uint32(254) - ue8m0) << Uint32(23), Float32
1609 )
1610 y0 = cutlass.min(
1611 cute.arch.fmax(q0 * inv_scale, Float32(-fp8_max)),
1612 Float32(fp8_max),
1613 )
1614 y1 = cutlass.min(
1615 cute.arch.fmax(q1 * inv_scale, Float32(-fp8_max)),
1616 Float32(fp8_max),
1617 )
1618 packed_fp8 = _fp32x2_to_fp8e4m3x2(y0, y1)
1619 out_base = value_base + (warp_id * quant_block + lane_id * 2).to(
1620 Int64
1621 )
1622 k_cache_u16.iterator[out_base // Int64(2)] = packed_fp8
1623 if lane_id == 0:
1624 k_cache.iterator[scale_base + warp_id.to(Int64)] = ue8m0.to(
1625 Uint8
1626 )
1627 if warp_id == 0:
1628 k_cache.iterator[scale_base + Int64(nope_blocks)] = Uint8(0)
1629
1630 @cute.jit
1631 def host_entrypoint(
1632 compressed_kv: cute.Tensor,
1633 positions: cute.Tensor,
1634 slot_mapping: cute.Tensor,
1635 rms_norm_weight: cute.Tensor,
1636 rms_norm_eps: Float32,
1637 cos_sin_cache: cute.Tensor,
1638 k_cache: cute.Tensor,
1639 kv_slot_mapping: cute.Tensor,
1640 stream: CUstream,
1641 ):
1642 grid = (slot_mapping.shape[0], 1, 1)
1643 device_kernel(
1644 compressed_kv,
1645 positions,
1646 slot_mapping,
1647 rms_norm_weight,
1648 rms_norm_eps,
1649 cos_sin_cache,
1650 k_cache,
1651 kv_slot_mapping,
1652 ).launch(grid=grid, block=(tb_size, 1, 1), stream=stream)
1653
1654 return host_entrypoint
1655
1656 def dispatch( # type: ignore[override]
1657 self,
1658 *,
1659 compress_ratio: int,
1660 cache_block_size: int,
1661 cache_alignment: int,
1662 norm_weight_dtype: type[cutlass.Numeric],
1663 head_size: int,
1664 rope_head_dim: int,
1665 runtime_kv_block_stride: int | None = None,
1666 ) -> CompileKey:
1667 raw_kv_cache_block_size = cache_block_size // compress_ratio
1668 kv_cache_block_size = (
1669 raw_kv_cache_block_size if raw_kv_cache_block_size >= 1 else 1
1670 )
1671 token_stride = 576
1672 scale_dim = 8
1673 return self.CompileKey(
1674 head_size=head_size,
1675 rope_head_dim=rope_head_dim,
1676 fp8_max=448.0,
1677 quant_block=64,
1678 token_stride=token_stride,
1679 scale_dim=scale_dim,
1680 kv_block_stride=(
1681 runtime_kv_block_stride
1682 if runtime_kv_block_stride is not None
1683 else round_up(
1684 kv_cache_block_size * (token_stride + scale_dim),
1685 cache_alignment,
1686 )
1687 ),
1688 compress_ratio=compress_ratio,
1689 norm_weight_dtype=norm_weight_dtype,
1690 kv_cache_block_size=kv_cache_block_size,
1691 )
1692
1693 def get_warmup_keys(
1694 self,
1695 vllm_config: Any,
1696 *,
1697 k_cache_prefix: str,
1698 compress_ratio: int,
1699 ) -> list[CompileKey]:
1700 hf_config = vllm_config.model_config.hf_config
1701 cache_config = vllm_config.cache_config
1702 k_cache_layer = vllm_config.compilation_config.static_forward_context[
1703 k_cache_prefix
1704 ]
1705 kv_cache = k_cache_layer.kv_cache
1706 return self._trace_dispatch(self.dispatch)(
1707 compress_ratio=compress_ratio,
1708 cache_block_size=cache_config.block_size,
1709 cache_alignment=576,
1710 norm_weight_dtype=torch_to_cute_dtype(vllm_config.model_config.dtype),
1711 head_size=hf_config.head_dim,
1712 rope_head_dim=hf_config.qk_rope_head_dim,
1713 runtime_kv_block_stride=kv_cache.stride(0),
1714 enabled=(
1715 cache_config.block_size > 0
1716 and compress_ratio == 128
1717 and kv_cache.ndim == 3
1718 and cache_config.cache_dtype == "fp8_ds_mla"
1719 ),
1720 _when=lambda *, enabled: enabled,
1721 )
1722
1723 def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
1724 head_size = compile_key.head_size
1725 rope_head_dim = compile_key.rope_head_dim
1726 quant_block = compile_key.quant_block
1727 token_stride = compile_key.token_stride
1728 scale_dim = compile_key.scale_dim
1729 kv_block_stride = compile_key.kv_block_stride
1730 norm_weight_dtype = compile_key.norm_weight_dtype
1731 static_kv_cache_block_size = compile_key.kv_cache_block_size
1732 if quant_block != 64:
1733 raise ValueError(
1734 "CuTe DSL sparse-attn store currently requires quant_block=64.",
1735 )
1736 if rope_head_dim != 64:
1737 raise ValueError(
1738 "CuTe DSL sparse-attn store currently requires rope_head_dim=64.",
1739 )
1740 if head_size % quant_block != 0:
1741 raise ValueError("head_size must be divisible by quant_block.")
1742 if token_stride < head_size + rope_head_dim:
1743 raise ValueError("token_stride is too small for the packed FP8/BF16 row.")
1744 expected_scale_dim = (head_size - rope_head_dim) // quant_block + 1
1745 if scale_dim < expected_scale_dim:
1746 raise ValueError("scale_dim is too small for the UE8M0 scale row.")
1747 if static_kv_cache_block_size <= 0:
1748 raise ValueError(
1749 "CuTe DSL sparse-attn store requires a positive static "
1750 "kv_cache_block_size.",
1751 )
1752 num_positions = cute.sym_int()
1753 num_slots = cute.sym_int()
1754 num_kv_slots = cute.sym_int()
1755 max_pos = cute.sym_int()
1756 num_blocks = cute.sym_int()
1757 compressed_kv = cute.runtime.make_fake_tensor(
1758 Float32,
1759 (num_slots, head_size),
1760 stride=(head_size, 1),
1761 assumed_align=4,
1762 )
1763 positions = make_fake_tensor(Int64, (num_positions,), divisibility=8)
1764 slot_mapping = make_fake_tensor(Int64, (num_slots,), divisibility=8)
1765 rms_norm_weight = make_fake_tensor(
1766 norm_weight_dtype,
1767 (head_size,),
1768 divisibility=4,
1769 )
1770 cos_sin_cache = cute.runtime.make_fake_tensor(
1771 Float32,
1772 (max_pos, rope_head_dim),
1773 stride=(rope_head_dim, 1),
1774 assumed_align=4,
1775 )
1776 k_cache = cute.runtime.make_fake_tensor(
1777 Uint8,
1778 (num_blocks, cute.sym_int(), cute.sym_int()),
1779 stride=(kv_block_stride, cute.sym_int64(divisibility=8), 1),
1780 assumed_align=16,
1781 )
1782 kv_slot_mapping = make_fake_tensor(Int64, (num_kv_slots,), divisibility=8)
1783 return (
1784 compressed_kv,
1785 positions,
1786 slot_mapping,
1787 rms_norm_weight,
1788 Float32(0.0),
1789 cos_sin_cache,
1790 k_cache,
1791 kv_slot_mapping,
1792 )
1793
1794 @kernel_launcher
1795 def __call__(
1796 self,
1797 *,
1798 compressed_kv: torch.Tensor,
1799 positions: torch.Tensor,
1800 slot_mapping: torch.Tensor,
1801 rms_norm_weight: torch.Tensor,
1802 rms_norm_eps: float,
1803 cos_sin_cache: torch.Tensor,
1804 kv_cache: torch.Tensor,
1805 kv_slot_mapping: torch.Tensor,
1806 compress_ratio: int,
1807 head_dim: int,
1808 rope_head_dim: int,
1809 ) -> CuTeDSLLaunchSpec[SparseAttnNormRopeStoreKernel.CompileKey]:
1810 compile_key = self.dispatch(
1811 compress_ratio=compress_ratio,
1812 cache_block_size=kv_cache.shape[1] * compress_ratio,
1813 cache_alignment=576,
1814 norm_weight_dtype=torch_to_cute_dtype(rms_norm_weight.dtype),
1815 head_size=head_dim,
1816 rope_head_dim=rope_head_dim,
1817 runtime_kv_block_stride=kv_cache.stride(0),
1818 )
1819 launch_args = (
1820 compressed_kv,
1821 positions,
1822 slot_mapping,
1823 rms_norm_weight,
1824 rms_norm_eps,
1825 cos_sin_cache,
1826 kv_cache,
1827 kv_slot_mapping,
1828 )
1829 return compile_key, launch_args
1830
1831
1832 class SparseAttnNormRopeStoreFullKernel(
1833 VllmCuTeDSLJitKernel["SparseAttnNormRopeStoreFullKernel.CompileKey"]
1834 ):
1835 @dataclass(frozen=True)
1836 class CompileKey:
1837 head_size: int
1838 rope_head_dim: int
1839 fp8_max: float
1840 quant_block: int
1841 compress_ratio: int
1842 store_full_fp8: bool
1843 norm_weight_dtype: type[cutlass.Numeric]
1844
1845 @staticmethod
1846 def kernel(compile_key: CompileKey) -> Any:
1847 head_dim = compile_key.head_size
1848 rope_dim = compile_key.rope_head_dim
1849 nope_dim = head_dim - rope_dim
1850 fp8_max = compile_key.fp8_max
1851 quant_block = compile_key.quant_block
1852 num_warps = head_dim // quant_block
1853 nope_blocks = nope_dim // quant_block
1854 tb_size = head_dim // 2
1855 compress_ratio = compile_key.compress_ratio
1856 store_full_fp8 = compile_key.store_full_fp8
1857
1858 @cute.kernel
1859 def device_kernel(
1860 compressed_kv: cute.Tensor,
1861 positions: cute.Tensor,
1862 slot_mapping: cute.Tensor,
1863 rms_norm_weight: cute.Tensor,
1864 rms_norm_eps: Float32,
1865 cos_sin_cache: cute.Tensor,
1866 k_cache: cute.Tensor,
1867 kv_slot_mapping: cute.Tensor,
1868 kv_cache_block_size: Int64,
1869 fp8_scale: cute.Tensor,
1870 ):
1871 token_idx, _, _ = cute.arch.block_idx()
1872 tid, _, _ = cute.arch.thread_idx()
1873 warp_id = cute.arch.make_warp_uniform(tid // 32)
1874 lane_id = tid % 32
1875 elem0 = tid * 2
1876
1877 slot_id = slot_mapping[token_idx]
1878 has_position = token_idx < positions.shape[0]
1879 position = Int64(0)
1880 if has_position:
1881 position = positions[token_idx]
1882 boundary = has_position and (
1883 (position + Int64(1)) % Int64(compress_ratio) == Int64(0)
1884 )
1885 has_kv_slot_idx = token_idx < kv_slot_mapping.shape[0]
1886 kv_slot_idx = Int64(-1)
1887 if has_kv_slot_idx:
1888 kv_slot_idx = kv_slot_mapping[token_idx]
1889 active = slot_id >= Int64(0) and boundary and kv_slot_idx >= Int64(0)
1890
1891 if active:
1892 base = token_idx.to(Int64) * compressed_kv.stride[0] + elem0.to(Int64)
1893 x0 = compressed_kv.iterator[base]
1894 x1 = compressed_kv.iterator[base + Int64(1)]
1895
1896 local_sumsq = x0 * x0 + x1 * x1
1897 warp_sum = local_sumsq
1898 for step in cutlass.range_constexpr(5):
1899 offset = const_expr(16 >> step)
1900 warp_sum += cute.arch.shuffle_sync_bfly(warp_sum, offset)
1901
1902 smem = cutlass.utils.SmemAllocator()
1903 partial_sums = smem.allocate_tensor(
1904 Float32, cute.make_layout((num_warps,)), byte_alignment=4
1905 )
1906 rrms_shared = smem.allocate_tensor(
1907 Float32, cute.make_layout((1,)), byte_alignment=4
1908 )
1909
1910 if lane_id == 0:
1911 partial_sums[warp_id] = warp_sum
1912 cute.arch.sync_threads()
1913 if tid == 0:
1914 total = Float32(0.0)
1915 for i in cutlass.range_constexpr(num_warps):
1916 total += partial_sums[i]
1917 rrms_shared[0] = cute.math.rsqrt(
1918 total / Float32(head_dim) + rms_norm_eps, fastmath=True
1919 )
1920 cute.arch.sync_threads()
1921
1922 rrms = rrms_shared[0]
1923 x0 = x0 * rrms * rms_norm_weight[elem0].to(Float32)
1924 x1 = x1 * rrms * rms_norm_weight[elem0 + 1].to(Float32)
1925
1926 page = kv_slot_idx // kv_cache_block_size
1927 kv_offset = kv_slot_idx - page * kv_cache_block_size
1928 value_base = page * k_cache.stride[0] + kv_offset * k_cache.stride[1]
1929
1930 if const_expr(store_full_fp8):
1931 k_cache_u16 = cute.recast_tensor(k_cache, Uint16)
1932 inv_fp8 = Float32(1.0) / fp8_scale[0]
1933 fp8_v0 = x0
1934 fp8_v1 = x1
1935 if warp_id == nope_blocks:
1936 compressed_pos = (position // Int64(compress_ratio)) * Int64(
1937 compress_ratio
1938 )
1939 pair_idx = lane_id
1940 cs_base = compressed_pos * cos_sin_cache.stride[
1941 0
1942 ] + pair_idx.to(Int64)
1943 cos_v = cos_sin_cache.iterator[cs_base]
1944 sin_v = cos_sin_cache.iterator[cs_base + Int64(rope_dim // 2)]
1945 fp8_v0 = x0 * cos_v - x1 * sin_v
1946 fp8_v1 = x0 * sin_v + x1 * cos_v
1947 fp8_packed_bf16 = _fp32x2_to_bf16x2(fp8_v0, fp8_v1)
1948 b0, b1 = _bf16x2_to_fp32(fp8_packed_bf16)
1949 y0 = cutlass.min(
1950 cutlass.max(b0 * inv_fp8, Float32(-fp8_max)),
1951 Float32(fp8_max),
1952 )
1953 y1 = cutlass.min(
1954 cutlass.max(b1 * inv_fp8, Float32(-fp8_max)),
1955 Float32(fp8_max),
1956 )
1957 packed_fp8 = _fp32x2_to_fp8e4m3x2(y0, y1)
1958 out_base = value_base + elem0.to(Int64)
1959 k_cache_u16.iterator[out_base // Int64(2)] = packed_fp8
1960 else:
1961 k_cache_u32 = cute.recast_tensor(k_cache, Uint32)
1962 bf16_v0 = x0
1963 bf16_v1 = x1
1964 if warp_id == nope_blocks:
1965 compressed_pos = (position // Int64(compress_ratio)) * Int64(
1966 compress_ratio
1967 )
1968 pair_idx = lane_id
1969 cs_base = compressed_pos * cos_sin_cache.stride[
1970 0
1971 ] + pair_idx.to(Int64)
1972 cos_v = cos_sin_cache.iterator[cs_base]
1973 sin_v = cos_sin_cache.iterator[cs_base + Int64(rope_dim // 2)]
1974 bf16_v0 = x0 * cos_v - x1 * sin_v
1975 bf16_v1 = x0 * sin_v + x1 * cos_v
1976 bf16_packed = _fp32x2_to_bf16x2(bf16_v0, bf16_v1)
1977 out_base = value_base + (elem0 * 2).to(Int64)
1978 k_cache_u32.iterator[out_base // Int64(4)] = bf16_packed
1979
1980 @cute.jit
1981 def host_entrypoint(
1982 compressed_kv: cute.Tensor,
1983 positions: cute.Tensor,
1984 slot_mapping: cute.Tensor,
1985 rms_norm_weight: cute.Tensor,
1986 rms_norm_eps: Float32,
1987 cos_sin_cache: cute.Tensor,
1988 k_cache: cute.Tensor,
1989 kv_slot_mapping: cute.Tensor,
1990 kv_cache_block_size: Int64,
1991 fp8_scale: cute.Tensor,
1992 stream: CUstream,
1993 ):
1994 grid = (slot_mapping.shape[0], 1, 1)
1995 device_kernel(
1996 compressed_kv,
1997 positions,
1998 slot_mapping,
1999 rms_norm_weight,
2000 rms_norm_eps,
2001 cos_sin_cache,
2002 k_cache,
2003 kv_slot_mapping,
2004 kv_cache_block_size,
2005 fp8_scale,
2006 ).launch(grid=grid, block=(tb_size, 1, 1), stream=stream)
2007
2008 return host_entrypoint
2009
2010 def dispatch( # type: ignore[override]
2011 self,
2012 *,
2013 store_full_fp8: bool,
2014 norm_weight_dtype: type[cutlass.Numeric],
2015 head_size: int,
2016 rope_head_dim: int,
2017 compress_ratio: int,
2018 ) -> CompileKey:
2019 return self.CompileKey(
2020 head_size=head_size,
2021 rope_head_dim=rope_head_dim,
2022 compress_ratio=compress_ratio,
2023 fp8_max=448.0,
2024 quant_block=64,
2025 store_full_fp8=store_full_fp8,
2026 norm_weight_dtype=norm_weight_dtype,
2027 )
2028
2029 def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
2030 hf_config = vllm_config.model_config.hf_config
2031 cache_config = vllm_config.cache_config
2032 return self._trace_dispatch(self.dispatch)(
2033 compress_ratio=128,
2034 store_full_fp8=(
2035 cache_config.cache_dtype.startswith("fp8")
2036 and cache_config.cache_dtype != "fp8_ds_mla"
2037 ),
2038 norm_weight_dtype=torch_to_cute_dtype(vllm_config.model_config.dtype),
2039 head_size=hf_config.head_dim,
2040 rope_head_dim=hf_config.qk_rope_head_dim,
2041 enabled=(
2042 cache_config.block_size > 0
2043 and 128 in hf_config.compress_ratios
2044 and cache_config.cache_dtype != "fp8_ds_mla"
2045 ),
2046 _when=lambda *, enabled: enabled,
2047 )
2048
2049 def warmup_inputs(self, compile_key: CompileKey) -> tuple[Any, ...]:
2050 head_size = compile_key.head_size
2051 rope_head_dim = compile_key.rope_head_dim
2052 quant_block = compile_key.quant_block
2053 norm_weight_dtype = compile_key.norm_weight_dtype
2054 if quant_block != 64:
2055 raise ValueError(
2056 "CuTe DSL sparse-attn store currently requires quant_block=64.",
2057 )
2058 if rope_head_dim != 64:
2059 raise ValueError(
2060 "CuTe DSL sparse-attn store currently requires rope_head_dim=64.",
2061 )
2062 if head_size % quant_block != 0:
2063 raise ValueError("head_size must be divisible by quant_block.")
2064 num_positions = cute.sym_int()
2065 num_slots = cute.sym_int()
2066 num_kv_slots = cute.sym_int()
2067 max_pos = cute.sym_int()
2068 num_blocks = cute.sym_int()
2069 compressed_kv = cute.runtime.make_fake_tensor(
2070 Float32,
2071 (num_slots, head_size),
2072 stride=(cute.sym_int64(divisibility=4), 1),
2073 assumed_align=4,
2074 )
2075 positions = make_fake_tensor(Int64, (num_positions,), divisibility=8)
2076 slot_mapping = make_fake_tensor(Int64, (num_slots,), divisibility=8)
2077 rms_norm_weight = make_fake_tensor(
2078 norm_weight_dtype,
2079 (head_size,),
2080 divisibility=4,
2081 )
2082 cos_sin_cache = cute.runtime.make_fake_tensor(
2083 Float32,
2084 (max_pos, rope_head_dim),
2085 stride=(cute.sym_int64(divisibility=4), 1),
2086 assumed_align=4,
2087 )
2088 k_cache = cute.runtime.make_fake_tensor(
2089 Uint8,
2090 (num_blocks, cute.sym_int(), cute.sym_int()),
2091 stride=(cute.sym_int64(divisibility=16), cute.sym_int64(divisibility=8), 1),
2092 assumed_align=16,
2093 )
2094 kv_slot_mapping = make_fake_tensor(Int64, (num_kv_slots,), divisibility=8)
2095 fp8_scale = make_fake_tensor(Float32, (1,), divisibility=1)
2096 return (
2097 compressed_kv,
2098 positions,
2099 slot_mapping,
2100 rms_norm_weight,
2101 Float32(0.0),
2102 cos_sin_cache,
2103 k_cache,
2104 kv_slot_mapping,
2105 Int64(0),
2106 fp8_scale,
2107 )
2108
2109 @kernel_launcher
2110 def __call__(
2111 self,
2112 *,
2113 compressed_kv: torch.Tensor,
2114 positions: torch.Tensor,
2115 slot_mapping: torch.Tensor,
2116 rms_norm_weight: torch.Tensor,
2117 rms_norm_eps: float,
2118 cos_sin_cache: torch.Tensor,
2119 kv_cache: torch.Tensor,
2120 kv_slot_mapping: torch.Tensor,
2121 compress_ratio: int,
2122 store_full_fp8: bool,
2123 fp8_scale: torch.Tensor | None,
2124 head_dim: int,
2125 rope_head_dim: int,
2126 ) -> CuTeDSLLaunchSpec[SparseAttnNormRopeStoreFullKernel.CompileKey]:
2127 compile_key = self.dispatch(
2128 compress_ratio=compress_ratio,
2129 store_full_fp8=store_full_fp8,
2130 norm_weight_dtype=torch_to_cute_dtype(rms_norm_weight.dtype),
2131 head_size=head_dim,
2132 rope_head_dim=rope_head_dim,
2133 )
2134 if fp8_scale is None:
2135 fp8_scale = torch.ones(1, dtype=torch.float32, device=kv_cache.device)
2136 launch_args = (
2137 compressed_kv,
2138 positions,
2139 slot_mapping,
2140 rms_norm_weight,
2141 rms_norm_eps,
2142 cos_sin_cache,
2143 kv_cache.view(torch.uint8),
2144 kv_slot_mapping,
2145 kv_cache.shape[1],
2146 fp8_scale,
2147 )
2148 return compile_key, launch_args
2149
2150
2151 def fused_kv_compress_norm_rope_insert_sparse_attn_cutedsl(
2152 state_cache: torch.Tensor,
2153 token_to_req_indices: torch.Tensor,
2154 positions: torch.Tensor,
2155 slot_mapping: torch.Tensor,
2156 block_table: torch.Tensor,
2157 block_size: int,
2158 rms_norm_weight: torch.Tensor,
2159 rms_norm_eps: float,
2160 cos_sin_cache: torch.Tensor,
2161 k_cache: torch.Tensor,
2162 kv_slot_mapping: torch.Tensor,
2163 kv_cache_block_size: int,
2164 kv_block_stride: int,
2165 head_size: int = 512,
2166 state_width: int = 1024,
2167 rope_head_dim: int = 64,
2168 fp8_max: float = 448.0,
2169 quant_block: int = 64,
2170 token_stride: int = 576,
2171 scale_dim: int = 8,
2172 compress_ratio: int = 4,
2173 overlap: bool = True,
2174 store_full_kv: bool = False,
2175 store_full_fp8: bool = False,
2176 fp8_scale: torch.Tensor | None = None,
2177 ) -> None:
2178 del fp8_max, quant_block, token_stride, scale_dim, kv_block_stride
2179 if not (
2180 head_size == 512
2181 and state_width == 2 * head_size
2182 and compress_ratio == 4
2183 and overlap
2184 ):
2185 raise ValueError(
2186 "CuTe DSL fused sparse-attn wrapper only supports the real "
2187 "DeepSeek V4 C4 layout: head_size=512, state_width=1024, "
2188 "compress_ratio=4, overlap=True."
2189 )
2190 if k_cache.ndim != 3:
2191 raise ValueError(
2192 "CuTe DSL sparse-attn fused store expects the real DeepSeek V4 "
2193 f"3D k_cache layout [num_blocks, block_size, 584], got ndim={k_cache.ndim}."
2194 )
2195 if kv_cache_block_size != k_cache.shape[1]:
2196 raise ValueError(
2197 "CuTe DSL fused sparse-attn wrapper expected kv_cache_block_size "
2198 f"to match k_cache.shape[1], got {kv_cache_block_size} and "
2199 f"{k_cache.shape[1]}."
2200 )
2201 if positions.numel() == 0:
2202 return
2203 if store_full_fp8 and not store_full_kv:
2204 raise ValueError("store_full_fp8 requires store_full_kv.")
2205
2206 if store_full_kv:
2207 _SPARSE_ATTN_COMPRESS_NORM_ROPE_STORE_FULL_C4_KERNEL(
2208 state_cache=state_cache,
2209 token_to_req_indices=token_to_req_indices,
2210 positions=positions,
2211 slot_mapping=slot_mapping,
2212 block_table=block_table,
2213 block_size=block_size,
2214 rms_norm_weight=rms_norm_weight,
2215 rms_norm_eps=rms_norm_eps,
2216 cos_sin_cache=cos_sin_cache,
2217 kv_cache=k_cache,
2218 kv_slot_mapping=kv_slot_mapping,
2219 compress_ratio=compress_ratio,
2220 store_full_fp8=store_full_fp8,
2221 fp8_scale=fp8_scale,
2222 head_dim=head_size,
2223 rope_head_dim=rope_head_dim,
2224 )
2225 return
2226 _SPARSE_ATTN_COMPRESS_NORM_ROPE_STORE_C4_KERNEL(
2227 state_cache=state_cache,
2228 token_to_req_indices=token_to_req_indices,
2229 positions=positions,
2230 slot_mapping=slot_mapping,
2231 block_table=block_table,
2232 block_size=block_size,
2233 rms_norm_weight=rms_norm_weight,
2234 rms_norm_eps=rms_norm_eps,
2235 cos_sin_cache=cos_sin_cache,
2236 kv_cache=k_cache,
2237 kv_slot_mapping=kv_slot_mapping,
2238 compress_ratio=compress_ratio,
2239 head_dim=head_size,
2240 rope_head_dim=rope_head_dim,
2241 )
2242
2243
2244 def split_kv_compress_norm_rope_insert_sparse_attn_cutedsl(
2245 state_cache: torch.Tensor,
2246 token_to_req_indices: torch.Tensor,
2247 positions: torch.Tensor,
2248 slot_mapping: torch.Tensor,
2249 block_table: torch.Tensor,
2250 block_size: int,
2251 compressed_kv: torch.Tensor,
2252 rms_norm_weight: torch.Tensor,
2253 rms_norm_eps: float,
2254 cos_sin_cache: torch.Tensor,
2255 k_cache: torch.Tensor,
2256 kv_slot_mapping: torch.Tensor,
2257 kv_cache_block_size: int,
2258 kv_block_stride: int,
2259 head_size: int = 512,
2260 state_width: int = 512,
2261 rope_head_dim: int = 64,
2262 fp8_max: float = 448.0,
2263 quant_block: int = 64,
2264 token_stride: int = 576,
2265 scale_dim: int = 8,
2266 compress_ratio: int = 128,
2267 overlap: bool = False,
2268 store_full_kv: bool = False,
2269 store_full_fp8: bool = False,
2270 fp8_scale: torch.Tensor | None = None,
2271 ) -> None:
2272 del fp8_max, quant_block, token_stride, scale_dim, kv_block_stride
2273 if not (
2274 head_size == 512
2275 and state_width == head_size
2276 and compress_ratio == 128
2277 and not overlap
2278 and block_size == 8
2279 ):
2280 raise ValueError(
2281 "CuTe DSL split sparse-attn wrapper only supports the real "
2282 "DeepSeek V4 C128 layout: head_size=512, state_width=512, "
2283 "compress_ratio=128, overlap=False, block_size=8."
2284 )
2285 if k_cache.ndim != 3:
2286 raise ValueError(
2287 "CuTe DSL sparse-attn store expects the real DeepSeek V4 "
2288 f"3D k_cache layout [num_blocks, block_size, 584], got ndim={k_cache.ndim}."
2289 )
2290 if not store_full_kv and kv_cache_block_size != k_cache.shape[1]:
2291 raise ValueError(
2292 "CuTe DSL split sparse-attn wrapper expected kv_cache_block_size "
2293 f"to match k_cache.shape[1], got {kv_cache_block_size} and "
2294 f"{k_cache.shape[1]}."
2295 )
2296 if positions.numel() == 0:
2297 return
2298 if store_full_fp8 and not store_full_kv:
2299 raise ValueError("store_full_fp8 requires store_full_kv.")
2300
2301 _SPARSE_ATTN_COMPRESS_C128_BLOCK8_KERNEL(
2302 state_cache=state_cache,
2303 num_actual=compressed_kv.shape[0],
2304 token_to_req_indices=token_to_req_indices,
2305 positions=positions,
2306 slot_mapping=slot_mapping,
2307 block_table=block_table,
2308 head_dim=head_size,
2309 compressed_kv=compressed_kv,
2310 )
2311 if store_full_kv:
2312 _SPARSE_ATTN_NORM_ROPE_STORE_FULL_KERNEL(
2313 compressed_kv=compressed_kv,
2314 positions=positions,
2315 slot_mapping=slot_mapping,
2316 rms_norm_weight=rms_norm_weight,
2317 rms_norm_eps=rms_norm_eps,
2318 cos_sin_cache=cos_sin_cache,
2319 kv_cache=k_cache,
2320 kv_slot_mapping=kv_slot_mapping,
2321 compress_ratio=compress_ratio,
2322 store_full_fp8=store_full_fp8,
2323 fp8_scale=fp8_scale,
2324 head_dim=head_size,
2325 rope_head_dim=rope_head_dim,
2326 )
2327 return
2328 _SPARSE_ATTN_NORM_ROPE_STORE_KERNEL(
2329 compressed_kv=compressed_kv,
2330 positions=positions,
2331 slot_mapping=slot_mapping,
2332 rms_norm_weight=rms_norm_weight,
2333 rms_norm_eps=rms_norm_eps,
2334 cos_sin_cache=cos_sin_cache,
2335 kv_cache=k_cache,
2336 kv_slot_mapping=kv_slot_mapping,
2337 compress_ratio=compress_ratio,
2338 head_dim=head_size,
2339 rope_head_dim=rope_head_dim,
2340 )
2341
2342
2343 class SparseAttnCompressorCuteDSL:
2344 def __init__(
2345 self,
2346 *,
2347 c4_store: SparseAttnCompressNormRopeStoreC4Kernel,
2348 c4_store_full: SparseAttnCompressNormRopeStoreFullC4Kernel,
2349 c128_compress: SparseAttnCompressC128Block8Kernel,
2350 c128_store: SparseAttnNormRopeStoreKernel,
2351 c128_store_full: SparseAttnNormRopeStoreFullKernel,
2352 ) -> None:
2353 self.c4_store = c4_store
2354 self.c4_store_full = c4_store_full
2355 self.c128_compress = c128_compress
2356 self.c128_store = c128_store
2357 self.c128_store_full = c128_store_full
2358
2359 def __call__(
2360 self,
2361 state_cache: torch.Tensor,
2362 num_actual: int,
2363 token_to_req_indices: torch.Tensor,
2364 positions: torch.Tensor,
2365 slot_mapping: torch.Tensor,
2366 block_table: torch.Tensor,
2367 block_size: int,
2368 state_width: int,
2369 cos_sin_cache: torch.Tensor,
2370 kv_cache: torch.Tensor,
2371 k_cache_metadata: Any,
2372 pdl_kwargs: dict,
2373 head_dim: int,
2374 rope_head_dim: int,
2375 compress_ratio: int,
2376 overlap: bool,
2377 use_fp4_cache: bool,
2378 rms_norm_weight: torch.Tensor,
2379 rms_norm_eps: float,
2380 quant_block: int,
2381 token_stride: int,
2382 scale_dim: int,
2383 store_full_kv: bool = False,
2384 store_full_fp8: bool = False,
2385 fp8_scale: torch.Tensor | None = None,
2386 ) -> None:
2387 del pdl_kwargs, state_width, overlap, use_fp4_cache
2388 del quant_block, token_stride, scale_dim
2389 if positions.numel() == 0:
2390 return
2391 if kv_cache.ndim != 3:
2392 raise ValueError(
2393 "CuTe DSL sparse-attn compressor expects the real DeepSeek V4 "
2394 f"3D kv_cache layout, got ndim={kv_cache.ndim}.",
2395 )
2396 if store_full_fp8 and (not store_full_kv):
2397 raise ValueError("store_full_fp8 requires store_full_kv.")
2398 if compress_ratio == 4:
2399 if store_full_kv:
2400 self.c4_store_full(
2401 state_cache=state_cache,
2402 token_to_req_indices=token_to_req_indices,
2403 positions=positions,
2404 slot_mapping=slot_mapping,
2405 block_table=block_table,
2406 block_size=block_size,
2407 rms_norm_weight=rms_norm_weight,
2408 rms_norm_eps=rms_norm_eps,
2409 cos_sin_cache=cos_sin_cache,
2410 kv_cache=kv_cache,
2411 kv_slot_mapping=k_cache_metadata.slot_mapping,
2412 compress_ratio=compress_ratio,
2413 store_full_fp8=store_full_fp8,
2414 fp8_scale=fp8_scale,
2415 head_dim=head_dim,
2416 rope_head_dim=rope_head_dim,
2417 )
2418 else:
2419 self.c4_store(
2420 state_cache=state_cache,
2421 token_to_req_indices=token_to_req_indices,
2422 positions=positions,
2423 slot_mapping=slot_mapping,
2424 block_table=block_table,
2425 block_size=block_size,
2426 rms_norm_weight=rms_norm_weight,
2427 rms_norm_eps=rms_norm_eps,
2428 cos_sin_cache=cos_sin_cache,
2429 kv_cache=kv_cache,
2430 kv_slot_mapping=k_cache_metadata.slot_mapping,
2431 compress_ratio=compress_ratio,
2432 head_dim=head_dim,
2433 rope_head_dim=rope_head_dim,
2434 )
2435 return
2436 if compress_ratio != 128:
2437 raise ValueError(
2438 "CuTe DSL sparse-attn compressor supports compress_ratio 4 or "
2439 f"128, got {compress_ratio}.",
2440 )
2441 if block_size != self.c128_compress.state_block_size:
2442 raise ValueError(
2443 "CuTe DSL C128 sparse-attn compressor requires state-cache "
2444 f"block_size={self.c128_compress.state_block_size}, got "
2445 f"{block_size}."
2446 )
2447 compressed_kv = self.c128_compress(
2448 state_cache=state_cache,
2449 num_actual=num_actual,
2450 token_to_req_indices=token_to_req_indices,
2451 positions=positions,
2452 slot_mapping=slot_mapping,
2453 block_table=block_table,
2454 head_dim=head_dim,
2455 )
2456 if store_full_kv:
2457 self.c128_store_full(
2458 compressed_kv=compressed_kv,
2459 positions=positions,
2460 slot_mapping=slot_mapping,
2461 rms_norm_weight=rms_norm_weight,
2462 rms_norm_eps=rms_norm_eps,
2463 cos_sin_cache=cos_sin_cache,
2464 kv_cache=kv_cache,
2465 kv_slot_mapping=k_cache_metadata.slot_mapping,
2466 compress_ratio=compress_ratio,
2467 store_full_fp8=store_full_fp8,
2468 fp8_scale=fp8_scale,
2469 head_dim=head_dim,
2470 rope_head_dim=rope_head_dim,
2471 )
2472 return
2473 self.c128_store(
2474 compressed_kv=compressed_kv,
2475 positions=positions,
2476 slot_mapping=slot_mapping,
2477 rms_norm_weight=rms_norm_weight,
2478 rms_norm_eps=rms_norm_eps,
2479 cos_sin_cache=cos_sin_cache,
2480 kv_cache=kv_cache,
2481 kv_slot_mapping=k_cache_metadata.slot_mapping,
2482 compress_ratio=compress_ratio,
2483 head_dim=head_dim,
2484 rope_head_dim=rope_head_dim,
2485 )
2486
2487
2488 _SPARSE_ATTN_COMPRESS_NORM_ROPE_STORE_C4_KERNEL = (
2489 SparseAttnCompressNormRopeStoreC4Kernel()
2490 )
2491 _SPARSE_ATTN_COMPRESS_NORM_ROPE_STORE_FULL_C4_KERNEL = (
2492 SparseAttnCompressNormRopeStoreFullC4Kernel()
2493 )
2494 _SPARSE_ATTN_COMPRESS_C128_BLOCK8_KERNEL = SparseAttnCompressC128Block8Kernel()
2495 _SPARSE_ATTN_NORM_ROPE_STORE_KERNEL = SparseAttnNormRopeStoreKernel()
2496 _SPARSE_ATTN_NORM_ROPE_STORE_FULL_KERNEL = SparseAttnNormRopeStoreFullKernel()
2497 _SPARSE_ATTN_COMPRESSOR_CUTEDSL_KERNEL = SparseAttnCompressorCuteDSL(
2498 c4_store=_SPARSE_ATTN_COMPRESS_NORM_ROPE_STORE_C4_KERNEL,
2499 c4_store_full=_SPARSE_ATTN_COMPRESS_NORM_ROPE_STORE_FULL_C4_KERNEL,
2500 c128_compress=_SPARSE_ATTN_COMPRESS_C128_BLOCK8_KERNEL,
2501 c128_store=_SPARSE_ATTN_NORM_ROPE_STORE_KERNEL,
2502 c128_store_full=_SPARSE_ATTN_NORM_ROPE_STORE_FULL_KERNEL,
2503 )