compressor_utils.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. from dataclasses import dataclass
  4. from typing import Any
  5. import torch
  6. from vllm.model_executor.warmup.jit_warmup import zip_inputs
  7. from vllm.model_executor.warmup.jit_warmup_triton_helper import (
  8. LaunchSpec,
  9. TritonWarmupTensor,
  10. VllmTritonJitKernel,
  11. kernel_launcher,
  12. triton_scalar_specialization_rep,
  13. )
  14. from vllm.triton_utils import tl, triton
  15. from vllm.utils.math_utils import cdiv
  16. _DSPARK_SWA_INDEX_ALIGNMENT = 64
  17. def get_dspark_swa_index_width(
  18. window_size: int,
  19. num_speculative_tokens: int,
  20. ) -> int:
  21. """Return the padded width of non-causal DSpark SWA indices."""
  22. width = max(int(window_size), 0) + max(int(num_speculative_tokens), 0)
  23. return cdiv(width, _DSPARK_SWA_INDEX_ALIGNMENT) * _DSPARK_SWA_INDEX_ALIGNMENT
  24. class CompressedSlotMappingKernel(
  25. VllmTritonJitKernel["CompressedSlotMappingKernel.CompileKey"]
  26. ):
  27. TRITON_BLOCK_SIZE = 1024
  28. @dataclass(frozen=True)
  29. class CompileKey:
  30. compress_ratio: int
  31. triton_block_size: int
  32. block_size: int
  33. dcp_world: int = 1
  34. dcp_rank: int = 0
  35. dcp_interleave: int = 1
  36. @staticmethod
  37. @triton.jit(do_not_specialize=["block_table_stride"])
  38. def kernel(
  39. # [num_tokens]
  40. slot_mapping_ptr,
  41. # [num_reqs + 1]
  42. query_start_loc_ptr,
  43. # [num_reqs]
  44. seq_lens_ptr,
  45. # [num_reqs, max_num_blocks]
  46. block_table_ptr,
  47. block_table_stride,
  48. # states per page on THIS rank (kv_cache_spec.num_states)
  49. block_size,
  50. COMPRESS_RATIO: tl.constexpr,
  51. PAD_ID: tl.constexpr,
  52. TRITON_BLOCK_SIZE: tl.constexpr,
  53. DCP_WORLD: tl.constexpr,
  54. DCP_RANK: tl.constexpr,
  55. DCP_INTERLEAVE: tl.constexpr,
  56. ):
  57. batch_idx = tl.program_id(0)
  58. query_start = tl.load(query_start_loc_ptr + batch_idx)
  59. query_end = tl.load(query_start_loc_ptr + batch_idx + 1)
  60. query_len = query_end - query_start
  61. seq_len = tl.load(seq_lens_ptr + batch_idx)
  62. start_pos = seq_len - query_len
  63. for i in range(0, query_len, TRITON_BLOCK_SIZE):
  64. offset = i + tl.arange(0, TRITON_BLOCK_SIZE)
  65. mask = offset < query_len
  66. pos = start_pos + i + tl.arange(0, TRITON_BLOCK_SIZE)
  67. is_valid = (pos + 1) % COMPRESS_RATIO == 0
  68. # Global compressed state index of the group this token closes.
  69. state = pos // COMPRESS_RATIO
  70. if DCP_WORLD > 1:
  71. # DCP shards the compressed cache over STATES with the runner's
  72. # token-space formula (worker/gpu/block_table.py) applied in
  73. # state space: one block-table entry spans block_size * DCP_WORLD
  74. # states, this rank owns the states whose interleave group is
  75. # congruent to DCP_RANK, and stores them contiguously per entry.
  76. virtual_block_size = block_size * DCP_WORLD
  77. virtual_block_idx = state // virtual_block_size
  78. virtual_off = state % virtual_block_size
  79. is_local = (virtual_off // DCP_INTERLEAVE) % DCP_WORLD == DCP_RANK
  80. local_off = (virtual_off // (DCP_INTERLEAVE * DCP_WORLD)) * DCP_INTERLEAVE + (
  81. virtual_off % DCP_INTERLEAVE
  82. )
  83. local_state = virtual_block_idx * block_size + local_off
  84. is_valid = is_valid & is_local
  85. else:
  86. local_state = state
  87. block_ids = local_state // block_size
  88. block_numbers = tl.load(
  89. block_table_ptr + batch_idx * block_table_stride + block_ids,
  90. mask=mask & is_valid,
  91. )
  92. slot_ids = block_numbers * block_size + local_state % block_size
  93. # NOTE
  94. slot_ids = tl.where(is_valid, slot_ids, PAD_ID)
  95. tl.store(slot_mapping_ptr + query_start + offset, slot_ids, mask=mask)
  96. def dispatch( # type: ignore[override]
  97. self,
  98. *,
  99. compress_ratio: int,
  100. block_size: int,
  101. dcp_world_size: int = 1,
  102. dcp_rank: int = 0,
  103. cp_kv_cache_interleave_size: int = 1,
  104. ) -> CompileKey:
  105. return self.CompileKey(
  106. compress_ratio=compress_ratio,
  107. triton_block_size=self.TRITON_BLOCK_SIZE,
  108. block_size=triton_scalar_specialization_rep(block_size),
  109. dcp_world=dcp_world_size,
  110. dcp_rank=dcp_rank,
  111. dcp_interleave=cp_kv_cache_interleave_size,
  112. )
  113. def get_warmup_keys(self, vllm_config: Any) -> list[CompileKey]:
  114. hf_config = vllm_config.model_config.hf_config
  115. parallel_config = vllm_config.parallel_config
  116. dcp_world_size = parallel_config.decode_context_parallel_size
  117. cp_interleave = parallel_config.cp_kv_cache_interleave_size
  118. dcp_rank = 0
  119. if dcp_world_size > 1:
  120. from vllm.distributed import get_dcp_group
  121. dcp_rank = get_dcp_group().rank_in_group
  122. configured_ratios = (
  123. *(getattr(hf_config, "compress_ratios", None) or ()),
  124. getattr(hf_config, "index_kpool", 1) or 1,
  125. )
  126. compress_ratios = tuple(
  127. dict.fromkeys(int(ratio) for ratio in configured_ratios if int(ratio) > 1)
  128. )
  129. if not compress_ratios:
  130. return []
  131. return self._trace_dispatch(self.dispatch)(
  132. zip_inputs(
  133. *(
  134. dict(
  135. compress_ratio=ratio,
  136. block_size=vllm_config.cache_config.block_size // ratio,
  137. dcp_world_size=dcp_world_size,
  138. dcp_rank=dcp_rank,
  139. cp_kv_cache_interleave_size=cp_interleave,
  140. )
  141. for ratio in compress_ratios
  142. )
  143. )
  144. )
  145. def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
  146. int32_ptr = TritonWarmupTensor(torch.int32)
  147. return dict(
  148. slot_mapping=TritonWarmupTensor(torch.int64),
  149. query_start_loc=int32_ptr,
  150. seq_lens=int32_ptr,
  151. block_table=int32_ptr,
  152. block_size=compile_key.block_size,
  153. compress_ratio=compile_key.compress_ratio,
  154. dcp_world_size=compile_key.dcp_world,
  155. dcp_rank=compile_key.dcp_rank,
  156. cp_kv_cache_interleave_size=compile_key.dcp_interleave,
  157. )
  158. @kernel_launcher
  159. def __call__(
  160. self,
  161. slot_mapping: torch.Tensor,
  162. query_start_loc: torch.Tensor,
  163. seq_lens: torch.Tensor,
  164. block_table: torch.Tensor,
  165. block_size: int,
  166. compress_ratio: int,
  167. dcp_world_size: int = 1,
  168. dcp_rank: int = 0,
  169. cp_kv_cache_interleave_size: int = 1,
  170. ) -> LaunchSpec:
  171. assert block_size % cp_kv_cache_interleave_size == 0, (
  172. f"states per page ({block_size}) must be a multiple of "
  173. f"cp_kv_cache_interleave_size ({cp_kv_cache_interleave_size})"
  174. )
  175. return (block_table.shape[0],), dict(
  176. block_table_stride=block_table.stride(0),
  177. COMPRESS_RATIO=compress_ratio,
  178. PAD_ID=-1,
  179. TRITON_BLOCK_SIZE=self.TRITON_BLOCK_SIZE,
  180. DCP_WORLD=dcp_world_size,
  181. DCP_RANK=dcp_rank,
  182. DCP_INTERLEAVE=cp_kv_cache_interleave_size,
  183. )
  184. def get_compressed_slot_mapping(
  185. num_tokens: int,
  186. query_start_loc: torch.Tensor,
  187. seq_lens: torch.Tensor,
  188. block_table: torch.Tensor,
  189. block_size: int,
  190. compress_ratio: int,
  191. out: torch.Tensor | None = None,
  192. dcp_world_size: int = 1,
  193. dcp_rank: int = 0,
  194. cp_kv_cache_interleave_size: int = 1,
  195. ) -> torch.Tensor:
  196. """Slot of the compressed state each token closes (PAD otherwise).
  197. ``block_size`` is the number of states per page on this rank. Under DCP
  198. (``dcp_world_size > 1``) states are sharded across ranks and only this
  199. rank's states get a slot; the block table is the shared (manager) table
  200. whose entries each span ``block_size * dcp_world_size`` states.
  201. """
  202. if out is not None:
  203. # Guard: for padded / invalid sequences.
  204. # Negative positions produce bogus block indices that lead to illegal memory
  205. # accesses inside the block_table load.
  206. # NOTE: Fill -1 to the whole tensor, not just the first `num_tokens`.
  207. out.fill_(-1)
  208. slot_mapping = out[:num_tokens]
  209. else:
  210. slot_mapping = torch.full(
  211. (num_tokens,), -1, dtype=torch.int64, device=query_start_loc.device
  212. )
  213. _COMPRESSED_SLOT_MAPPING_KERNEL(
  214. slot_mapping,
  215. query_start_loc,
  216. seq_lens,
  217. block_table,
  218. block_size,
  219. compress_ratio,
  220. dcp_world_size,
  221. dcp_rank,
  222. cp_kv_cache_interleave_size,
  223. )
  224. return slot_mapping
  225. _COMPRESSED_SLOT_MAPPING_KERNEL = CompressedSlotMappingKernel()