main
py 646 lines 24.5 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Fused QSA prepare kernel for Qwen4Exp."""
4
5 import torch
6
7 from vllm.triton_utils import tl, triton
8
9
10 @triton.jit
11 def _norm_rope(
12 x,
13 pos_t,
14 pos_h,
15 pos_w,
16 cos_sin_ptr,
17 cos_sin_stride,
18 norm_weight_ptr,
19 eps,
20 IS_MROPE: tl.constexpr,
21 MROPE_H: tl.constexpr,
22 MROPE_W: tl.constexpr,
23 ):
24 """Apply Gemma RMSNorm and selected-axis NeoX RoPE to register rows."""
25 TILE_T: tl.constexpr = x.shape[0]
26 TILE_H: tl.constexpr = x.shape[1]
27 D: tl.constexpr = x.shape[2]
28 ROWS: tl.constexpr = TILE_T * TILE_H
29 HALF: tl.constexpr = D // 2
30 QUARTER: tl.constexpr = D // 4
31 pairs = tl.arange(0, QUARTER)
32 if IS_MROPE:
33 # Qwen interleaves temporal, height, and width rotary pairs. Each axis
34 # still indexes the same position-major cos/sin table.
35 h_mask = ((pairs % 3) == 1) & (pairs <= 3 * MROPE_H)
36 w_mask = ((pairs % 3) == 2) & (pairs <= 3 * MROPE_W)
37 t_mask = ~(h_mask | w_mask)
38 base = cos_sin_ptr + pairs[None, :]
39 pos_rows = (pos_t, pos_h, pos_w)
40 axis_masks = (t_mask, h_mask, w_mask)
41 cos = tl.zeros((TILE_T, QUARTER), dtype=cos_sin_ptr.dtype.element_ty)
42 sin = tl.zeros((TILE_T, QUARTER), dtype=cos_sin_ptr.dtype.element_ty)
43 for axis in tl.static_range(3):
44 cos += tl.load(
45 base + pos_rows[axis][:, None] * cos_sin_stride,
46 mask=axis_masks[axis][None, :],
47 other=0,
48 )
49 sin += tl.load(
50 base + pos_rows[axis][:, None] * cos_sin_stride + QUARTER,
51 mask=axis_masks[axis][None, :],
52 other=0,
53 )
54 else:
55 cos = tl.load(cos_sin_ptr + pos_t[:, None] * cos_sin_stride + pairs[None, :])
56 sin = tl.load(
57 cos_sin_ptr + pos_t[:, None] * cos_sin_stride + QUARTER + pairs[None, :]
58 )
59
60 cos = tl.reshape(
61 tl.broadcast_to(cos[:, None, :], (TILE_T, TILE_H, QUARTER)),
62 (ROWS, QUARTER),
63 )
64 sin = tl.reshape(
65 tl.broadcast_to(sin[:, None, :], (TILE_T, TILE_H, QUARTER)),
66 (ROWS, QUARTER),
67 )
68 x = tl.reshape(x, (ROWS, D)).to(tl.float32)
69 weight = tl.load(norm_weight_ptr + tl.arange(0, D)).to(tl.float32) + 1.0
70 rrms = tl.rsqrt(tl.sum(x * x, axis=1) / D + eps)
71 y = (x * rrms[:, None] * weight[None, :]).to(cos.dtype)
72 rotated, passthrough = tl.split(
73 tl.permute(tl.reshape(y, (ROWS, 2, HALF)), (0, 2, 1))
74 )
75 r0, r1 = tl.split(tl.permute(tl.reshape(rotated, (ROWS, 2, QUARTER)), (0, 2, 1)))
76 out0 = r0 * cos - r1 * sin
77 out1 = r1 * cos + r0 * sin
78 rotated = tl.reshape(tl.permute(tl.join(out0, out1), (0, 2, 1)), (ROWS, HALF))
79 result = tl.reshape(tl.permute(tl.join(rotated, passthrough), (0, 2, 1)), (ROWS, D))
80 return tl.reshape(result, (TILE_T, TILE_H, D))
81
82
83 @triton.jit
84 def _to_dst_dtype(x, dst, scale):
85 """Round to BF16 like the unfused path, then scale for an FP8 destination."""
86 out_ty = dst.dtype.element_ty
87 x = x.to(tl.bfloat16)
88 if out_ty == tl.float8e4nv:
89 x = x.to(tl.float32) / scale
90 return x.to(out_ty)
91
92
93 @triton.jit
94 def _store_rotated(dst, y, o1, o2, scale):
95 """Store a normalized head whose first ``2 * len(o1)`` dims are rotated."""
96 HALF: tl.constexpr = o1.shape[0]
97 dims = tl.arange(0, y.shape[0])
98 rot = tl.arange(0, HALF)
99 tl.store(dst + dims, _to_dst_dtype(y, dst, scale), mask=dims >= 2 * HALF)
100 tl.store(dst + rot, _to_dst_dtype(o1, dst, scale))
101 tl.store(dst + HALF + rot, _to_dst_dtype(o2, dst, scale))
102
103
104 @triton.jit(
105 do_not_specialize=[
106 "num_tokens",
107 "num_state_blocks",
108 "num_compressed_blocks",
109 "num_k_work",
110 ]
111 )
112 def _qsa_prepare_kernel(
113 q_ptr,
114 q_stride_token,
115 k_ptr,
116 k_stride_token,
117 pos_ptr,
118 pos_stride_axis,
119 pos_stride_token,
120 cos_sin_ptr,
121 q_norm_weight_ptr,
122 k_norm_weight_ptr,
123 eps,
124 q_out_ptr,
125 q_out_stride_token,
126 q_out_stride_head,
127 state_cache_ptr,
128 state_cache_stride_block,
129 state_cache_stride_token,
130 state_slots_ptr,
131 state_table_ptr,
132 state_table_stride_req,
133 query_start_loc_ptr,
134 logical_positions_ptr,
135 compressed_slots_ptr,
136 k_work_metadata_ptr,
137 compressed_cache_ptr,
138 compressed_cache_stride_block,
139 compressed_cache_stride_token,
140 num_tokens,
141 num_state_blocks,
142 num_compressed_blocks,
143 num_k_work,
144 HQ: tl.constexpr,
145 D: tl.constexpr,
146 TILE_T_Q: tl.constexpr,
147 TILE_H_Q: tl.constexpr,
148 COMPRESS_RATIO: tl.constexpr,
149 STATE_SIZE: tl.constexpr,
150 COMP_PAGE_SIZE: tl.constexpr,
151 IS_2D_POSITIONS: tl.constexpr,
152 IS_K_MROPE: tl.constexpr,
153 CACHE_HAS_ROPE_POS: tl.constexpr,
154 MROPE_H: tl.constexpr,
155 MROPE_W: tl.constexpr,
156 main_qkv_ptr,
157 main_q_norm_weight_ptr,
158 main_k_norm_weight_ptr,
159 main_eps,
160 main_q_out_ptr,
161 main_gate_out_ptr,
162 main_cache_ptr,
163 main_cache_stride_block,
164 main_cache_stride_token,
165 main_cache_stride_head,
166 main_slots_ptr,
167 main_k_scale,
168 main_v_scale,
169 MAIN_HQ: tl.constexpr,
170 MAIN_HK: tl.constexpr,
171 MAIN_D: tl.constexpr,
172 MAIN_PAGE_SIZE: tl.constexpr,
173 ):
174 pid = tl.program_id(0)
175 num_index_work = num_k_work + tl.cdiv(num_tokens, TILE_T_Q) * tl.cdiv(HQ, TILE_H_Q)
176 if pid >= num_index_work:
177 # Main attention: one program per (token, Q or KV head) after the
178 # indexer work. RoPE covers the first D // 2 dims, the width of the
179 # shared cos/sin table.
180 main_pid = pid - num_index_work
181 token = main_pid // (MAIN_HQ + MAIN_HK)
182 head = main_pid % (MAIN_HQ + MAIN_HK)
183 HALF: tl.constexpr = D // 4
184 dims = tl.arange(0, MAIN_D)
185 rot = tl.arange(0, HALF)
186 row = main_qkv_ptr + token * (2 * (MAIN_HQ + MAIN_HK) * MAIN_D)
187 is_k = head >= MAIN_HQ
188 kv_head = head - MAIN_HQ
189 if is_k:
190 src = row + (2 * MAIN_HQ + kv_head) * MAIN_D
191 weight_ptr = main_k_norm_weight_ptr
192 else:
193 src = row + 2 * head * MAIN_D
194 weight_ptr = main_q_norm_weight_ptr
195 x = tl.load(src + dims).to(tl.float32)
196 inv_rms = tl.rsqrt(tl.sum(x * x, axis=0) / MAIN_D + main_eps)
197 w = tl.load(weight_ptr + dims).to(tl.float32) + 1.0
198 y = x * inv_rms * w
199 x1 = tl.load(src + rot).to(tl.float32)
200 x2 = tl.load(src + HALF + rot).to(tl.float32)
201 w1 = tl.load(weight_ptr + rot).to(tl.float32) + 1.0
202 w2 = tl.load(weight_ptr + HALF + rot).to(tl.float32) + 1.0
203 x1 = (x1 * inv_rms * w1).to(tl.bfloat16).to(tl.float32)
204 x2 = (x2 * inv_rms * w2).to(tl.bfloat16).to(tl.float32)
205 pos = tl.load(pos_ptr + token * pos_stride_token).to(tl.int64)
206 if IS_2D_POSITIONS:
207 pos_h = tl.load(pos_ptr + pos_stride_axis + token * pos_stride_token)
208 pos_w = tl.load(pos_ptr + 2 * pos_stride_axis + token * pos_stride_token)
209 is_h = (rot % 3 == 1) & (rot < 3 * MROPE_H)
210 is_w = (rot % 3 == 2) & (rot < 3 * MROPE_W)
211 pos = tl.where(
212 is_h, pos_h.to(tl.int64), tl.where(is_w, pos_w.to(tl.int64), pos)
213 )
214 cos = tl.load(cos_sin_ptr + pos * (D // 2) + rot).to(tl.float32)
215 sin = tl.load(cos_sin_ptr + pos * (D // 2) + HALF + rot).to(tl.float32)
216 o1 = x1 * cos - x2 * sin
217 o2 = x2 * cos + x1 * sin
218 if is_k:
219 slot = tl.load(main_slots_ptr + token).to(tl.int64)
220 if slot >= 0:
221 dst = (
222 main_cache_ptr
223 + (slot // MAIN_PAGE_SIZE) * main_cache_stride_block
224 + (slot % MAIN_PAGE_SIZE) * main_cache_stride_token
225 + kv_head * main_cache_stride_head
226 )
227 _store_rotated(dst, y, o1, o2, main_k_scale)
228 v = tl.load(src + MAIN_HK * MAIN_D + dims)
229 tl.store(dst + MAIN_D + dims, _to_dst_dtype(v, dst, main_v_scale))
230 else:
231 out = (token * MAIN_HQ + head) * MAIN_D
232 _store_rotated(main_q_out_ptr + out, y, o1, o2, None)
233 gate = tl.load(src + MAIN_D + dims)
234 tl.store(main_gate_out_ptr + out + dims, gate)
235 return
236 # K work occupies the first programs; the remaining programs tile Q. This
237 # keeps both paths in one launch while leaving their register shapes
238 # independent.
239 if pid >= num_k_work:
240 q_pid = pid - num_k_work
241 num_head_tiles: tl.constexpr = tl.cdiv(HQ, TILE_H_Q)
242 token_tile = q_pid // num_head_tiles
243 head_tile = q_pid % num_head_tiles
244 tokens = token_tile * TILE_T_Q + tl.arange(0, TILE_T_Q)
245 heads = head_tile * TILE_H_Q + tl.arange(0, TILE_H_Q)
246 valid_tokens = tokens < num_tokens
247 valid_heads = heads < HQ
248 dims = tl.arange(0, D)
249 mask = valid_tokens[:, None, None] & valid_heads[None, :, None]
250 x = tl.load(
251 q_ptr
252 + tokens[:, None, None] * q_stride_token
253 + heads[None, :, None] * D
254 + dims[None, None, :],
255 mask=mask,
256 other=0.0,
257 )
258 pos_t = tl.load(pos_ptr + tokens * pos_stride_token, mask=valid_tokens, other=0)
259 if IS_2D_POSITIONS:
260 pos_h = tl.load(
261 pos_ptr + pos_stride_axis + tokens * pos_stride_token,
262 mask=valid_tokens,
263 other=0,
264 )
265 pos_w = tl.load(
266 pos_ptr + 2 * pos_stride_axis + tokens * pos_stride_token,
267 mask=valid_tokens,
268 other=0,
269 )
270 else:
271 pos_h = pos_t
272 pos_w = pos_t
273 y = _norm_rope(
274 x,
275 pos_t,
276 pos_h,
277 pos_w,
278 cos_sin_ptr,
279 D // 2,
280 q_norm_weight_ptr,
281 eps,
282 IS_2D_POSITIONS,
283 MROPE_H,
284 MROPE_W,
285 )
286 tl.store(
287 q_out_ptr
288 + tokens[:, None, None] * q_out_stride_token
289 + heads[None, :, None] * q_out_stride_head
290 + dims[None, None, :],
291 y,
292 mask=mask,
293 )
294
295 if pid < num_k_work:
296 # One work item owns one completed compression group. Work item zero
297 # additionally commits the request's current raw-K suffix below.
298 work_metadata = tl.load(k_work_metadata_ptr + pid * 2 + tl.arange(0, 2))
299 request, work_in_request = tl.split(work_metadata)
300 if request < 0:
301 return
302
303 query_start = tl.load(query_start_loc_ptr + request)
304 query_end = tl.load(query_start_loc_ptr + request + 1)
305 query_len = query_end - query_start
306 chunk_end = tl.load(logical_positions_ptr + query_end - 1)
307 chunk_start = chunk_end - query_len + 1
308 num_groups = (chunk_end + 1) // COMPRESS_RATIO - chunk_start // COMPRESS_RATIO
309 dims = tl.arange(0, D)
310
311 if work_in_request < num_groups:
312 first_boundary = (
313 (chunk_start + COMPRESS_RATIO) // COMPRESS_RATIO
314 ) * COMPRESS_RATIO - 1
315 end_position = first_boundary + work_in_request * COMPRESS_RATIO
316 boundary_token = query_start + end_position - chunk_start
317 valid_token = (
318 (boundary_token >= query_start)
319 & (boundary_token < query_end)
320 & (boundary_token < num_tokens)
321 )
322 compressed_slot = tl.load(
323 compressed_slots_ptr + boundary_token,
324 mask=valid_token,
325 other=-1,
326 )
327 valid = (
328 valid_token
329 & (compressed_slot >= 0)
330 & (compressed_slot < num_compressed_blocks * COMP_PAGE_SIZE)
331 )
332 state_block = tl.load(state_table_ptr + request * state_table_stride_req)
333 state_block_valid = (state_block >= 0) & (state_block < num_state_blocks)
334 safe_state_block = tl.maximum(state_block, 0).to(tl.int64)
335 group_offsets = tl.arange(0, COMPRESS_RATIO)
336 source_positions = end_position - (COMPRESS_RATIO - 1) + group_offsets
337 source_in_chunk = source_positions >= chunk_start
338 source_tokens = query_start + source_positions - chunk_start
339 source_tokens_valid = (
340 (source_tokens >= query_start)
341 & (source_tokens < query_end)
342 & (source_tokens < num_tokens)
343 )
344 current_base = (
345 k_ptr + tl.maximum(source_tokens, 0)[:, None] * k_stride_token
346 )
347 cached_base = (
348 state_cache_ptr
349 + safe_state_block * state_cache_stride_block
350 + (source_positions % STATE_SIZE)[:, None] * state_cache_stride_token
351 )
352 # Only the first completed group can cross the chunk boundary. Select
353 # historical rows from the ring without issuing two masked loads.
354 source_base = tl.where(source_in_chunk[:, None], current_base, cached_base)
355 # Pointer selection obscures alignment from Triton's analysis.
356 source_base = tl.multiple_of(source_base, (8, 8))
357 source_valid = tl.where(
358 source_in_chunk, source_tokens_valid, state_block_valid
359 )
360 source = tl.load(
361 source_base + dims[None, :],
362 mask=valid & source_valid[:, None],
363 other=0.0,
364 ).to(tl.float32)
365 # Match the unfused path's BF16 pooled tensor before RMSNorm.
366 pooled = (
367 (tl.sum(source, axis=0) / COMPRESS_RATIO).to(tl.bfloat16).to(tl.float32)
368 )
369
370 first_position = end_position - (COMPRESS_RATIO - 1)
371 if CACHE_HAS_ROPE_POS:
372 # RoPE uses the first token in the pooled group. Its exact MRoPE
373 # coordinates may live in this chunk or the raw-state ring.
374 first_in_chunk = first_position >= chunk_start
375 first_token = query_start + first_position - chunk_start
376 first_token_valid = (
377 (first_token >= query_start)
378 & (first_token < query_end)
379 & (first_token < num_tokens)
380 )
381 safe_first_token = tl.maximum(first_token, 0)
382 load_current_position = first_in_chunk & first_token_valid
383 first_pos_t = tl.load(
384 pos_ptr + safe_first_token * pos_stride_token,
385 mask=load_current_position,
386 other=0,
387 )
388 if IS_2D_POSITIONS:
389 first_pos_h = tl.load(
390 pos_ptr + pos_stride_axis + safe_first_token * pos_stride_token,
391 mask=load_current_position,
392 other=0,
393 )
394 first_pos_w = tl.load(
395 pos_ptr
396 + 2 * pos_stride_axis
397 + safe_first_token * pos_stride_token,
398 mask=load_current_position,
399 other=0,
400 )
401 else:
402 first_pos_h = first_pos_t
403 first_pos_w = first_pos_t
404 tail = (
405 state_cache_ptr
406 + safe_state_block * state_cache_stride_block
407 + (first_position % STATE_SIZE) * state_cache_stride_token
408 + D
409 ).to(tl.pointer_type(tl.int64))
410 load_cached_position = ~first_in_chunk & state_block_valid
411 cached_pos_t = tl.load(tail, mask=load_cached_position, other=0)
412 cached_pos_h = tl.load(tail + 1, mask=load_cached_position, other=0)
413 cached_pos_w = tl.load(tail + 2, mask=load_cached_position, other=0)
414 pos_t = tl.where(first_in_chunk, first_pos_t.to(tl.int64), cached_pos_t)
415 pos_h = tl.where(first_in_chunk, first_pos_h.to(tl.int64), cached_pos_h)
416 pos_w = tl.where(first_in_chunk, first_pos_w.to(tl.int64), cached_pos_w)
417 else:
418 pos_t = first_position
419 pos_h = first_position
420 pos_w = first_position
421 y = _norm_rope(
422 tl.reshape(pooled, (1, 1, D)),
423 pos_t + tl.arange(0, 1),
424 pos_h + tl.arange(0, 1),
425 pos_w + tl.arange(0, 1),
426 cos_sin_ptr,
427 D // 2,
428 k_norm_weight_ptr,
429 eps,
430 IS_K_MROPE,
431 MROPE_H,
432 MROPE_W,
433 )
434 compressed_block = (compressed_slot // COMP_PAGE_SIZE).to(tl.int64)
435 compressed_row = compressed_slot % COMP_PAGE_SIZE
436 tl.store(
437 compressed_cache_ptr
438 + compressed_block * compressed_cache_stride_block
439 + compressed_row * compressed_cache_stride_token
440 + dims,
441 tl.reshape(y, (D,)),
442 mask=valid,
443 )
444
445 if work_in_request == 0:
446 # This CTA may have just read historical rows from the circular buffer.
447 # Keep every lane past those loads before overwriting the same ring.
448 tl.debug_barrier()
449 # One CTA per request commits only the suffix retained by the ring.
450 num_state_rows = tl.minimum(query_len, STATE_SIZE)
451 for state_offset in tl.range(0, num_state_rows):
452 token = query_end - num_state_rows + state_offset
453 valid_token = (
454 (token >= query_start) & (token < query_end) & (token < num_tokens)
455 )
456 slot = tl.load(state_slots_ptr + token, mask=valid_token, other=-1)
457 valid_slot = (
458 valid_token & (slot >= 0) & (slot < num_state_blocks * STATE_SIZE)
459 )
460 safe_slot = tl.maximum(slot, 0)
461 state_row = (
462 state_cache_ptr
463 + (safe_slot // STATE_SIZE).to(tl.int64) * state_cache_stride_block
464 + (safe_slot % STATE_SIZE) * state_cache_stride_token
465 )
466 k = tl.load(
467 k_ptr + tl.maximum(token, 0) * k_stride_token + dims,
468 mask=valid_slot,
469 other=0.0,
470 )
471 tl.store(state_row + dims, k, mask=valid_slot)
472 if CACHE_HAS_ROPE_POS:
473 pos_t = tl.load(
474 pos_ptr + tl.maximum(token, 0) * pos_stride_token,
475 mask=valid_slot,
476 other=0,
477 )
478 if IS_2D_POSITIONS:
479 pos_h = tl.load(
480 pos_ptr
481 + pos_stride_axis
482 + tl.maximum(token, 0) * pos_stride_token,
483 mask=valid_slot,
484 other=0,
485 )
486 pos_w = tl.load(
487 pos_ptr
488 + 2 * pos_stride_axis
489 + tl.maximum(token, 0) * pos_stride_token,
490 mask=valid_slot,
491 other=0,
492 )
493 else:
494 pos_h = pos_t
495 pos_w = pos_t
496 tail = (state_row + D).to(tl.pointer_type(tl.int64))
497 tl.store(tail, pos_t.to(tl.int64), mask=valid_slot)
498 tl.store(tail + 1, pos_h.to(tl.int64), mask=valid_slot)
499 tl.store(tail + 2, pos_w.to(tl.int64), mask=valid_slot)
500
501
502 def qsa_prepare(
503 q: torch.Tensor,
504 k: torch.Tensor,
505 positions: torch.Tensor,
506 cos_sin_cache: torch.Tensor,
507 q_norm_weight: torch.Tensor,
508 k_norm_weight: torch.Tensor,
509 eps: float,
510 q_out: torch.Tensor,
511 state_cache: torch.Tensor,
512 state_slots: torch.Tensor,
513 state_block_table: torch.Tensor,
514 query_start_loc: torch.Tensor,
515 logical_positions: torch.Tensor,
516 compressed_cache: torch.Tensor,
517 compressed_slots: torch.Tensor,
518 k_work_metadata: torch.Tensor,
519 *,
520 compress_ratio: int,
521 mrope_section: tuple[int, int, int] | None,
522 rope_pos_offset: int | None,
523 main_qkv: torch.Tensor,
524 main_q_norm_weight: torch.Tensor,
525 main_k_norm_weight: torch.Tensor,
526 main_eps: float,
527 main_kv_cache: torch.Tensor,
528 main_slot_mapping: torch.Tensor,
529 main_k_scale: float,
530 main_v_scale: float,
531 ) -> tuple[torch.Tensor, torch.Tensor]:
532 """Normalize Q, compress K, then update the circular raw state.
533
534 Also prepares the main attention (QK-norm/RoPE, gate copy, K/V cache write)
535 and returns its Q and gate.
536 """
537 num_tokens = q.shape[0]
538 main_head_dim = main_kv_cache.shape[-1] // 2
539 num_main_kv_heads = main_kv_cache.shape[2]
540 num_main_q_heads = main_qkv.shape[1] // (2 * main_head_dim) - num_main_kv_heads
541 main_q_out = main_qkv.new_empty(num_tokens, num_main_q_heads, main_head_dim)
542 main_gate_out = torch.empty_like(main_q_out)
543 if num_tokens == 0:
544 return main_q_out, main_gate_out
545 num_q_heads, head_dim = q_out.shape[1:]
546 assert cos_sin_cache.shape[-1] * 2 == head_dim
547 assert q.shape == (num_tokens, num_q_heads * head_dim)
548 assert k.shape == (num_tokens, head_dim)
549 assert q.stride(-1) == 1
550 assert k.stride(-1) == 1
551 assert q_out.stride(-1) == 1
552 assert cos_sin_cache.is_contiguous()
553 assert state_cache.stride(-1) == 1
554 assert compressed_cache.stride(-1) == 1
555 assert k_work_metadata.ndim == 2 and k_work_metadata.shape[1] == 2
556 is_2d_positions = positions.ndim == 2
557 is_k_mrope = bool(mrope_section)
558 cache_has_rope_pos = rope_pos_offset is not None
559 assert rope_pos_offset is None or rope_pos_offset == head_dim
560 if is_2d_positions:
561 assert positions.shape == (3, num_tokens)
562 assert is_k_mrope
563 pos_stride_axis, pos_stride_token = positions.stride()
564 else:
565 assert positions.shape == (num_tokens,)
566 pos_stride_axis, pos_stride_token = 0, positions.stride(0)
567 section = mrope_section if mrope_section is not None else (0, 0, 0)
568 assert len(section) == 3
569 qkv_width = 2 * (num_main_q_heads + num_main_kv_heads) * main_head_dim
570 assert main_qkv.shape == (num_tokens, qkv_width) and main_qkv.is_contiguous()
571 assert main_slot_mapping.shape == (num_tokens,)
572
573 if num_tokens <= 4096:
574 TILE_T_Q, TILE_H_Q = 2, 2
575 else:
576 TILE_T_Q, TILE_H_Q = 2, 4
577 num_k_work = k_work_metadata.shape[0]
578 num_q_work = triton.cdiv(num_tokens, TILE_T_Q) * triton.cdiv(num_q_heads, TILE_H_Q)
579 num_main_work = num_tokens * (num_main_q_heads + num_main_kv_heads)
580 _qsa_prepare_kernel[(num_k_work + num_q_work + num_main_work,)](
581 q,
582 q.stride(0),
583 k,
584 k.stride(0),
585 positions,
586 pos_stride_axis,
587 pos_stride_token,
588 cos_sin_cache,
589 q_norm_weight,
590 k_norm_weight,
591 eps,
592 q_out,
593 q_out.stride(0),
594 q_out.stride(1),
595 state_cache,
596 state_cache.stride(0),
597 state_cache.stride(1),
598 state_slots,
599 state_block_table,
600 state_block_table.stride(0),
601 query_start_loc,
602 logical_positions,
603 compressed_slots,
604 k_work_metadata,
605 compressed_cache,
606 compressed_cache.stride(0),
607 compressed_cache.stride(1),
608 num_tokens,
609 state_cache.shape[0],
610 compressed_cache.shape[0],
611 num_k_work,
612 HQ=num_q_heads,
613 D=head_dim,
614 TILE_T_Q=TILE_T_Q,
615 TILE_H_Q=TILE_H_Q,
616 COMPRESS_RATIO=compress_ratio,
617 STATE_SIZE=state_cache.shape[1],
618 COMP_PAGE_SIZE=compressed_cache.shape[1],
619 IS_2D_POSITIONS=is_2d_positions,
620 IS_K_MROPE=is_k_mrope,
621 CACHE_HAS_ROPE_POS=cache_has_rope_pos,
622 MROPE_H=section[1],
623 MROPE_W=section[2],
624 main_qkv_ptr=main_qkv,
625 main_q_norm_weight_ptr=main_q_norm_weight,
626 main_k_norm_weight_ptr=main_k_norm_weight,
627 main_eps=main_eps,
628 main_q_out_ptr=main_q_out,
629 main_gate_out_ptr=main_gate_out,
630 main_cache_ptr=main_kv_cache,
631 main_cache_stride_block=main_kv_cache.stride(0),
632 main_cache_stride_token=main_kv_cache.stride(1),
633 main_cache_stride_head=main_kv_cache.stride(2),
634 main_slots_ptr=main_slot_mapping,
635 main_k_scale=main_k_scale,
636 main_v_scale=main_v_scale,
637 MAIN_HQ=num_main_q_heads,
638 MAIN_HK=num_main_kv_heads,
639 MAIN_D=main_head_dim,
640 MAIN_PAGE_SIZE=main_kv_cache.shape[1],
641 num_warps=1,
642 )
643 return main_q_out, main_gate_out
644
645
646 __all__ = ["qsa_prepare"]