main
py 725 lines 24.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from collections.abc import Sequence
4 from dataclasses import dataclass
5 from typing import TYPE_CHECKING
6
7 import numpy as np
8 import torch
9
10 from vllm.triton_utils import tl, triton
11 from vllm.utils import random_uuid
12 from vllm.utils.math_utils import cdiv
13
14 if TYPE_CHECKING:
15 from vllm.v1.worker.gpu.attn_utils import FastPrefillBatchMetadata
16 from vllm.v1.worker.gpu.block_table import BlockTables
17
18
19 class InputBuffers:
20 def __init__(self, max_num_reqs: int, max_num_tokens: int, device: torch.device):
21 self.max_num_reqs = max_num_reqs
22 self.max_num_tokens = max_num_tokens
23 self.device = device
24
25 self.input_ids = torch.zeros(max_num_tokens, dtype=torch.int32, device=device)
26 self.positions = torch.zeros(max_num_tokens, dtype=torch.int64, device=device)
27 self.is_padding = torch.zeros(max_num_tokens, dtype=torch.bool, device=device)
28 self.query_start_loc = torch.zeros(
29 max_num_reqs + 1, dtype=torch.int32, device=device
30 )
31 self.seq_lens = torch.zeros(max_num_reqs, dtype=torch.int32, device=device)
32 # DCP: per-request local seq_lens buffer
33 self.dcp_local_seq_lens = torch.zeros(
34 max_num_reqs, dtype=torch.int32, device=device
35 )
36
37
38 @dataclass
39 class InputBatch:
40 # batch_idx -> req_id
41 req_ids: list[str]
42 num_reqs: int
43 num_reqs_after_padding: int
44
45 # batch_idx -> req_state_idx
46 idx_mapping: torch.Tensor
47 idx_mapping_np: np.ndarray
48 # Identical to idx_mapping except for spec decoding.
49 expanded_idx_mapping: torch.Tensor
50 # [total_num_logits] position within request for each logit
51 expanded_local_pos: torch.Tensor
52
53 # [num_reqs]
54 # batch_idx -> num_scheduled_tokens, (upper bound when using adaptive verification)
55 num_scheduled_tokens: np.ndarray
56 # number of tokens in the batch,
57 # may be < sum(num_scheduled_tokens) when using adaptive verification
58 num_tokens: int
59 num_tokens_after_padding: int
60 # Sum of draft tokens scheduled across requests.
61 num_draft_tokens: int
62 # [num_reqs] number of draft tokens scheduled for each request, if any.
63 num_draft_tokens_per_req: np.ndarray | None
64
65 # [num_reqs + 1]
66 query_start_loc: torch.Tensor
67 query_start_loc_np: np.ndarray
68 # [num_reqs]
69 seq_lens: torch.Tensor
70 # [num_reqs] CPU upper bound on seq_lens (see CommonAttentionMetadata).
71 seq_lens_cpu_upper_bound: torch.Tensor
72 # [num_reqs]
73 dcp_local_seq_lens: torch.Tensor | None
74 # [num_reqs]
75 num_computed_tokens_np: np.ndarray
76 # [num_reqs]
77 prefill_len_np: np.ndarray
78 # [num_reqs]
79 num_computed_prefill_tokens_np: np.ndarray
80 # [num_reqs] CPU bool array == (num_computed_prefill_tokens_np < prefill_len_np).
81 is_prefilling_np: np.ndarray
82 # == np.any(is_prefilling_np)
83 has_prefill: bool
84 # No prefills, or only prefill rows that run as decodes.
85 decode_graph_eligible: bool
86
87 # [num_tokens_after_padding]
88 input_ids: torch.Tensor
89 # [num_tokens_after_padding]
90 positions: torch.Tensor
91 # [num_tokens_after_padding]
92 is_padding: torch.Tensor
93
94 # [total_num_logits]
95 logits_indices: torch.Tensor
96 # [num_reqs + 1]
97 cu_num_logits: torch.Tensor
98 cu_num_logits_np: np.ndarray
99
100 # Whether any requests in batch use structured output.
101 has_structured_output_reqs: bool
102
103 # [num_reqs] per-request prompt length, only populated for R-SWA.
104 prompt_lens: torch.Tensor | None
105
106 # [num_reqs] only populated for non-dummy pipeline-parallel batches.
107 max_seq_len_np: np.ndarray | None = None
108
109 # Longest query the batch may contain. Set when a cudagraph descriptor promises
110 # a query length this batch's own split does not reach, so attention metadata
111 # stays valid for every replay the graph serves.
112 max_query_len: int | None = None
113
114 # Arms the KV-sharing fast prefill path for this step. Absent for dummy
115 # (cudagraph capture) batches, which run the KV-sharing layers in full.
116 fast_prefill: "FastPrefillBatchMetadata | None" = None
117
118 # [num_reqs] set only under PCP+DCP (see CommonAttentionMetadata).
119 dcp_local_seq_lens_cpu_upper_bound: torch.Tensor | None = None
120
121 # [num_reqs] prefilling rows that schedule one new prompt token (excluding
122 # drafts) over existing context and so compute exactly like decodes.
123 # None if there are no prefills.
124 prefill_runs_as_decode_np: np.ndarray | None = None
125
126 @classmethod
127 def make_dummy(
128 cls,
129 num_reqs: int,
130 num_tokens: int,
131 input_buffers: InputBuffers,
132 max_query_len: int | None = None,
133 is_padding: bool = True,
134 ) -> "InputBatch":
135 assert 0 < num_reqs <= num_tokens
136 device = input_buffers.device
137
138 req_ids = [f"req_{i}_{random_uuid()}" for i in range(num_reqs)]
139 idx_mapping_np = np.arange(num_reqs, dtype=np.intp)
140 idx_mapping = torch.arange(num_reqs, dtype=torch.int32, device=device)
141 expanded_idx_mapping = idx_mapping
142 expanded_local_pos = torch.zeros(num_reqs, dtype=torch.int32, device=device)
143
144 # Distribute the remainder evenly so that no dummy request exceeds
145 # ceil(num_tokens / num_reqs) <= max_model_len tokens. Varlen graphs
146 # accept any split with non-empty slots, so this shape works for them
147 # too; attention metadata is built from the promised max_query_len.
148 base_tokens = num_tokens // num_reqs
149 num_extra = num_tokens % num_reqs
150 assert max_query_len is None or base_tokens + (num_extra > 0) <= max_query_len
151 num_scheduled_tokens = np.full(num_reqs, base_tokens, dtype=np.int32)
152 if num_extra > 0:
153 num_scheduled_tokens[-num_extra:] += 1
154 assert int(num_scheduled_tokens.sum()) == num_tokens
155
156 # seq_len equals to query_len
157 input_buffers.seq_lens[: num_reqs - num_extra] = base_tokens
158 input_buffers.seq_lens[num_reqs - num_extra : num_reqs] = base_tokens + 1
159 # Pad for full CUDA graph mode.
160 input_buffers.seq_lens[num_reqs:] = 0
161 seq_lens = input_buffers.seq_lens[:num_reqs]
162
163 query_start_loc_np = np.empty(num_reqs + 1, dtype=np.int32)
164 query_start_loc_np[0] = 0
165 np.cumsum(num_scheduled_tokens, out=query_start_loc_np[1:])
166 input_buffers.query_start_loc[:1] = 0
167 torch.cumsum(
168 seq_lens, dim=0, out=input_buffers.query_start_loc[1 : num_reqs + 1]
169 )
170 # Pad for full CUDA graph mode.
171 input_buffers.query_start_loc[num_reqs + 1 :] = num_tokens
172 query_start_loc = input_buffers.query_start_loc[: num_reqs + 1]
173
174 input_ids = input_buffers.input_ids[:num_tokens].zero_()
175 positions = input_buffers.positions[:num_tokens].zero_()
176
177 input_buffers.is_padding[:num_tokens].fill_(is_padding)
178 is_padding = input_buffers.is_padding[:num_tokens]
179
180 logits_indices = query_start_loc[1:] - 1
181 cu_num_logits = torch.arange(num_reqs + 1, device=device, dtype=torch.int32)
182 cu_num_logits_np = np.arange(num_reqs + 1, dtype=np.int32)
183 # Copy so set_dummy_context can add context in place without touching
184 # num_scheduled_tokens.
185 seq_lens_cpu_upper_bound = torch.from_numpy(num_scheduled_tokens.copy())
186 return cls(
187 req_ids=req_ids,
188 num_reqs=num_reqs,
189 num_reqs_after_padding=num_reqs,
190 idx_mapping=idx_mapping,
191 idx_mapping_np=idx_mapping_np,
192 expanded_idx_mapping=expanded_idx_mapping,
193 expanded_local_pos=expanded_local_pos,
194 num_scheduled_tokens=num_scheduled_tokens,
195 num_tokens=num_tokens,
196 num_tokens_after_padding=num_tokens,
197 num_draft_tokens=0,
198 num_draft_tokens_per_req=None,
199 query_start_loc=query_start_loc,
200 query_start_loc_np=query_start_loc_np,
201 seq_lens=seq_lens,
202 seq_lens_cpu_upper_bound=seq_lens_cpu_upper_bound,
203 dcp_local_seq_lens=None,
204 num_computed_tokens_np=np.zeros(num_reqs, dtype=np.int32),
205 prefill_len_np=np.zeros(num_reqs, dtype=np.int32),
206 num_computed_prefill_tokens_np=np.zeros(num_reqs, dtype=np.int32),
207 is_prefilling_np=np.zeros(num_reqs, dtype=np.bool_),
208 has_prefill=False,
209 max_seq_len_np=None,
210 decode_graph_eligible=True,
211 input_ids=input_ids,
212 positions=positions,
213 is_padding=is_padding,
214 logits_indices=logits_indices,
215 cu_num_logits=cu_num_logits,
216 cu_num_logits_np=cu_num_logits_np,
217 has_structured_output_reqs=False,
218 prompt_lens=None,
219 max_query_len=max_query_len,
220 )
221
222
223 def set_dummy_context(
224 input_batch: InputBatch,
225 block_tables: "BlockTables",
226 context_len: int,
227 num_kv_blocks: int,
228 max_model_len: int,
229 input_block_tables: Sequence[torch.Tensor] | None = None,
230 ) -> None:
231 """Give each dummy request context_len of context, used when profiling step cost."""
232 if input_block_tables is None:
233 input_block_tables = block_tables.input_block_tables
234 if not input_block_tables:
235 # Attention-free models have no KV context to fabricate.
236 return
237 num_reqs = input_batch.num_reqs
238 query_len = input_batch.max_query_len or int(input_batch.num_scheduled_tokens.max())
239 context_len = max(min(context_len, max_model_len - query_len), 0)
240 if not context_len:
241 return
242
243 # Decode-like shape: each request continues after context_len
244 # already-computed tokens.
245 input_batch.seq_lens += context_len
246 input_batch.seq_lens_cpu_upper_bound += context_len
247 input_batch.num_computed_tokens_np.fill(context_len)
248 input_batch.num_computed_prefill_tokens_np.fill(context_len)
249 local_pos = np.arange(input_batch.num_tokens, dtype=np.int64) - np.repeat(
250 input_batch.query_start_loc_np[:-1], input_batch.num_scheduled_tokens
251 )
252 input_batch.positions.copy_(torch.from_numpy(local_pos + context_len))
253
254 seq_len = context_len + query_len
255 for block_table, block_size, bpk in zip(
256 input_block_tables,
257 block_tables.kernel_block_sizes,
258 block_tables.blocks_per_kv_block,
259 ):
260 num_blocks = min(cdiv(seq_len, block_size), block_table.shape[1])
261 # Spans are disjoint until the pool runs out, then they wrap and share
262 # blocks: profiling only needs the reads to be realistic, not distinct.
263 block_ids = torch.arange(
264 num_reqs * num_blocks, dtype=block_table.dtype, device=block_table.device
265 ) % (num_kv_blocks * bpk)
266 block_table[:num_reqs, :num_blocks] = block_ids.view(num_reqs, num_blocks)
267
268
269 @triton.jit
270 def _prepare_prefill_inputs_kernel(
271 input_ids_ptr,
272 next_prefill_tokens_ptr,
273 next_prefill_tokens_stride,
274 num_lookahead,
275 idx_mapping_ptr,
276 query_start_loc_ptr,
277 all_token_ids_ptr,
278 all_token_ids_stride,
279 prefill_lens_ptr,
280 num_computed_tokens_ptr,
281 BLOCK_SIZE: tl.constexpr,
282 LOOKAHEAD_BLOCK: tl.constexpr,
283 ):
284 batch_idx = tl.program_id(0)
285 req_state_idx = tl.load(idx_mapping_ptr + batch_idx)
286 prefill_len = tl.load(prefill_lens_ptr + req_state_idx)
287 num_computed = tl.load(num_computed_tokens_ptr + req_state_idx)
288 if num_computed >= prefill_len:
289 # Not prefill.
290 return
291
292 query_start = tl.load(query_start_loc_ptr + batch_idx)
293 query_end = tl.load(query_start_loc_ptr + batch_idx + 1)
294 query_len = query_end - query_start
295
296 request_ptr = all_token_ids_ptr + req_state_idx * all_token_ids_stride
297 for i in range(0, query_len, BLOCK_SIZE):
298 block = i + tl.arange(0, BLOCK_SIZE)
299 mask = block < query_len
300 tokens = tl.load(request_ptr + num_computed + block, mask=mask)
301 tl.store(input_ids_ptr + query_start + block, tokens, mask=mask)
302
303 # Store the next num_lookahead prefill tokens.
304 lookahead = tl.arange(0, LOOKAHEAD_BLOCK)
305 pos = num_computed + query_len + lookahead
306 in_lookahead = lookahead < num_lookahead
307 tokens = tl.load(
308 request_ptr + pos, mask=in_lookahead & (pos < prefill_len), other=0
309 )
310 tl.store(
311 next_prefill_tokens_ptr
312 + lookahead * next_prefill_tokens_stride
313 + req_state_idx,
314 tokens,
315 mask=in_lookahead,
316 )
317
318
319 def prepare_prefill_inputs(
320 input_ids: torch.Tensor,
321 next_prefill_tokens: torch.Tensor,
322 idx_mapping: torch.Tensor,
323 query_start_loc: torch.Tensor,
324 all_token_ids: torch.Tensor,
325 prefill_len: torch.Tensor,
326 num_computed_tokens: torch.Tensor,
327 ) -> None:
328 num_reqs = idx_mapping.shape[0]
329 num_lookahead = next_prefill_tokens.shape[0]
330 _prepare_prefill_inputs_kernel[(num_reqs,)](
331 input_ids,
332 next_prefill_tokens,
333 next_prefill_tokens.stride(0),
334 num_lookahead,
335 idx_mapping,
336 query_start_loc,
337 all_token_ids,
338 all_token_ids.stride(0),
339 prefill_len,
340 num_computed_tokens,
341 BLOCK_SIZE=1024,
342 LOOKAHEAD_BLOCK=triton.next_power_of_2(num_lookahead),
343 )
344
345
346 @triton.jit
347 def _prepare_pos_seq_lens_kernel(
348 pos_ptr,
349 seq_lens_ptr,
350 idx_mapping_ptr,
351 query_start_loc_ptr,
352 num_computed_tokens_ptr,
353 max_num_reqs,
354 BLOCK_SIZE: tl.constexpr,
355 ):
356 req_id = tl.program_id(0)
357 num_reqs = tl.num_programs(0) - 1
358 if req_id == num_reqs:
359 # Pad unused seq_lens as 0 for full CUDA graphs.
360 for i in tl.range(num_reqs, max_num_reqs, BLOCK_SIZE):
361 block = i + tl.arange(0, BLOCK_SIZE)
362 mask = block < max_num_reqs
363 tl.store(seq_lens_ptr + block, 0, mask=mask)
364 return
365
366 req_state_idx = tl.load(idx_mapping_ptr + req_id)
367 num_computed_tokens = tl.load(num_computed_tokens_ptr + req_state_idx)
368
369 start = tl.load(query_start_loc_ptr + req_id)
370 end = tl.load(query_start_loc_ptr + req_id + 1)
371 query_len = end - start
372
373 seq_len = num_computed_tokens + query_len
374 tl.store(seq_lens_ptr + req_id, seq_len)
375
376 for i in tl.range(0, query_len, BLOCK_SIZE):
377 block = i + tl.arange(0, BLOCK_SIZE)
378 mask = block < query_len
379 pos = num_computed_tokens + block
380 tl.store(pos_ptr + start + block, pos, mask=mask)
381
382
383 def prepare_pos_seq_lens(
384 idx_mapping: torch.Tensor,
385 query_start_loc: torch.Tensor,
386 num_computed_tokens: torch.Tensor,
387 pos: torch.Tensor,
388 seq_lens: torch.Tensor,
389 ) -> None:
390 num_reqs = idx_mapping.shape[0]
391 # NOTE(woosuk): We do +1 because the last thread block is used
392 # to pad unused seq_lens as 0 for full CUDA graphs.
393 _prepare_pos_seq_lens_kernel[(num_reqs + 1,)](
394 pos,
395 seq_lens,
396 idx_mapping,
397 query_start_loc,
398 num_computed_tokens,
399 seq_lens.shape[0],
400 BLOCK_SIZE=1024,
401 )
402
403
404 @triton.jit
405 def _combine_sampled_and_draft_tokens_kernel(
406 input_ids_ptr,
407 idx_mapping_ptr,
408 last_sampled_tokens_ptr,
409 query_start_loc_ptr,
410 seq_lens_ptr,
411 prefill_len_ptr,
412 draft_tokens_ptr,
413 draft_tokens_stride,
414 cu_num_logits_ptr,
415 logits_indices_ptr,
416 BLOCK_SIZE: tl.constexpr,
417 NUM_NEW_SAMPLED_TOKENS: tl.constexpr = 1,
418 ):
419 batch_idx = tl.program_id(0)
420 req_state_idx = tl.load(idx_mapping_ptr + batch_idx)
421
422 # Get the number of logits and draft tokens.
423 cu_num_logits_start = tl.load(cu_num_logits_ptr + batch_idx)
424 cu_num_logits_end = tl.load(cu_num_logits_ptr + batch_idx + 1)
425 num_logits = cu_num_logits_end - cu_num_logits_start
426 num_draft_tokens = num_logits - NUM_NEW_SAMPLED_TOKENS
427
428 # Compute the logits indices.
429 block = tl.arange(0, BLOCK_SIZE)
430 query_end = tl.load(query_start_loc_ptr + batch_idx + 1)
431 logits_start = query_end - num_logits
432 tl.store(
433 logits_indices_ptr + cu_num_logits_start + block,
434 logits_start + block,
435 mask=block < num_logits,
436 )
437
438 seq_len = tl.load(seq_lens_ptr + batch_idx)
439 prefill_len = tl.load(prefill_len_ptr + req_state_idx)
440 if seq_len <= prefill_len:
441 # Handling prefill tokens. No sampled or draft tokens.
442 return
443
444 # Keep prompt-tail slots intact; only rewrite generated-token slots.
445 first_logit_seq_pos = seq_len - num_logits
446 if NUM_NEW_SAMPLED_TOKENS > 0 and first_logit_seq_pos >= prefill_len:
447 # Write the last sampled token ID to input_ids.
448 last_token_id = tl.load(last_sampled_tokens_ptr + req_state_idx)
449 tl.store(input_ids_ptr + logits_start, last_token_id)
450
451 # Write the draft tokens (if any) to input_ids.
452 if num_draft_tokens > 0:
453 mask = block < num_draft_tokens
454 draft_tokens = tl.load(
455 draft_tokens_ptr + req_state_idx * draft_tokens_stride + block,
456 mask=mask,
457 )
458 tl.store(
459 input_ids_ptr + query_end - num_draft_tokens + block,
460 draft_tokens,
461 mask=mask,
462 )
463
464
465 def combine_sampled_and_draft_tokens(
466 input_ids: torch.Tensor,
467 idx_mapping: torch.Tensor,
468 last_sampled_tokens: torch.Tensor,
469 query_start_loc: torch.Tensor,
470 seq_lens: torch.Tensor,
471 prefill_len: torch.Tensor,
472 draft_tokens: torch.Tensor,
473 cu_num_logits: torch.Tensor,
474 num_logits: int,
475 num_new_sampled_tokens: int = 1, # excl accepted draft tokens, a.k.a bonus tokens
476 ) -> torch.Tensor:
477 assert num_new_sampled_tokens in (0, 1), (
478 f"num_new_sampled_tokens must be 0 or 1, got {num_new_sampled_tokens}"
479 )
480 # use idx_mapping.shape[0] for actual request count
481 num_reqs = idx_mapping.shape[0]
482 num_speculative_steps = draft_tokens.shape[-1]
483
484 logits_indices = torch.empty(num_logits, dtype=torch.int64, device=input_ids.device)
485 _combine_sampled_and_draft_tokens_kernel[(num_reqs,)](
486 input_ids,
487 idx_mapping,
488 last_sampled_tokens,
489 query_start_loc,
490 seq_lens,
491 prefill_len,
492 draft_tokens,
493 draft_tokens.stride(0),
494 cu_num_logits,
495 logits_indices,
496 NUM_NEW_SAMPLED_TOKENS=num_new_sampled_tokens,
497 # NOTE(woosuk): Add num_new_sampled_tokens to ensure the block covers the
498 # last sampled token in addition to all draft tokens.
499 BLOCK_SIZE=triton.next_power_of_2(
500 num_speculative_steps + num_new_sampled_tokens
501 ),
502 )
503 return logits_indices
504
505
506 @triton.jit
507 def _get_num_sampled_and_rejected_kernel(
508 num_sampled_ptr,
509 num_rejected_ptr,
510 seq_lens_ptr,
511 cu_num_logits_ptr,
512 idx_mapping_ptr,
513 prefill_len_ptr,
514 ):
515 batch_idx = tl.program_id(0)
516 req_state_idx = tl.load(idx_mapping_ptr + batch_idx)
517
518 seq_len = tl.load(seq_lens_ptr + batch_idx)
519 prefill_len = tl.load(prefill_len_ptr + req_state_idx)
520 is_chunked_prefilling = seq_len < prefill_len
521
522 num_sampled = tl.load(num_sampled_ptr + batch_idx)
523 num_sampled = tl.where(is_chunked_prefilling, 0, num_sampled)
524 tl.store(num_sampled_ptr + batch_idx, num_sampled)
525
526 logits_start = tl.load(cu_num_logits_ptr + batch_idx)
527 logits_end = tl.load(cu_num_logits_ptr + batch_idx + 1)
528 num_logits = logits_end - logits_start
529
530 num_rejected = num_logits - num_sampled
531 num_rejected = tl.where(is_chunked_prefilling, 0, num_rejected)
532 tl.store(num_rejected_ptr + batch_idx, num_rejected)
533
534
535 def get_num_sampled_and_rejected(
536 num_sampled: torch.Tensor,
537 seq_lens: torch.Tensor,
538 cu_num_logits: torch.Tensor,
539 idx_mapping: torch.Tensor,
540 prefill_len: torch.Tensor,
541 ) -> tuple[torch.Tensor, torch.Tensor]:
542 num_reqs = idx_mapping.shape[0]
543 num_rejected = torch.empty_like(num_sampled)
544 _get_num_sampled_and_rejected_kernel[(num_reqs,)](
545 num_sampled,
546 num_rejected,
547 seq_lens,
548 cu_num_logits,
549 idx_mapping,
550 prefill_len,
551 )
552 return num_sampled, num_rejected
553
554
555 @triton.jit
556 def _post_update_kernel(
557 idx_mapping_ptr,
558 num_computed_tokens_ptr,
559 last_sampled_tokens_ptr,
560 output_bin_counts_ptr,
561 output_bin_counts_stride,
562 sampled_tokens_ptr,
563 sampled_tokens_stride,
564 num_sampled_ptr,
565 num_rejected_ptr,
566 query_start_loc_ptr,
567 all_token_ids_ptr,
568 all_token_ids_stride,
569 total_len_ptr,
570 ):
571 req_id = tl.program_id(0)
572 req_state_idx = tl.load(idx_mapping_ptr + req_id)
573 if req_state_idx < 0:
574 # Filter rows with negative index entries.
575 return
576
577 total_len = tl.load(total_len_ptr + req_state_idx)
578 num_sampled = tl.load(num_sampled_ptr + req_id)
579 if num_sampled > 0:
580 token_id = tl.load(
581 sampled_tokens_ptr + req_id * sampled_tokens_stride + num_sampled - 1
582 )
583 tl.store(last_sampled_tokens_ptr + req_state_idx, token_id)
584 tl.store(total_len_ptr + req_state_idx, total_len + num_sampled)
585
586 for i in range(num_sampled):
587 token_id = tl.load(sampled_tokens_ptr + req_id * sampled_tokens_stride + i)
588 tl.store(
589 all_token_ids_ptr + req_state_idx * all_token_ids_stride + total_len + i,
590 token_id,
591 )
592
593 if output_bin_counts_ptr is not None:
594 token_ptr = (
595 output_bin_counts_ptr
596 + req_state_idx * output_bin_counts_stride
597 + token_id
598 )
599 count = tl.load(token_ptr)
600 tl.store(token_ptr, count + 1)
601
602 if query_start_loc_ptr is None:
603 query_len = 0
604 else:
605 query_start = tl.load(query_start_loc_ptr + req_id)
606 query_end = tl.load(query_start_loc_ptr + req_id + 1)
607 query_len = query_end - query_start
608 num_rejected = tl.load(num_rejected_ptr + req_id)
609
610 computed_delta = query_len - num_rejected
611 if computed_delta != 0:
612 num_computed = tl.load(num_computed_tokens_ptr + req_state_idx)
613 tl.store(num_computed_tokens_ptr + req_state_idx, num_computed + computed_delta)
614
615
616 def post_update(
617 # [num_reqs] batch_idx -> req_state_idx; negative index means skip.
618 idx_mapping: torch.Tensor,
619 # [max_num_reqs]
620 num_computed_tokens: torch.Tensor,
621 # [max_num_reqs]
622 last_sampled_tokens: torch.Tensor,
623 # [max_num_reqs, vocab_size]
624 output_bin_counts: torch.Tensor | None,
625 # [num_reqs, num_speculative_steps + 1]
626 sampled_tokens: torch.Tensor,
627 # [num_reqs]
628 num_sampled: torch.Tensor,
629 # [num_reqs]
630 num_rejected: torch.Tensor,
631 # [num_reqs + 1]
632 query_start_loc: torch.Tensor | None,
633 # [max_num_reqs, max_model_len]
634 all_token_ids: torch.Tensor,
635 # [max_num_reqs]
636 total_len: torch.Tensor,
637 ) -> None:
638 num_reqs = idx_mapping.shape[0]
639 _post_update_kernel[(num_reqs,)](
640 idx_mapping,
641 num_computed_tokens,
642 last_sampled_tokens,
643 output_bin_counts,
644 output_bin_counts.stride(0) if output_bin_counts is not None else 0,
645 sampled_tokens,
646 sampled_tokens.stride(0),
647 num_sampled,
648 num_rejected,
649 query_start_loc,
650 all_token_ids,
651 all_token_ids.stride(0),
652 total_len,
653 num_warps=1,
654 )
655
656
657 @triton.jit
658 def _post_update_num_computed_tokens_kernel(
659 idx_mapping_ptr,
660 num_computed_tokens_ptr,
661 query_start_loc_ptr,
662 ):
663 batch_id = tl.program_id(0)
664 query_start = tl.load(query_start_loc_ptr + batch_id)
665 query_end = tl.load(query_start_loc_ptr + batch_id + 1)
666 query_len = query_end - query_start
667
668 req_state_idx = tl.load(idx_mapping_ptr + batch_id)
669 num_computed = tl.load(num_computed_tokens_ptr + req_state_idx)
670 tl.store(num_computed_tokens_ptr + req_state_idx, num_computed + query_len)
671
672
673 def post_update_num_computed_tokens(
674 # [num_reqs]
675 idx_mapping: torch.Tensor,
676 # [max_num_reqs]
677 num_computed_tokens: torch.Tensor,
678 # [num_reqs + 1]
679 query_start_loc: torch.Tensor,
680 ) -> None:
681 num_reqs = idx_mapping.shape[0]
682 _post_update_num_computed_tokens_kernel[(num_reqs,)](
683 idx_mapping,
684 num_computed_tokens,
685 query_start_loc,
686 )
687
688
689 @triton.jit
690 def _expand_idx_mapping_kernel(
691 idx_mapping_ptr,
692 expanded_idx_mapping_ptr,
693 expanded_local_pos_ptr,
694 cu_num_logits_ptr,
695 BLOCK_SIZE: tl.constexpr,
696 ):
697 req_idx = tl.program_id(0)
698 start_idx = tl.load(cu_num_logits_ptr + req_idx)
699 end_idx = tl.load(cu_num_logits_ptr + req_idx + 1)
700 num_tokens = end_idx - start_idx
701
702 block = tl.arange(0, BLOCK_SIZE)
703 mask = block < num_tokens
704 req_state_idx = tl.load(idx_mapping_ptr + req_idx)
705 tl.store(expanded_idx_mapping_ptr + start_idx + block, req_state_idx, mask=mask)
706 tl.store(expanded_local_pos_ptr + start_idx + block, block, mask=mask)
707
708
709 def expand_idx_mapping(
710 idx_mapping: torch.Tensor,
711 total_num_logits: int,
712 cu_num_logits: torch.Tensor,
713 max_expand_len: int,
714 ) -> tuple[torch.Tensor, torch.Tensor]:
715 num_reqs = idx_mapping.shape[0]
716 expanded_idx_mapping = idx_mapping.new_empty(total_num_logits)
717 expanded_local_pos = idx_mapping.new_empty(total_num_logits)
718 _expand_idx_mapping_kernel[(num_reqs,)](
719 idx_mapping,
720 expanded_idx_mapping,
721 expanded_local_pos,
722 cu_num_logits,
723 BLOCK_SIZE=triton.next_power_of_2(max_expand_len),
724 )
725 return expanded_idx_mapping, expanded_local_pos