candidate_blocks.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. import torch
  4. from vllm.triton_utils import tl, triton
  5. @triton.jit
  6. def _max_with_nan(a, b):
  7. return tl.maximum(a, b, propagate_nan=tl.PropagateNan.ALL)
  8. @triton.jit(do_not_specialize=["width", "nblocks"])
  9. def _block_scores_kernel(
  10. logits,
  11. starts,
  12. ends,
  13. scores,
  14. stride_row,
  15. stride_col,
  16. stride_start,
  17. stride_end,
  18. width,
  19. nblocks,
  20. BLOCK_SIZE: tl.constexpr,
  21. HAS_STARTS: tl.constexpr,
  22. ROW_REPEAT: tl.constexpr,
  23. TILE: tl.constexpr,
  24. ):
  25. row = tl.program_id(0).to(tl.int64)
  26. blocks = tl.program_id(1) * TILE + tl.arange(0, TILE)
  27. start = tl.load(starts + row // ROW_REPEAT * stride_start) if HAS_STARTS else 0
  28. end = tl.load(ends + row // ROW_REPEAT * stride_end)
  29. offsets = tl.arange(0, triton.next_power_of_2(BLOCK_SIZE))
  30. cols = start + blocks[:, None] * BLOCK_SIZE + offsets[None, :]
  31. values = tl.load(
  32. logits + row * stride_row + cols * stride_col,
  33. (blocks[:, None] < nblocks)
  34. & (offsets[None, :] < BLOCK_SIZE)
  35. & (cols < end)
  36. & (cols < width),
  37. other=-float("inf"),
  38. )
  39. reduced = tl.reduce(values, 1, _max_with_nan)
  40. reduced = tl.where(
  41. (end > start) & (blocks == (end - start - 1) // BLOCK_SIZE),
  42. float("inf"),
  43. reduced,
  44. )
  45. tl.store(scores + row * nblocks + blocks, reduced, blocks < nblocks)
  46. @triton.jit(do_not_specialize=["k"])
  47. def _store_candidates_kernel(
  48. values,
  49. indices,
  50. output,
  51. out_stride_row,
  52. out_stride_col,
  53. k,
  54. OUT_K: tl.constexpr,
  55. TILE: tl.constexpr,
  56. ):
  57. row = tl.program_id(0).to(tl.int64)
  58. cols = tl.program_id(1) * TILE + tl.arange(0, TILE)
  59. value = tl.load(values + row * k + cols, cols < k, other=-float("inf"))
  60. index = tl.load(indices + row * k + cols, cols < k, other=-1)
  61. tl.store(
  62. output + row * out_stride_row + cols * out_stride_col,
  63. tl.where(value > -float("inf"), index, -1),
  64. cols < OUT_K,
  65. )
  66. @triton.jit(do_not_specialize=["width", "nblocks"])
  67. def _candidate_flags_kernel(
  68. candidates,
  69. starts,
  70. flags,
  71. stride_row,
  72. stride_col,
  73. stride_start,
  74. width,
  75. nblocks,
  76. BLOCK_SIZE: tl.constexpr,
  77. K: tl.constexpr,
  78. HAS_STARTS: tl.constexpr,
  79. ROW_REPEAT: tl.constexpr,
  80. ):
  81. row = tl.program_id(0).to(tl.int64)
  82. offsets = tl.arange(0, 1024)
  83. for tile in range(tl.cdiv(nblocks + 1, 1024)):
  84. slots = tile * 1024 + offsets
  85. tl.store(flags + row * (nblocks + 1) + slots, 0, slots <= nblocks)
  86. start = tl.load(starts + row // ROW_REPEAT * stride_start) if HAS_STARTS else 0
  87. cols = tl.arange(0, triton.next_power_of_2(K))
  88. block = tl.load(
  89. candidates + row * stride_row + cols * stride_col, cols < K, other=-1
  90. ).to(tl.int64)
  91. # Preserve the packed-column clamp for candidates beyond the logits width.
  92. block = tl.where(start + block * BLOCK_SIZE >= width, nblocks, block)
  93. tl.debug_barrier()
  94. tl.store(flags + row * (nblocks + 1) + block, 1, (cols < K) & (block >= 0))
  95. @triton.jit(do_not_specialize=["width", "nblocks"])
  96. def _mask_candidates_kernel(
  97. logits,
  98. starts,
  99. ends,
  100. flags,
  101. stride_row,
  102. stride_col,
  103. stride_start,
  104. stride_end,
  105. width,
  106. nblocks,
  107. BLOCK_SIZE: tl.constexpr,
  108. HAS_STARTS: tl.constexpr,
  109. ROW_REPEAT: tl.constexpr,
  110. TILE: tl.constexpr,
  111. ):
  112. row = tl.program_id(0).to(tl.int64)
  113. cols = tl.program_id(1) * TILE + tl.arange(0, TILE)
  114. start = tl.load(starts + row // ROW_REPEAT * stride_start) if HAS_STARTS else 0
  115. end = tl.load(ends + row // ROW_REPEAT * stride_end)
  116. valid = (cols >= start) & (cols < end) & (cols < width)
  117. block = (cols - start) // BLOCK_SIZE
  118. keep = tl.load(flags + row * (nblocks + 1) + block, valid, other=0)
  119. edge = tl.load(flags + row * (nblocks + 1) + nblocks)
  120. keep = (keep != 0) | ((cols == width - 1) & (edge != 0))
  121. tl.store(
  122. logits + row * stride_row + cols * stride_col,
  123. -float("inf"),
  124. (cols < width) & ~(valid & keep),
  125. )
  126. def select_candidate_blocks(
  127. logits: torch.Tensor,
  128. row_ks: torch.Tensor | None,
  129. row_ke: torch.Tensor,
  130. topk_blocks: int,
  131. block_size: int,
  132. out: torch.Tensor,
  133. row_repeat: int = 1,
  134. ) -> None:
  135. """Select local block IDs by maximum score, pinning each row's newest block.
  136. Row bounds are in packed column space; absent starts mean zero.
  137. Decode rows share bounds in groups of ``row_repeat``. Output is -1 padded.
  138. """
  139. assert logits.is_cuda
  140. rows, width = logits.shape
  141. if not rows:
  142. return
  143. if not width:
  144. out.fill_(-1)
  145. return
  146. nblocks = triton.cdiv(width, block_size)
  147. scores = logits.new_empty((rows, nblocks))
  148. _block_scores_kernel[(rows, triton.cdiv(nblocks, 128))](
  149. logits,
  150. row_ks,
  151. row_ke,
  152. scores,
  153. *logits.stride(),
  154. row_ks.stride(0) if row_ks is not None else 0,
  155. row_ke.stride(0),
  156. width,
  157. nblocks,
  158. block_size,
  159. row_ks is not None,
  160. row_repeat,
  161. 128,
  162. )
  163. # Keep the existing top-k tie behavior.
  164. top = scores.topk(min(topk_blocks, nblocks), dim=-1)
  165. _store_candidates_kernel[(rows, triton.cdiv(topk_blocks, 256))](
  166. top.values,
  167. top.indices,
  168. out,
  169. *out.stride(),
  170. top.values.shape[1],
  171. topk_blocks,
  172. 256,
  173. )
  174. def apply_candidate_mask(
  175. logits: torch.Tensor,
  176. row_ks: torch.Tensor | None,
  177. row_ke: torch.Tensor,
  178. candidate_blocks: torch.Tensor,
  179. block_size: int,
  180. row_repeat: int = 1,
  181. ) -> None:
  182. """Mask packed logits outside causal bounds and request-local candidates."""
  183. assert logits.is_cuda
  184. rows, width = logits.shape
  185. if not rows or not width:
  186. return
  187. nblocks = triton.cdiv(width, block_size)
  188. flags = torch.empty((rows, nblocks + 1), device=logits.device, dtype=torch.uint8)
  189. start_stride = row_ks.stride(0) if row_ks is not None else 0
  190. _candidate_flags_kernel[(rows,)](
  191. candidate_blocks,
  192. row_ks,
  193. flags,
  194. *candidate_blocks.stride(),
  195. start_stride,
  196. width,
  197. nblocks,
  198. block_size,
  199. candidate_blocks.shape[1],
  200. row_ks is not None,
  201. row_repeat,
  202. )
  203. _mask_candidates_kernel[(rows, triton.cdiv(width, 1024))](
  204. logits,
  205. row_ks,
  206. row_ke,
  207. flags,
  208. *logits.stride(),
  209. start_stride,
  210. row_ke.stride(0),
  211. width,
  212. nblocks,
  213. block_size,
  214. row_ks is not None,
  215. row_repeat,
  216. 1024,
  217. )