02-model_executor_kernels_attention_dsa_candidate_blocks.py.diff 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. --- baked-ours/model_executor/kernels/attention/dsa/candidate_blocks.py 2026-09-10 19:56:14.000000000 +0200
  2. +++ baked/model_executor/kernels/attention/dsa/candidate_blocks.py 2026-09-11 10:13:47.000000000 +0200
  3. @@ -1,11 +1,67 @@
  4. # SPDX-License-Identifier: Apache-2.0
  5. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  6. +import os
  7. +
  8. import torch
  9. from vllm.triton_utils import tl, triton
  10. +def _cpu_kernels_allowed() -> bool:
  11. + # CPU tests run the Triton kernels through the interpreter.
  12. + return os.environ.get("TRITON_INTERPRET", "0") == "1"
  13. +
  14. +
  15. +# Scratch cap for the DCP candidate-score all-reduce (bytes per slice).
  16. +_DCP_REDUCE_BYTES = 128 * 1024 * 1024
  17. +
  18. +# Stage 7: opt-in prefill phase timing (DSV41_PREFILL_TIMING=1). Phases add GPU
  19. +# milliseconds here; the runner reports and clears them after each eager step
  20. +# of >= 1024 tokens. Inert (no events, no syncs) unless enabled.
  21. +_PREFILL_TIMING = os.environ.get("DSV41_PREFILL_TIMING", "0") == "1"
  22. +PREFILL_ACC: dict[str, float] = {}
  23. +
  24. +
  25. +def prefill_acc_add(key: str, ms: float) -> None:
  26. + PREFILL_ACC[key] = PREFILL_ACC.get(key, 0.0) + ms
  27. +
  28. +
  29. +def prefill_acc_take() -> dict[str, float]:
  30. + d = dict(PREFILL_ACC)
  31. + PREFILL_ACC.clear()
  32. + return d
  33. +
  34. +
  35. +class _PrefillTimer:
  36. + """`with _PrefillTimer(key, num_tokens): ...` — CUDA events around the block,
  37. + synchronised at exit (eager prefill steps only; never while capturing)."""
  38. +
  39. + __slots__ = ("key", "on", "e0", "e1")
  40. +
  41. + def __init__(self, key: str, num_tokens: int) -> None:
  42. + self.key = key
  43. + self.on = (
  44. + _PREFILL_TIMING
  45. + and num_tokens >= 1024
  46. + and not torch.cuda.is_current_stream_capturing()
  47. + )
  48. +
  49. + def __enter__(self):
  50. + if self.on:
  51. + self.e0 = torch.cuda.Event(enable_timing=True)
  52. + self.e1 = torch.cuda.Event(enable_timing=True)
  53. + self.e0.record()
  54. + return self
  55. +
  56. + def __exit__(self, *exc):
  57. + if self.on:
  58. + self.e1.record()
  59. + self.e1.synchronize()
  60. + prefill_acc_add(self.key, self.e0.elapsed_time(self.e1))
  61. + return False
  62. +
  63. +
  64. @triton.jit
  65. def _max_with_nan(a, b):
  66. return tl.maximum(a, b, propagate_nan=tl.PropagateNan.ALL)
  67. @@ -27,6 +83,7 @@
  68. HAS_STARTS: tl.constexpr,
  69. ROW_REPEAT: tl.constexpr,
  70. TILE: tl.constexpr,
  71. + PIN_NEWEST: tl.constexpr = True,
  72. ):
  73. row = tl.program_id(0).to(tl.int64)
  74. blocks = tl.program_id(1) * TILE + tl.arange(0, TILE)
  75. @@ -43,11 +100,12 @@
  76. other=-float("inf"),
  77. )
  78. reduced = tl.reduce(values, 1, _max_with_nan)
  79. - reduced = tl.where(
  80. - (end > start) & (blocks == (end - start - 1) // BLOCK_SIZE),
  81. - float("inf"),
  82. - reduced,
  83. - )
  84. + if PIN_NEWEST:
  85. + reduced = tl.where(
  86. + (end > start) & (blocks == (end - start - 1) // BLOCK_SIZE),
  87. + float("inf"),
  88. + reduced,
  89. + )
  90. tl.store(scores + row * nblocks + blocks, reduced, blocks < nblocks)
  91. @@ -137,27 +195,15 @@
  92. )
  93. -def select_candidate_blocks(
  94. +def _block_scores(
  95. logits: torch.Tensor,
  96. row_ks: torch.Tensor | None,
  97. row_ke: torch.Tensor,
  98. - topk_blocks: int,
  99. block_size: int,
  100. - out: torch.Tensor,
  101. - row_repeat: int = 1,
  102. -) -> None:
  103. - """Select local block IDs by maximum score, pinning each row's newest block.
  104. -
  105. - Row bounds are in packed column space; absent starts mean zero.
  106. - Decode rows share bounds in groups of ``row_repeat``. Output is -1 padded.
  107. - """
  108. - assert logits.is_cuda
  109. + row_repeat: int,
  110. + pin_newest: bool,
  111. +) -> torch.Tensor:
  112. rows, width = logits.shape
  113. - if not rows:
  114. - return
  115. - if not width:
  116. - out.fill_(-1)
  117. - return
  118. nblocks = triton.cdiv(width, block_size)
  119. scores = logits.new_empty((rows, nblocks))
  120. _block_scores_kernel[(rows, triton.cdiv(nblocks, 128))](
  121. @@ -174,7 +220,109 @@
  122. row_ks is not None,
  123. row_repeat,
  124. 128,
  125. + PIN_NEWEST=pin_newest,
  126. )
  127. + return scores
  128. +
  129. +
  130. +def select_candidate_blocks(logits: torch.Tensor, *args, **kwargs) -> None:
  131. + """Stage 7: timed wrapper (phase "candidates"); see _select_candidate_blocks_impl."""
  132. + with _PrefillTimer("candidates", int(logits.shape[0])):
  133. + return _select_candidate_blocks_impl(logits, *args, **kwargs)
  134. +
  135. +
  136. +def _select_candidate_blocks_impl(
  137. + logits: torch.Tensor,
  138. + row_ks: torch.Tensor | None,
  139. + row_ke: torch.Tensor,
  140. + topk_blocks: int,
  141. + block_size: int,
  142. + out: torch.Tensor,
  143. + row_repeat: int = 1,
  144. + *,
  145. + dcp_group=None,
  146. + global_row_ke: torch.Tensor | None = None,
  147. + global_block_size: int | None = None,
  148. + max_global_blocks: int | None = None,
  149. +) -> None:
  150. + """Select local block IDs by maximum score, pinning each row's newest block.
  151. +
  152. + Row bounds are in packed column space; absent starts mean zero.
  153. + Decode rows share bounds in groups of ``row_repeat``. Output is -1 padded.
  154. +
  155. + Under DCP (``dcp_group`` given) ``logits`` hold this rank's local packed
  156. + columns and ``block_size`` is the LOCAL block, ``global_block_size //
  157. + dcp_world_size``: with interleave 1, local block ``b`` is exactly this
  158. + rank's states of global block ``b``, so the global block score is the MAX
  159. + over ranks of the local block scores (one all-reduce over
  160. + ``[rows, max_global_blocks]``) and the block ids need no translation. The
  161. + newest block is pinned from the GLOBAL per-row context ``global_row_ke``
  162. + (states, per row or per ``row_repeat`` group) after the reduce, so every
  163. + rank pins the same block. Collective: every DCP rank must call this with
  164. + the same ``rows`` and ``max_global_blocks`` (an empty local shard is fine).
  165. + """
  166. + assert logits.is_cuda or _cpu_kernels_allowed()
  167. + rows, width = logits.shape
  168. + if not rows:
  169. + return
  170. + if dcp_group is None:
  171. + if not width:
  172. + out.fill_(-1)
  173. + return
  174. + scores = _block_scores(logits, row_ks, row_ke, block_size, row_repeat, True)
  175. + nblocks = scores.shape[1]
  176. + else:
  177. + assert global_row_ke is not None
  178. + assert global_block_size is not None and max_global_blocks is not None
  179. + assert global_block_size == block_size * dcp_group.world_size, (
  180. + "DCP candidate blocks need block_size == global_block_size // world"
  181. + )
  182. + nblocks = max(int(max_global_blocks), 1)
  183. + ends = global_row_ke.reshape(-1)
  184. + if row_repeat > 1:
  185. + ends = ends.repeat_interleave(row_repeat)
  186. + ends = ends[:rows].to(torch.int64)
  187. + # Pin each row's newest GLOBAL block (DCP1 pins (end - start - 1) //
  188. + # block from the global bounds); rows without context select nothing.
  189. + pin = ((ends - 1) // global_block_size).clamp_(0, nblocks - 1).view(-1, 1)
  190. + local = (
  191. + _block_scores(logits, row_ks, row_ke, block_size, row_repeat, False)
  192. + if width
  193. + else None
  194. + )
  195. + # Reduce in row slices: a 16K-token prefill chunk at 128K context would
  196. + # otherwise need a ~1 GiB scratch per rank (GB10 unified memory is the
  197. + # binding constraint). Slice count derives from rows/nblocks, which are
  198. + # identical on every rank, so the collectives still line up.
  199. + k = min(topk_blocks, nblocks)
  200. + rows_per_slice = max(1, _DCP_REDUCE_BYTES // (nblocks * 4))
  201. + top_values = logits.new_empty((rows, k))
  202. + top_indices = torch.empty((rows, k), dtype=torch.int64, device=logits.device)
  203. + for r0 in range(0, rows, rows_per_slice):
  204. + r1 = min(r0 + rows_per_slice, rows)
  205. + scores = logits.new_full((r1 - r0, nblocks), -float("inf"))
  206. + if local is not None:
  207. + n = min(local.shape[1], nblocks)
  208. + scores[:, :n] = local[r0:r1, :n]
  209. + torch.distributed.all_reduce(
  210. + scores, op=torch.distributed.ReduceOp.MAX, group=dcp_group.device_group
  211. + )
  212. + scores.scatter_(1, pin[r0:r1], float("inf"))
  213. + scores.masked_fill_((ends[r0:r1] <= 0).view(-1, 1), -float("inf"))
  214. + # Keep the existing top-k tie behavior.
  215. + sl = scores.topk(k, dim=-1)
  216. + top_values[r0:r1] = sl.values
  217. + top_indices[r0:r1] = sl.indices
  218. + _store_candidates_kernel[(rows, triton.cdiv(topk_blocks, 256))](
  219. + top_values,
  220. + top_indices,
  221. + out,
  222. + *out.stride(),
  223. + k,
  224. + topk_blocks,
  225. + 256,
  226. + )
  227. + return
  228. # Keep the existing top-k tie behavior.
  229. top = scores.topk(min(topk_blocks, nblocks), dim=-1)
  230. _store_candidates_kernel[(rows, triton.cdiv(topk_blocks, 256))](
  231. @@ -196,8 +344,13 @@
  232. block_size: int,
  233. row_repeat: int = 1,
  234. ) -> None:
  235. - """Mask packed logits outside causal bounds and request-local candidates."""
  236. - assert logits.is_cuda
  237. + """Mask packed logits outside causal bounds and request-local candidates.
  238. +
  239. + Under DCP pass the LOCAL block size (global // world): local block ``b``
  240. + is this rank's shard of global block ``b``, so the candidate ids apply
  241. + unchanged.
  242. + """
  243. + assert logits.is_cuda or _cpu_kernels_allowed()
  244. rows, width = logits.shape
  245. if not rows or not width:
  246. return