indexer.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. import os
  4. from dataclasses import dataclass
  5. import numpy as np
  6. import torch
  7. import vllm.envs as envs
  8. from vllm.config import VllmConfig
  9. from vllm.logger import init_logger
  10. from vllm.platforms import current_platform
  11. from vllm.triton_utils import tl, triton
  12. from vllm.utils.math_utils import cdiv
  13. from vllm.utils.platform_utils import num_compute_units
  14. from vllm.v1.attention.backend import (
  15. AttentionBackend,
  16. AttentionCGSupport,
  17. AttentionMetadataBuilder,
  18. CommonAttentionMetadata,
  19. MultipleOf,
  20. )
  21. from vllm.v1.attention.backends.mla.compressor_utils import get_compressed_slot_mapping
  22. from vllm.v1.attention.backends.utils import get_dcp_local_seq_lens
  23. from vllm.v1.kv_cache_interface import AttentionSpec, MLAAttentionSpec
  24. from vllm.v1.worker.cp_utils import get_total_cp_world_size
  25. logger = init_logger(__name__)
  26. _B12X_PAGED_INDEX_SUPERTILE_K_DEFAULT = 32768
  27. _B12X_PAGED_INDEX_TILE_BLOCK_K = 512
  28. def _get_b12x_paged_indexer_supertile_k() -> int:
  29. raw = os.environ.get("B12X_PAGED_INDEX_SUPERTILE_K")
  30. tokens = _B12X_PAGED_INDEX_SUPERTILE_K_DEFAULT if raw is None else int(raw)
  31. tokens = max(tokens, _B12X_PAGED_INDEX_TILE_BLOCK_K)
  32. return (
  33. (tokens + _B12X_PAGED_INDEX_TILE_BLOCK_K - 1)
  34. // _B12X_PAGED_INDEX_TILE_BLOCK_K
  35. * _B12X_PAGED_INDEX_TILE_BLOCK_K
  36. )
  37. @triton.jit
  38. def _prepare_uniform_decode_kernel(
  39. seq_lens_ptr,
  40. decode_seq_lens_ptr,
  41. block_table_ptr,
  42. block_table_stride,
  43. expanded_block_table_ptr,
  44. expanded_bt_stride,
  45. decode_lens_ptr,
  46. max_decode_len,
  47. BLOCK_SIZE: tl.constexpr,
  48. ):
  49. idx = tl.program_id(0)
  50. req_id = idx // max_decode_len
  51. local_idx = idx % max_decode_len
  52. # Compute number of KVs attended to by this token.
  53. seq_len = tl.load(seq_lens_ptr + req_id)
  54. per_token_seq_len = seq_len - max_decode_len + local_idx + 1
  55. tl.store(decode_seq_lens_ptr + idx, per_token_seq_len)
  56. # Copy block table row.
  57. src = block_table_ptr + req_id * block_table_stride
  58. dst = expanded_block_table_ptr + idx * expanded_bt_stride
  59. for i in tl.range(0, expanded_bt_stride, BLOCK_SIZE):
  60. off = i + tl.arange(0, BLOCK_SIZE)
  61. mask = off < expanded_bt_stride
  62. src_block = tl.load(src + off, mask=mask)
  63. tl.store(dst + off, src_block, mask=mask)
  64. # All reqs now have decode_len = 1.
  65. tl.store(decode_lens_ptr + idx, 1)
  66. def split_indexer_prefill_chunks(
  67. seq_lens_cpu: torch.Tensor | np.ndarray,
  68. query_lens_cpu: torch.Tensor | np.ndarray,
  69. workspace_size: int,
  70. max_logits_bytes: int,
  71. request_offset: int = 0,
  72. ) -> list[tuple[slice, slice]]:
  73. """
  74. Split prefill requests into chunks for the sparse indexer, respecting:
  75. - N constraint: total_seq_lens <= workspace_size (existing O(N) workspace)
  76. - Logits constraint: M * N * 4 <= max_logits_bytes
  77. When a single request-level chunk still exceeds the logits budget,
  78. sub-chunks on the query dimension (M) to bound peak memory.
  79. Returns list of (req_slice, query_slice) tuples.
  80. """
  81. if isinstance(seq_lens_cpu, torch.Tensor):
  82. seq_lens_cpu = seq_lens_cpu.numpy()
  83. if isinstance(query_lens_cpu, torch.Tensor):
  84. query_lens_cpu = query_lens_cpu.numpy()
  85. chunks: list[tuple[slice, slice]] = []
  86. n = len(seq_lens_cpu)
  87. max_logits_elems = max_logits_bytes // 4
  88. end = 0
  89. while end < n:
  90. start, chunk_m, chunk_n = end, 0, 0
  91. while end < n:
  92. q, s = int(query_lens_cpu[end]), int(seq_lens_cpu[end])
  93. new_m, new_n = chunk_m + q, chunk_n + s
  94. if new_n <= workspace_size and new_m * new_n <= max_logits_elems:
  95. chunk_m, chunk_n = new_m, new_n
  96. end += 1
  97. else:
  98. break
  99. # A single request can exceed the budget, requiring sub-chunking
  100. # on the query dimension.
  101. if end == start:
  102. chunk_m, chunk_n = int(query_lens_cpu[end]), int(seq_lens_cpu[end])
  103. end += 1
  104. req_slice = slice(start + request_offset, end + request_offset)
  105. max_q = max(1, max_logits_elems // chunk_n) if chunk_n > 0 else chunk_m
  106. for q_off in range(0, chunk_m, max_q):
  107. sub_m = min(max_q, chunk_m - q_off)
  108. chunks.append((req_slice, slice(q_off, q_off + sub_m)))
  109. return chunks
  110. class DeepseekV32IndexerBackend(AttentionBackend):
  111. @staticmethod
  112. def get_name() -> str:
  113. return "DEEPSEEK_V32_INDEXER"
  114. @staticmethod
  115. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  116. return [1, 64] if current_platform.is_rocm() else [64]
  117. @classmethod
  118. def get_supported_head_sizes(cls) -> list[int]:
  119. return [32, 64, 128]
  120. @staticmethod
  121. def get_builder_cls() -> type["DeepseekV32IndexerMetadataBuilder"]:
  122. return DeepseekV32IndexerMetadataBuilder
  123. @staticmethod
  124. def get_kv_cache_shape(
  125. num_blocks: int,
  126. block_size: int,
  127. num_kv_heads: int,
  128. head_size: int,
  129. cache_dtype_str: str = "auto",
  130. ) -> tuple[int, ...]:
  131. assert num_kv_heads == 1
  132. return (num_blocks, block_size, head_size)
  133. @staticmethod
  134. def get_kv_cache_stride_order(
  135. include_num_layers_dimension: bool = False,
  136. ) -> tuple[int, ...]:
  137. if include_num_layers_dimension:
  138. # DeepseekV32Indexer kernels do not support cross-layer
  139. # KV cache layout. Identity permutation keeps num_layers
  140. # first, signaling incompatibility.
  141. return (0, 1, 2, 3)
  142. return (0, 1, 2)
  143. class B12xNonCompressedIndexerBackend(DeepseekV32IndexerBackend):
  144. @staticmethod
  145. def get_name() -> str:
  146. return "B12X_NON_COMPRESSED_INDEXER"
  147. class DeepseekV4IndexerBackend(DeepseekV32IndexerBackend):
  148. @staticmethod
  149. def get_name() -> str:
  150. return "DEEPSEEK_V4_INDEXER"
  151. @staticmethod
  152. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  153. return [256]
  154. @dataclass
  155. class DeepseekV32IndexerPrefillChunkMetadata:
  156. block_table: torch.Tensor
  157. cu_seqlen_ks: torch.Tensor
  158. cu_seqlen_ke: torch.Tensor
  159. cu_seq_lens: torch.Tensor
  160. token_to_seq: torch.Tensor
  161. total_seq_lens: int
  162. token_start: int
  163. token_end: int
  164. num_reqs: int
  165. skip_kv_gather: bool = False
  166. @dataclass
  167. class DeepseekV32IndexerPrefillMetadata:
  168. chunks: list[DeepseekV32IndexerPrefillChunkMetadata]
  169. @dataclass
  170. class DeepSeekV32IndexerDecodeMetadata:
  171. block_table: torch.Tensor
  172. # seq_lens: per-token effective context lengths.
  173. # - flatten path / plain decode: 1D (batch_size,)
  174. # - native MTP path: 2D (B, next_n) where [b,j] = L_b - next_n + j + 1
  175. # Both fp8_fp4_paged_mqa_logits and the topk kernels accept both shapes.
  176. seq_lens: torch.Tensor
  177. max_seq_len: int
  178. decode_lens: torch.Tensor
  179. requires_padding: bool
  180. schedule_metadata: torch.Tensor | None
  181. compress_ratio: int = 1
  182. # Live scorer window (max compressed context across the batch) in cache
  183. # tokens, computed host-side in build() — a metadata tensor read by the
  184. # captured indexer kernel, never an in-kernel reduction. None => b12x uses
  185. # the capacity cap.
  186. active_width: torch.Tensor | None = None
  187. @dataclass
  188. class DeepseekV32IndexerMetadata:
  189. # FIXME (zyongye)
  190. # hacky way to access the data now, need to be in chunked meta
  191. seq_lens: torch.Tensor
  192. max_seq_len: int
  193. slot_mapping: torch.Tensor
  194. # New for MLA (compared to FlashAttention)
  195. # For handling prefill decode split
  196. num_decodes: int
  197. num_decode_tokens: int
  198. num_prefills: int
  199. num_prefill_tokens: int
  200. decode: DeepSeekV32IndexerDecodeMetadata | None = None
  201. prefill: DeepseekV32IndexerPrefillMetadata | None = None
  202. dcp_world_size: int = 1
  203. dcp_rank: int = 0
  204. cp_interleave_size: int = 1
  205. def get_max_prefill_buffer_size(vllm_config: VllmConfig):
  206. max_model_len = vllm_config.model_config.max_model_len
  207. # NOTE(Chen): 40 is a magic number for controlling the prefill buffer size.
  208. # Each entry is 128 fp8 bytes and 4 scale bytes for a total of 132 bytes.
  209. # The flashmla_sparse backend uses a workspace size of 5 * max_model_len.
  210. # The memory usage of the workspace there is 576 * 2 bytes; so we size this as
  211. # (576 * 2 // 132) * 5 = 40 to maximize this workspace size while still fitting
  212. # within the flashmla_sparse workspace.
  213. # For DeepSeek-V3.2, the max_model_len is 163840.
  214. # 40 * 163840 * 132 = 865075200 bytes = 825 MB
  215. return max_model_len * 40
  216. class DeepseekV32IndexerMetadataBuilder(AttentionMetadataBuilder):
  217. reorder_batch_threshold: int = 1
  218. @classmethod
  219. def get_cudagraph_support(
  220. cls,
  221. vllm_config: VllmConfig,
  222. kv_cache_spec: AttentionSpec,
  223. ) -> AttentionCGSupport:
  224. return AttentionCGSupport.UNIFORM_BATCH
  225. def __init__(self, *args, **kwargs):
  226. super().__init__(*args, **kwargs)
  227. self.storage_block_size = int(self.kv_cache_spec.storage_block_size)
  228. scheduler_config = self.vllm_config.scheduler_config
  229. parallel_config = self.vllm_config.parallel_config
  230. self.dcp_world_size = parallel_config.decode_context_parallel_size
  231. self.pcp_world_size = parallel_config.prefill_context_parallel_size
  232. self.cp_kv_cache_interleave_size = (
  233. parallel_config.cp_kv_cache_interleave_size
  234. )
  235. self.dcp_rank = 0
  236. if self.dcp_world_size > 1:
  237. assert self.pcp_world_size == 1, (
  238. "DeepseekV32IndexerMetadataBuilder supports DCP but not PCP."
  239. )
  240. from vllm.distributed.parallel_state import get_dcp_group
  241. self.dcp_rank = get_dcp_group().rank_in_group
  242. # NOTE(Chen):an estimated max size of flattened_kv. Need to double check.
  243. self.max_prefill_buffer_size = get_max_prefill_buffer_size(self.vllm_config)
  244. self.num_speculative_tokens = (
  245. self.vllm_config.speculative_config.num_speculative_tokens
  246. if self.vllm_config.speculative_config
  247. else 0
  248. )
  249. self.use_fp4_indexer_cache = (
  250. self.vllm_config.attention_config.use_fp4_indexer_cache
  251. )
  252. assert (
  253. current_platform.is_device_capability_family(100)
  254. or not self.use_fp4_indexer_cache
  255. ), (
  256. "use_fp4_indexer_cache requires Blackwell datacenter GPUs "
  257. "(sm_10x, e.g. B200/GB200); sm_120 (consumer Blackwell) and "
  258. "earlier architectures are not supported."
  259. )
  260. next_n = self.num_speculative_tokens + 1
  261. self.reorder_batch_threshold += self.num_speculative_tokens
  262. # NOTE: SM100 datacenter GPUs support any next_n natively via the
  263. # multi-atom paged MQA logits kernels (FP8 and FP4 indexer
  264. # caches). Outside the SM100 family the FP8
  265. # paged MQA logits kernel only supports next_n in (1, 2)
  266. # (deepgemm smxx_fp8_fp4_paged_mqa_logits.hpp:233), so flatten there.
  267. self.use_flattening = not current_platform.is_device_capability_family(
  268. 100
  269. ) and next_n not in (1, 2)
  270. logger.info_once(
  271. "DSA indexer decode path: use_flattening=%s "
  272. "(next_n=%d, use_fp4_indexer_cache=%s)",
  273. self.use_flattening,
  274. next_n,
  275. self.use_fp4_indexer_cache,
  276. )
  277. sm_count = num_compute_units(self.device.index)
  278. self.num_sms = sm_count
  279. self.offsets_buffer = torch.arange(
  280. next_n, device=self.device, dtype=torch.int32
  281. )
  282. self.decode_lens_buffer = torch.zeros(
  283. (scheduler_config.max_num_batched_tokens,),
  284. dtype=torch.int32,
  285. device=self.device,
  286. )
  287. # Shared workspace for decode seq_lens. Native MTP views this as
  288. # (B, max_decode_len) at runtime, keeping context_lens contiguous even
  289. # when max_decode_len is smaller than next_n.
  290. self.decode_seq_lens_buffer = torch.zeros(
  291. (scheduler_config.max_num_batched_tokens,),
  292. dtype=torch.int32,
  293. device=self.device,
  294. )
  295. self.arange_buffer = torch.arange(
  296. max(
  297. scheduler_config.max_num_seqs * next_n,
  298. scheduler_config.max_num_batched_tokens,
  299. ),
  300. dtype=torch.int32,
  301. device=self.device,
  302. )
  303. max_num_blocks_per_req = cdiv(
  304. self.vllm_config.model_config.max_model_len,
  305. self.kv_cache_spec.block_size * get_total_cp_world_size(),
  306. )
  307. self.expanded_block_table_buffer = torch.zeros(
  308. (
  309. scheduler_config.max_num_batched_tokens,
  310. max_num_blocks_per_req + 8, # reaper fix: runner tables run wider (461 vs 462 off-by-one)
  311. ),
  312. dtype=torch.int32,
  313. device=self.device,
  314. )
  315. # See: DeepGMM/csrc/apis/attention.hpp
  316. self.scheduler_metadata_buffer = torch.empty(
  317. (self.num_sms + 1, 2), dtype=torch.int32, device=self.device
  318. )
  319. # Persistent live-active-width buffer for the b12x indexer decode
  320. # scorer window. Filled host-side each build() (outside cudagraph
  321. # capture) and read by the captured kernel at a stable address.
  322. self.b12x_active_width_buffer = torch.zeros(
  323. (1,), dtype=torch.int32, device=self.device
  324. )
  325. # KV compression. Default to 1 for no compression.
  326. self.compress_ratio = 1
  327. # Get compress_ratio for DeepseekV4 support
  328. if isinstance(self.kv_cache_spec, MLAAttentionSpec):
  329. self.compress_ratio = self.kv_cache_spec.compress_ratio
  330. _prewarm_prefill_chunk_metadata_kernel(
  331. device=self.device,
  332. dcp_world_size=self.dcp_world_size,
  333. dcp_rank=self.dcp_rank,
  334. cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
  335. compress_ratio=self.compress_ratio,
  336. )
  337. # DCP writes the indexer KV cache through rank-local pages even when
  338. # compress_ratio == 1 (GLM/Kimi). Keep the mapped slots graph-stable.
  339. if self.compress_ratio > 1 or self.dcp_world_size > 1:
  340. self.compressed_slot_mapping_buffer = torch.zeros(
  341. (scheduler_config.max_num_batched_tokens,),
  342. dtype=torch.int64,
  343. device=self.device,
  344. )
  345. # Pre-allocate buffers for CUDA graph compatibility when
  346. if self.compress_ratio > 1:
  347. # compress_ratio > 1 (DeepseekV4)
  348. # Buffer for compressed seq_lens in decode path
  349. self.expanded_seq_lens_buffer = torch.zeros(
  350. (scheduler_config.max_num_batched_tokens,),
  351. dtype=torch.int32,
  352. device=self.device,
  353. )
  354. def _maybe_build_b12x_schedule_metadata(
  355. self,
  356. seq_lens: torch.Tensor,
  357. block_table: torch.Tensor,
  358. num_decode_tokens: int,
  359. requires_padding: bool,
  360. ) -> torch.Tensor | None:
  361. if not envs.VLLM_USE_B12X_SPARSE_INDEXER or requires_padding:
  362. return None
  363. schedule_seq_lens = seq_lens
  364. if schedule_seq_lens.dim() == 2:
  365. batch_size, next_n = schedule_seq_lens.shape
  366. if num_decode_tokens != int(batch_size * next_n):
  367. return None
  368. schedule_seq_lens = schedule_seq_lens.reshape(-1)
  369. if schedule_seq_lens.dim() != 1:
  370. return None
  371. from b12x.attention.indexer import (
  372. build_paged_mqa_schedule_metadata,
  373. uses_paged_mqa_schedule,
  374. )
  375. if not uses_paged_mqa_schedule(
  376. q_rows=int(schedule_seq_lens.shape[0]),
  377. max_pages=int(block_table.shape[1]),
  378. ):
  379. return None
  380. return build_paged_mqa_schedule_metadata(
  381. schedule_seq_lens.contiguous(),
  382. self.storage_block_size,
  383. self.num_sms,
  384. out=self.scheduler_metadata_buffer,
  385. )
  386. def _maybe_build_deep_gemm_schedule_metadata(
  387. self,
  388. seq_lens: torch.Tensor,
  389. ) -> torch.Tensor:
  390. if current_platform.is_cuda():
  391. from vllm.utils.deep_gemm import (
  392. get_paged_mqa_logits_metadata,
  393. has_deep_gemm,
  394. )
  395. if has_deep_gemm():
  396. self.scheduler_metadata_buffer[:] = get_paged_mqa_logits_metadata(
  397. seq_lens,
  398. self.storage_block_size,
  399. self.num_sms,
  400. )
  401. return self.scheduler_metadata_buffer
  402. def _decode_topk_max_seq_len_from_cpu(
  403. self,
  404. common_attn_metadata: CommonAttentionMetadata,
  405. num_decodes: int,
  406. decode_lens_np: np.ndarray,
  407. max_decode_len: int,
  408. use_native: bool,
  409. dcp_local_seq_lens_cpu: torch.Tensor | None,
  410. ) -> int | None:
  411. """Return the exact decode max seq-len without reading CUDA tensors.
  412. B12X needs a host scalar for metadata/scheduling. Computing it with
  413. ``seq_lens.max().item()`` synchronizes every decode step. The model
  414. runner already maintains CPU shadows for normal decode; use those
  415. directly and mirror only the shape transforms that can affect the max.
  416. If async-spec has made the CPU shadow non-authoritative, return None so
  417. the caller can fall back to a safe graph-stable bound.
  418. """
  419. cpu_seq_lens = (
  420. dcp_local_seq_lens_cpu
  421. if dcp_local_seq_lens_cpu is not None
  422. else common_attn_metadata._seq_lens_cpu
  423. )
  424. if cpu_seq_lens is None:
  425. return None
  426. if cpu_seq_lens.device.type != "cpu":
  427. return None
  428. seq_lens_np = cpu_seq_lens[:num_decodes].numpy()
  429. if seq_lens_np.size == 0:
  430. return 0
  431. # Flattened variable-length MTP writes only real decode tokens and
  432. # zero-fills the tail. Ignore padded decode rows to match that max.
  433. if not use_native and max_decode_len > 1:
  434. valid_decode_rows = decode_lens_np[:num_decodes] > 0
  435. if not bool(np.any(valid_decode_rows)):
  436. return 0
  437. seq_lens_np = seq_lens_np[valid_decode_rows]
  438. max_seq_len = int(seq_lens_np.max())
  439. if self.compress_ratio > 1:
  440. max_seq_len //= self.compress_ratio
  441. return max_seq_len
  442. def _prepare_decode_tensors(
  443. self,
  444. seq_lens: torch.Tensor,
  445. block_table: torch.Tensor,
  446. decode_lens: torch.Tensor,
  447. decode_lens_np: np.ndarray,
  448. query_start_loc: torch.Tensor,
  449. num_decodes: int,
  450. num_decode_tokens: int,
  451. use_native: bool,
  452. next_n: int,
  453. max_decode_len: int,
  454. global_seq_lens: torch.Tensor | None = None,
  455. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, int, bool]:
  456. """Expand seq_lens/block_table/decode_lens for the decode kernels.
  457. Flatten path (not use_native, max_decode_len > 1):
  458. Each multi-token decode request is expanded into individual
  459. single-token entries so the kernel always sees next_n=1.
  460. Native path (use_native or max_decode_len == 1):
  461. Plain decode or spec-decode with 2D per-token context lengths.
  462. Returns (seq_lens, block_table, decode_lens, batch_size, requires_padding).
  463. seq_lens is 1D (batch_size,) for flatten/plain, 2D (B, max_decode_len)
  464. for native MTP.
  465. """
  466. min_decode_len = int(decode_lens_np.min())
  467. if not use_native and max_decode_len > 1:
  468. assert self.decode_seq_lens_buffer.dim() == 1
  469. if min_decode_len == max_decode_len:
  470. # Uniform decode lengths.
  471. num_decode_tokens = num_decodes * max_decode_len
  472. _prepare_uniform_decode_kernel[(num_decode_tokens,)](
  473. seq_lens,
  474. self.decode_seq_lens_buffer,
  475. block_table,
  476. block_table.stride(0),
  477. self.expanded_block_table_buffer,
  478. self.expanded_block_table_buffer.stride(0),
  479. self.decode_lens_buffer,
  480. max_decode_len,
  481. BLOCK_SIZE=1024,
  482. )
  483. if global_seq_lens is not None and self.dcp_world_size > 1:
  484. expanded_global = (
  485. global_seq_lens[:num_decodes].unsqueeze(1)
  486. - max_decode_len
  487. + 1
  488. + self.offsets_buffer[:max_decode_len]
  489. ).reshape(-1)
  490. self.decode_seq_lens_buffer[:num_decode_tokens].copy_(
  491. get_dcp_local_seq_lens(
  492. expanded_global,
  493. self.dcp_world_size,
  494. self.dcp_rank,
  495. self.cp_kv_cache_interleave_size,
  496. ),
  497. non_blocking=True,
  498. )
  499. self.decode_seq_lens_buffer[num_decode_tokens:] = 0
  500. seq_lens = self.decode_seq_lens_buffer[:num_decode_tokens]
  501. block_table = self.expanded_block_table_buffer[:num_decode_tokens]
  502. decode_lens = self.decode_lens_buffer[:num_decode_tokens]
  503. return seq_lens, block_table, decode_lens, num_decode_tokens, False
  504. else:
  505. # Variable decode lengths.
  506. # Assume 4 requests with seq_lens [10, 7, 12, 0] (the final req is
  507. # padding) and decode_lens [3, 1, 4, 0] in the below example comments.
  508. # The context lengths are therefore
  509. # [10-3, 7-1, 12-4, 0-0] = [7, 6, 8, 0].
  510. # 3 + 1 + 4 + 0 = 8
  511. actual_expanded = int(decode_lens_np.sum())
  512. # Fuse expanded_base and expanded_starts into a single
  513. # repeat_interleave:
  514. # seq_len_i = (context_start[b] - query_start_loc[b]) + arange[i] + 1
  515. # where context_start[b] = seq_lens[b] - decode_lens[b].
  516. # Example: offsets = [7-0, 6-3, 8-4, 0-8] = [7, 3, 4, -8]
  517. # expanded_offsets = [7, 7, 7, 3, 4, 4, 4, 4]
  518. # result = [8, 9, 10, 7, 9, 10, 11, 12]
  519. seq_lens_for_expansion = (
  520. global_seq_lens if global_seq_lens is not None else seq_lens
  521. )
  522. expanded_offsets = torch.repeat_interleave(
  523. seq_lens_for_expansion - decode_lens - query_start_loc,
  524. decode_lens,
  525. output_size=actual_expanded,
  526. )
  527. # [8, 9, 10, 7, 9, 10, 11, 12, ...] where ... is unused buffer space
  528. expanded_seq_lens = (
  529. expanded_offsets + self.arange_buffer[:actual_expanded] + 1
  530. )
  531. if global_seq_lens is not None and self.dcp_world_size > 1:
  532. expanded_seq_lens = get_dcp_local_seq_lens(
  533. expanded_seq_lens,
  534. self.dcp_world_size,
  535. self.dcp_rank,
  536. self.cp_kv_cache_interleave_size,
  537. )
  538. self.decode_seq_lens_buffer[:actual_expanded] = expanded_seq_lens
  539. self.decode_seq_lens_buffer[actual_expanded:] = 0
  540. seq_lens = self.decode_seq_lens_buffer[:num_decode_tokens]
  541. # Give each of the flattened entries the same block table row as the
  542. # original request.
  543. if int(block_table.shape[1]) > int(
  544. self.expanded_block_table_buffer.shape[1]):
  545. # reaper fix: grow-on-demand if a batch arrives wider than alloc
  546. self.expanded_block_table_buffer = torch.zeros(
  547. (int(self.expanded_block_table_buffer.shape[0]),
  548. int(block_table.shape[1])),
  549. dtype=torch.int32,
  550. device=self.expanded_block_table_buffer.device,
  551. )
  552. bt_w = int(block_table.shape[1])
  553. self.expanded_block_table_buffer[:actual_expanded, :bt_w] = (
  554. torch.repeat_interleave(
  555. block_table, decode_lens, dim=0, output_size=actual_expanded
  556. )
  557. )
  558. if actual_expanded < num_decode_tokens:
  559. self.expanded_block_table_buffer[
  560. actual_expanded:num_decode_tokens, 0
  561. ] = 0
  562. block_table = self.expanded_block_table_buffer[:num_decode_tokens]
  563. # All reqs now have decode_len=1
  564. self.decode_lens_buffer[:num_decode_tokens] = 1
  565. decode_lens = self.decode_lens_buffer[:num_decode_tokens]
  566. return seq_lens, block_table, decode_lens, num_decode_tokens, False
  567. else:
  568. # Native path: plain decode (next_n==1) or spec decode
  569. # with 2D per-token context lengths (next_n > 1).
  570. #
  571. # When decode_lens are not truly uniform (e.g. some requests have
  572. # decode_len < next_n due to padding or short prefills), the simple
  573. # reshape in sparse_attn_indexer won't work. Use pack_seq_triton
  574. # (requires_padding) instead.
  575. requires_padding = min_decode_len != max_decode_len
  576. if use_native and next_n > 1:
  577. assert self.decode_seq_lens_buffer.dim() == 1
  578. # (B, max_decode_len): token j attends to
  579. # L - max_decode_len + j + 1 KV tokens.
  580. seq_lens_buffer = self.decode_seq_lens_buffer[
  581. : num_decodes * max_decode_len
  582. ].view(num_decodes, max_decode_len)
  583. seq_lens_for_expansion = (
  584. global_seq_lens if global_seq_lens is not None else seq_lens
  585. )
  586. expanded_seq_lens = (
  587. seq_lens_for_expansion.unsqueeze(1)
  588. - max_decode_len
  589. + 1
  590. + self.offsets_buffer[:max_decode_len]
  591. )
  592. if global_seq_lens is not None and self.dcp_world_size > 1:
  593. expanded_seq_lens = get_dcp_local_seq_lens(
  594. expanded_seq_lens.reshape(-1),
  595. self.dcp_world_size,
  596. self.dcp_rank,
  597. self.cp_kv_cache_interleave_size,
  598. ).view(num_decodes, max_decode_len)
  599. seq_lens_buffer[:] = expanded_seq_lens
  600. seq_lens = seq_lens_buffer
  601. return seq_lens, block_table, decode_lens, num_decodes, requires_padding
  602. def build(
  603. self,
  604. common_prefix_len: int,
  605. common_attn_metadata: CommonAttentionMetadata,
  606. fast_build: bool = False,
  607. ) -> DeepseekV32IndexerMetadata:
  608. num_reqs = common_attn_metadata.num_reqs
  609. num_tokens = common_attn_metadata.num_actual_tokens
  610. query_start_loc = common_attn_metadata.query_start_loc
  611. query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu
  612. if common_attn_metadata.batch_topology is not None:
  613. query_start_loc_np = common_attn_metadata.batch_topology.query_start_loc_np[
  614. : num_reqs + 1
  615. ]
  616. query_lens_np = common_attn_metadata.batch_topology.query_lens_np
  617. else:
  618. query_start_loc_np = query_start_loc_cpu.numpy()
  619. query_lens_np = np.diff(query_start_loc_np)
  620. seq_lens = common_attn_metadata.seq_lens
  621. slot_mapping = common_attn_metadata.slot_mapping
  622. block_table = common_attn_metadata.block_table_tensor
  623. use_dcp_local_kv = (
  624. self.dcp_world_size > 1
  625. and common_attn_metadata.dcp_local_seq_lens is not None
  626. )
  627. effective_dcp_world_size = self.dcp_world_size if use_dcp_local_kv else 1
  628. effective_dcp_rank = self.dcp_rank if use_dcp_local_kv else 0
  629. num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
  630. common_attn_metadata.split_decodes_and_prefills(
  631. decode_threshold=self.reorder_batch_threshold,
  632. require_uniform=not self.use_flattening,
  633. )
  634. )
  635. if (num_decodes + num_prefills != num_reqs
  636. or num_decode_tokens + num_prefill_tokens != num_tokens):
  637. raise AssertionError(
  638. f"[idxfix-diag] split mismatch: decodes={num_decodes} "
  639. f"prefills={num_prefills} reqs={num_reqs} "
  640. f"decode_toks={num_decode_tokens} prefill_toks={num_prefill_tokens} "
  641. f"toks={num_tokens} threshold={self.reorder_batch_threshold} "
  642. f"uniform={not self.use_flattening} "
  643. f"qsl={common_attn_metadata.query_start_loc_cpu.tolist()[:12]} "
  644. f"seq={common_attn_metadata._seq_lens_cpu.tolist()[:12] if common_attn_metadata._seq_lens_cpu is not None else None}"
  645. )
  646. compressed_slot_mapping = slot_mapping
  647. logical_compressed_seq_lens = seq_lens
  648. compressed_seq_lens = seq_lens
  649. if self.compress_ratio > 1 or use_dcp_local_kv:
  650. compressed_slot_mapping = get_compressed_slot_mapping(
  651. num_tokens,
  652. query_start_loc,
  653. seq_lens,
  654. block_table,
  655. self.storage_block_size,
  656. self.compress_ratio,
  657. out=self.compressed_slot_mapping_buffer,
  658. dcp_world_size=effective_dcp_world_size,
  659. dcp_rank=effective_dcp_rank,
  660. cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
  661. )
  662. logical_compressed_seq_lens = seq_lens // self.compress_ratio
  663. compressed_seq_lens = logical_compressed_seq_lens
  664. if use_dcp_local_kv:
  665. compressed_seq_lens = get_dcp_local_seq_lens(
  666. logical_compressed_seq_lens,
  667. self.dcp_world_size,
  668. self.dcp_rank,
  669. self.cp_kv_cache_interleave_size,
  670. )
  671. prefill_metadata = None
  672. if num_prefills > 0:
  673. # This CPU value is an upper bound for async-spec extend rows. It
  674. # is safe for chunking/allocation because CUDA metadata below is
  675. # built from exact device seq_lens and gather ignores the tail.
  676. assert common_attn_metadata.seq_lens_cpu_upper_bound is not None
  677. seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
  678. logical_compressed_seq_lens_cpu = (
  679. seq_lens_cpu // self.compress_ratio
  680. if self.compress_ratio > 1
  681. else seq_lens_cpu
  682. )
  683. compressed_seq_lens_cpu = logical_compressed_seq_lens_cpu
  684. if use_dcp_local_kv:
  685. compressed_seq_lens_cpu = get_dcp_local_seq_lens(
  686. logical_compressed_seq_lens_cpu,
  687. self.dcp_world_size,
  688. self.dcp_rank,
  689. self.cp_kv_cache_interleave_size,
  690. )
  691. compressed_seq_lens_cpu_np = compressed_seq_lens_cpu.numpy()
  692. prefill_query_lens_cpu = query_lens_np[
  693. num_decodes : num_decodes + num_prefills
  694. ]
  695. max_logits_bytes = envs.VLLM_SPARSE_INDEXER_MAX_LOGITS_MB * 1024 * 1024
  696. if envs.VLLM_USE_B12X_SPARSE_INDEXER:
  697. chunk_specs = []
  698. b12x_budget_seq_lens = np.array(
  699. [_get_b12x_paged_indexer_supertile_k()],
  700. dtype=compressed_seq_lens_cpu_np.dtype,
  701. )
  702. for prefill_idx in range(num_prefills):
  703. req_idx = num_decodes + prefill_idx
  704. chunk_specs.extend(
  705. split_indexer_prefill_chunks(
  706. b12x_budget_seq_lens,
  707. prefill_query_lens_cpu[
  708. prefill_idx : prefill_idx + 1
  709. ],
  710. self.max_prefill_buffer_size,
  711. max_logits_bytes,
  712. request_offset=req_idx,
  713. )
  714. )
  715. else:
  716. chunk_specs = split_indexer_prefill_chunks(
  717. compressed_seq_lens_cpu_np[num_decodes:],
  718. prefill_query_lens_cpu,
  719. self.max_prefill_buffer_size,
  720. max_logits_bytes,
  721. request_offset=num_decodes,
  722. )
  723. chunks = []
  724. for req_slice, query_slice in chunk_specs:
  725. metadata = build_prefill_chunk_metadata(
  726. req_slice.start,
  727. req_slice.stop,
  728. query_start_loc,
  729. query_start_loc_cpu,
  730. seq_lens,
  731. compressed_seq_lens,
  732. compressed_seq_lens_cpu,
  733. common_attn_metadata.block_table_tensor,
  734. self.compress_ratio,
  735. query_slice=query_slice,
  736. query_start_loc_np=query_start_loc_np,
  737. compressed_seq_lens_np=compressed_seq_lens_cpu_np,
  738. skip_kv_gather=query_slice.start > 0,
  739. dcp_world_size=effective_dcp_world_size,
  740. dcp_rank=effective_dcp_rank,
  741. cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
  742. )
  743. # Skip when total_seq_lens is 0 (i.e., no compressed token).
  744. if metadata is not None:
  745. chunks.append(metadata)
  746. prefill_metadata = DeepseekV32IndexerPrefillMetadata(chunks)
  747. decode_metadata = None
  748. if num_decodes > 0:
  749. torch.diff(
  750. common_attn_metadata.query_start_loc[: num_decodes + 1],
  751. out=self.decode_lens_buffer[:num_decodes],
  752. )
  753. decode_lens = self.decode_lens_buffer[:num_decodes]
  754. decode_lens_np = query_lens_np[:num_decodes]
  755. global_decode_seq_lens = common_attn_metadata.seq_lens[:num_decodes]
  756. dcp_local_seq_lens = (
  757. common_attn_metadata.dcp_local_seq_lens[:num_decodes]
  758. if self.compress_ratio == 1
  759. and use_dcp_local_kv
  760. and common_attn_metadata.dcp_local_seq_lens is not None
  761. else None
  762. )
  763. dcp_local_seq_lens_cpu = (
  764. common_attn_metadata.dcp_local_seq_lens_cpu[:num_decodes]
  765. if dcp_local_seq_lens is not None
  766. and common_attn_metadata.dcp_local_seq_lens_cpu is not None
  767. else None
  768. )
  769. seq_lens = (
  770. dcp_local_seq_lens
  771. if dcp_local_seq_lens is not None
  772. else global_decode_seq_lens
  773. )
  774. block_table = common_attn_metadata.block_table_tensor[:num_decodes, ...]
  775. max_decode_len = int(decode_lens_np.max())
  776. next_n = 1 + self.num_speculative_tokens
  777. use_native = not self.use_flattening and max_decode_len <= next_n
  778. seq_lens, block_table, decode_lens, batch_size, requires_padding = (
  779. self._prepare_decode_tensors(
  780. seq_lens=seq_lens,
  781. block_table=block_table,
  782. decode_lens=decode_lens,
  783. decode_lens_np=decode_lens_np,
  784. query_start_loc=common_attn_metadata.query_start_loc[:num_decodes],
  785. num_decodes=num_decodes,
  786. num_decode_tokens=num_decode_tokens,
  787. use_native=use_native,
  788. next_n=next_n,
  789. max_decode_len=max_decode_len,
  790. global_seq_lens=global_decode_seq_lens
  791. if dcp_local_seq_lens is not None
  792. else None,
  793. )
  794. )
  795. # For DeepseekV4 (compress_ratio > 1), the indexer KV cache stores
  796. # compressed tokens. Convert uncompressed seq_lens to compressed.
  797. if self.compress_ratio > 1:
  798. # True iff seq_lens aliases decode_seq_lens_buffer (flatten or
  799. # native wrote it); False iff it aliases common_attn_metadata.
  800. seq_lens_is_local_view = (use_native and next_n > 1) or (
  801. not use_native and max_decode_len > 1
  802. )
  803. if seq_lens_is_local_view:
  804. seq_lens //= self.compress_ratio
  805. if use_dcp_local_kv:
  806. if seq_lens.dim() == 1:
  807. dcp_seq_lens = get_dcp_local_seq_lens(
  808. seq_lens,
  809. self.dcp_world_size,
  810. self.dcp_rank,
  811. self.cp_kv_cache_interleave_size,
  812. )
  813. else:
  814. dcp_seq_lens = get_dcp_local_seq_lens(
  815. seq_lens.reshape(-1),
  816. self.dcp_world_size,
  817. self.dcp_rank,
  818. self.cp_kv_cache_interleave_size,
  819. ).view(seq_lens.shape)
  820. seq_lens.copy_(dcp_seq_lens)
  821. else:
  822. # Copy to avoid mutating shared state; keeps CG address stable.
  823. compressed_decode_seq_lens = seq_lens // self.compress_ratio
  824. if use_dcp_local_kv:
  825. compressed_decode_seq_lens = get_dcp_local_seq_lens(
  826. compressed_decode_seq_lens,
  827. self.dcp_world_size,
  828. self.dcp_rank,
  829. self.cp_kv_cache_interleave_size,
  830. )
  831. self.expanded_seq_lens_buffer[:num_decodes] = (
  832. compressed_decode_seq_lens
  833. )
  834. self.expanded_seq_lens_buffer[num_decodes:num_decode_tokens] = 0
  835. seq_lens = self.expanded_seq_lens_buffer[:num_decode_tokens]
  836. elif use_dcp_local_kv and dcp_local_seq_lens is None:
  837. if seq_lens.dim() == 1:
  838. seq_lens = get_dcp_local_seq_lens(
  839. seq_lens,
  840. self.dcp_world_size,
  841. self.dcp_rank,
  842. self.cp_kv_cache_interleave_size,
  843. )
  844. else:
  845. seq_lens_shape = seq_lens.shape
  846. seq_lens = get_dcp_local_seq_lens(
  847. seq_lens.reshape(-1),
  848. self.dcp_world_size,
  849. self.dcp_rank,
  850. self.cp_kv_cache_interleave_size,
  851. ).view(seq_lens_shape)
  852. # Non-MTP: deep_gemm paged MQA logits requires 2D context_lens
  853. # (csrc/apis/attention.hpp). Unsqueeze to (B, 1) so downstream
  854. # kernels see the same (B, next_n) layout as the MTP path.
  855. if seq_lens.dim() == 1:
  856. seq_lens = seq_lens.unsqueeze(-1)
  857. active_width = None
  858. active_width_tokens = None
  859. if self.compress_ratio > 1:
  860. active_width_tokens = -(
  861. -int(common_attn_metadata.max_seq_len) //
  862. self.compress_ratio
  863. )
  864. if envs.VLLM_USE_B12X_SPARSE_INDEXER:
  865. # Live scorer window in cache tokens. ceil(max_seq_len /
  866. # compress_ratio) is an upper bound on the max compressed
  867. # context across the batch, so windowing to it is top-k-identical
  868. # to the capacity cap and only skips wasted k-tiles. Computed on
  869. # the host here (metadata-prep, outside cudagraph capture) and
  870. # filled into the persistent buffer the captured kernel reads.
  871. if active_width_tokens is None:
  872. active_width_tokens = int(common_attn_metadata.max_seq_len)
  873. self.b12x_active_width_buffer.fill_(active_width_tokens)
  874. active_width = self.b12x_active_width_buffer
  875. if self.compress_ratio > 1:
  876. # Compressed-MLA models already bound the B12X scorer with
  877. # active_width. Avoiding a host reduction here removes a
  878. # per-decode sync without changing the scorer window.
  879. decode_topk_max_seq_len = active_width_tokens
  880. else:
  881. # GLM/Kimi-style uncompressed indexer rows need the exact
  882. # scalar for best throughput; use the CPU shadow maintained
  883. # by the runner instead of synchronizing on seq_lens.
  884. decode_topk_max_seq_len = (
  885. self._decode_topk_max_seq_len_from_cpu(
  886. common_attn_metadata,
  887. num_decodes,
  888. decode_lens_np,
  889. max_decode_len,
  890. use_native,
  891. dcp_local_seq_lens_cpu,
  892. )
  893. )
  894. if decode_topk_max_seq_len is None:
  895. # Async-spec can make GPU seq_lens authoritative. In
  896. # that mode there is no exact host scalar without a
  897. # sync, so use the same graph-stable live-window bound
  898. # already consumed by the B12X scorer.
  899. decode_topk_max_seq_len = active_width_tokens
  900. elif active_width_tokens is not None:
  901. # DeepGEMM still receives exact per-row seq_lens on device.
  902. # The scalar max_seq_len is only a host scorer bound; using the
  903. # metadata upper bound avoids a per-token CUDA scalar sync.
  904. decode_topk_max_seq_len = active_width_tokens
  905. else:
  906. decode_topk_max_seq_len = int(seq_lens.max().item())
  907. if envs.VLLM_USE_B12X_SPARSE_INDEXER:
  908. schedule_metadata = self._maybe_build_b12x_schedule_metadata(
  909. seq_lens,
  910. block_table,
  911. num_decode_tokens,
  912. requires_padding,
  913. )
  914. else:
  915. # DeepGEMM is required for paged MQA logits on CUDA devices.
  916. schedule_metadata = self._maybe_build_deep_gemm_schedule_metadata(
  917. seq_lens
  918. )
  919. decode_metadata = DeepSeekV32IndexerDecodeMetadata(
  920. block_table=block_table,
  921. seq_lens=seq_lens,
  922. max_seq_len=decode_topk_max_seq_len,
  923. decode_lens=decode_lens,
  924. requires_padding=requires_padding,
  925. schedule_metadata=schedule_metadata,
  926. compress_ratio=self.compress_ratio,
  927. active_width=active_width,
  928. )
  929. attn_metadata = DeepseekV32IndexerMetadata(
  930. seq_lens=common_attn_metadata.seq_lens,
  931. max_seq_len=common_attn_metadata.max_seq_len,
  932. slot_mapping=compressed_slot_mapping,
  933. num_decodes=num_decodes,
  934. num_decode_tokens=num_decode_tokens,
  935. num_prefills=num_prefills,
  936. num_prefill_tokens=num_prefill_tokens,
  937. prefill=prefill_metadata,
  938. decode=decode_metadata,
  939. dcp_world_size=effective_dcp_world_size,
  940. dcp_rank=effective_dcp_rank,
  941. cp_interleave_size=self.cp_kv_cache_interleave_size,
  942. )
  943. return attn_metadata
  944. def build_prefill_chunk_metadata(
  945. start_idx: int,
  946. end_idx: int,
  947. query_start_loc: torch.Tensor,
  948. query_start_loc_cpu: torch.Tensor,
  949. uncompressed_seq_lens: torch.Tensor,
  950. compressed_seq_lens: torch.Tensor,
  951. compressed_seq_lens_cpu: torch.Tensor,
  952. block_table: torch.Tensor,
  953. compress_ratio: int,
  954. query_slice: slice | None = None,
  955. query_start_loc_np: np.ndarray | None = None,
  956. compressed_seq_lens_np: np.ndarray | None = None,
  957. skip_kv_gather: bool = False,
  958. dcp_world_size: int = 1,
  959. dcp_rank: int = 0,
  960. cp_kv_cache_interleave_size: int = 1,
  961. ) -> DeepseekV32IndexerPrefillChunkMetadata | None:
  962. if compressed_seq_lens_np is not None:
  963. total_seq_lens = int(compressed_seq_lens_np[start_idx:end_idx].sum())
  964. else:
  965. total_seq_lens = compressed_seq_lens_cpu[start_idx:end_idx].sum().item()
  966. if total_seq_lens == 0:
  967. return None
  968. num_reqs = end_idx - start_idx
  969. device = block_table.device
  970. token_to_seq = torch.empty(total_seq_lens, dtype=torch.int32, device=device)
  971. cu_seq_lens = torch.empty(num_reqs + 1, dtype=torch.int32, device=device)
  972. # Assigning to slice avoids cpu sync.
  973. cu_seq_lens[:1] = 0
  974. torch.cumsum(compressed_seq_lens[start_idx:end_idx], dim=0, out=cu_seq_lens[1:])
  975. query_start_loc = (
  976. query_start_loc[start_idx : end_idx + 1] - query_start_loc[start_idx]
  977. )
  978. if query_start_loc_np is not None:
  979. total_query_len = int(
  980. query_start_loc_np[end_idx] - query_start_loc_np[start_idx]
  981. )
  982. else:
  983. total_query_len = int(
  984. (query_start_loc_cpu[end_idx] - query_start_loc_cpu[start_idx]).item()
  985. )
  986. if query_slice is not None:
  987. qs_start = query_slice.start
  988. qs_stop = query_slice.stop
  989. else:
  990. qs_start = 0
  991. qs_stop = total_query_len
  992. output_query_len = qs_stop - qs_start
  993. cu_seq_len_ks = torch.empty(output_query_len, dtype=torch.int32, device=device)
  994. cu_seq_len_ke = torch.empty(output_query_len, dtype=torch.int32, device=device)
  995. _build_prefill_chunk_metadata_kernel[(num_reqs,)](
  996. query_start_loc,
  997. uncompressed_seq_lens[start_idx:end_idx],
  998. cu_seq_lens,
  999. token_to_seq,
  1000. cu_seq_len_ks,
  1001. cu_seq_len_ke,
  1002. qs_start,
  1003. qs_stop,
  1004. DCP_WORLD_SIZE=dcp_world_size,
  1005. DCP_RANK=dcp_rank,
  1006. CP_KV_CACHE_INTERLEAVE_SIZE=cp_kv_cache_interleave_size,
  1007. BLOCK_SIZE=1024,
  1008. COMPRESS_RATIO=compress_ratio,
  1009. )
  1010. token_start = (
  1011. int(query_start_loc_np[start_idx])
  1012. if query_start_loc_np is not None
  1013. else query_start_loc_cpu[start_idx].item()
  1014. )
  1015. if query_slice is not None:
  1016. token_end = token_start + qs_stop
  1017. token_start = token_start + qs_start
  1018. skip_kv_gather = skip_kv_gather or qs_start > 0
  1019. else:
  1020. token_end = (
  1021. int(query_start_loc_np[end_idx])
  1022. if query_start_loc_np is not None
  1023. else query_start_loc_cpu[end_idx].item()
  1024. )
  1025. return DeepseekV32IndexerPrefillChunkMetadata(
  1026. cu_seqlen_ks=cu_seq_len_ks,
  1027. cu_seqlen_ke=cu_seq_len_ke,
  1028. cu_seq_lens=cu_seq_lens,
  1029. token_to_seq=token_to_seq,
  1030. total_seq_lens=total_seq_lens,
  1031. block_table=block_table[start_idx:end_idx],
  1032. token_start=token_start,
  1033. token_end=token_end,
  1034. num_reqs=num_reqs,
  1035. skip_kv_gather=skip_kv_gather,
  1036. )
  1037. @triton.jit(do_not_specialize=["query_slice_start", "query_slice_stop"])
  1038. def _build_prefill_chunk_metadata_kernel(
  1039. # Inputs
  1040. query_start_loc_ptr,
  1041. uncompressed_seq_lens_ptr,
  1042. cu_compressed_seq_lens_ptr,
  1043. # Outputs
  1044. token_to_seq_ptr,
  1045. cu_compressed_seq_len_ks_ptr,
  1046. cu_compressed_seq_len_ke_ptr,
  1047. query_slice_start,
  1048. query_slice_stop,
  1049. DCP_WORLD_SIZE: tl.constexpr,
  1050. DCP_RANK: tl.constexpr,
  1051. CP_KV_CACHE_INTERLEAVE_SIZE: tl.constexpr,
  1052. BLOCK_SIZE: tl.constexpr,
  1053. COMPRESS_RATIO: tl.constexpr,
  1054. ):
  1055. batch_idx = tl.program_id(0)
  1056. query_start = tl.load(query_start_loc_ptr + batch_idx)
  1057. query_end = tl.load(query_start_loc_ptr + batch_idx + 1)
  1058. query_len = query_end - query_start
  1059. seq_start = tl.load(cu_compressed_seq_lens_ptr + batch_idx)
  1060. seq_end = tl.load(cu_compressed_seq_lens_ptr + batch_idx + 1)
  1061. compressed_seq_len = seq_end - seq_start
  1062. uncompressed_seq_len = tl.load(uncompressed_seq_lens_ptr + batch_idx)
  1063. start_pos = uncompressed_seq_len - query_len
  1064. for i in range(0, query_len, BLOCK_SIZE):
  1065. offset = i + tl.arange(0, BLOCK_SIZE)
  1066. abs_pos = query_start + offset
  1067. mask = (
  1068. (offset < query_len)
  1069. & (abs_pos >= query_slice_start)
  1070. & (abs_pos < query_slice_stop)
  1071. )
  1072. out_pos = abs_pos - query_slice_start
  1073. # Compute cu_seq_len_ks
  1074. tl.store(cu_compressed_seq_len_ks_ptr + out_pos, seq_start, mask=mask)
  1075. # Compute cu_seq_len_ke
  1076. seq_len_per_token = (start_pos + 1 + offset) // COMPRESS_RATIO
  1077. if DCP_WORLD_SIZE > 1:
  1078. base = (
  1079. seq_len_per_token
  1080. // CP_KV_CACHE_INTERLEAVE_SIZE
  1081. // DCP_WORLD_SIZE
  1082. * CP_KV_CACHE_INTERLEAVE_SIZE
  1083. )
  1084. remainder = seq_len_per_token - base * DCP_WORLD_SIZE
  1085. rank_remainder = tl.minimum(
  1086. tl.maximum(
  1087. remainder - DCP_RANK * CP_KV_CACHE_INTERLEAVE_SIZE,
  1088. 0,
  1089. ),
  1090. CP_KV_CACHE_INTERLEAVE_SIZE,
  1091. )
  1092. seq_len_per_token = base + rank_remainder
  1093. tl.store(
  1094. cu_compressed_seq_len_ke_ptr + out_pos,
  1095. seq_start + seq_len_per_token,
  1096. mask=mask,
  1097. )
  1098. # Compute token_to_seq
  1099. for i in range(0, compressed_seq_len, BLOCK_SIZE):
  1100. offset = i + tl.arange(0, BLOCK_SIZE)
  1101. mask = offset < compressed_seq_len
  1102. tl.store(token_to_seq_ptr + seq_start + offset, batch_idx, mask=mask)
  1103. def _prewarm_prefill_chunk_metadata_kernel(
  1104. *,
  1105. device: torch.device,
  1106. dcp_world_size: int,
  1107. dcp_rank: int,
  1108. cp_kv_cache_interleave_size: int,
  1109. compress_ratio: int,
  1110. ) -> None:
  1111. if not current_platform.is_cuda():
  1112. return
  1113. query_start_loc = torch.tensor([0, 1], dtype=torch.int32, device=device)
  1114. uncompressed_seq_lens = torch.tensor([1], dtype=torch.int32, device=device)
  1115. cu_seq_lens = torch.tensor([0, 1], dtype=torch.int32, device=device)
  1116. token_to_seq = torch.empty((1,), dtype=torch.int32, device=device)
  1117. cu_seq_len_ks = torch.empty((1,), dtype=torch.int32, device=device)
  1118. cu_seq_len_ke = torch.empty((1,), dtype=torch.int32, device=device)
  1119. _build_prefill_chunk_metadata_kernel[(1,)](
  1120. query_start_loc,
  1121. uncompressed_seq_lens,
  1122. cu_seq_lens,
  1123. token_to_seq,
  1124. cu_seq_len_ks,
  1125. cu_seq_len_ke,
  1126. 0,
  1127. 1,
  1128. DCP_WORLD_SIZE=int(dcp_world_size),
  1129. DCP_RANK=int(dcp_rank),
  1130. CP_KV_CACHE_INTERLEAVE_SIZE=int(cp_kv_cache_interleave_size),
  1131. BLOCK_SIZE=1024,
  1132. COMPRESS_RATIO=int(compress_ratio),
  1133. )