indexer.py 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456
  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. # Per query token: GLOBAL compressed context length (states), i.e. the
  272. # un-sharded `cu_seqlen_ke - cu_seqlen_ks`. Under DCP the candidate-block
  273. # selection pins each row's newest global block from this.
  274. global_ctx_lens: torch.Tensor | None = None
  275. max_global_ctx: int = 0
  276. class BuildPrefillChunkMetadataKernel(
  277. VllmTritonJitKernel["BuildPrefillChunkMetadataKernel.CompileKey"]
  278. ):
  279. BLOCK_SIZE = 1024
  280. @dataclass(frozen=True)
  281. class CompileKey:
  282. query_slice_start: int
  283. query_slice_stop: int
  284. dcp_rank: int
  285. dcp_world: int
  286. dcp_interleave: int
  287. block_size: int
  288. compress_ratio: int
  289. input_variant: TritonPointerInputVariant
  290. @staticmethod
  291. @triton.jit
  292. def kernel(
  293. # Inputs
  294. query_start_loc_ptr,
  295. uncompressed_seq_lens_ptr,
  296. cu_compressed_seq_lens_ptr,
  297. # Row-start base for cu_seq_len_ks/ke: local cumulative lens under DCP,
  298. # aliases cu_compressed_seq_lens_ptr otherwise.
  299. row_start_cu_compressed_seq_lens_ptr,
  300. # Outputs
  301. token_to_seq_ptr,
  302. cu_compressed_seq_len_ks_ptr,
  303. cu_compressed_seq_len_ke_ptr,
  304. # [output_query_len] global (un-sharded) compressed context per token
  305. global_ctx_len_ptr,
  306. query_slice_start,
  307. query_slice_stop,
  308. DCP_RANK,
  309. DCP_WORLD,
  310. DCP_INTERLEAVE,
  311. BLOCK_SIZE: tl.constexpr,
  312. COMPRESS_RATIO: tl.constexpr,
  313. ):
  314. batch_idx = tl.program_id(0)
  315. query_start = tl.load(query_start_loc_ptr + batch_idx)
  316. query_end = tl.load(query_start_loc_ptr + batch_idx + 1)
  317. query_len = query_end - query_start
  318. seq_start = tl.load(cu_compressed_seq_lens_ptr + batch_idx)
  319. seq_end = tl.load(cu_compressed_seq_lens_ptr + batch_idx + 1)
  320. compressed_seq_len = seq_end - seq_start
  321. # Row start for the (possibly localized) cu_seq_len_ks/ke. Equals seq_start
  322. # when DCP is disabled (the pointer aliases cu_compressed_seq_lens_ptr).
  323. row_start = tl.load(row_start_cu_compressed_seq_lens_ptr + batch_idx)
  324. uncompressed_seq_len = tl.load(uncompressed_seq_lens_ptr + batch_idx)
  325. start_pos = uncompressed_seq_len - query_len
  326. for i in range(0, query_len, BLOCK_SIZE):
  327. offset = i + tl.arange(0, BLOCK_SIZE)
  328. abs_pos = query_start + offset
  329. mask = (
  330. (offset < query_len)
  331. & (abs_pos >= query_slice_start)
  332. & (abs_pos < query_slice_stop)
  333. )
  334. out_pos = abs_pos - query_slice_start
  335. # cu_seq_len_ks: row start in the gathered K buffer.
  336. tl.store(cu_compressed_seq_len_ks_ptr + out_pos, row_start, mask=mask)
  337. # cu_seq_len_ke: row start + per-token context length. Under DCP the
  338. # global per-token length is sharded across ranks.
  339. global_ctx = start_pos + 1 + offset
  340. len_per_token = global_ctx // COMPRESS_RATIO
  341. tl.store(global_ctx_len_ptr + out_pos, len_per_token, mask=mask)
  342. if DCP_WORLD > 1:
  343. # Per-rank local context length under interleave-aware DCP, matching
  344. # get_dcp_local_seq_lens. K == 1 reduces to (len + world-1-rank)//world.
  345. base = (len_per_token // DCP_INTERLEAVE // DCP_WORLD) * DCP_INTERLEAVE
  346. remainder = len_per_token - base * DCP_WORLD
  347. remainder = tl.minimum(
  348. tl.maximum(remainder - DCP_RANK * DCP_INTERLEAVE, 0), DCP_INTERLEAVE
  349. )
  350. len_per_token = base + remainder
  351. tl.store(
  352. cu_compressed_seq_len_ke_ptr + out_pos,
  353. row_start + len_per_token,
  354. mask=mask,
  355. )
  356. # Compute token_to_seq
  357. for i in range(0, compressed_seq_len, BLOCK_SIZE):
  358. offset = i + tl.arange(0, BLOCK_SIZE)
  359. mask = offset < compressed_seq_len
  360. tl.store(token_to_seq_ptr + seq_start + offset, batch_idx, mask=mask)
  361. def dispatch( # type: ignore[override]
  362. self,
  363. *,
  364. query_slice_start: int,
  365. query_slice_stop: int,
  366. DCP_RANK: int,
  367. DCP_WORLD: int,
  368. DCP_INTERLEAVE: int,
  369. BLOCK_SIZE: int,
  370. COMPRESS_RATIO: int,
  371. input_variant: TritonPointerInputVariant,
  372. ) -> CompileKey:
  373. return self.CompileKey(
  374. query_slice_start=triton_scalar_specialization_rep(query_slice_start),
  375. query_slice_stop=triton_scalar_specialization_rep(query_slice_stop),
  376. dcp_rank=triton_scalar_specialization_rep(DCP_RANK),
  377. dcp_world=triton_scalar_specialization_rep(DCP_WORLD),
  378. dcp_interleave=triton_scalar_specialization_rep(DCP_INTERLEAVE),
  379. block_size=BLOCK_SIZE,
  380. compress_ratio=COMPRESS_RATIO,
  381. input_variant=input_variant,
  382. )
  383. def get_warmup_keys(self, vllm_config: VllmConfig) -> list[CompileKey]:
  384. max_tokens = max(1, min(vllm_config.scheduler_config.max_num_batched_tokens, 8))
  385. hf_config = vllm_config.model_config.hf_config
  386. parallel_config = vllm_config.parallel_config
  387. dcp_world = parallel_config.decode_context_parallel_size
  388. dcp_interleave = parallel_config.cp_kv_cache_interleave_size
  389. dcp_rank = get_dcp_group().rank_in_group if dcp_world > 1 else 0
  390. compress_ratios = tuple(
  391. dict.fromkeys(
  392. max(1, int(ratio))
  393. for ratio in (
  394. *(getattr(hf_config, "compress_ratios", None) or (1,)),
  395. getattr(hf_config, "index_kpool", 1) or 1,
  396. )
  397. )
  398. )
  399. index_kpool = getattr(hf_config, "index_kpool", None)
  400. if index_kpool and index_kpool > 1 and index_kpool not in compress_ratios:
  401. compress_ratios = compress_ratios + (index_kpool,)
  402. return self._trace_dispatch(self.dispatch)(
  403. # Cover Triton's divisible, exact-one, and generic i32 classes.
  404. query_slice_start=(0, 1, 2),
  405. query_slice_stop=(1, 2 * max_tokens - 1, 2 * max_tokens),
  406. DCP_RANK=dcp_rank,
  407. DCP_WORLD=dcp_world,
  408. DCP_INTERLEAVE=dcp_interleave,
  409. BLOCK_SIZE=self.BLOCK_SIZE,
  410. COMPRESS_RATIO=list(compress_ratios),
  411. input_variant=(
  412. TritonPointerInputVariant.from_alignment(uncompressed_seq_lens=True),
  413. TritonPointerInputVariant.from_alignment(uncompressed_seq_lens=False),
  414. ),
  415. )
  416. def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
  417. int32_ptr = TritonWarmupTensor(torch.int32)
  418. return dict(
  419. query_start_loc=int32_ptr,
  420. uncompressed_seq_lens=compile_key.input_variant.pointer(
  421. "uncompressed_seq_lens", torch.int32
  422. ),
  423. cu_compressed_seq_lens=int32_ptr,
  424. row_start_cu_compressed_seq_lens=int32_ptr,
  425. token_to_seq=int32_ptr,
  426. cu_compressed_seq_len_ks=int32_ptr,
  427. cu_compressed_seq_len_ke=int32_ptr,
  428. global_ctx_len=int32_ptr,
  429. query_slice_start=compile_key.query_slice_start,
  430. query_slice_stop=compile_key.query_slice_stop,
  431. DCP_RANK=compile_key.dcp_rank,
  432. DCP_WORLD=compile_key.dcp_world,
  433. DCP_INTERLEAVE=compile_key.dcp_interleave,
  434. num_reqs=1,
  435. COMPRESS_RATIO=compile_key.compress_ratio,
  436. )
  437. @kernel_launcher
  438. def __call__(
  439. self,
  440. query_start_loc: torch.Tensor,
  441. uncompressed_seq_lens: torch.Tensor,
  442. cu_compressed_seq_lens: torch.Tensor,
  443. row_start_cu_compressed_seq_lens: torch.Tensor,
  444. token_to_seq: torch.Tensor,
  445. cu_compressed_seq_len_ks: torch.Tensor,
  446. cu_compressed_seq_len_ke: torch.Tensor,
  447. global_ctx_len: torch.Tensor,
  448. query_slice_start: int,
  449. query_slice_stop: int,
  450. DCP_RANK: int,
  451. DCP_WORLD: int,
  452. DCP_INTERLEAVE: int,
  453. *,
  454. num_reqs: int,
  455. COMPRESS_RATIO: int,
  456. ) -> LaunchSpec:
  457. return (num_reqs,), dict(
  458. BLOCK_SIZE=self.BLOCK_SIZE,
  459. COMPRESS_RATIO=COMPRESS_RATIO,
  460. )
  461. @dataclass
  462. class DeepseekV32IndexerPrefillMetadata:
  463. chunks: list[DeepseekV32IndexerPrefillChunkMetadata]
  464. max_prefill_seq_len: int = -1
  465. @dataclass
  466. class DeepSeekV32IndexerDecodeMetadata:
  467. block_table: torch.Tensor
  468. # seq_lens: per-token effective context lengths.
  469. # - flatten path / plain decode: 1D (batch_size,)
  470. # - native MTP path: 2D (B, next_n) where [b,j] = L_b - next_n + j + 1
  471. # Both fp8_fp4_paged_mqa_logits and the topk kernels accept both shapes.
  472. seq_lens: torch.Tensor
  473. decode_lens: torch.Tensor
  474. requires_padding: bool
  475. schedule_metadata: torch.Tensor
  476. global_seq_lens: torch.Tensor | None = None
  477. per_req_decode_lens: torch.Tensor | None = None
  478. decode_is_uniform: bool = True
  479. write_max_decode_len: int = 0
  480. indices: torch.Tensor | None = None
  481. @dataclass
  482. class DeepseekV32IndexerMetadata:
  483. # FIXME (zyongye)
  484. # hacky way to access the data now, need to be in chunked meta
  485. seq_lens: torch.Tensor
  486. max_seq_len: int
  487. slot_mapping: torch.Tensor
  488. # New for MLA (compared to FlashAttention)
  489. # For handling prefill decode split
  490. num_decodes: int
  491. num_decode_tokens: int
  492. num_prefills: int
  493. num_prefill_tokens: int
  494. decode: DeepSeekV32IndexerDecodeMetadata | None = None
  495. prefill: DeepseekV32IndexerPrefillMetadata | None = None
  496. def compute_kpool_tail_slot_mapping(
  497. slot_mapping: torch.Tensor,
  498. block_table: torch.Tensor,
  499. query_start_loc: torch.Tensor,
  500. positions: torch.Tensor,
  501. num_actual_tokens: int,
  502. num_reqs: int,
  503. kpool: int,
  504. out: torch.Tensor | None = None,
  505. ) -> torch.Tensor:
  506. """Map every token to its request's one circular tail block."""
  507. if out is None:
  508. out = slot_mapping.clone()
  509. else:
  510. assert out.shape == slot_mapping.shape
  511. out.copy_(slot_mapping)
  512. if num_actual_tokens == 0:
  513. return out
  514. tokens = torch.arange(num_actual_tokens, device=slot_mapping.device)
  515. req = torch.searchsorted(query_start_loc, tokens, right=True) - 1
  516. req = req.clamp_(min=0, max=num_reqs - 1)
  517. own_block = block_table[:num_reqs, 0].index_select(0, req).to(torch.int64)
  518. pos = positions[:num_actual_tokens].to(torch.int64)
  519. out[:num_actual_tokens] = own_block * kpool + torch.remainder(pos, kpool)
  520. return out
  521. class KpoolTailMetadataBuilder(AttentionMetadataBuilder):
  522. """Build only the circular slot mapping needed by the storage-only tail."""
  523. _cudagraph_support = AttentionCGSupport.ALWAYS
  524. supports_update_block_table = False
  525. reorder_batch_threshold = None
  526. def __init__(
  527. self,
  528. kv_cache_spec: AttentionSpec,
  529. layer_names: list[str],
  530. vllm_config: VllmConfig,
  531. device: torch.device,
  532. ):
  533. super().__init__(kv_cache_spec, layer_names, vllm_config, device)
  534. self.slot_mapping_buffer = torch.empty(
  535. vllm_config.scheduler_config.max_num_batched_tokens,
  536. dtype=torch.int64,
  537. device=device,
  538. )
  539. def build(
  540. self,
  541. common_prefix_len: int,
  542. common_attn_metadata: CommonAttentionMetadata,
  543. fast_build: bool = False,
  544. ) -> DeepseekV32IndexerMetadata:
  545. num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
  546. split_decodes_and_prefills(common_attn_metadata)
  547. )
  548. slot_mapping = common_attn_metadata.slot_mapping
  549. positions = common_attn_metadata.positions
  550. if positions is not None:
  551. slot_mapping_buffer = self.slot_mapping_buffer[
  552. : slot_mapping.numel()
  553. ].view_as(slot_mapping)
  554. slot_mapping = compute_kpool_tail_slot_mapping(
  555. slot_mapping,
  556. common_attn_metadata.block_table_tensor,
  557. common_attn_metadata.query_start_loc,
  558. positions,
  559. common_attn_metadata.num_actual_tokens,
  560. common_attn_metadata.num_reqs,
  561. self.kv_cache_spec.block_size,
  562. out=slot_mapping_buffer,
  563. )
  564. return DeepseekV32IndexerMetadata(
  565. seq_lens=common_attn_metadata.seq_lens,
  566. max_seq_len=common_attn_metadata.max_seq_len,
  567. slot_mapping=slot_mapping,
  568. num_decodes=num_decodes,
  569. num_decode_tokens=num_decode_tokens,
  570. num_prefills=num_prefills,
  571. num_prefill_tokens=num_prefill_tokens,
  572. )
  573. def get_max_prefill_buffer_size(vllm_config: VllmConfig):
  574. max_model_len = vllm_config.model_config.max_model_len
  575. # NOTE(Chen): 40 is a magic number for controlling the prefill buffer size.
  576. # Each entry is 128 fp8 bytes and 4 scale bytes for a total of 132 bytes.
  577. # The flashmla_sparse backend uses a workspace size of 5 * max_model_len.
  578. # The memory usage of the workspace there is 576 * 2 bytes; so we size this as
  579. # (576 * 2 // 132) * 5 = 40 to maximize this workspace size while still fitting
  580. # within the flashmla_sparse workspace.
  581. # For DeepSeek-V3.2, the max_model_len is 163840.
  582. # 40 * 163840 * 132 = 865075200 bytes = 825 MB
  583. return max_model_len * 40
  584. def _supports_varlen_paged_mqa_logits() -> bool:
  585. return (
  586. current_platform.is_cuda()
  587. and current_platform.is_device_capability_family(100)
  588. and has_deep_gemm()
  589. )
  590. def _supports_flattened_device_query_lens() -> bool:
  591. return (
  592. current_platform.is_cuda()
  593. and current_platform.is_device_capability_family(90)
  594. and has_deep_gemm()
  595. )
  596. def _supports_native_decode(next_n: int) -> bool:
  597. """Whether decode can pass `next_n` Q rows per request to the kernel
  598. instead of flattening to one single-token row per query, which re-reads
  599. the KV tile once per row.
  600. """
  601. if not (current_platform.is_cuda() and has_deep_gemm()):
  602. return next_n in (1, 2)
  603. if current_platform.is_device_capability_family(100):
  604. return True
  605. if current_platform.is_device_capability_family(90):
  606. return native_next_n_supported(next_n)
  607. return next_n in (1, 2)
  608. def _use_flattening(vllm_config: VllmConfig) -> bool:
  609. speculative_config = vllm_config.speculative_config
  610. next_n = 1 + vllm_config.num_speculative_tokens
  611. return not _supports_native_decode(next_n) or (
  612. speculative_config is not None
  613. and speculative_config.enable_adaptive_verification
  614. and _supports_flattened_device_query_lens()
  615. )
  616. class DeepseekV32IndexerMetadataBuilder(AttentionMetadataBuilder):
  617. # The indexer opts out of the shared reorder-threshold vote (see __init__),
  618. # so this is None; its own split uses self.decode_threshold.
  619. reorder_batch_threshold: int | None = None
  620. requires_block_table_width = True
  621. # DeepSeek-V4.1 DSpark under DCP: the draft is SWA-only (no indexer); the
  622. # target's non-causal verification rows use the DCP indexer path.
  623. supports_non_causal_multi_token_dcp = True
  624. @classmethod
  625. def get_cudagraph_support(
  626. cls,
  627. vllm_config: VllmConfig,
  628. kv_cache_spec: KVCacheSpec,
  629. ) -> AttentionCGSupport:
  630. if _supports_varlen_paged_mqa_logits() or _use_flattening(vllm_config):
  631. return AttentionCGSupport.ALWAYS
  632. return AttentionCGSupport.UNIFORM_BATCH
  633. def __init__(self, *args, block_table_width: int, **kwargs) -> None:
  634. super().__init__(*args, **kwargs)
  635. scheduler_config = self.vllm_config.scheduler_config
  636. parallel_config = self.vllm_config.parallel_config
  637. self.dcp_world_size = parallel_config.decode_context_parallel_size
  638. self.dcp_rank = get_dcp_group().rank_in_group if self.dcp_world_size > 1 else 0
  639. self.pcp_world_size = parallel_config.prefill_context_parallel_size
  640. self.use_pcp = self.pcp_world_size > 1
  641. self.cp_kv_cache_interleave_size = parallel_config.cp_kv_cache_interleave_size
  642. # The DCP sparse-indexer code is parameterized by interleave size, but
  643. # interleave > 1 is not yet validated end-to-end (gsm8k parity fails),
  644. # so fail closed here rather than silently produce wrong output.
  645. if self.dcp_world_size > 1 and self.cp_kv_cache_interleave_size > 1:
  646. raise NotImplementedError(
  647. "DCP sparse indexer currently supports only "
  648. f"cp_kv_cache_interleave_size=1 (got "
  649. f"{self.cp_kv_cache_interleave_size})."
  650. )
  651. # NOTE(Chen):an estimated max size of flattened_kv. Need to double check.
  652. self.max_prefill_buffer_size = get_max_prefill_buffer_size(self.vllm_config)
  653. self.num_speculative_tokens = (
  654. self.vllm_config.speculative_config.num_speculative_tokens
  655. if self.vllm_config.speculative_config
  656. else 0
  657. )
  658. self.use_fp4_indexer_cache = dsa_indexer_uses_fp4(self.vllm_config)
  659. next_n = self.num_speculative_tokens + 1
  660. self.decode_threshold = next_n
  661. self.reorder_batch_threshold = None
  662. self.use_flattening = _use_flattening(self.vllm_config)
  663. self.supports_varlen = _supports_varlen_paged_mqa_logits()
  664. logger.info_once(
  665. "DSA indexer decode path: use_flattening=%s supports_varlen=%s "
  666. "(next_n=%d, use_fp4_cache=%s)",
  667. self.use_flattening,
  668. self.supports_varlen,
  669. next_n,
  670. self.use_fp4_indexer_cache,
  671. )
  672. sm_count = num_compute_units(self.device.index)
  673. self.num_sms = sm_count
  674. self.offsets_buffer = torch.arange(
  675. next_n, device=self.device, dtype=torch.int32
  676. )
  677. self.decode_lens_buffer = torch.zeros(
  678. (scheduler_config.max_num_batched_tokens,),
  679. dtype=torch.int32,
  680. device=self.device,
  681. )
  682. self.per_req_decode_lens_buffer = torch.zeros(
  683. (scheduler_config.max_num_batched_tokens,),
  684. dtype=torch.int32,
  685. device=self.device,
  686. )
  687. # Shared workspace for decode seq_lens. Native MTP views this as
  688. # (B, max_decode_len) at runtime, keeping context_lens contiguous even
  689. # when max_decode_len is smaller than next_n.
  690. self.decode_seq_lens_buffer = torch.zeros(
  691. (scheduler_config.max_num_batched_tokens,),
  692. dtype=torch.int32,
  693. device=self.device,
  694. )
  695. self.global_decode_seq_lens_buffer = torch.zeros(
  696. (scheduler_config.max_num_batched_tokens,),
  697. dtype=torch.int32,
  698. device=self.device,
  699. )
  700. self.decode_indices_buffer = torch.zeros(
  701. (scheduler_config.max_num_batched_tokens,),
  702. dtype=torch.int32,
  703. device=self.device,
  704. )
  705. self.arange_buffer = torch.arange(
  706. max(
  707. scheduler_config.max_num_seqs * next_n,
  708. scheduler_config.max_num_batched_tokens,
  709. ),
  710. dtype=torch.int32,
  711. device=self.device,
  712. )
  713. self.expanded_block_table_buffer = torch.zeros(
  714. (scheduler_config.max_num_batched_tokens, block_table_width),
  715. dtype=torch.int32,
  716. device=self.device,
  717. )
  718. # See: DeepGMM/csrc/apis/attention.hpp. Sized for one slot per SM;
  719. # build() narrows it to whatever the kernel actually schedules.
  720. self.scheduler_metadata_buffer = torch.empty(
  721. (self.num_sms + 1, 2), dtype=torch.int32, device=self.device
  722. )
  723. # KV compression. Default to 1 for no compression.
  724. self.compress_ratio = 1
  725. # Get compress_ratio for DeepseekV4 support
  726. if isinstance(self.kv_cache_spec, MLAAttentionSpec):
  727. # MLA compression is a whole number of tokens per state (fractions
  728. # are whisper block pooling and never reach MLA).
  729. assert isinstance(self.kv_cache_spec.tokens_per_state, int)
  730. self.compress_ratio = self.kv_cache_spec.tokens_per_state
  731. if self.dcp_world_size > 1 and self.compress_ratio > 1:
  732. # DCP + compressed indexer cache (DeepSeek-V4.1 ratio-2 index
  733. # sources): the K cache is sharded over compressed STATES; the
  734. # compressed slot mapping and the decode bounds below are computed
  735. # in state coordinates (DCP2-DESIGN-stage23.md §A.1).
  736. logger.info_once(
  737. "DSA indexer: DCP%d with compress_ratio=%d (state-sharded K cache)",
  738. self.dcp_world_size,
  739. self.compress_ratio,
  740. )
  741. # Pre-allocate buffers for CUDA graph compatibility when
  742. if self.compress_ratio > 1:
  743. # compress_ratio > 1 (DeepseekV4)
  744. # Compressed slot mapping output buffer
  745. self.compressed_slot_mapping_buffer = torch.zeros(
  746. (scheduler_config.max_num_batched_tokens,),
  747. dtype=torch.int64,
  748. device=self.device,
  749. )
  750. # Buffer for compressed seq_lens in decode path
  751. self.expanded_seq_lens_buffer = torch.zeros(
  752. (scheduler_config.max_num_batched_tokens,),
  753. dtype=torch.int32,
  754. device=self.device,
  755. )
  756. self.indexer_decode_block_table_buffer: torch.Tensor | None = None
  757. self._max_num_batched_tokens = scheduler_config.max_num_batched_tokens
  758. def _dcp_localize_decode_seq_lens(
  759. self,
  760. seq_lens: torch.Tensor,
  761. num_decodes: int,
  762. seq_lens_is_buffer_view: bool,
  763. ) -> torch.Tensor:
  764. local_seq_lens = get_dcp_local_seq_lens(
  765. seq_lens,
  766. self.dcp_world_size,
  767. self.dcp_rank,
  768. self.cp_kv_cache_interleave_size,
  769. )
  770. if seq_lens_is_buffer_view:
  771. seq_lens.copy_(local_seq_lens)
  772. return seq_lens
  773. out = self.decode_seq_lens_buffer[:num_decodes]
  774. out.copy_(local_seq_lens)
  775. return out
  776. def _prepare_decode_tensors(
  777. self,
  778. seq_lens: torch.Tensor,
  779. block_table: torch.Tensor,
  780. decode_lens: torch.Tensor,
  781. decode_lens_cpu: torch.Tensor,
  782. query_start_loc: torch.Tensor,
  783. num_decodes: int,
  784. num_decode_tokens: int,
  785. use_native: bool,
  786. next_n: int,
  787. max_decode_len: int,
  788. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, int, bool]:
  789. """Prepare native or per-token flattened decode tensors."""
  790. spec_config = self.vllm_config.speculative_config
  791. adaptive = bool(spec_config and spec_config.enable_adaptive_verification)
  792. min_decode_len = int(decode_lens_cpu.min().item())
  793. if not use_native:
  794. assert self.decode_seq_lens_buffer.dim() == 1
  795. if (
  796. not self.supports_varlen
  797. and (num_decodes == 1 or not adaptive)
  798. and min_decode_len == max_decode_len
  799. and num_decodes * max_decode_len == num_decode_tokens
  800. ):
  801. # Uniform decode lengths with no cudagraph token padding.
  802. _PREPARE_UNIFORM_DECODE_KERNEL(
  803. seq_lens,
  804. self.decode_seq_lens_buffer,
  805. block_table,
  806. self.expanded_block_table_buffer,
  807. self.decode_lens_buffer,
  808. num_decode_tokens,
  809. max_decode_len,
  810. )
  811. self.decode_seq_lens_buffer[num_decode_tokens:] = 0
  812. seq_lens = self.decode_seq_lens_buffer[:num_decode_tokens]
  813. block_table = self.expanded_block_table_buffer[:num_decode_tokens]
  814. decode_lens = self.decode_lens_buffer[:num_decode_tokens]
  815. return seq_lens, block_table, decode_lens, num_decode_tokens, False
  816. else:
  817. # Variable decode lengths.
  818. # Assume 4 requests with seq_lens [10, 7, 12, 0] (the final req is
  819. # padding) and decode_lens [3, 1, 4, 0] in the below example comments.
  820. # The context lengths are therefore
  821. # [10-3, 7-1, 12-4, 0-0] = [7, 6, 8, 0].
  822. # 3 + 1 + 4 + 0 = 8
  823. actual_expanded = int(decode_lens_cpu.sum().item())
  824. # Fuse expanded_base and expanded_starts into a single
  825. # repeat_interleave:
  826. # seq_len_i = (context_start[b] - query_start_loc[b]) + arange[i] + 1
  827. # where context_start[b] = seq_lens[b] - decode_lens[b].
  828. # Example: offsets = [7-0, 6-3, 8-4, 0-8] = [7, 3, 4, -8]
  829. # expanded_offsets = [7, 7, 7, 3, 4, 4, 4, 4]
  830. # result = [8, 9, 10, 7, 9, 10, 11, 12]
  831. expanded_offsets = torch.repeat_interleave(
  832. seq_lens - decode_lens - query_start_loc,
  833. decode_lens,
  834. output_size=actual_expanded,
  835. )
  836. # [8, 9, 10, 7, 9, 10, 11, 12, ...] where ... is unused buffer space
  837. self.decode_seq_lens_buffer[:actual_expanded] = (
  838. expanded_offsets + self.arange_buffer[:actual_expanded] + 1
  839. )
  840. self.decode_seq_lens_buffer[actual_expanded:] = 0
  841. seq_lens = self.decode_seq_lens_buffer[:num_decode_tokens]
  842. # Give each of the flattened entries the same block table row as the
  843. # original request.
  844. self.expanded_block_table_buffer[:actual_expanded] = (
  845. torch.repeat_interleave(
  846. block_table, decode_lens, dim=0, output_size=actual_expanded
  847. )
  848. )
  849. if actual_expanded < num_decode_tokens:
  850. self.expanded_block_table_buffer[
  851. actual_expanded:num_decode_tokens, 0
  852. ] = 0
  853. block_table = self.expanded_block_table_buffer[:num_decode_tokens]
  854. # All reqs now have decode_len=1
  855. self.decode_lens_buffer[:num_decode_tokens] = 1
  856. decode_lens = self.decode_lens_buffer[:num_decode_tokens]
  857. return seq_lens, block_table, decode_lens, num_decode_tokens, False
  858. else:
  859. # Native path: plain decode (next_n==1) or spec decode
  860. # with 2D per-token context lengths (next_n > 1).
  861. #
  862. # When decode_lens are not truly uniform (e.g. some requests have
  863. # decode_len < next_n due to padding or short prefills), the simple
  864. # reshape in sparse_attn_indexer won't work. Use pack_seq_triton
  865. # (requires_padding) instead.
  866. requires_padding = min_decode_len != max_decode_len
  867. if use_native and next_n > 1:
  868. assert self.decode_seq_lens_buffer.dim() == 1
  869. # (B, max_decode_len): token j attends to
  870. # L - max_decode_len + j + 1 KV tokens.
  871. seq_lens_buffer = self.decode_seq_lens_buffer[
  872. : num_decodes * max_decode_len
  873. ].view(num_decodes, max_decode_len)
  874. # Clamp at 0: padding requests have seq_len == 0, which would
  875. # otherwise make token 0 negative (next_n=2 gives 0-2+1+0 = -1).
  876. # Downstream kernels read these as uint32, turning -1 into ~4e9.
  877. seq_lens_buffer[:] = (
  878. seq_lens.unsqueeze(1)
  879. - max_decode_len
  880. + 1
  881. + self.offsets_buffer[:max_decode_len]
  882. ).clamp_(min=0)
  883. seq_lens = seq_lens_buffer
  884. return seq_lens, block_table, decode_lens, num_decodes, requires_padding
  885. def _prepare_global_decode_seq_lens(
  886. self,
  887. global_seq_lens: torch.Tensor | None,
  888. decode_lens: torch.Tensor,
  889. decode_lens_cpu: torch.Tensor,
  890. query_start_loc: torch.Tensor,
  891. num_decode_tokens: int,
  892. use_native: bool,
  893. max_decode_len: int,
  894. ) -> torch.Tensor | None:
  895. if global_seq_lens is None:
  896. return None
  897. if use_native or max_decode_len <= 1:
  898. return global_seq_lens
  899. actual_expanded = int(decode_lens_cpu.sum().item())
  900. if actual_expanded > 0:
  901. expanded_offsets = torch.repeat_interleave(
  902. global_seq_lens - decode_lens - query_start_loc,
  903. decode_lens,
  904. output_size=actual_expanded,
  905. )
  906. self.global_decode_seq_lens_buffer[:actual_expanded] = (
  907. expanded_offsets + self.arange_buffer[:actual_expanded] + 1
  908. )
  909. self.global_decode_seq_lens_buffer[actual_expanded:num_decode_tokens] = 0
  910. return self.global_decode_seq_lens_buffer[:num_decode_tokens]
  911. def _build_varlen_decode_indices(
  912. self,
  913. decode_lens: torch.Tensor,
  914. decode_lens_cpu: torch.Tensor,
  915. num_decode_tokens: int,
  916. ) -> torch.Tensor:
  917. """Build request ids for flattened SM100 varlen rows."""
  918. indices = self.decode_indices_buffer[:num_decode_tokens]
  919. actual_expanded = int(decode_lens_cpu.sum().item())
  920. num_decodes = decode_lens.shape[0]
  921. indices[:actual_expanded] = torch.repeat_interleave(
  922. self.arange_buffer[:num_decodes],
  923. decode_lens,
  924. output_size=actual_expanded,
  925. )
  926. if actual_expanded < num_decode_tokens:
  927. pad = num_decode_tokens - actual_expanded
  928. indices[actual_expanded:num_decode_tokens] = (
  929. num_decodes + self.arange_buffer[:pad]
  930. )
  931. return indices
  932. def build(
  933. self,
  934. common_prefix_len: int,
  935. common_attn_metadata: CommonAttentionMetadata,
  936. fast_build: bool = False,
  937. ) -> DeepseekV32IndexerMetadata:
  938. num_reqs = common_attn_metadata.num_reqs
  939. num_tokens = common_attn_metadata.num_actual_tokens
  940. query_start_loc = common_attn_metadata.query_start_loc
  941. query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu
  942. seq_lens = common_attn_metadata.seq_lens
  943. slot_mapping = common_attn_metadata.slot_mapping
  944. block_table = common_attn_metadata.block_table_tensor
  945. dcp_local_seq_lens = common_attn_metadata.dcp_local_seq_lens
  946. num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
  947. split_decodes_and_prefills(
  948. common_attn_metadata,
  949. decode_threshold=self.decode_threshold,
  950. require_uniform=not (self.use_flattening or self.supports_varlen),
  951. treat_short_extends_as_decodes=not self.use_pcp,
  952. )
  953. )
  954. assert num_decodes + num_prefills == num_reqs
  955. assert num_decode_tokens + num_prefill_tokens == num_tokens
  956. compressed_slot_mapping = slot_mapping
  957. compressed_seq_lens = seq_lens
  958. indexer_block_table = block_table
  959. if self.compress_ratio > 1:
  960. kernel_block_size = self.kernel_block_size
  961. if (
  962. kernel_block_size is not None
  963. and self.kv_cache_spec.block_size != kernel_block_size
  964. and self.kv_cache_spec.block_size % kernel_block_size == 0
  965. ):
  966. factor = self.kv_cache_spec.block_size // kernel_block_size
  967. assert self.dcp_world_size == 1, (
  968. "DCP with a compressed indexer cache needs kernel_block_size == "
  969. f"kv_cache_spec.block_size (got factor {factor})."
  970. )
  971. indexer_block_table = (block_table[:, ::factor] // factor).contiguous()
  972. padded_num_tokens = num_tokens
  973. if self.pcp_world_size > 1:
  974. padded_num_tokens = slot_mapping.shape[0] // self.pcp_world_size
  975. compressed_slot_mapping = get_compressed_slot_mapping(
  976. num_tokens,
  977. query_start_loc,
  978. seq_lens,
  979. indexer_block_table,
  980. self.kv_cache_spec.num_states,
  981. self.compress_ratio,
  982. out=self.compressed_slot_mapping_buffer,
  983. dcp_world_size=self.dcp_world_size,
  984. dcp_rank=self.dcp_rank,
  985. cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
  986. )
  987. if self.pcp_world_size > 1:
  988. compressed_slot_mapping = get_pcp_group().all_gather(
  989. self.compressed_slot_mapping_buffer[:padded_num_tokens],
  990. dim=0,
  991. )
  992. compressed_seq_lens = seq_lens // self.compress_ratio
  993. prefill_metadata = None
  994. if num_prefills > 0:
  995. # This CPU value is an upper bound for async-spec extend rows. It
  996. # is safe for chunking/allocation because CUDA metadata below is
  997. # built from exact device seq_lens and gather ignores the tail.
  998. assert common_attn_metadata.seq_lens_cpu_upper_bound is not None
  999. seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
  1000. compressed_seq_lens_cpu = (
  1001. seq_lens_cpu // self.compress_ratio
  1002. if self.compress_ratio > 1
  1003. else seq_lens_cpu
  1004. )
  1005. prefill_query_lens_cpu = torch.diff(
  1006. query_start_loc_cpu[num_decodes : num_decodes + num_prefills + 1]
  1007. )
  1008. max_logits_bytes = envs.VLLM_SPARSE_INDEXER_MAX_LOGITS_MB * 1024 * 1024
  1009. # Upper bound is exact for prefill rows (the `[num_decodes:]`
  1010. # slice below).
  1011. assert common_attn_metadata.seq_lens_cpu_upper_bound is not None
  1012. seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
  1013. chunk_specs = split_indexer_prefill_chunks(
  1014. compressed_seq_lens_cpu[num_decodes:],
  1015. prefill_query_lens_cpu,
  1016. self.max_prefill_buffer_size,
  1017. max_logits_bytes,
  1018. request_offset=num_decodes,
  1019. )
  1020. chunks = []
  1021. for req_slice, query_slice in chunk_specs:
  1022. metadata = build_prefill_chunk_metadata(
  1023. req_slice.start,
  1024. req_slice.stop,
  1025. query_start_loc,
  1026. query_start_loc_cpu,
  1027. seq_lens,
  1028. compressed_seq_lens,
  1029. compressed_seq_lens_cpu,
  1030. indexer_block_table,
  1031. self.compress_ratio,
  1032. query_slice=query_slice,
  1033. skip_kv_gather=query_slice.start > 0,
  1034. dcp_rank=self.dcp_rank,
  1035. dcp_world_size=self.dcp_world_size,
  1036. cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
  1037. )
  1038. # Skip when total_seq_lens is 0 (i.e., no compressed token).
  1039. if metadata is not None:
  1040. chunks.append(metadata)
  1041. prefill_metadata = DeepseekV32IndexerPrefillMetadata(
  1042. chunks,
  1043. max_prefill_seq_len=(
  1044. int(seq_lens_cpu[num_decodes:].max().item())
  1045. if num_prefills > 0
  1046. else 0
  1047. ),
  1048. )
  1049. decode_metadata = None
  1050. if num_decodes > 0:
  1051. torch.diff(
  1052. common_attn_metadata.query_start_loc[: num_decodes + 1],
  1053. out=self.decode_lens_buffer[:num_decodes],
  1054. )
  1055. decode_lens = self.decode_lens_buffer[:num_decodes]
  1056. self.per_req_decode_lens_buffer[:num_decodes].copy_(decode_lens)
  1057. decode_lens_cpu = torch.diff(
  1058. common_attn_metadata.query_start_loc_cpu[: num_decodes + 1]
  1059. )
  1060. # Under DCP the per-token decode bounds must be localized AFTER the
  1061. # per-token expansion below, not before. Expanding from a
  1062. # request-level localized length subtracts decode offsets in local
  1063. # space and yields too-short bounds (e.g. world=2, rank=1, global
  1064. # per-token bounds [8, 9, 10] -> [3, 4, 5] instead of [4, 4, 5]), so
  1065. # the first decode token would run top-k against too short a local KV
  1066. # range and miss valid tokens. Keep the global seq_lens here and
  1067. # localize the expanded bounds further down.
  1068. global_seq_lens_for_decode: torch.Tensor | None = None
  1069. if dcp_local_seq_lens is not None:
  1070. global_seq_lens_for_decode = common_attn_metadata.seq_lens[:num_decodes]
  1071. seq_lens = common_attn_metadata.seq_lens[:num_decodes]
  1072. block_table = common_attn_metadata.block_table_tensor[:num_decodes, ...]
  1073. max_decode_len = int(decode_lens_cpu.max().item())
  1074. min_decode_len = int(decode_lens_cpu.min().item())
  1075. write_is_uniform = min_decode_len == max_decode_len
  1076. next_n = 1 + self.num_speculative_tokens
  1077. # The kernel sees max_decode_len Q rows, not the configured next_n,
  1078. # so legality is per-step: on SM90 a uniformly 3-deep batch has no
  1079. # native kernel. max_decode_len <= 1 always has one.
  1080. step_next_n_ok = max_decode_len <= 1 or _supports_native_decode(
  1081. max_decode_len
  1082. )
  1083. use_native = (
  1084. not (self.use_flattening or self.supports_varlen)
  1085. and max_decode_len <= next_n
  1086. and step_next_n_ok
  1087. )
  1088. global_seq_lens_for_decode = self._prepare_global_decode_seq_lens(
  1089. global_seq_lens=global_seq_lens_for_decode,
  1090. decode_lens=decode_lens,
  1091. decode_lens_cpu=decode_lens_cpu,
  1092. query_start_loc=common_attn_metadata.query_start_loc[:num_decodes],
  1093. num_decode_tokens=num_decode_tokens,
  1094. use_native=use_native,
  1095. max_decode_len=max_decode_len,
  1096. )
  1097. decode_indices = None
  1098. if self.supports_varlen:
  1099. decode_indices = self._build_varlen_decode_indices(
  1100. decode_lens=decode_lens,
  1101. decode_lens_cpu=decode_lens_cpu,
  1102. num_decode_tokens=num_decode_tokens,
  1103. )
  1104. seq_lens, block_table, decode_lens, batch_size, requires_padding = (
  1105. self._prepare_decode_tensors(
  1106. seq_lens=seq_lens,
  1107. block_table=block_table,
  1108. decode_lens=decode_lens,
  1109. decode_lens_cpu=decode_lens_cpu,
  1110. query_start_loc=common_attn_metadata.query_start_loc[:num_decodes],
  1111. num_decodes=num_decodes,
  1112. num_decode_tokens=num_decode_tokens,
  1113. use_native=use_native,
  1114. next_n=next_n,
  1115. max_decode_len=max_decode_len,
  1116. )
  1117. )
  1118. if self.compress_ratio > 1:
  1119. kernel_block_size = self.kernel_block_size
  1120. if (
  1121. kernel_block_size is not None
  1122. and self.kv_cache_spec.block_size != kernel_block_size
  1123. and self.kv_cache_spec.block_size % kernel_block_size == 0
  1124. ):
  1125. factor = self.kv_cache_spec.block_size // kernel_block_size
  1126. compressed = block_table[:, ::factor] // factor
  1127. rows, cols = compressed.shape
  1128. if self.indexer_decode_block_table_buffer is None:
  1129. self.indexer_decode_block_table_buffer = torch.zeros(
  1130. (self._max_num_batched_tokens, cols),
  1131. dtype=torch.int32,
  1132. device=self.device,
  1133. )
  1134. self.indexer_decode_block_table_buffer[:rows, :cols].copy_(
  1135. compressed
  1136. )
  1137. block_table = self.indexer_decode_block_table_buffer[:rows, :cols]
  1138. # Flattening always returns a buffer view, including single-token
  1139. # batches. Keep its address stable across varlen graph replays.
  1140. seq_lens_is_buffer_view = not use_native or next_n > 1
  1141. # For DeepseekV4 (compress_ratio > 1), the indexer KV cache stores
  1142. # compressed tokens. Convert uncompressed seq_lens to compressed.
  1143. # This must happen BEFORE the DCP localization below: the cache is
  1144. # sharded over compressed states, so this rank's share is
  1145. # localize(len // R), not localize(len) // R (len=10, R=2, W=2:
  1146. # rank 0 owns states {0, 2, 4} -> 3, but 5 // 2 == 2).
  1147. if self.compress_ratio > 1:
  1148. if seq_lens_is_buffer_view:
  1149. seq_lens //= self.compress_ratio
  1150. else:
  1151. # Copy to avoid mutating shared state; keeps CG address stable.
  1152. self.expanded_seq_lens_buffer[:num_decodes] = (
  1153. seq_lens // self.compress_ratio
  1154. )
  1155. self.expanded_seq_lens_buffer[num_decodes:num_decode_tokens] = 0
  1156. seq_lens = self.expanded_seq_lens_buffer[:num_decode_tokens]
  1157. # The copy is private: localize it in place below.
  1158. seq_lens_is_buffer_view = True
  1159. # DCP: localize the now-expanded (and now-compressed) per-token
  1160. # global bounds to this rank's owned KV. Done here (after expansion)
  1161. # so each token's global causal length is localized individually;
  1162. # see the comment above.
  1163. if dcp_local_seq_lens is not None:
  1164. seq_lens = self._dcp_localize_decode_seq_lens(
  1165. seq_lens, num_decodes, seq_lens_is_buffer_view
  1166. )
  1167. # Non-MTP: deep_gemm paged MQA logits requires 2D context_lens
  1168. # (csrc/apis/attention.hpp). Unsqueeze to (B, 1) so downstream
  1169. # kernels see the same (B, next_n) layout as the MTP path.
  1170. if seq_lens.dim() == 1:
  1171. seq_lens = seq_lens.unsqueeze(-1)
  1172. # DeepGEMM is required for the paged MQA logits on CUDA devices
  1173. schedule_metadata = self.scheduler_metadata_buffer
  1174. if current_platform.is_cuda() and has_deep_gemm():
  1175. metadata = get_paged_mqa_logits_metadata(
  1176. seq_lens,
  1177. self.kv_cache_spec.num_states,
  1178. self.num_sms,
  1179. indices=decode_indices,
  1180. )
  1181. schedule_metadata = self.scheduler_metadata_buffer[: metadata.shape[0]]
  1182. schedule_metadata[:] = metadata
  1183. decode_metadata = DeepSeekV32IndexerDecodeMetadata(
  1184. block_table=block_table,
  1185. seq_lens=seq_lens,
  1186. decode_lens=decode_lens,
  1187. requires_padding=requires_padding,
  1188. schedule_metadata=schedule_metadata,
  1189. indices=decode_indices,
  1190. global_seq_lens=global_seq_lens_for_decode,
  1191. per_req_decode_lens=self.per_req_decode_lens_buffer[:num_decodes],
  1192. decode_is_uniform=write_is_uniform,
  1193. write_max_decode_len=max_decode_len,
  1194. )
  1195. attn_metadata = DeepseekV32IndexerMetadata(
  1196. seq_lens=common_attn_metadata.seq_lens,
  1197. max_seq_len=common_attn_metadata.max_seq_len,
  1198. slot_mapping=compressed_slot_mapping,
  1199. num_decodes=num_decodes,
  1200. num_decode_tokens=num_decode_tokens,
  1201. num_prefills=num_prefills,
  1202. num_prefill_tokens=num_prefill_tokens,
  1203. prefill=prefill_metadata,
  1204. decode=decode_metadata,
  1205. )
  1206. return attn_metadata
  1207. def build_prefill_chunk_metadata(
  1208. start_idx: int,
  1209. end_idx: int,
  1210. query_start_loc: torch.Tensor,
  1211. query_start_loc_cpu: torch.Tensor,
  1212. uncompressed_seq_lens: torch.Tensor,
  1213. compressed_seq_lens: torch.Tensor,
  1214. compressed_seq_lens_cpu: torch.Tensor,
  1215. block_table: torch.Tensor,
  1216. compress_ratio: int,
  1217. query_slice: slice | None = None,
  1218. skip_kv_gather: bool = False,
  1219. dcp_rank: int = 0,
  1220. dcp_world_size: int = 1,
  1221. cp_kv_cache_interleave_size: int = 1,
  1222. ) -> DeepseekV32IndexerPrefillChunkMetadata | None:
  1223. total_seq_lens = compressed_seq_lens_cpu[start_idx:end_idx].sum().item()
  1224. if total_seq_lens == 0:
  1225. return None
  1226. num_reqs = end_idx - start_idx
  1227. device = block_table.device
  1228. token_to_seq = torch.empty(total_seq_lens, dtype=torch.int32, device=device)
  1229. cu_seq_lens = torch.empty(num_reqs + 1, dtype=torch.int32, device=device)
  1230. # Assigning to slice avoids cpu sync.
  1231. cu_seq_lens[:1] = 0
  1232. torch.cumsum(compressed_seq_lens[start_idx:end_idx], dim=0, out=cu_seq_lens[1:])
  1233. local_cu_seq_lens = cu_seq_lens
  1234. local_total_seq_lens = total_seq_lens
  1235. max_local_total_seq_lens = total_seq_lens
  1236. if dcp_world_size > 1:
  1237. # Per-rank local KV length under interleave-aware DCP sharding, shape
  1238. # [num_reqs, dcp_world_size]. Reuse the canonical CP helper so the
  1239. # sharding matches the rest of the DCP pipeline (decode/prefill).
  1240. local_seq_lens = get_dcp_local_seq_lens(
  1241. compressed_seq_lens[start_idx:end_idx],
  1242. dcp_world_size,
  1243. None,
  1244. cp_kv_cache_interleave_size,
  1245. )
  1246. this_rank_counts = local_seq_lens[:, dcp_rank].to(torch.int32)
  1247. local_cu_seq_lens = torch.zeros(num_reqs + 1, dtype=torch.int32, device=device)
  1248. torch.cumsum(this_rank_counts, dim=0, out=local_cu_seq_lens[1:])
  1249. local_total_seq_lens = int(local_cu_seq_lens[-1].item())
  1250. max_local_total_seq_lens = int(local_seq_lens.sum(dim=0).max().item())
  1251. query_start_loc = (
  1252. query_start_loc[start_idx : end_idx + 1] - query_start_loc[start_idx]
  1253. )
  1254. total_query_len = int(
  1255. (query_start_loc_cpu[end_idx] - query_start_loc_cpu[start_idx]).item()
  1256. )
  1257. if query_slice is not None:
  1258. qs_start = query_slice.start
  1259. qs_stop = query_slice.stop
  1260. else:
  1261. qs_start = 0
  1262. qs_stop = total_query_len
  1263. output_query_len = qs_stop - qs_start
  1264. cu_seq_len_ks = torch.empty(output_query_len, dtype=torch.int32, device=device)
  1265. cu_seq_len_ke = torch.empty(output_query_len, dtype=torch.int32, device=device)
  1266. global_ctx_len = torch.empty(output_query_len, dtype=torch.int32, device=device)
  1267. # Under DCP the kernel writes this rank's local row bounds into
  1268. # cu_seq_len_ks/ke; otherwise local_cu_seq_lens aliases cu_seq_lens.
  1269. _BUILD_PREFILL_CHUNK_METADATA_KERNEL(
  1270. query_start_loc,
  1271. uncompressed_seq_lens[start_idx:end_idx],
  1272. cu_seq_lens,
  1273. local_cu_seq_lens,
  1274. token_to_seq,
  1275. cu_seq_len_ks,
  1276. cu_seq_len_ke,
  1277. global_ctx_len,
  1278. qs_start,
  1279. qs_stop,
  1280. dcp_rank,
  1281. dcp_world_size,
  1282. cp_kv_cache_interleave_size,
  1283. num_reqs=num_reqs,
  1284. COMPRESS_RATIO=compress_ratio,
  1285. )
  1286. token_start = query_start_loc_cpu[start_idx].item()
  1287. if query_slice is not None:
  1288. token_end = token_start + qs_stop
  1289. token_start = token_start + qs_start
  1290. skip_kv_gather = skip_kv_gather or qs_start > 0
  1291. else:
  1292. token_end = query_start_loc_cpu[end_idx].item()
  1293. return DeepseekV32IndexerPrefillChunkMetadata(
  1294. cu_seqlen_ks=cu_seq_len_ks,
  1295. cu_seqlen_ke=cu_seq_len_ke,
  1296. cu_seq_lens=cu_seq_lens,
  1297. token_to_seq=token_to_seq,
  1298. total_seq_lens=total_seq_lens,
  1299. block_table=block_table[start_idx:end_idx],
  1300. token_start=token_start,
  1301. token_end=token_end,
  1302. num_reqs=num_reqs,
  1303. skip_kv_gather=skip_kv_gather,
  1304. local_cu_seq_lens=local_cu_seq_lens,
  1305. local_total_seq_lens=local_total_seq_lens,
  1306. max_local_total_seq_lens=max_local_total_seq_lens,
  1307. global_ctx_lens=global_ctx_len if dcp_world_size > 1 else None,
  1308. max_global_ctx=(
  1309. int(compressed_seq_lens_cpu[start_idx:end_idx].max().item())
  1310. if dcp_world_size > 1
  1311. else 0
  1312. ),
  1313. )
  1314. _PREPARE_UNIFORM_DECODE_KERNEL = PrepareUniformDecodeKernel()
  1315. _BUILD_PREFILL_CHUNK_METADATA_KERNEL = BuildPrefillChunkMetadataKernel()