| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- # SPDX-License-Identifier: Apache-2.0
- # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
- import os
- import torch
- from vllm.triton_utils import tl, triton
- def _cpu_kernels_allowed() -> bool:
- # CPU tests run the Triton kernels through the interpreter.
- return os.environ.get("TRITON_INTERPRET", "0") == "1"
- # Scratch cap for the DCP candidate-score all-reduce (bytes per slice).
- _DCP_REDUCE_BYTES = 128 * 1024 * 1024
- # Stage 7: opt-in prefill phase timing (DSV41_PREFILL_TIMING=1). Phases add GPU
- # milliseconds here; the runner reports and clears them after each eager step
- # of >= 1024 tokens. Inert (no events, no syncs) unless enabled.
- _PREFILL_TIMING = os.environ.get("DSV41_PREFILL_TIMING", "0") == "1"
- PREFILL_ACC: dict[str, float] = {}
- def prefill_acc_add(key: str, ms: float) -> None:
- PREFILL_ACC[key] = PREFILL_ACC.get(key, 0.0) + ms
- def prefill_acc_take() -> dict[str, float]:
- d = dict(PREFILL_ACC)
- PREFILL_ACC.clear()
- return d
- class _PrefillTimer:
- """`with _PrefillTimer(key, num_tokens): ...` — CUDA events around the block,
- synchronised at exit (eager prefill steps only; never while capturing)."""
- __slots__ = ("key", "on", "e0", "e1")
- def __init__(self, key: str, num_tokens: int) -> None:
- self.key = key
- self.on = (
- _PREFILL_TIMING
- and num_tokens >= 1024
- and not torch.cuda.is_current_stream_capturing()
- )
- def __enter__(self):
- if self.on:
- self.e0 = torch.cuda.Event(enable_timing=True)
- self.e1 = torch.cuda.Event(enable_timing=True)
- self.e0.record()
- return self
- def __exit__(self, *exc):
- if self.on:
- self.e1.record()
- self.e1.synchronize()
- prefill_acc_add(self.key, self.e0.elapsed_time(self.e1))
- return False
- @triton.jit
- def _max_with_nan(a, b):
- return tl.maximum(a, b, propagate_nan=tl.PropagateNan.ALL)
- @triton.jit(do_not_specialize=["width", "nblocks"])
- def _block_scores_kernel(
- logits,
- starts,
- ends,
- scores,
- stride_row,
- stride_col,
- stride_start,
- stride_end,
- width,
- nblocks,
- BLOCK_SIZE: tl.constexpr,
- HAS_STARTS: tl.constexpr,
- ROW_REPEAT: tl.constexpr,
- TILE: tl.constexpr,
- PIN_NEWEST: tl.constexpr = True,
- ):
- row = tl.program_id(0).to(tl.int64)
- blocks = tl.program_id(1) * TILE + tl.arange(0, TILE)
- start = tl.load(starts + row // ROW_REPEAT * stride_start) if HAS_STARTS else 0
- end = tl.load(ends + row // ROW_REPEAT * stride_end)
- offsets = tl.arange(0, triton.next_power_of_2(BLOCK_SIZE))
- cols = start + blocks[:, None] * BLOCK_SIZE + offsets[None, :]
- values = tl.load(
- logits + row * stride_row + cols * stride_col,
- (blocks[:, None] < nblocks)
- & (offsets[None, :] < BLOCK_SIZE)
- & (cols < end)
- & (cols < width),
- other=-float("inf"),
- )
- reduced = tl.reduce(values, 1, _max_with_nan)
- if PIN_NEWEST:
- reduced = tl.where(
- (end > start) & (blocks == (end - start - 1) // BLOCK_SIZE),
- float("inf"),
- reduced,
- )
- tl.store(scores + row * nblocks + blocks, reduced, blocks < nblocks)
- @triton.jit(do_not_specialize=["k"])
- def _store_candidates_kernel(
- values,
- indices,
- output,
- out_stride_row,
- out_stride_col,
- k,
- OUT_K: tl.constexpr,
- TILE: tl.constexpr,
- ):
- row = tl.program_id(0).to(tl.int64)
- cols = tl.program_id(1) * TILE + tl.arange(0, TILE)
- value = tl.load(values + row * k + cols, cols < k, other=-float("inf"))
- index = tl.load(indices + row * k + cols, cols < k, other=-1)
- tl.store(
- output + row * out_stride_row + cols * out_stride_col,
- tl.where(value > -float("inf"), index, -1),
- cols < OUT_K,
- )
- @triton.jit(do_not_specialize=["width", "nblocks"])
- def _candidate_flags_kernel(
- candidates,
- starts,
- flags,
- stride_row,
- stride_col,
- stride_start,
- width,
- nblocks,
- BLOCK_SIZE: tl.constexpr,
- K: tl.constexpr,
- HAS_STARTS: tl.constexpr,
- ROW_REPEAT: tl.constexpr,
- ):
- row = tl.program_id(0).to(tl.int64)
- offsets = tl.arange(0, 1024)
- for tile in range(tl.cdiv(nblocks + 1, 1024)):
- slots = tile * 1024 + offsets
- tl.store(flags + row * (nblocks + 1) + slots, 0, slots <= nblocks)
- start = tl.load(starts + row // ROW_REPEAT * stride_start) if HAS_STARTS else 0
- cols = tl.arange(0, triton.next_power_of_2(K))
- block = tl.load(
- candidates + row * stride_row + cols * stride_col, cols < K, other=-1
- ).to(tl.int64)
- # Preserve the packed-column clamp for candidates beyond the logits width.
- block = tl.where(start + block * BLOCK_SIZE >= width, nblocks, block)
- tl.debug_barrier()
- tl.store(flags + row * (nblocks + 1) + block, 1, (cols < K) & (block >= 0))
- @triton.jit(do_not_specialize=["width", "nblocks"])
- def _mask_candidates_kernel(
- logits,
- starts,
- ends,
- flags,
- stride_row,
- stride_col,
- stride_start,
- stride_end,
- width,
- nblocks,
- BLOCK_SIZE: tl.constexpr,
- HAS_STARTS: tl.constexpr,
- ROW_REPEAT: tl.constexpr,
- TILE: tl.constexpr,
- ):
- row = tl.program_id(0).to(tl.int64)
- cols = tl.program_id(1) * TILE + tl.arange(0, TILE)
- start = tl.load(starts + row // ROW_REPEAT * stride_start) if HAS_STARTS else 0
- end = tl.load(ends + row // ROW_REPEAT * stride_end)
- valid = (cols >= start) & (cols < end) & (cols < width)
- block = (cols - start) // BLOCK_SIZE
- keep = tl.load(flags + row * (nblocks + 1) + block, valid, other=0)
- edge = tl.load(flags + row * (nblocks + 1) + nblocks)
- keep = (keep != 0) | ((cols == width - 1) & (edge != 0))
- tl.store(
- logits + row * stride_row + cols * stride_col,
- -float("inf"),
- (cols < width) & ~(valid & keep),
- )
- def _block_scores(
- logits: torch.Tensor,
- row_ks: torch.Tensor | None,
- row_ke: torch.Tensor,
- block_size: int,
- row_repeat: int,
- pin_newest: bool,
- ) -> torch.Tensor:
- rows, width = logits.shape
- nblocks = triton.cdiv(width, block_size)
- scores = logits.new_empty((rows, nblocks))
- _block_scores_kernel[(rows, triton.cdiv(nblocks, 128))](
- logits,
- row_ks,
- row_ke,
- scores,
- *logits.stride(),
- row_ks.stride(0) if row_ks is not None else 0,
- row_ke.stride(0),
- width,
- nblocks,
- block_size,
- row_ks is not None,
- row_repeat,
- 128,
- PIN_NEWEST=pin_newest,
- )
- return scores
- def select_candidate_blocks(logits: torch.Tensor, *args, **kwargs) -> None:
- """Stage 7: timed wrapper (phase "candidates"); see _select_candidate_blocks_impl."""
- with _PrefillTimer("candidates", int(logits.shape[0])):
- return _select_candidate_blocks_impl(logits, *args, **kwargs)
- def _select_candidate_blocks_impl(
- logits: torch.Tensor,
- row_ks: torch.Tensor | None,
- row_ke: torch.Tensor,
- topk_blocks: int,
- block_size: int,
- out: torch.Tensor,
- row_repeat: int = 1,
- *,
- dcp_group=None,
- global_row_ke: torch.Tensor | None = None,
- global_block_size: int | None = None,
- max_global_blocks: int | None = None,
- ) -> None:
- """Select local block IDs by maximum score, pinning each row's newest block.
- Row bounds are in packed column space; absent starts mean zero.
- Decode rows share bounds in groups of ``row_repeat``. Output is -1 padded.
- Under DCP (``dcp_group`` given) ``logits`` hold this rank's local packed
- columns and ``block_size`` is the LOCAL block, ``global_block_size //
- dcp_world_size``: with interleave 1, local block ``b`` is exactly this
- rank's states of global block ``b``, so the global block score is the MAX
- over ranks of the local block scores (one all-reduce over
- ``[rows, max_global_blocks]``) and the block ids need no translation. The
- newest block is pinned from the GLOBAL per-row context ``global_row_ke``
- (states, per row or per ``row_repeat`` group) after the reduce, so every
- rank pins the same block. Collective: every DCP rank must call this with
- the same ``rows`` and ``max_global_blocks`` (an empty local shard is fine).
- """
- assert logits.is_cuda or _cpu_kernels_allowed()
- rows, width = logits.shape
- if not rows:
- return
- if dcp_group is None:
- if not width:
- out.fill_(-1)
- return
- scores = _block_scores(logits, row_ks, row_ke, block_size, row_repeat, True)
- nblocks = scores.shape[1]
- else:
- assert global_row_ke is not None
- assert global_block_size is not None and max_global_blocks is not None
- assert global_block_size == block_size * dcp_group.world_size, (
- "DCP candidate blocks need block_size == global_block_size // world"
- )
- nblocks = max(int(max_global_blocks), 1)
- ends = global_row_ke.reshape(-1)
- if row_repeat > 1:
- ends = ends.repeat_interleave(row_repeat)
- ends = ends[:rows].to(torch.int64)
- # Pin each row's newest GLOBAL block (DCP1 pins (end - start - 1) //
- # block from the global bounds); rows without context select nothing.
- pin = ((ends - 1) // global_block_size).clamp_(0, nblocks - 1).view(-1, 1)
- local = (
- _block_scores(logits, row_ks, row_ke, block_size, row_repeat, False)
- if width
- else None
- )
- # Reduce in row slices: a 16K-token prefill chunk at 128K context would
- # otherwise need a ~1 GiB scratch per rank (GB10 unified memory is the
- # binding constraint). Slice count derives from rows/nblocks, which are
- # identical on every rank, so the collectives still line up.
- k = min(topk_blocks, nblocks)
- rows_per_slice = max(1, _DCP_REDUCE_BYTES // (nblocks * 4))
- top_values = logits.new_empty((rows, k))
- top_indices = torch.empty((rows, k), dtype=torch.int64, device=logits.device)
- for r0 in range(0, rows, rows_per_slice):
- r1 = min(r0 + rows_per_slice, rows)
- scores = logits.new_full((r1 - r0, nblocks), -float("inf"))
- if local is not None:
- n = min(local.shape[1], nblocks)
- scores[:, :n] = local[r0:r1, :n]
- torch.distributed.all_reduce(
- scores, op=torch.distributed.ReduceOp.MAX, group=dcp_group.device_group
- )
- scores.scatter_(1, pin[r0:r1], float("inf"))
- scores.masked_fill_((ends[r0:r1] <= 0).view(-1, 1), -float("inf"))
- # Keep the existing top-k tie behavior.
- sl = scores.topk(k, dim=-1)
- top_values[r0:r1] = sl.values
- top_indices[r0:r1] = sl.indices
- _store_candidates_kernel[(rows, triton.cdiv(topk_blocks, 256))](
- top_values,
- top_indices,
- out,
- *out.stride(),
- k,
- topk_blocks,
- 256,
- )
- return
- # Keep the existing top-k tie behavior.
- top = scores.topk(min(topk_blocks, nblocks), dim=-1)
- _store_candidates_kernel[(rows, triton.cdiv(topk_blocks, 256))](
- top.values,
- top.indices,
- out,
- *out.stride(),
- top.values.shape[1],
- topk_blocks,
- 256,
- )
- def apply_candidate_mask(
- logits: torch.Tensor,
- row_ks: torch.Tensor | None,
- row_ke: torch.Tensor,
- candidate_blocks: torch.Tensor,
- block_size: int,
- row_repeat: int = 1,
- ) -> None:
- """Mask packed logits outside causal bounds and request-local candidates.
- Under DCP pass the LOCAL block size (global // world): local block ``b``
- is this rank's shard of global block ``b``, so the candidate ids apply
- unchanged.
- """
- assert logits.is_cuda or _cpu_kernels_allowed()
- rows, width = logits.shape
- if not rows or not width:
- return
- nblocks = triton.cdiv(width, block_size)
- flags = torch.empty((rows, nblocks + 1), device=logits.device, dtype=torch.uint8)
- start_stride = row_ks.stride(0) if row_ks is not None else 0
- _candidate_flags_kernel[(rows,)](
- candidate_blocks,
- row_ks,
- flags,
- *candidate_blocks.stride(),
- start_stride,
- width,
- nblocks,
- block_size,
- candidate_blocks.shape[1],
- row_ks is not None,
- row_repeat,
- )
- _mask_candidates_kernel[(rows, triton.cdiv(width, 1024))](
- logits,
- row_ks,
- row_ke,
- flags,
- *logits.stride(),
- start_stride,
- row_ke.stride(0),
- width,
- nblocks,
- block_size,
- row_ks is not None,
- row_repeat,
- 1024,
- )
|