indexer.py 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
  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. import vllm.envs as envs
  7. from vllm.config import VllmConfig
  8. from vllm.distributed import get_dcp_group, get_pcp_group
  9. from vllm.logger import init_logger
  10. from vllm.model_executor.warmup.jit_warmup_triton_helper import (
  11. LaunchSpec,
  12. TritonPointerInputVariant,
  13. TritonWarmupTensor,
  14. VllmTritonJitKernel,
  15. kernel_launcher,
  16. triton_scalar_specialization_rep,
  17. )
  18. from vllm.platforms import current_platform
  19. from vllm.triton_utils import tl, triton
  20. from vllm.utils.deep_gemm import (
  21. get_paged_mqa_logits_metadata,
  22. has_deep_gemm,
  23. native_next_n_supported,
  24. )
  25. from vllm.utils.platform_utils import num_compute_units
  26. from vllm.v1.attention.backend import (
  27. AttentionBackend,
  28. AttentionCGSupport,
  29. AttentionMetadataBuilder,
  30. CommonAttentionMetadata,
  31. MultipleOf,
  32. )
  33. from vllm.v1.attention.backends.mla.compressor_utils import get_compressed_slot_mapping
  34. from vllm.v1.attention.backends.utils import (
  35. get_dcp_local_seq_lens,
  36. split_decodes_and_prefills,
  37. )
  38. from vllm.v1.kv_cache_interface import (
  39. AttentionSpec,
  40. KVCacheLayout,
  41. KVCacheSpec,
  42. MLAAttentionSpec,
  43. )
  44. logger = init_logger(__name__)
  45. # The DSA indexer K cache is always quantized; "auto" means fp8 (V3.2 layout)
  46. # and mxfp4 is the opt-in Blackwell path.
  47. DSA_INDEXER_KV_DTYPES = ("fp8", "mxfp4")
  48. def dsa_indexer_uses_fp4(vllm_config: VllmConfig) -> bool:
  49. """Whether the DeepSeek sparse indexer should use the MXFP4 K cache."""
  50. kv_dtype = vllm_config.attention_config.resolve_indexer_kv_dtype("fp8")
  51. if kv_dtype not in DSA_INDEXER_KV_DTYPES:
  52. raise ValueError(
  53. f"indexer_kv_dtype={kv_dtype!r} is not supported by the DeepSeek "
  54. f"sparse indexer (expected one of {DSA_INDEXER_KV_DTYPES})."
  55. )
  56. use_fp4 = kv_dtype == "mxfp4"
  57. if use_fp4 and not current_platform.is_device_capability_family(100):
  58. raise ValueError(
  59. "indexer_kv_dtype='mxfp4' requires Blackwell datacenter GPUs "
  60. "(sm_10x, e.g. B200/GB200); sm_120 (consumer Blackwell) and "
  61. "earlier architectures are not supported."
  62. )
  63. return use_fp4
  64. class PrepareUniformDecodeKernel(
  65. VllmTritonJitKernel["PrepareUniformDecodeKernel.CompileKey"]
  66. ):
  67. BLOCK_SIZE = 1024
  68. @dataclass(frozen=True)
  69. class CompileKey:
  70. block_size: int
  71. block_table_stride: int
  72. expanded_bt_stride: int
  73. max_decode_len: int
  74. @staticmethod
  75. @triton.jit
  76. def kernel(
  77. seq_lens_ptr,
  78. decode_seq_lens_ptr,
  79. block_table_ptr,
  80. block_table_stride,
  81. expanded_block_table_ptr,
  82. expanded_bt_stride,
  83. decode_lens_ptr,
  84. max_decode_len,
  85. BLOCK_SIZE: tl.constexpr,
  86. ):
  87. idx = tl.program_id(0)
  88. req_id = idx // max_decode_len
  89. local_idx = idx % max_decode_len
  90. # Compute number of KVs attended to by this token. Padding requests have
  91. # seq_len == 0, which would otherwise make the first token of each padded
  92. # request negative (e.g. next_n=2 gives 0-2+0+1 = -1). Downstream kernels
  93. # read these as uint32, turning -1 into ~4e9.
  94. seq_len = tl.load(seq_lens_ptr + req_id)
  95. per_token_seq_len = tl.maximum(seq_len - max_decode_len + local_idx + 1, 0)
  96. tl.store(decode_seq_lens_ptr + idx, per_token_seq_len)
  97. # Copy block table row.
  98. src = block_table_ptr + req_id * block_table_stride
  99. dst = expanded_block_table_ptr + idx * expanded_bt_stride
  100. for i in tl.range(0, expanded_bt_stride, BLOCK_SIZE):
  101. off = i + tl.arange(0, BLOCK_SIZE)
  102. mask = off < expanded_bt_stride
  103. src_block = tl.load(src + off, mask=mask)
  104. tl.store(dst + off, src_block, mask=mask)
  105. # All reqs now have decode_len = 1.
  106. tl.store(decode_lens_ptr + idx, 1)
  107. def dispatch( # type: ignore[override]
  108. self,
  109. *,
  110. block_size: int,
  111. block_table_stride: int,
  112. expanded_bt_stride: int,
  113. max_decode_len: int,
  114. ) -> CompileKey:
  115. return self.CompileKey(
  116. block_size=block_size,
  117. block_table_stride=triton_scalar_specialization_rep(block_table_stride),
  118. expanded_bt_stride=triton_scalar_specialization_rep(expanded_bt_stride),
  119. max_decode_len=triton_scalar_specialization_rep(max_decode_len),
  120. )
  121. def get_warmup_keys(self) -> list[CompileKey]:
  122. return self._trace_dispatch(self.dispatch)(
  123. block_size=self.BLOCK_SIZE,
  124. block_table_stride=(1, 2, 16),
  125. expanded_bt_stride=(1, 2, 16),
  126. max_decode_len=(1, 2, 16),
  127. )
  128. def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
  129. int32_ptr = TritonWarmupTensor(torch.int32)
  130. return dict(
  131. seq_lens=int32_ptr,
  132. decode_seq_lens=int32_ptr,
  133. block_table=TritonWarmupTensor(
  134. torch.int32,
  135. shape=(1, compile_key.block_table_stride),
  136. ),
  137. expanded_block_table=TritonWarmupTensor(
  138. torch.int32,
  139. shape=(1, compile_key.expanded_bt_stride),
  140. ),
  141. decode_lens=int32_ptr,
  142. num_decode_tokens=1,
  143. max_decode_len=compile_key.max_decode_len,
  144. )
  145. @kernel_launcher
  146. def __call__(
  147. self,
  148. seq_lens: torch.Tensor,
  149. decode_seq_lens: torch.Tensor,
  150. block_table: torch.Tensor,
  151. expanded_block_table: torch.Tensor,
  152. decode_lens: torch.Tensor,
  153. num_decode_tokens: int,
  154. max_decode_len: int,
  155. ) -> LaunchSpec:
  156. return (num_decode_tokens,), dict(
  157. block_table_stride=block_table.stride(0),
  158. expanded_bt_stride=expanded_block_table.stride(0),
  159. BLOCK_SIZE=self.BLOCK_SIZE,
  160. )
  161. def split_indexer_prefill_chunks(
  162. seq_lens_cpu: torch.Tensor,
  163. query_lens_cpu: torch.Tensor,
  164. workspace_size: int,
  165. max_logits_bytes: int,
  166. request_offset: int = 0,
  167. ) -> list[tuple[slice, slice]]:
  168. """
  169. Split prefill requests into chunks for the sparse indexer, respecting:
  170. - N constraint: total_seq_lens <= workspace_size (existing O(N) workspace)
  171. - Logits constraint: M * N * 4 <= max_logits_bytes
  172. When a single request-level chunk still exceeds the logits budget,
  173. sub-chunks on the query dimension (M) to bound peak memory.
  174. Returns list of (req_slice, query_slice) tuples.
  175. """
  176. chunks: list[tuple[slice, slice]] = []
  177. n = len(seq_lens_cpu)
  178. max_logits_elems = max_logits_bytes // 4
  179. end = 0
  180. while end < n:
  181. start, chunk_m, chunk_n = end, 0, 0
  182. while end < n:
  183. q, s = query_lens_cpu[end].item(), seq_lens_cpu[end].item()
  184. new_m, new_n = chunk_m + q, chunk_n + s
  185. if new_n <= workspace_size and new_m * new_n <= max_logits_elems:
  186. chunk_m, chunk_n = new_m, new_n
  187. end += 1
  188. else:
  189. break
  190. # A single request can exceed the budget, requiring sub-chunking
  191. # on the query dimension.
  192. if end == start:
  193. chunk_m, chunk_n = query_lens_cpu[end].item(), seq_lens_cpu[end].item()
  194. end += 1
  195. req_slice = slice(start + request_offset, end + request_offset)
  196. max_q = max(1, max_logits_elems // chunk_n) if chunk_n > 0 else max(1, chunk_m)
  197. for q_off in range(0, chunk_m, max_q):
  198. sub_m = min(max_q, chunk_m - q_off)
  199. chunks.append((req_slice, slice(q_off, q_off + sub_m)))
  200. return chunks
  201. class DeepseekV32IndexerBackend(AttentionBackend):
  202. @classmethod
  203. def supports_pcp(cls) -> bool:
  204. return True
  205. @classmethod
  206. def supports_device_cpu_query_lens_mismatch(cls) -> bool:
  207. # Only the varlen paged MQA logits kernel takes per-request query
  208. # lengths from device tensors natively. Hopper can instead flatten each
  209. # query into a single-token row using device-built metadata.
  210. return _supports_varlen_paged_mqa_logits() or (
  211. _supports_flattened_device_query_lens()
  212. )
  213. @staticmethod
  214. def get_name() -> str:
  215. return "DEEPSEEK_V32_INDEXER"
  216. @staticmethod
  217. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  218. return [1, MultipleOf(16)] if current_platform.is_rocm() else [64]
  219. @classmethod
  220. def get_supported_head_sizes(cls) -> list[int]:
  221. return [32, 64, 128]
  222. @staticmethod
  223. def get_builder_cls() -> type["DeepseekV32IndexerMetadataBuilder"]:
  224. return DeepseekV32IndexerMetadataBuilder
  225. class KpoolTailBackend(DeepseekV32IndexerBackend):
  226. """Storage-only backend for the GLM-5.3-Flash kpool tail cache."""
  227. @classmethod
  228. def supported_kv_cache_layouts(cls) -> tuple[KVCacheLayout, ...]:
  229. return (KVCacheLayout.LBHNC,)
  230. @staticmethod
  231. def get_name() -> str:
  232. return "KPOOL_TAIL"
  233. @classmethod
  234. def get_supported_head_sizes(cls) -> list[int]:
  235. return []
  236. @staticmethod
  237. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  238. return [MultipleOf(1)]
  239. @staticmethod
  240. def get_builder_cls() -> type["KpoolTailMetadataBuilder"]: # type: ignore[override]
  241. return KpoolTailMetadataBuilder
  242. class DeepseekV4IndexerBackend(DeepseekV32IndexerBackend):
  243. @staticmethod
  244. def get_name() -> str:
  245. return "DEEPSEEK_V4_INDEXER"
  246. @classmethod
  247. def supported_kv_cache_layouts(cls) -> tuple[KVCacheLayout, ...]:
  248. # DeepSeek-V4 packs the indexer pages beside the MLA latent pages inside
  249. # each block, so the layer dim must sit inside the block dim.
  250. return (KVCacheLayout.BLHNC, KVCacheLayout.BLNHC)
  251. @staticmethod
  252. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  253. return [64 if current_platform.is_device_capability_family(90) else 128]
  254. @dataclass
  255. class DeepseekV32IndexerPrefillChunkMetadata:
  256. block_table: torch.Tensor
  257. # Under DCP (dcp_world_size > 1) these hold this rank's local row bounds;
  258. # otherwise they hold the global bounds.
  259. cu_seqlen_ks: torch.Tensor
  260. cu_seqlen_ke: torch.Tensor
  261. cu_seq_lens: torch.Tensor
  262. token_to_seq: torch.Tensor
  263. total_seq_lens: int
  264. token_start: int
  265. token_end: int
  266. num_reqs: int
  267. skip_kv_gather: bool = False
  268. local_cu_seq_lens: torch.Tensor | None = None
  269. local_total_seq_lens: int = 0
  270. max_local_total_seq_lens: int = 0
  271. class BuildPrefillChunkMetadataKernel(
  272. VllmTritonJitKernel["BuildPrefillChunkMetadataKernel.CompileKey"]
  273. ):
  274. BLOCK_SIZE = 1024
  275. @dataclass(frozen=True)
  276. class CompileKey:
  277. query_slice_start: int
  278. query_slice_stop: int
  279. dcp_rank: int
  280. dcp_world: int
  281. dcp_interleave: int
  282. block_size: int
  283. compress_ratio: int
  284. input_variant: TritonPointerInputVariant
  285. @staticmethod
  286. @triton.jit
  287. def kernel(
  288. # Inputs
  289. query_start_loc_ptr,
  290. uncompressed_seq_lens_ptr,
  291. cu_compressed_seq_lens_ptr,
  292. # Row-start base for cu_seq_len_ks/ke: local cumulative lens under DCP,
  293. # aliases cu_compressed_seq_lens_ptr otherwise.
  294. row_start_cu_compressed_seq_lens_ptr,
  295. # Outputs
  296. token_to_seq_ptr,
  297. cu_compressed_seq_len_ks_ptr,
  298. cu_compressed_seq_len_ke_ptr,
  299. query_slice_start,
  300. query_slice_stop,
  301. DCP_RANK,
  302. DCP_WORLD,
  303. DCP_INTERLEAVE,
  304. BLOCK_SIZE: tl.constexpr,
  305. COMPRESS_RATIO: tl.constexpr,
  306. ):
  307. batch_idx = tl.program_id(0)
  308. query_start = tl.load(query_start_loc_ptr + batch_idx)
  309. query_end = tl.load(query_start_loc_ptr + batch_idx + 1)
  310. query_len = query_end - query_start
  311. seq_start = tl.load(cu_compressed_seq_lens_ptr + batch_idx)
  312. seq_end = tl.load(cu_compressed_seq_lens_ptr + batch_idx + 1)
  313. compressed_seq_len = seq_end - seq_start
  314. # Row start for the (possibly localized) cu_seq_len_ks/ke. Equals seq_start
  315. # when DCP is disabled (the pointer aliases cu_compressed_seq_lens_ptr).
  316. row_start = tl.load(row_start_cu_compressed_seq_lens_ptr + batch_idx)
  317. uncompressed_seq_len = tl.load(uncompressed_seq_lens_ptr + batch_idx)
  318. start_pos = uncompressed_seq_len - query_len
  319. for i in range(0, query_len, BLOCK_SIZE):
  320. offset = i + tl.arange(0, BLOCK_SIZE)
  321. abs_pos = query_start + offset
  322. mask = (
  323. (offset < query_len)
  324. & (abs_pos >= query_slice_start)
  325. & (abs_pos < query_slice_stop)
  326. )
  327. out_pos = abs_pos - query_slice_start
  328. # cu_seq_len_ks: row start in the gathered K buffer.
  329. tl.store(cu_compressed_seq_len_ks_ptr + out_pos, row_start, mask=mask)
  330. # cu_seq_len_ke: row start + per-token context length. Under DCP the
  331. # global per-token length is sharded across ranks.
  332. global_ctx = start_pos + 1 + offset
  333. len_per_token = global_ctx // COMPRESS_RATIO
  334. if DCP_WORLD > 1:
  335. # Per-rank local context length under interleave-aware DCP, matching
  336. # get_dcp_local_seq_lens. K == 1 reduces to (len + world-1-rank)//world.
  337. base = (len_per_token // DCP_INTERLEAVE // DCP_WORLD) * DCP_INTERLEAVE
  338. remainder = len_per_token - base * DCP_WORLD
  339. remainder = tl.minimum(
  340. tl.maximum(remainder - DCP_RANK * DCP_INTERLEAVE, 0), DCP_INTERLEAVE
  341. )
  342. len_per_token = base + remainder
  343. tl.store(
  344. cu_compressed_seq_len_ke_ptr + out_pos,
  345. row_start + len_per_token,
  346. mask=mask,
  347. )
  348. # Compute token_to_seq
  349. for i in range(0, compressed_seq_len, BLOCK_SIZE):
  350. offset = i + tl.arange(0, BLOCK_SIZE)
  351. mask = offset < compressed_seq_len
  352. tl.store(token_to_seq_ptr + seq_start + offset, batch_idx, mask=mask)
  353. def dispatch( # type: ignore[override]
  354. self,
  355. *,
  356. query_slice_start: int,
  357. query_slice_stop: int,
  358. DCP_RANK: int,
  359. DCP_WORLD: int,
  360. DCP_INTERLEAVE: int,
  361. BLOCK_SIZE: int,
  362. COMPRESS_RATIO: int,
  363. input_variant: TritonPointerInputVariant,
  364. ) -> CompileKey:
  365. return self.CompileKey(
  366. query_slice_start=triton_scalar_specialization_rep(query_slice_start),
  367. query_slice_stop=triton_scalar_specialization_rep(query_slice_stop),
  368. dcp_rank=triton_scalar_specialization_rep(DCP_RANK),
  369. dcp_world=triton_scalar_specialization_rep(DCP_WORLD),
  370. dcp_interleave=triton_scalar_specialization_rep(DCP_INTERLEAVE),
  371. block_size=BLOCK_SIZE,
  372. compress_ratio=COMPRESS_RATIO,
  373. input_variant=input_variant,
  374. )
  375. def get_warmup_keys(self, vllm_config: VllmConfig) -> list[CompileKey]:
  376. max_tokens = max(1, min(vllm_config.scheduler_config.max_num_batched_tokens, 8))
  377. hf_config = vllm_config.model_config.hf_config
  378. parallel_config = vllm_config.parallel_config
  379. dcp_world = parallel_config.decode_context_parallel_size
  380. dcp_interleave = parallel_config.cp_kv_cache_interleave_size
  381. dcp_rank = get_dcp_group().rank_in_group if dcp_world > 1 else 0
  382. compress_ratios = tuple(
  383. dict.fromkeys(
  384. max(1, int(ratio))
  385. for ratio in (
  386. *(getattr(hf_config, "compress_ratios", None) or (1,)),
  387. getattr(hf_config, "index_kpool", 1) or 1,
  388. )
  389. )
  390. )
  391. index_kpool = getattr(hf_config, "index_kpool", None)
  392. if index_kpool and index_kpool > 1 and index_kpool not in compress_ratios:
  393. compress_ratios = compress_ratios + (index_kpool,)
  394. return self._trace_dispatch(self.dispatch)(
  395. # Cover Triton's divisible, exact-one, and generic i32 classes.
  396. query_slice_start=(0, 1, 2),
  397. query_slice_stop=(1, 2 * max_tokens - 1, 2 * max_tokens),
  398. DCP_RANK=dcp_rank,
  399. DCP_WORLD=dcp_world,
  400. DCP_INTERLEAVE=dcp_interleave,
  401. BLOCK_SIZE=self.BLOCK_SIZE,
  402. COMPRESS_RATIO=list(compress_ratios),
  403. input_variant=(
  404. TritonPointerInputVariant.from_alignment(uncompressed_seq_lens=True),
  405. TritonPointerInputVariant.from_alignment(uncompressed_seq_lens=False),
  406. ),
  407. )
  408. def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
  409. int32_ptr = TritonWarmupTensor(torch.int32)
  410. return dict(
  411. query_start_loc=int32_ptr,
  412. uncompressed_seq_lens=compile_key.input_variant.pointer(
  413. "uncompressed_seq_lens", torch.int32
  414. ),
  415. cu_compressed_seq_lens=int32_ptr,
  416. row_start_cu_compressed_seq_lens=int32_ptr,
  417. token_to_seq=int32_ptr,
  418. cu_compressed_seq_len_ks=int32_ptr,
  419. cu_compressed_seq_len_ke=int32_ptr,
  420. query_slice_start=compile_key.query_slice_start,
  421. query_slice_stop=compile_key.query_slice_stop,
  422. DCP_RANK=compile_key.dcp_rank,
  423. DCP_WORLD=compile_key.dcp_world,
  424. DCP_INTERLEAVE=compile_key.dcp_interleave,
  425. num_reqs=1,
  426. COMPRESS_RATIO=compile_key.compress_ratio,
  427. )
  428. @kernel_launcher
  429. def __call__(
  430. self,
  431. query_start_loc: torch.Tensor,
  432. uncompressed_seq_lens: torch.Tensor,
  433. cu_compressed_seq_lens: torch.Tensor,
  434. row_start_cu_compressed_seq_lens: torch.Tensor,
  435. token_to_seq: torch.Tensor,
  436. cu_compressed_seq_len_ks: torch.Tensor,
  437. cu_compressed_seq_len_ke: torch.Tensor,
  438. query_slice_start: int,
  439. query_slice_stop: int,
  440. DCP_RANK: int,
  441. DCP_WORLD: int,
  442. DCP_INTERLEAVE: int,
  443. *,
  444. num_reqs: int,
  445. COMPRESS_RATIO: int,
  446. ) -> LaunchSpec:
  447. return (num_reqs,), dict(
  448. BLOCK_SIZE=self.BLOCK_SIZE,
  449. COMPRESS_RATIO=COMPRESS_RATIO,
  450. )
  451. @dataclass
  452. class DeepseekV32IndexerPrefillMetadata:
  453. chunks: list[DeepseekV32IndexerPrefillChunkMetadata]
  454. max_prefill_seq_len: int = -1
  455. @dataclass
  456. class DeepSeekV32IndexerDecodeMetadata:
  457. block_table: torch.Tensor
  458. # seq_lens: per-token effective context lengths.
  459. # - flatten path / plain decode: 1D (batch_size,)
  460. # - native MTP path: 2D (B, next_n) where [b,j] = L_b - next_n + j + 1
  461. # Both fp8_fp4_paged_mqa_logits and the topk kernels accept both shapes.
  462. seq_lens: torch.Tensor
  463. decode_lens: torch.Tensor
  464. requires_padding: bool
  465. schedule_metadata: torch.Tensor
  466. global_seq_lens: torch.Tensor | None = None
  467. per_req_decode_lens: torch.Tensor | None = None
  468. decode_is_uniform: bool = True
  469. write_max_decode_len: int = 0
  470. indices: torch.Tensor | None = None
  471. @dataclass
  472. class DeepseekV32IndexerMetadata:
  473. # FIXME (zyongye)
  474. # hacky way to access the data now, need to be in chunked meta
  475. seq_lens: torch.Tensor
  476. max_seq_len: int
  477. slot_mapping: torch.Tensor
  478. # New for MLA (compared to FlashAttention)
  479. # For handling prefill decode split
  480. num_decodes: int
  481. num_decode_tokens: int
  482. num_prefills: int
  483. num_prefill_tokens: int
  484. decode: DeepSeekV32IndexerDecodeMetadata | None = None
  485. prefill: DeepseekV32IndexerPrefillMetadata | None = None
  486. def compute_kpool_tail_slot_mapping(
  487. slot_mapping: torch.Tensor,
  488. block_table: torch.Tensor,
  489. query_start_loc: torch.Tensor,
  490. positions: torch.Tensor,
  491. num_actual_tokens: int,
  492. num_reqs: int,
  493. kpool: int,
  494. out: torch.Tensor | None = None,
  495. ) -> torch.Tensor:
  496. """Map every token to its request's one circular tail block."""
  497. if out is None:
  498. out = slot_mapping.clone()
  499. else:
  500. assert out.shape == slot_mapping.shape
  501. out.copy_(slot_mapping)
  502. if num_actual_tokens == 0:
  503. return out
  504. tokens = torch.arange(num_actual_tokens, device=slot_mapping.device)
  505. req = torch.searchsorted(query_start_loc, tokens, right=True) - 1
  506. req = req.clamp_(min=0, max=num_reqs - 1)
  507. own_block = block_table[:num_reqs, 0].index_select(0, req).to(torch.int64)
  508. pos = positions[:num_actual_tokens].to(torch.int64)
  509. out[:num_actual_tokens] = own_block * kpool + torch.remainder(pos, kpool)
  510. return out
  511. class KpoolTailMetadataBuilder(AttentionMetadataBuilder):
  512. """Build only the circular slot mapping needed by the storage-only tail."""
  513. _cudagraph_support = AttentionCGSupport.ALWAYS
  514. supports_update_block_table = False
  515. reorder_batch_threshold = None
  516. def __init__(
  517. self,
  518. kv_cache_spec: AttentionSpec,
  519. layer_names: list[str],
  520. vllm_config: VllmConfig,
  521. device: torch.device,
  522. ):
  523. super().__init__(kv_cache_spec, layer_names, vllm_config, device)
  524. self.slot_mapping_buffer = torch.empty(
  525. vllm_config.scheduler_config.max_num_batched_tokens,
  526. dtype=torch.int64,
  527. device=device,
  528. )
  529. def build(
  530. self,
  531. common_prefix_len: int,
  532. common_attn_metadata: CommonAttentionMetadata,
  533. fast_build: bool = False,
  534. ) -> DeepseekV32IndexerMetadata:
  535. num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
  536. split_decodes_and_prefills(common_attn_metadata)
  537. )
  538. slot_mapping = common_attn_metadata.slot_mapping
  539. positions = common_attn_metadata.positions
  540. if positions is not None:
  541. slot_mapping_buffer = self.slot_mapping_buffer[
  542. : slot_mapping.numel()
  543. ].view_as(slot_mapping)
  544. slot_mapping = compute_kpool_tail_slot_mapping(
  545. slot_mapping,
  546. common_attn_metadata.block_table_tensor,
  547. common_attn_metadata.query_start_loc,
  548. positions,
  549. common_attn_metadata.num_actual_tokens,
  550. common_attn_metadata.num_reqs,
  551. self.kv_cache_spec.block_size,
  552. out=slot_mapping_buffer,
  553. )
  554. return DeepseekV32IndexerMetadata(
  555. seq_lens=common_attn_metadata.seq_lens,
  556. max_seq_len=common_attn_metadata.max_seq_len,
  557. slot_mapping=slot_mapping,
  558. num_decodes=num_decodes,
  559. num_decode_tokens=num_decode_tokens,
  560. num_prefills=num_prefills,
  561. num_prefill_tokens=num_prefill_tokens,
  562. )
  563. def get_max_prefill_buffer_size(vllm_config: VllmConfig):
  564. max_model_len = vllm_config.model_config.max_model_len
  565. # NOTE(Chen): 40 is a magic number for controlling the prefill buffer size.
  566. # Each entry is 128 fp8 bytes and 4 scale bytes for a total of 132 bytes.
  567. # The flashmla_sparse backend uses a workspace size of 5 * max_model_len.
  568. # The memory usage of the workspace there is 576 * 2 bytes; so we size this as
  569. # (576 * 2 // 132) * 5 = 40 to maximize this workspace size while still fitting
  570. # within the flashmla_sparse workspace.
  571. # For DeepSeek-V3.2, the max_model_len is 163840.
  572. # 40 * 163840 * 132 = 865075200 bytes = 825 MB
  573. return max_model_len * 40
  574. def _supports_varlen_paged_mqa_logits() -> bool:
  575. return (
  576. current_platform.is_cuda()
  577. and current_platform.is_device_capability_family(100)
  578. and has_deep_gemm()
  579. )
  580. def _supports_flattened_device_query_lens() -> bool:
  581. return (
  582. current_platform.is_cuda()
  583. and current_platform.is_device_capability_family(90)
  584. and has_deep_gemm()
  585. )
  586. def _supports_native_decode(next_n: int) -> bool:
  587. """Whether decode can pass `next_n` Q rows per request to the kernel
  588. instead of flattening to one single-token row per query, which re-reads
  589. the KV tile once per row.
  590. """
  591. if not (current_platform.is_cuda() and has_deep_gemm()):
  592. return next_n in (1, 2)
  593. if current_platform.is_device_capability_family(100):
  594. return True
  595. if current_platform.is_device_capability_family(90):
  596. return native_next_n_supported(next_n)
  597. return next_n in (1, 2)
  598. def _use_flattening(vllm_config: VllmConfig) -> bool:
  599. speculative_config = vllm_config.speculative_config
  600. next_n = 1 + vllm_config.num_speculative_tokens
  601. return not _supports_native_decode(next_n) or (
  602. speculative_config is not None
  603. and speculative_config.enable_adaptive_verification
  604. and _supports_flattened_device_query_lens()
  605. )
  606. class DeepseekV32IndexerMetadataBuilder(AttentionMetadataBuilder):
  607. # The indexer opts out of the shared reorder-threshold vote (see __init__),
  608. # so this is None; its own split uses self.decode_threshold.
  609. reorder_batch_threshold: int | None = None
  610. requires_block_table_width = True
  611. @classmethod
  612. def get_cudagraph_support(
  613. cls,
  614. vllm_config: VllmConfig,
  615. kv_cache_spec: KVCacheSpec,
  616. ) -> AttentionCGSupport:
  617. if _supports_varlen_paged_mqa_logits() or _use_flattening(vllm_config):
  618. return AttentionCGSupport.ALWAYS
  619. return AttentionCGSupport.UNIFORM_BATCH
  620. def __init__(self, *args, block_table_width: int, **kwargs) -> None:
  621. super().__init__(*args, **kwargs)
  622. scheduler_config = self.vllm_config.scheduler_config
  623. parallel_config = self.vllm_config.parallel_config
  624. self.dcp_world_size = parallel_config.decode_context_parallel_size
  625. self.dcp_rank = get_dcp_group().rank_in_group if self.dcp_world_size > 1 else 0
  626. self.pcp_world_size = parallel_config.prefill_context_parallel_size
  627. self.use_pcp = self.pcp_world_size > 1
  628. self.cp_kv_cache_interleave_size = parallel_config.cp_kv_cache_interleave_size
  629. # The DCP sparse-indexer code is parameterized by interleave size, but
  630. # interleave > 1 is not yet validated end-to-end (gsm8k parity fails),
  631. # so fail closed here rather than silently produce wrong output.
  632. if self.dcp_world_size > 1 and self.cp_kv_cache_interleave_size > 1:
  633. raise NotImplementedError(
  634. "DCP sparse indexer currently supports only "
  635. f"cp_kv_cache_interleave_size=1 (got "
  636. f"{self.cp_kv_cache_interleave_size})."
  637. )
  638. # NOTE(Chen):an estimated max size of flattened_kv. Need to double check.
  639. self.max_prefill_buffer_size = get_max_prefill_buffer_size(self.vllm_config)
  640. self.num_speculative_tokens = (
  641. self.vllm_config.speculative_config.num_speculative_tokens
  642. if self.vllm_config.speculative_config
  643. else 0
  644. )
  645. self.use_fp4_indexer_cache = dsa_indexer_uses_fp4(self.vllm_config)
  646. next_n = self.num_speculative_tokens + 1
  647. self.decode_threshold = next_n
  648. self.reorder_batch_threshold = None
  649. self.use_flattening = _use_flattening(self.vllm_config)
  650. self.supports_varlen = _supports_varlen_paged_mqa_logits()
  651. logger.info_once(
  652. "DSA indexer decode path: use_flattening=%s supports_varlen=%s "
  653. "(next_n=%d, use_fp4_cache=%s)",
  654. self.use_flattening,
  655. self.supports_varlen,
  656. next_n,
  657. self.use_fp4_indexer_cache,
  658. )
  659. sm_count = num_compute_units(self.device.index)
  660. self.num_sms = sm_count
  661. self.offsets_buffer = torch.arange(
  662. next_n, device=self.device, dtype=torch.int32
  663. )
  664. self.decode_lens_buffer = torch.zeros(
  665. (scheduler_config.max_num_batched_tokens,),
  666. dtype=torch.int32,
  667. device=self.device,
  668. )
  669. self.per_req_decode_lens_buffer = torch.zeros(
  670. (scheduler_config.max_num_batched_tokens,),
  671. dtype=torch.int32,
  672. device=self.device,
  673. )
  674. # Shared workspace for decode seq_lens. Native MTP views this as
  675. # (B, max_decode_len) at runtime, keeping context_lens contiguous even
  676. # when max_decode_len is smaller than next_n.
  677. self.decode_seq_lens_buffer = torch.zeros(
  678. (scheduler_config.max_num_batched_tokens,),
  679. dtype=torch.int32,
  680. device=self.device,
  681. )
  682. self.global_decode_seq_lens_buffer = torch.zeros(
  683. (scheduler_config.max_num_batched_tokens,),
  684. dtype=torch.int32,
  685. device=self.device,
  686. )
  687. self.decode_indices_buffer = torch.zeros(
  688. (scheduler_config.max_num_batched_tokens,),
  689. dtype=torch.int32,
  690. device=self.device,
  691. )
  692. self.arange_buffer = torch.arange(
  693. max(
  694. scheduler_config.max_num_seqs * next_n,
  695. scheduler_config.max_num_batched_tokens,
  696. ),
  697. dtype=torch.int32,
  698. device=self.device,
  699. )
  700. self.expanded_block_table_buffer = torch.zeros(
  701. (scheduler_config.max_num_batched_tokens, block_table_width),
  702. dtype=torch.int32,
  703. device=self.device,
  704. )
  705. # See: DeepGMM/csrc/apis/attention.hpp. Sized for one slot per SM;
  706. # build() narrows it to whatever the kernel actually schedules.
  707. self.scheduler_metadata_buffer = torch.empty(
  708. (self.num_sms + 1, 2), dtype=torch.int32, device=self.device
  709. )
  710. # KV compression. Default to 1 for no compression.
  711. self.compress_ratio = 1
  712. # Get compress_ratio for DeepseekV4 support
  713. if isinstance(self.kv_cache_spec, MLAAttentionSpec):
  714. # MLA compression is a whole number of tokens per state (fractions
  715. # are whisper block pooling and never reach MLA).
  716. assert isinstance(self.kv_cache_spec.tokens_per_state, int)
  717. self.compress_ratio = self.kv_cache_spec.tokens_per_state
  718. if self.dcp_world_size > 1 and self.compress_ratio > 1:
  719. raise NotImplementedError(
  720. "DCP is not supported with sparse indexer KV compression "
  721. f"(compress_ratio={self.compress_ratio})."
  722. )
  723. # Pre-allocate buffers for CUDA graph compatibility when
  724. if self.compress_ratio > 1:
  725. # compress_ratio > 1 (DeepseekV4)
  726. # Compressed slot mapping output buffer
  727. self.compressed_slot_mapping_buffer = torch.zeros(
  728. (scheduler_config.max_num_batched_tokens,),
  729. dtype=torch.int64,
  730. device=self.device,
  731. )
  732. # Buffer for compressed seq_lens in decode path
  733. self.expanded_seq_lens_buffer = torch.zeros(
  734. (scheduler_config.max_num_batched_tokens,),
  735. dtype=torch.int32,
  736. device=self.device,
  737. )
  738. self.indexer_decode_block_table_buffer: torch.Tensor | None = None
  739. self._max_num_batched_tokens = scheduler_config.max_num_batched_tokens
  740. def _dcp_localize_decode_seq_lens(
  741. self,
  742. seq_lens: torch.Tensor,
  743. num_decodes: int,
  744. seq_lens_is_buffer_view: bool,
  745. ) -> torch.Tensor:
  746. local_seq_lens = get_dcp_local_seq_lens(
  747. seq_lens,
  748. self.dcp_world_size,
  749. self.dcp_rank,
  750. self.cp_kv_cache_interleave_size,
  751. )
  752. if seq_lens_is_buffer_view:
  753. seq_lens.copy_(local_seq_lens)
  754. return seq_lens
  755. out = self.decode_seq_lens_buffer[:num_decodes]
  756. out.copy_(local_seq_lens)
  757. return out
  758. def _prepare_decode_tensors(
  759. self,
  760. seq_lens: torch.Tensor,
  761. block_table: torch.Tensor,
  762. decode_lens: torch.Tensor,
  763. decode_lens_cpu: torch.Tensor,
  764. query_start_loc: torch.Tensor,
  765. num_decodes: int,
  766. num_decode_tokens: int,
  767. use_native: bool,
  768. next_n: int,
  769. max_decode_len: int,
  770. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, int, bool]:
  771. """Prepare native or per-token flattened decode tensors."""
  772. spec_config = self.vllm_config.speculative_config
  773. adaptive = bool(spec_config and spec_config.enable_adaptive_verification)
  774. min_decode_len = int(decode_lens_cpu.min().item())
  775. if not use_native:
  776. assert self.decode_seq_lens_buffer.dim() == 1
  777. if (
  778. not self.supports_varlen
  779. and (num_decodes == 1 or not adaptive)
  780. and min_decode_len == max_decode_len
  781. and num_decodes * max_decode_len == num_decode_tokens
  782. ):
  783. # Uniform decode lengths with no cudagraph token padding.
  784. _PREPARE_UNIFORM_DECODE_KERNEL(
  785. seq_lens,
  786. self.decode_seq_lens_buffer,
  787. block_table,
  788. self.expanded_block_table_buffer,
  789. self.decode_lens_buffer,
  790. num_decode_tokens,
  791. max_decode_len,
  792. )
  793. self.decode_seq_lens_buffer[num_decode_tokens:] = 0
  794. seq_lens = self.decode_seq_lens_buffer[:num_decode_tokens]
  795. block_table = self.expanded_block_table_buffer[:num_decode_tokens]
  796. decode_lens = self.decode_lens_buffer[:num_decode_tokens]
  797. return seq_lens, block_table, decode_lens, num_decode_tokens, False
  798. else:
  799. # Variable decode lengths.
  800. # Assume 4 requests with seq_lens [10, 7, 12, 0] (the final req is
  801. # padding) and decode_lens [3, 1, 4, 0] in the below example comments.
  802. # The context lengths are therefore
  803. # [10-3, 7-1, 12-4, 0-0] = [7, 6, 8, 0].
  804. # 3 + 1 + 4 + 0 = 8
  805. actual_expanded = int(decode_lens_cpu.sum().item())
  806. # Fuse expanded_base and expanded_starts into a single
  807. # repeat_interleave:
  808. # seq_len_i = (context_start[b] - query_start_loc[b]) + arange[i] + 1
  809. # where context_start[b] = seq_lens[b] - decode_lens[b].
  810. # Example: offsets = [7-0, 6-3, 8-4, 0-8] = [7, 3, 4, -8]
  811. # expanded_offsets = [7, 7, 7, 3, 4, 4, 4, 4]
  812. # result = [8, 9, 10, 7, 9, 10, 11, 12]
  813. expanded_offsets = torch.repeat_interleave(
  814. seq_lens - decode_lens - query_start_loc,
  815. decode_lens,
  816. output_size=actual_expanded,
  817. )
  818. # [8, 9, 10, 7, 9, 10, 11, 12, ...] where ... is unused buffer space
  819. self.decode_seq_lens_buffer[:actual_expanded] = (
  820. expanded_offsets + self.arange_buffer[:actual_expanded] + 1
  821. )
  822. self.decode_seq_lens_buffer[actual_expanded:] = 0
  823. seq_lens = self.decode_seq_lens_buffer[:num_decode_tokens]
  824. # Give each of the flattened entries the same block table row as the
  825. # original request.
  826. self.expanded_block_table_buffer[:actual_expanded] = (
  827. torch.repeat_interleave(
  828. block_table, decode_lens, dim=0, output_size=actual_expanded
  829. )
  830. )
  831. if actual_expanded < num_decode_tokens:
  832. self.expanded_block_table_buffer[
  833. actual_expanded:num_decode_tokens, 0
  834. ] = 0
  835. block_table = self.expanded_block_table_buffer[:num_decode_tokens]
  836. # All reqs now have decode_len=1
  837. self.decode_lens_buffer[:num_decode_tokens] = 1
  838. decode_lens = self.decode_lens_buffer[:num_decode_tokens]
  839. return seq_lens, block_table, decode_lens, num_decode_tokens, False
  840. else:
  841. # Native path: plain decode (next_n==1) or spec decode
  842. # with 2D per-token context lengths (next_n > 1).
  843. #
  844. # When decode_lens are not truly uniform (e.g. some requests have
  845. # decode_len < next_n due to padding or short prefills), the simple
  846. # reshape in sparse_attn_indexer won't work. Use pack_seq_triton
  847. # (requires_padding) instead.
  848. requires_padding = min_decode_len != max_decode_len
  849. if use_native and next_n > 1:
  850. assert self.decode_seq_lens_buffer.dim() == 1
  851. # (B, max_decode_len): token j attends to
  852. # L - max_decode_len + j + 1 KV tokens.
  853. seq_lens_buffer = self.decode_seq_lens_buffer[
  854. : num_decodes * max_decode_len
  855. ].view(num_decodes, max_decode_len)
  856. # Clamp at 0: padding requests have seq_len == 0, which would
  857. # otherwise make token 0 negative (next_n=2 gives 0-2+1+0 = -1).
  858. # Downstream kernels read these as uint32, turning -1 into ~4e9.
  859. seq_lens_buffer[:] = (
  860. seq_lens.unsqueeze(1)
  861. - max_decode_len
  862. + 1
  863. + self.offsets_buffer[:max_decode_len]
  864. ).clamp_(min=0)
  865. seq_lens = seq_lens_buffer
  866. return seq_lens, block_table, decode_lens, num_decodes, requires_padding
  867. def _prepare_global_decode_seq_lens(
  868. self,
  869. global_seq_lens: torch.Tensor | None,
  870. decode_lens: torch.Tensor,
  871. decode_lens_cpu: torch.Tensor,
  872. query_start_loc: torch.Tensor,
  873. num_decode_tokens: int,
  874. use_native: bool,
  875. max_decode_len: int,
  876. ) -> torch.Tensor | None:
  877. if global_seq_lens is None:
  878. return None
  879. if use_native or max_decode_len <= 1:
  880. return global_seq_lens
  881. actual_expanded = int(decode_lens_cpu.sum().item())
  882. if actual_expanded > 0:
  883. expanded_offsets = torch.repeat_interleave(
  884. global_seq_lens - decode_lens - query_start_loc,
  885. decode_lens,
  886. output_size=actual_expanded,
  887. )
  888. self.global_decode_seq_lens_buffer[:actual_expanded] = (
  889. expanded_offsets + self.arange_buffer[:actual_expanded] + 1
  890. )
  891. self.global_decode_seq_lens_buffer[actual_expanded:num_decode_tokens] = 0
  892. return self.global_decode_seq_lens_buffer[:num_decode_tokens]
  893. def _build_varlen_decode_indices(
  894. self,
  895. decode_lens: torch.Tensor,
  896. decode_lens_cpu: torch.Tensor,
  897. num_decode_tokens: int,
  898. ) -> torch.Tensor:
  899. """Build request ids for flattened SM100 varlen rows."""
  900. indices = self.decode_indices_buffer[:num_decode_tokens]
  901. actual_expanded = int(decode_lens_cpu.sum().item())
  902. num_decodes = decode_lens.shape[0]
  903. indices[:actual_expanded] = torch.repeat_interleave(
  904. self.arange_buffer[:num_decodes],
  905. decode_lens,
  906. output_size=actual_expanded,
  907. )
  908. if actual_expanded < num_decode_tokens:
  909. pad = num_decode_tokens - actual_expanded
  910. indices[actual_expanded:num_decode_tokens] = (
  911. num_decodes + self.arange_buffer[:pad]
  912. )
  913. return indices
  914. def build(
  915. self,
  916. common_prefix_len: int,
  917. common_attn_metadata: CommonAttentionMetadata,
  918. fast_build: bool = False,
  919. ) -> DeepseekV32IndexerMetadata:
  920. num_reqs = common_attn_metadata.num_reqs
  921. num_tokens = common_attn_metadata.num_actual_tokens
  922. query_start_loc = common_attn_metadata.query_start_loc
  923. query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu
  924. seq_lens = common_attn_metadata.seq_lens
  925. slot_mapping = common_attn_metadata.slot_mapping
  926. block_table = common_attn_metadata.block_table_tensor
  927. dcp_local_seq_lens = common_attn_metadata.dcp_local_seq_lens
  928. num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
  929. split_decodes_and_prefills(
  930. common_attn_metadata,
  931. decode_threshold=self.decode_threshold,
  932. require_uniform=not (self.use_flattening or self.supports_varlen),
  933. treat_short_extends_as_decodes=not self.use_pcp,
  934. )
  935. )
  936. assert num_decodes + num_prefills == num_reqs
  937. assert num_decode_tokens + num_prefill_tokens == num_tokens
  938. compressed_slot_mapping = slot_mapping
  939. compressed_seq_lens = seq_lens
  940. indexer_block_table = block_table
  941. if self.compress_ratio > 1:
  942. kernel_block_size = self.kernel_block_size
  943. if (
  944. kernel_block_size is not None
  945. and self.kv_cache_spec.block_size != kernel_block_size
  946. and self.kv_cache_spec.block_size % kernel_block_size == 0
  947. ):
  948. factor = self.kv_cache_spec.block_size // kernel_block_size
  949. indexer_block_table = (block_table[:, ::factor] // factor).contiguous()
  950. padded_num_tokens = num_tokens
  951. if self.pcp_world_size > 1:
  952. padded_num_tokens = slot_mapping.shape[0] // self.pcp_world_size
  953. compressed_slot_mapping = get_compressed_slot_mapping(
  954. num_tokens,
  955. query_start_loc,
  956. seq_lens,
  957. indexer_block_table,
  958. self.kv_cache_spec.num_states,
  959. self.compress_ratio,
  960. out=self.compressed_slot_mapping_buffer,
  961. )
  962. if self.pcp_world_size > 1:
  963. compressed_slot_mapping = get_pcp_group().all_gather(
  964. self.compressed_slot_mapping_buffer[:padded_num_tokens],
  965. dim=0,
  966. )
  967. compressed_seq_lens = seq_lens // self.compress_ratio
  968. prefill_metadata = None
  969. if num_prefills > 0:
  970. # This CPU value is an upper bound for async-spec extend rows. It
  971. # is safe for chunking/allocation because CUDA metadata below is
  972. # built from exact device seq_lens and gather ignores the tail.
  973. assert common_attn_metadata.seq_lens_cpu_upper_bound is not None
  974. seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
  975. compressed_seq_lens_cpu = (
  976. seq_lens_cpu // self.compress_ratio
  977. if self.compress_ratio > 1
  978. else seq_lens_cpu
  979. )
  980. prefill_query_lens_cpu = torch.diff(
  981. query_start_loc_cpu[num_decodes : num_decodes + num_prefills + 1]
  982. )
  983. max_logits_bytes = envs.VLLM_SPARSE_INDEXER_MAX_LOGITS_MB * 1024 * 1024
  984. # Upper bound is exact for prefill rows (the `[num_decodes:]`
  985. # slice below).
  986. assert common_attn_metadata.seq_lens_cpu_upper_bound is not None
  987. seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
  988. chunk_specs = split_indexer_prefill_chunks(
  989. compressed_seq_lens_cpu[num_decodes:],
  990. prefill_query_lens_cpu,
  991. self.max_prefill_buffer_size,
  992. max_logits_bytes,
  993. request_offset=num_decodes,
  994. )
  995. chunks = []
  996. for req_slice, query_slice in chunk_specs:
  997. metadata = build_prefill_chunk_metadata(
  998. req_slice.start,
  999. req_slice.stop,
  1000. query_start_loc,
  1001. query_start_loc_cpu,
  1002. seq_lens,
  1003. compressed_seq_lens,
  1004. compressed_seq_lens_cpu,
  1005. indexer_block_table,
  1006. self.compress_ratio,
  1007. query_slice=query_slice,
  1008. skip_kv_gather=query_slice.start > 0,
  1009. dcp_rank=self.dcp_rank,
  1010. dcp_world_size=self.dcp_world_size,
  1011. cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
  1012. )
  1013. # Skip when total_seq_lens is 0 (i.e., no compressed token).
  1014. if metadata is not None:
  1015. chunks.append(metadata)
  1016. prefill_metadata = DeepseekV32IndexerPrefillMetadata(
  1017. chunks,
  1018. max_prefill_seq_len=(
  1019. int(seq_lens_cpu[num_decodes:].max().item())
  1020. if num_prefills > 0
  1021. else 0
  1022. ),
  1023. )
  1024. decode_metadata = None
  1025. if num_decodes > 0:
  1026. torch.diff(
  1027. common_attn_metadata.query_start_loc[: num_decodes + 1],
  1028. out=self.decode_lens_buffer[:num_decodes],
  1029. )
  1030. decode_lens = self.decode_lens_buffer[:num_decodes]
  1031. self.per_req_decode_lens_buffer[:num_decodes].copy_(decode_lens)
  1032. decode_lens_cpu = torch.diff(
  1033. common_attn_metadata.query_start_loc_cpu[: num_decodes + 1]
  1034. )
  1035. # Under DCP the per-token decode bounds must be localized AFTER the
  1036. # per-token expansion below, not before. Expanding from a
  1037. # request-level localized length subtracts decode offsets in local
  1038. # space and yields too-short bounds (e.g. world=2, rank=1, global
  1039. # per-token bounds [8, 9, 10] -> [3, 4, 5] instead of [4, 4, 5]), so
  1040. # the first decode token would run top-k against too short a local KV
  1041. # range and miss valid tokens. Keep the global seq_lens here and
  1042. # localize the expanded bounds further down.
  1043. global_seq_lens_for_decode: torch.Tensor | None = None
  1044. if dcp_local_seq_lens is not None:
  1045. global_seq_lens_for_decode = common_attn_metadata.seq_lens[:num_decodes]
  1046. seq_lens = common_attn_metadata.seq_lens[:num_decodes]
  1047. block_table = common_attn_metadata.block_table_tensor[:num_decodes, ...]
  1048. max_decode_len = int(decode_lens_cpu.max().item())
  1049. min_decode_len = int(decode_lens_cpu.min().item())
  1050. write_is_uniform = min_decode_len == max_decode_len
  1051. next_n = 1 + self.num_speculative_tokens
  1052. # The kernel sees max_decode_len Q rows, not the configured next_n,
  1053. # so legality is per-step: on SM90 a uniformly 3-deep batch has no
  1054. # native kernel. max_decode_len <= 1 always has one.
  1055. step_next_n_ok = max_decode_len <= 1 or _supports_native_decode(
  1056. max_decode_len
  1057. )
  1058. use_native = (
  1059. not (self.use_flattening or self.supports_varlen)
  1060. and max_decode_len <= next_n
  1061. and step_next_n_ok
  1062. )
  1063. global_seq_lens_for_decode = self._prepare_global_decode_seq_lens(
  1064. global_seq_lens=global_seq_lens_for_decode,
  1065. decode_lens=decode_lens,
  1066. decode_lens_cpu=decode_lens_cpu,
  1067. query_start_loc=common_attn_metadata.query_start_loc[:num_decodes],
  1068. num_decode_tokens=num_decode_tokens,
  1069. use_native=use_native,
  1070. max_decode_len=max_decode_len,
  1071. )
  1072. decode_indices = None
  1073. if self.supports_varlen:
  1074. decode_indices = self._build_varlen_decode_indices(
  1075. decode_lens=decode_lens,
  1076. decode_lens_cpu=decode_lens_cpu,
  1077. num_decode_tokens=num_decode_tokens,
  1078. )
  1079. seq_lens, block_table, decode_lens, batch_size, requires_padding = (
  1080. self._prepare_decode_tensors(
  1081. seq_lens=seq_lens,
  1082. block_table=block_table,
  1083. decode_lens=decode_lens,
  1084. decode_lens_cpu=decode_lens_cpu,
  1085. query_start_loc=common_attn_metadata.query_start_loc[:num_decodes],
  1086. num_decodes=num_decodes,
  1087. num_decode_tokens=num_decode_tokens,
  1088. use_native=use_native,
  1089. next_n=next_n,
  1090. max_decode_len=max_decode_len,
  1091. )
  1092. )
  1093. if self.compress_ratio > 1:
  1094. kernel_block_size = self.kernel_block_size
  1095. if (
  1096. kernel_block_size is not None
  1097. and self.kv_cache_spec.block_size != kernel_block_size
  1098. and self.kv_cache_spec.block_size % kernel_block_size == 0
  1099. ):
  1100. factor = self.kv_cache_spec.block_size // kernel_block_size
  1101. compressed = block_table[:, ::factor] // factor
  1102. rows, cols = compressed.shape
  1103. if self.indexer_decode_block_table_buffer is None:
  1104. self.indexer_decode_block_table_buffer = torch.zeros(
  1105. (self._max_num_batched_tokens, cols),
  1106. dtype=torch.int32,
  1107. device=self.device,
  1108. )
  1109. self.indexer_decode_block_table_buffer[:rows, :cols].copy_(
  1110. compressed
  1111. )
  1112. block_table = self.indexer_decode_block_table_buffer[:rows, :cols]
  1113. # Flattening always returns a buffer view, including single-token
  1114. # batches. Keep its address stable across varlen graph replays.
  1115. seq_lens_is_buffer_view = not use_native or next_n > 1
  1116. # DCP: localize the now-expanded per-token global bounds to this
  1117. # rank's owned KV. Done here (after expansion) so each token's global
  1118. # causal length is localized individually; see the comment above.
  1119. if dcp_local_seq_lens is not None:
  1120. seq_lens = self._dcp_localize_decode_seq_lens(
  1121. seq_lens, num_decodes, seq_lens_is_buffer_view
  1122. )
  1123. # For DeepseekV4 (compress_ratio > 1), the indexer KV cache stores
  1124. # compressed tokens. Convert uncompressed seq_lens to compressed.
  1125. if self.compress_ratio > 1:
  1126. if seq_lens_is_buffer_view:
  1127. seq_lens //= self.compress_ratio
  1128. else:
  1129. # Copy to avoid mutating shared state; keeps CG address stable.
  1130. self.expanded_seq_lens_buffer[:num_decodes] = (
  1131. seq_lens // self.compress_ratio
  1132. )
  1133. self.expanded_seq_lens_buffer[num_decodes:num_decode_tokens] = 0
  1134. seq_lens = self.expanded_seq_lens_buffer[:num_decode_tokens]
  1135. # Non-MTP: deep_gemm paged MQA logits requires 2D context_lens
  1136. # (csrc/apis/attention.hpp). Unsqueeze to (B, 1) so downstream
  1137. # kernels see the same (B, next_n) layout as the MTP path.
  1138. if seq_lens.dim() == 1:
  1139. seq_lens = seq_lens.unsqueeze(-1)
  1140. # DeepGEMM is required for the paged MQA logits on CUDA devices
  1141. schedule_metadata = self.scheduler_metadata_buffer
  1142. if current_platform.is_cuda() and has_deep_gemm():
  1143. metadata = get_paged_mqa_logits_metadata(
  1144. seq_lens,
  1145. self.kv_cache_spec.num_states,
  1146. self.num_sms,
  1147. indices=decode_indices,
  1148. )
  1149. schedule_metadata = self.scheduler_metadata_buffer[: metadata.shape[0]]
  1150. schedule_metadata[:] = metadata
  1151. decode_metadata = DeepSeekV32IndexerDecodeMetadata(
  1152. block_table=block_table,
  1153. seq_lens=seq_lens,
  1154. decode_lens=decode_lens,
  1155. requires_padding=requires_padding,
  1156. schedule_metadata=schedule_metadata,
  1157. indices=decode_indices,
  1158. global_seq_lens=global_seq_lens_for_decode,
  1159. per_req_decode_lens=self.per_req_decode_lens_buffer[:num_decodes],
  1160. decode_is_uniform=write_is_uniform,
  1161. write_max_decode_len=max_decode_len,
  1162. )
  1163. attn_metadata = DeepseekV32IndexerMetadata(
  1164. seq_lens=common_attn_metadata.seq_lens,
  1165. max_seq_len=common_attn_metadata.max_seq_len,
  1166. slot_mapping=compressed_slot_mapping,
  1167. num_decodes=num_decodes,
  1168. num_decode_tokens=num_decode_tokens,
  1169. num_prefills=num_prefills,
  1170. num_prefill_tokens=num_prefill_tokens,
  1171. prefill=prefill_metadata,
  1172. decode=decode_metadata,
  1173. )
  1174. return attn_metadata
  1175. def build_prefill_chunk_metadata(
  1176. start_idx: int,
  1177. end_idx: int,
  1178. query_start_loc: torch.Tensor,
  1179. query_start_loc_cpu: torch.Tensor,
  1180. uncompressed_seq_lens: torch.Tensor,
  1181. compressed_seq_lens: torch.Tensor,
  1182. compressed_seq_lens_cpu: torch.Tensor,
  1183. block_table: torch.Tensor,
  1184. compress_ratio: int,
  1185. query_slice: slice | None = None,
  1186. skip_kv_gather: bool = False,
  1187. dcp_rank: int = 0,
  1188. dcp_world_size: int = 1,
  1189. cp_kv_cache_interleave_size: int = 1,
  1190. ) -> DeepseekV32IndexerPrefillChunkMetadata | None:
  1191. total_seq_lens = compressed_seq_lens_cpu[start_idx:end_idx].sum().item()
  1192. if total_seq_lens == 0:
  1193. return None
  1194. num_reqs = end_idx - start_idx
  1195. device = block_table.device
  1196. token_to_seq = torch.empty(total_seq_lens, dtype=torch.int32, device=device)
  1197. cu_seq_lens = torch.empty(num_reqs + 1, dtype=torch.int32, device=device)
  1198. # Assigning to slice avoids cpu sync.
  1199. cu_seq_lens[:1] = 0
  1200. torch.cumsum(compressed_seq_lens[start_idx:end_idx], dim=0, out=cu_seq_lens[1:])
  1201. local_cu_seq_lens = cu_seq_lens
  1202. local_total_seq_lens = total_seq_lens
  1203. max_local_total_seq_lens = total_seq_lens
  1204. if dcp_world_size > 1:
  1205. # Per-rank local KV length under interleave-aware DCP sharding, shape
  1206. # [num_reqs, dcp_world_size]. Reuse the canonical CP helper so the
  1207. # sharding matches the rest of the DCP pipeline (decode/prefill).
  1208. local_seq_lens = get_dcp_local_seq_lens(
  1209. compressed_seq_lens[start_idx:end_idx],
  1210. dcp_world_size,
  1211. None,
  1212. cp_kv_cache_interleave_size,
  1213. )
  1214. this_rank_counts = local_seq_lens[:, dcp_rank].to(torch.int32)
  1215. local_cu_seq_lens = torch.zeros(num_reqs + 1, dtype=torch.int32, device=device)
  1216. torch.cumsum(this_rank_counts, dim=0, out=local_cu_seq_lens[1:])
  1217. local_total_seq_lens = int(local_cu_seq_lens[-1].item())
  1218. max_local_total_seq_lens = int(local_seq_lens.sum(dim=0).max().item())
  1219. query_start_loc = (
  1220. query_start_loc[start_idx : end_idx + 1] - query_start_loc[start_idx]
  1221. )
  1222. total_query_len = int(
  1223. (query_start_loc_cpu[end_idx] - query_start_loc_cpu[start_idx]).item()
  1224. )
  1225. if query_slice is not None:
  1226. qs_start = query_slice.start
  1227. qs_stop = query_slice.stop
  1228. else:
  1229. qs_start = 0
  1230. qs_stop = total_query_len
  1231. output_query_len = qs_stop - qs_start
  1232. cu_seq_len_ks = torch.empty(output_query_len, dtype=torch.int32, device=device)
  1233. cu_seq_len_ke = torch.empty(output_query_len, dtype=torch.int32, device=device)
  1234. # Under DCP the kernel writes this rank's local row bounds into
  1235. # cu_seq_len_ks/ke; otherwise local_cu_seq_lens aliases cu_seq_lens.
  1236. _BUILD_PREFILL_CHUNK_METADATA_KERNEL(
  1237. query_start_loc,
  1238. uncompressed_seq_lens[start_idx:end_idx],
  1239. cu_seq_lens,
  1240. local_cu_seq_lens,
  1241. token_to_seq,
  1242. cu_seq_len_ks,
  1243. cu_seq_len_ke,
  1244. qs_start,
  1245. qs_stop,
  1246. dcp_rank,
  1247. dcp_world_size,
  1248. cp_kv_cache_interleave_size,
  1249. num_reqs=num_reqs,
  1250. COMPRESS_RATIO=compress_ratio,
  1251. )
  1252. token_start = query_start_loc_cpu[start_idx].item()
  1253. if query_slice is not None:
  1254. token_end = token_start + qs_stop
  1255. token_start = token_start + qs_start
  1256. skip_kv_gather = skip_kv_gather or qs_start > 0
  1257. else:
  1258. token_end = query_start_loc_cpu[end_idx].item()
  1259. return DeepseekV32IndexerPrefillChunkMetadata(
  1260. cu_seqlen_ks=cu_seq_len_ks,
  1261. cu_seqlen_ke=cu_seq_len_ke,
  1262. cu_seq_lens=cu_seq_lens,
  1263. token_to_seq=token_to_seq,
  1264. total_seq_lens=total_seq_lens,
  1265. block_table=block_table[start_idx:end_idx],
  1266. token_start=token_start,
  1267. token_end=token_end,
  1268. num_reqs=num_reqs,
  1269. skip_kv_gather=skip_kv_gather,
  1270. local_cu_seq_lens=local_cu_seq_lens,
  1271. local_total_seq_lens=local_total_seq_lens,
  1272. max_local_total_seq_lens=max_local_total_seq_lens,
  1273. )
  1274. _PREPARE_UNIFORM_DECODE_KERNEL = PrepareUniformDecodeKernel()
  1275. _BUILD_PREFILL_CHUNK_METADATA_KERNEL = BuildPrefillChunkMetadataKernel()