main
py 688 lines 22.3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Fused Qwen4Exp PLE kernels.
4
5 N-gram IDs hash each suffix with
6 ``offset[h] + (xor_i(token[t-i] * multiplier[i]) % size[h])``. The gate computes
7 ``d = dot(RMSNorm(key), RMSNorm(query)) / sqrt(H)`` and
8 ``g = sigmoid(sign(d) * sqrt(max(abs(d), 1e-6)))``. Short convolution adds
9 ``silu(sum_k weight[k] * history[t + k * dilation])`` to the gated output and
10 updates its persistent history.
11 """
12
13 from typing import Literal
14
15 import torch
16
17 from vllm.platforms import current_platform
18 from vllm.triton_utils import tl, triton
19 from vllm.utils.torch_utils import direct_register_custom_op
20 from vllm.v1.attention.backends.utils import NULL_BLOCK_ID
21
22
23 # N-gram ID generation
24 @triton.jit(do_not_specialize=["num_tokens", "num_reqs", "binary_search_iters"])
25 def _ple_ngram_ids_kernel(
26 input_ids_ptr,
27 qsl_ptr,
28 ctx_ptr,
29 multipliers_ptr,
30 sizes_ptr,
31 offsets_ptr,
32 out_ptr,
33 num_tokens,
34 num_reqs,
35 eos_token_id,
36 binary_search_iters,
37 NGRAM_CONTEXT_LEN: tl.constexpr,
38 HEADS_PER_NGRAM: tl.constexpr,
39 BLOCK_T: tl.constexpr,
40 launch_pdl: tl.constexpr,
41 ):
42 NGRAM_HEADS: tl.constexpr = NGRAM_CONTEXT_LEN * HEADS_PER_NGRAM
43 BLOCK_H: tl.constexpr = triton.next_power_of_2(NGRAM_HEADS)
44 pid = tl.program_id(0)
45 token_offsets = pid * BLOCK_T + tl.arange(0, BLOCK_T)
46 token_mask = token_offsets < num_tokens
47
48 if launch_pdl:
49 tl.extra.cuda.gdc_wait()
50
51 # A flattened token offset does not identify its request when query chunks
52 # have different lengths. Binary-search the request boundaries instead.
53 p_lo = tl.full([BLOCK_T], 1, tl.int32)
54 p_hi = tl.full([BLOCK_T], num_reqs + 1, tl.int32)
55 for _ in range(binary_search_iters):
56 mid = (p_lo + p_hi) // 2
57 qmid = tl.load(qsl_ptr + mid, mask=token_mask & (mid <= num_reqs), other=0)
58 pred = qmid <= token_offsets
59 p_lo = tl.where(pred, mid + 1, p_lo)
60 p_hi = tl.where(pred, p_hi, mid)
61 req = tl.minimum(p_lo - 1, num_reqs - 1).to(tl.int64)
62 request_start = tl.load(qsl_ptr + req, mask=token_mask, other=0)
63 chunk_pos = token_offsets - request_start
64
65 # Initialize every head with the current-token term.
66 current_token = tl.load(input_ids_ptr + token_offsets, mask=token_mask, other=0).to(
67 tl.int64
68 )
69 current_multiplier = tl.load(multipliers_ptr)
70 mixed = current_token[:, None] * current_multiplier
71
72 g = tl.arange(0, BLOCK_H)
73 head_mask = g < NGRAM_HEADS
74 ngram_order = g // HEADS_PER_NGRAM + 2
75
76 # Walk predecessors from newest to oldest. At chunk boundaries they come
77 # from ngram_context; otherwise they come from this step's input_ids.
78 crossed = tl.zeros([BLOCK_T], tl.int1)
79 for shift in tl.static_range(1, NGRAM_CONTEXT_LEN + 1):
80 in_step = chunk_pos >= shift
81 ctx_col = NGRAM_CONTEXT_LEN - shift + chunk_pos
82 step_token = tl.load(
83 input_ids_ptr + token_offsets - shift,
84 mask=token_mask & in_step,
85 other=0,
86 )
87 context_token = tl.load(
88 ctx_ptr + req * NGRAM_CONTEXT_LEN + ctx_col,
89 mask=token_mask & (~in_step),
90 other=0,
91 )
92 candidate = tl.where(in_step, step_token, context_token).to(tl.int64)
93 # Older positions remain behind the first EOS boundary.
94 candidate = tl.where(crossed, eos_token_id, candidate)
95 crossed = crossed | (candidate == eos_token_id)
96 multiplier = tl.load(multipliers_ptr + shift)
97 term = candidate[:, None] * multiplier
98 mixed = mixed ^ tl.where((ngram_order > shift)[None, :], term, 0)
99
100 # Map each hash into its embedding-table partition.
101 sizes = tl.load(sizes_ptr + g, mask=head_mask, other=1)[None, :]
102 head_offsets = tl.load(offsets_ptr + g, mask=head_mask, other=0)[None, :]
103 # Hash products may overflow int64; preserve torch.remainder semantics.
104 remainders = mixed % sizes
105 remainders = tl.where(remainders < 0, remainders + sizes, remainders)
106 ids = remainders + head_offsets
107 if launch_pdl:
108 tl.extra.cuda.gdc_launch_dependents()
109 tl.store(
110 out_ptr + token_offsets[:, None] * NGRAM_HEADS + g[None, :],
111 ids,
112 mask=token_mask[:, None] & head_mask[None, :],
113 )
114
115
116 def _ple_ngram_ids(
117 input_ids: torch.Tensor,
118 query_start_loc: torch.Tensor,
119 ngram_context: torch.Tensor,
120 layer_multipliers: torch.Tensor,
121 ngram_heads_vocab_sizes: torch.Tensor,
122 ngram_heads_offsets: torch.Tensor,
123 output: torch.Tensor,
124 eos_token_id: int,
125 heads_per_ngram: int,
126 ) -> None:
127 input_ids = input_ids.reshape(-1)
128 num_tokens = input_ids.shape[0]
129 num_reqs = query_start_loc.numel() - 1
130 ctx_len = ngram_context.shape[1]
131 BLOCK_T = 8
132 launch_pdl = current_platform.is_arch_support_pdl()
133 _ple_ngram_ids_kernel[(triton.cdiv(num_tokens, BLOCK_T),)](
134 input_ids,
135 query_start_loc,
136 ngram_context,
137 layer_multipliers,
138 ngram_heads_vocab_sizes,
139 ngram_heads_offsets,
140 output,
141 num_tokens,
142 num_reqs,
143 eos_token_id,
144 binary_search_iters=num_reqs.bit_length(),
145 NGRAM_CONTEXT_LEN=ctx_len,
146 HEADS_PER_NGRAM=heads_per_ngram,
147 BLOCK_T=BLOCK_T,
148 launch_pdl=launch_pdl,
149 num_warps=4,
150 )
151
152
153 def ple_ngram_ids(
154 input_ids: torch.Tensor,
155 query_start_loc: torch.Tensor,
156 ngram_context: torch.Tensor,
157 layer_multipliers: torch.Tensor,
158 ngram_heads_vocab_sizes: torch.Tensor,
159 ngram_heads_offsets: torch.Tensor,
160 eos_token_id: int,
161 heads_per_ngram: int,
162 output: torch.Tensor | None = None,
163 ) -> torch.Tensor:
164 if output is None:
165 output = torch.empty(
166 (input_ids.numel(), ngram_context.shape[1] * heads_per_ngram),
167 dtype=torch.int64,
168 device=input_ids.device,
169 )
170 _ple_ngram_ids(
171 input_ids,
172 query_start_loc,
173 ngram_context,
174 layer_multipliers,
175 ngram_heads_vocab_sizes,
176 ngram_heads_offsets,
177 output,
178 eos_token_id,
179 heads_per_ngram,
180 )
181 return output
182
183
184 # Gating and normalization
185 @triton.jit
186 def _ple_gate_kernel(
187 key_ptr, # [T, HC*H] with row stride key_rs
188 value_ptr, # [T, H] with row stride value_rs
189 hidden_ptr, # [T, HC*H]
190 nk_ptr, # [HC*H] norm_key weight
191 nq_ptr, # [HC*H] norm_query weight
192 ncw_ptr, # [HC*H] norm_conv weight
193 gated_ptr, # [T, HC*H] out
194 normed_ptr, # [T, HC*H] out
195 key_rs,
196 value_rs,
197 eps,
198 H: tl.constexpr,
199 HC: tl.constexpr,
200 BLOCK_H: tl.constexpr,
201 launch_pdl: tl.constexpr,
202 ):
203 t = tl.program_id(0)
204 s = tl.program_id(1)
205 lanes = tl.arange(0, BLOCK_H)
206 mask = lanes < H
207 offs = s * H + lanes
208 dtype: tl.constexpr = key_ptr.dtype.element_ty
209
210 if launch_pdl:
211 tl.extra.cuda.gdc_wait()
212
213 k = tl.load(key_ptr + t * key_rs + offs, mask=mask, other=0.0).to(tl.float32)
214 q = tl.load(hidden_ptr + t * HC * H + offs, mask=mask, other=0.0).to(tl.float32)
215 nk = tl.load(nk_ptr + offs, mask=mask, other=0.0).to(tl.float32)
216 nq = tl.load(nq_ptr + offs, mask=mask, other=0.0).to(tl.float32)
217
218 # Match eager materialization at each intermediate tensor boundary.
219 k_n = (k * tl.rsqrt(tl.sum(k * k) / H + eps) * (1.0 + nk)).to(dtype)
220 q_n = (q * tl.rsqrt(tl.sum(q * q) / H + eps) * (1.0 + nq)).to(dtype)
221 products = (k_n.to(tl.float32) * q_n.to(tl.float32)).to(dtype)
222 dot = tl.sum(products.to(tl.float32)).to(dtype).to(tl.float32)
223 d = dot / tl.sqrt(float(H))
224 d = d.to(dtype).to(tl.float32)
225 sign = tl.where(d < 0, -1.0, 0.0)
226 sign = tl.where(d > 0, 1.0, sign)
227 magnitude = tl.sqrt(tl.maximum(tl.abs(d), 1e-6)).to(dtype)
228 g = tl.sigmoid(sign * magnitude)
229 g = g.to(dtype).to(tl.float32)
230
231 v = tl.load(value_ptr + t * value_rs + lanes, mask=mask, other=0.0).to(tl.float32)
232 gated = (g * v).to(dtype)
233 gf = gated.to(tl.float32)
234 ncw = tl.load(ncw_ptr + offs, mask=mask, other=0.0).to(tl.float32)
235 normed = gf * tl.rsqrt(tl.sum(gf * gf) / H + eps) * (1.0 + ncw)
236
237 if launch_pdl:
238 tl.extra.cuda.gdc_launch_dependents()
239 tl.store(gated_ptr + t * HC * H + offs, gated, mask=mask)
240 tl.store(normed_ptr + t * HC * H + offs, normed, mask=mask)
241
242
243 def _ple_gate(
244 key: torch.Tensor,
245 value: torch.Tensor,
246 hidden: torch.Tensor,
247 norm_key_w: torch.Tensor,
248 norm_query_w: torch.Tensor,
249 norm_conv_w: torch.Tensor,
250 eps: float,
251 ) -> tuple[torch.Tensor, torch.Tensor]:
252 num_tokens = hidden.shape[0]
253 h = value.shape[-1]
254 hc = hidden.shape[-1] // h
255 assert key.stride(1) == 1 and value.stride(1) == 1
256 assert hidden.is_contiguous()
257 if key.dtype != value.dtype or key.dtype != hidden.dtype:
258 raise ValueError("key, value, and hidden must have the same dtype")
259 if key.dtype not in (torch.bfloat16, torch.float16):
260 raise ValueError("PLE gate supports BF16 and FP16 inputs")
261 gated = torch.empty_like(hidden)
262 normed = torch.empty_like(hidden)
263 _ple_gate_kernel[(num_tokens, hc)](
264 key,
265 value,
266 hidden,
267 norm_key_w,
268 norm_query_w,
269 norm_conv_w,
270 gated,
271 normed,
272 key.stride(0),
273 value.stride(0),
274 eps,
275 H=h,
276 HC=hc,
277 BLOCK_H=triton.next_power_of_2(h),
278 num_warps=4,
279 launch_pdl=current_platform.is_arch_support_pdl(),
280 )
281 return gated, normed
282
283
284 def _ple_gate_fake(
285 key: torch.Tensor,
286 value: torch.Tensor,
287 hidden: torch.Tensor,
288 norm_key_w: torch.Tensor,
289 norm_query_w: torch.Tensor,
290 norm_conv_w: torch.Tensor,
291 eps: float,
292 ) -> tuple[torch.Tensor, torch.Tensor]:
293 return torch.empty_like(hidden), torch.empty_like(hidden)
294
295
296 direct_register_custom_op(
297 op_name="qwen4_exp_ple_gate",
298 op_func=_ple_gate,
299 mutates_args=[],
300 fake_impl=_ple_gate_fake,
301 )
302
303
304 def ple_gate(
305 key: torch.Tensor,
306 value: torch.Tensor,
307 hidden: torch.Tensor,
308 norm_key_w: torch.Tensor,
309 norm_query_w: torch.Tensor,
310 norm_conv_w: torch.Tensor,
311 eps: float,
312 ) -> tuple[torch.Tensor, torch.Tensor]:
313 return torch.ops.vllm.qwen4_exp_ple_gate(
314 key, value, hidden, norm_key_w, norm_query_w, norm_conv_w, eps
315 )
316
317
318 # Dilated short convolution
319 @triton.jit(do_not_specialize=["num_reqs", "bs_iters", "has_token_map"])
320 def _ple_conv_kernel(
321 x_ptr,
322 state_ptr,
323 w_ptr,
324 residual_ptr,
325 outer_residual_ptr,
326 state_idx_ptr,
327 qsl_ptr,
328 num_acc_ptr,
329 has_init_ptr,
330 token_idx_ptr,
331 has_token_map,
332 num_reqs,
333 bs_iters,
334 state_idx_stride,
335 state_bs,
336 state_ws,
337 state_cs,
338 C: tl.constexpr,
339 BLOCK_C: tl.constexpr,
340 STATE_LEN: tl.constexpr,
341 DILATION: tl.constexpr,
342 KERNEL_SIZE: tl.constexpr,
343 SPEC_QUERY_LEN: tl.constexpr,
344 MODE: tl.constexpr,
345 HAS_INIT: tl.constexpr,
346 NULL_STATE_ID: tl.constexpr,
347 launch_pdl: tl.constexpr,
348 ):
349 t = tl.program_id(0)
350 pid_c = tl.program_id(1)
351 c_offs = pid_c * BLOCK_C + tl.arange(0, BLOCK_C)
352 c_mask = c_offs < C
353
354 if launch_pdl:
355 tl.extra.cuda.gdc_wait()
356
357 # Mode-specific launches use logical row indices. Mixed batches map them
358 # back to the original token order without gathering inputs or residuals.
359 output_t = tl.load(token_idx_ptr + t, mask=has_token_map, other=t).to(tl.int64)
360
361 if MODE == "decode":
362 r = t
363 q_start = t
364 j = tl.full([], 0, tl.int32)
365 slot_off = tl.full([], 0, tl.int32)
366 in_range = True
367 else:
368 # Locate the request containing this token.
369 lo = tl.full([], 1, tl.int32)
370 hi = tl.full([], num_reqs + 1, tl.int32)
371 for _ in range(bs_iters):
372 mid = (lo + hi) // 2
373 qmid = tl.load(qsl_ptr + mid, mask=mid <= num_reqs, other=0)
374 pred = qmid <= t
375 lo = tl.where(pred, mid + 1, lo)
376 hi = tl.where(pred, hi, mid)
377 r = tl.minimum(lo - 1, num_reqs - 1)
378 q_start = tl.load(qsl_ptr + r)
379 j = (t - q_start).to(tl.int32)
380 total_real = tl.load(qsl_ptr + num_reqs)
381 in_range = t < total_real
382 if MODE == "spec":
383 num_acc = tl.load(num_acc_ptr + r)
384 # Roll back draft state to the last accepted token.
385 slot_off = tl.minimum(tl.maximum(num_acc - 1, 0), SPEC_QUERY_LEN - 1).to(
386 tl.int32
387 )
388 else:
389 slot_off = tl.full([], 0, tl.int32)
390
391 sid = tl.load(state_idx_ptr + r * state_idx_stride).to(tl.int64)
392 state_ok = sid != NULL_STATE_ID
393 sid_safe = tl.where(state_ok, sid, 0)
394 if HAS_INIT:
395 has_init = tl.load(has_init_ptr + r, mask=state_ok, other=0) != 0
396 else:
397 has_init = state_ok
398 if MODE == "spec":
399 # A new spec chunk can produce output from its own tokens without a
400 # prior state; null slots in other modes denote inactive rows.
401 read_state = state_ok
402 out_ok = in_range
403 else:
404 read_state = state_ok & has_init
405 out_ok = in_range & state_ok
406
407 # state[sid, w, c] may be a strided view into a shared pool.
408 base_state = state_ptr + sid_safe * state_bs
409 acc = tl.zeros([BLOCK_C], tl.float32)
410 for k in tl.static_range(0, KERNEL_SIZE):
411 h = j + DILATION * k
412 from_state = h <= STATE_LEN - 1
413 state_tap = tl.load(
414 base_state + (h + slot_off) * state_ws + c_offs * state_cs,
415 mask=c_mask & read_state & from_state,
416 other=0.0,
417 )
418 input_t = q_start + h - STATE_LEN
419 input_t = tl.load(
420 token_idx_ptr + input_t,
421 mask=has_token_map & out_ok & (~from_state),
422 other=input_t,
423 ).to(tl.int64)
424 input_tap = tl.load(
425 x_ptr + input_t * C + c_offs,
426 mask=c_mask & out_ok & (~from_state),
427 other=0.0,
428 )
429 tap = tl.where(from_state, state_tap, input_tap).to(tl.float32)
430 weight = tl.load(
431 w_ptr + c_offs * KERNEL_SIZE + k,
432 mask=c_mask,
433 other=0.0,
434 ).to(tl.float32)
435 acc += weight * tap
436
437 # F.conv1d materializes its output dtype before SiLU.
438 conv = acc.to(residual_ptr.dtype.element_ty).to(tl.float32)
439 y = conv * tl.sigmoid(conv)
440 conv_output = tl.where(out_ok, y, 0.0).to(residual_ptr.dtype.element_ty)
441 residual = tl.load(
442 residual_ptr + output_t * C + c_offs,
443 mask=c_mask,
444 other=0.0,
445 )
446 # Preserve the original eager operation boundaries: short convolution is
447 # first accumulated into the BF16/FP16 PLE output, then the outer residual
448 # is added to that rounded value.
449 ple_output = (residual + conv_output).to(residual_ptr.dtype.element_ty)
450 outer_residual = tl.load(
451 outer_residual_ptr + output_t * C + c_offs,
452 mask=c_mask,
453 other=0.0,
454 )
455 ple_output = outer_residual.to(tl.float32) + ple_output.to(tl.float32)
456 if launch_pdl:
457 tl.extra.cuda.gdc_launch_dependents()
458 tl.store(
459 residual_ptr + output_t * C + c_offs,
460 ple_output,
461 mask=c_mask,
462 )
463
464 if MODE == "decode":
465 # Decode has one program per request and channel block, so no peer can
466 # still be reading this state slice when it is updated.
467 decode_input = tl.load(
468 x_ptr + output_t * C + c_offs,
469 mask=c_mask & state_ok,
470 other=0.0,
471 )
472 for i in tl.static_range(0, STATE_LEN):
473 if i < STATE_LEN - 1:
474 next_state = tl.load(
475 base_state + (i + 1) * state_ws + c_offs * state_cs,
476 mask=c_mask & state_ok & has_init,
477 other=0.0,
478 )
479 else:
480 next_state = decode_input
481 tl.store(
482 base_state + i * state_ws + c_offs * state_cs,
483 next_state,
484 mask=c_mask & state_ok,
485 )
486
487
488 # Prefill and spec write back separately so every token reads the old state.
489 @triton.jit(do_not_specialize=["has_token_map"])
490 def _ple_conv_writeback_kernel(
491 x_ptr,
492 state_ptr,
493 state_idx_ptr,
494 qsl_ptr,
495 num_acc_ptr,
496 has_init_ptr,
497 token_idx_ptr,
498 has_token_map,
499 state_idx_stride,
500 state_bs,
501 state_ws,
502 state_cs,
503 C: tl.constexpr,
504 BLOCK_C: tl.constexpr,
505 STATE_LEN: tl.constexpr,
506 SPEC_QUERY_LEN: tl.constexpr,
507 STATE_WIDTH: tl.constexpr,
508 MODE: tl.constexpr,
509 HAS_INIT: tl.constexpr,
510 NULL_STATE_ID: tl.constexpr,
511 ):
512 r = tl.program_id(0)
513 pid_c = tl.program_id(1)
514 c_offs = pid_c * BLOCK_C + tl.arange(0, BLOCK_C)
515 c_mask = c_offs < C
516
517 sid = tl.load(state_idx_ptr + r * state_idx_stride).to(tl.int64)
518 state_ok = sid != NULL_STATE_ID
519 if not state_ok:
520 return
521
522 q_start = tl.load(qsl_ptr + r)
523 q_end = tl.load(qsl_ptr + r + 1)
524 qlen = (q_end - q_start).to(tl.int32)
525 if MODE == "spec":
526 num_acc = tl.load(num_acc_ptr + r)
527 # Use the same rollback window as the output kernel.
528 slot_off = tl.minimum(tl.maximum(num_acc - 1, 0), SPEC_QUERY_LEN - 1).to(
529 tl.int32
530 )
531 shift = 1
532 else:
533 slot_off = tl.full([], 0, tl.int32)
534 shift = qlen
535 if qlen <= 0:
536 return
537
538 has_init = tl.load(has_init_ptr + r) != 0 if HAS_INIT else True
539 src_ok = state_ok if MODE == "spec" else state_ok & has_init
540
541 WRITE_W: tl.constexpr = STATE_WIDTH if MODE == "spec" else STATE_LEN
542 base_state = state_ptr + sid * state_bs
543 for i in tl.static_range(0, WRITE_W):
544 m = shift + i
545 from_state = m <= STATE_LEN - 1
546 # Short or graph-padded spec chunks preserve the unused state tail.
547 do_write = i < STATE_LEN + qlen - 1 if MODE == "spec" else True
548 state_value = tl.load(
549 base_state + (slot_off + m) * state_ws + c_offs * state_cs,
550 mask=c_mask & from_state & src_ok,
551 other=0.0,
552 )
553 input_t = q_start + m - STATE_LEN
554 input_t = tl.load(
555 token_idx_ptr + input_t,
556 mask=has_token_map & (~from_state) & do_write,
557 other=input_t,
558 ).to(tl.int64)
559 input_value = tl.load(
560 x_ptr + input_t * C + c_offs,
561 mask=c_mask & (~from_state) & do_write,
562 other=0.0,
563 )
564 value = tl.where(from_state, state_value, input_value)
565 tl.store(
566 base_state + i * state_ws + c_offs * state_cs,
567 value,
568 mask=c_mask & do_write,
569 )
570
571
572 def ple_conv(
573 inputs: torch.Tensor,
574 residual: torch.Tensor,
575 conv_state: torch.Tensor,
576 conv_weights: torch.Tensor,
577 state_indices: torch.Tensor,
578 outer_residual: torch.Tensor,
579 *,
580 mode: Literal["decode", "spec", "prefill"],
581 dilation: int,
582 query_start_loc: torch.Tensor | None = None,
583 num_accepted_tokens: torch.Tensor | None = None,
584 has_initial_states: torch.Tensor | None = None,
585 spec_query_len: int = 1,
586 token_indices: torch.Tensor | None = None,
587 ) -> None:
588 """Add short convolution and the outer residual; update state."""
589 BLOCK_C = 512
590 kernel_spec_query_len = spec_query_len if mode == "spec" else 1
591 T, C = inputs.shape
592 K = conv_weights.shape[1]
593 state_len = (K - 1) * dilation
594 state_width = state_len + kernel_spec_query_len - 1
595 if token_indices is not None:
596 T = token_indices.numel()
597 if conv_state.shape[1] != C or conv_state.shape[2] < state_width:
598 raise ValueError(
599 "conv_state must have shape [slots, channels, window], with "
600 f"channels={C} and window >= {state_width}"
601 )
602 state_bs, state_cs, state_ws = conv_state.stride()
603
604 if mode == "decode":
605 num_reqs = T
606 binary_search_iters = 1
607 has_initial_states_arg = has_initial_states is not None
608 elif mode == "spec":
609 if query_start_loc is None or num_accepted_tokens is None:
610 raise ValueError(
611 "query_start_loc and num_accepted_tokens are required for spec decode"
612 )
613 num_reqs = state_indices.numel()
614 binary_search_iters = max(num_reqs, 1).bit_length()
615 has_initial_states_arg = False
616 elif mode == "prefill":
617 if query_start_loc is None or has_initial_states is None:
618 raise ValueError(
619 "query_start_loc and has_initial_states are required for prefill"
620 )
621 num_reqs = state_indices.numel()
622 binary_search_iters = max(num_reqs, 1).bit_length()
623 has_initial_states_arg = True
624 else:
625 raise ValueError(f"Unsupported short-conv mode: {mode}")
626
627 num_warps = 4 if mode == "prefill" else 8
628 launch_pdl = current_platform.is_arch_support_pdl()
629 # Pure-prefill indices can be a strided block-table column view.
630 state_idx_stride = state_indices.stride(0)
631
632 # Constexpr flags eliminate accesses to optional None arguments. Without a
633 # token map, state_indices is an unused but device-resident placeholder.
634 _ple_conv_kernel[(T, triton.cdiv(C, BLOCK_C))](
635 inputs,
636 conv_state,
637 conv_weights,
638 residual,
639 outer_residual,
640 state_indices,
641 query_start_loc,
642 num_accepted_tokens,
643 has_initial_states,
644 token_indices if token_indices is not None else state_indices,
645 token_indices is not None,
646 num_reqs,
647 binary_search_iters,
648 state_idx_stride,
649 state_bs,
650 state_ws,
651 state_cs,
652 C=C,
653 BLOCK_C=BLOCK_C,
654 STATE_LEN=state_len,
655 DILATION=dilation,
656 KERNEL_SIZE=K,
657 SPEC_QUERY_LEN=kernel_spec_query_len,
658 MODE=mode,
659 HAS_INIT=has_initial_states_arg,
660 NULL_STATE_ID=NULL_BLOCK_ID,
661 launch_pdl=launch_pdl,
662 num_warps=num_warps,
663 )
664 # conv state update is fused with the kernel above for decode
665 if mode != "decode":
666 _ple_conv_writeback_kernel[(num_reqs, triton.cdiv(C, BLOCK_C))](
667 inputs,
668 conv_state,
669 state_indices,
670 query_start_loc,
671 num_accepted_tokens,
672 has_initial_states,
673 token_indices if token_indices is not None else state_indices,
674 token_indices is not None,
675 state_idx_stride,
676 state_bs,
677 state_ws,
678 state_cs,
679 C=C,
680 BLOCK_C=BLOCK_C,
681 STATE_LEN=state_len,
682 SPEC_QUERY_LEN=kernel_spec_query_len,
683 STATE_WIDTH=state_width,
684 MODE=mode,
685 HAS_INIT=has_initial_states_arg,
686 NULL_STATE_ID=NULL_BLOCK_ID,
687 num_warps=num_warps,
688 )