sparse_swa.py 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. from dataclasses import dataclass, field
  4. from typing import Any, ClassVar, cast
  5. import torch
  6. from vllm.config import CacheConfig, VllmConfig, get_current_vllm_config
  7. from vllm.model_executor.layers.attention_layer_base import AttentionLayerBase
  8. from vllm.model_executor.warmup.jit_warmup import (
  9. WarmupIntRange,
  10. zip_inputs,
  11. )
  12. from vllm.model_executor.warmup.jit_warmup_triton_helper import (
  13. LaunchSpec,
  14. TritonWarmupTensor,
  15. VllmTritonJitKernel,
  16. kernel_launcher,
  17. )
  18. from vllm.platforms import current_platform
  19. from vllm.triton_utils import tl, triton
  20. from vllm.utils.math_utils import cdiv, next_power_of_2
  21. from vllm.v1.attention.backend import (
  22. AttentionBackend,
  23. AttentionCGSupport,
  24. AttentionMetadataBuilder,
  25. CommonAttentionMetadata,
  26. MultipleOf,
  27. )
  28. from vllm.v1.attention.backends.mla.compressor_utils import (
  29. get_dspark_swa_index_width,
  30. )
  31. from vllm.v1.attention.backends.utils import split_decodes_and_prefills
  32. from vllm.v1.attention.ops.flashmla import FlashMLASchedMeta, get_mla_metadata
  33. from vllm.v1.kv_cache_interface import (
  34. KVCacheSpec,
  35. MLAAttentionSpec,
  36. SlidingWindowMLASpec,
  37. get_kv_quant_mode,
  38. )
  39. # DeepseekV4 decode layer types, keyed by compress_ratio. Each type has a distinct
  40. # (topk, extra_topk, extra_page_block_size) config, so they cannot share a
  41. # FlashMLA tile-scheduler plan. Within a type, all ~60 DeepseekV4 layers share one
  42. # plan per step because b / s_q / h_q / page_block_sizes / topks are identical.
  43. _LAYER_TYPE_SWAONLY = "swaonly"
  44. _LAYER_TYPE_C4A = "c4a"
  45. _LAYER_TYPE_C128A = "c128a"
  46. # v4.1 ratio-1 / ratio-2 indexer layers: same indexer-path shape as C4A, but
  47. # their compressed page block is block_size // ratio, so each gets its own plan.
  48. # v4.1 builders classify layer types themselves (ratio 0 is SWA-only there); see
  49. # deepseek_v4_1/sparse_mla.py.
  50. _LAYER_TYPE_C1A = "c1a"
  51. _LAYER_TYPE_C2A = "c2a"
  52. def _layer_type_for(compress_ratio: int) -> str:
  53. if compress_ratio <= 1:
  54. return _LAYER_TYPE_SWAONLY
  55. if compress_ratio == 2:
  56. return _LAYER_TYPE_C2A
  57. if compress_ratio == 4:
  58. return _LAYER_TYPE_C4A
  59. if compress_ratio == 128:
  60. return _LAYER_TYPE_C128A
  61. raise ValueError(
  62. f"Unsupported DeepseekV4 compress_ratio={compress_ratio}; "
  63. "expected 1, 2, 4, or 128."
  64. )
  65. class DeepseekV4SWACache(torch.nn.Module, AttentionLayerBase):
  66. def __init__(
  67. self,
  68. head_dim: int,
  69. window_size: int,
  70. dtype: torch.dtype,
  71. prefix: str,
  72. cache_config: CacheConfig,
  73. backend_cls: "type[AttentionBackend] | None" = None,
  74. block_size: int = 64,
  75. ):
  76. super().__init__()
  77. self.backend_cls = backend_cls or DeepseekSparseSWABackend
  78. self.kv_cache = torch.tensor([])
  79. self.head_dim = head_dim
  80. self.window_size = window_size
  81. self.prefix = prefix
  82. self.cache_config = cache_config
  83. self.dtype = dtype
  84. compilation_config = get_current_vllm_config().compilation_config
  85. if prefix in compilation_config.static_forward_context:
  86. raise ValueError(f"Duplicate layer name: {prefix}")
  87. compilation_config.static_forward_context[prefix] = self
  88. # Any multiple of 32; the sparse decode kernels take the page size at
  89. # runtime.
  90. self.block_size = block_size
  91. # uint8: fp8_ds_mla UE8M0 paged layout. bfloat16 / float8_e4m3fn:
  92. # contiguous full-cache layout.
  93. assert self.dtype in (torch.uint8, torch.bfloat16, torch.float8_e4m3fn)
  94. def bind_kv_cache(self, kv_cache: torch.Tensor) -> None:
  95. # [B, H=1, N, C] -> [B, N, C]
  96. self.kv_cache = kv_cache.squeeze(1)
  97. def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
  98. # fp8_ds_mla's UE8M0 paged layout needs 576B alignment; contiguous
  99. # bf16/fp8 cache uses the natural element-size page.
  100. uses_fp8_ds_mla_layout = self.cache_config.cache_dtype == "fp8_ds_mla"
  101. return SlidingWindowMLASpec(
  102. block_size=self.block_size,
  103. num_kv_heads=1,
  104. head_size=self.head_dim,
  105. dtype=self.dtype,
  106. sliding_window=self.window_size,
  107. cache_dtype_str=self.cache_config.cache_dtype,
  108. # DeepseekV4 fp8_ds_mla: 584B per token (448B NoPE + 128B RoPE + 8B scales)
  109. state_content_bytes=584 if uses_fp8_ds_mla_layout else None,
  110. # 576B for FlashMLA packing; 512B for FlashInfer sparse (#44577).
  111. alignment=576 if uses_fp8_ds_mla_layout else 512,
  112. model_version="deepseek_v4",
  113. kv_quant_mode=get_kv_quant_mode(self.cache_config.cache_dtype),
  114. # DCP: the 128-token window is tiny; every rank keeps all of it
  115. # (no sharding), so SWA needs no cross-rank combine.
  116. dcp_replicated=True,
  117. )
  118. def forward(self): ...
  119. def get_attn_backend(self) -> type[AttentionBackend]:
  120. return self.backend_cls
  121. class DeepseekSparseSWABackend(AttentionBackend):
  122. @staticmethod
  123. def get_name() -> str:
  124. return "DEEPSEEK_SPARSE_SWA"
  125. @staticmethod
  126. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  127. return [MultipleOf(32)]
  128. @classmethod
  129. def get_swa_block_size(cls) -> int:
  130. """Tech2Wild/Kai 2026-09-10: tokens per SWA cache page handed to
  131. DeepseekV4SWACache. FlashMLA / TRTLLM-gen take the page size at
  132. runtime (32); kernels instantiated for one page size override this."""
  133. return 32
  134. @classmethod
  135. def get_preferred_block_size(cls, default_block_size: int) -> int:
  136. return 256
  137. @classmethod
  138. def get_supported_head_sizes(cls) -> list[int]:
  139. return [512]
  140. @staticmethod
  141. def get_builder_cls() -> type["DeepseekSparseSWAMetadataBuilder"]:
  142. if current_platform.is_rocm():
  143. from vllm.models.deepseek_v4.amd.rocm import (
  144. DeepseekV4ROCMAiterSparseSWAMetadataBuilder,
  145. )
  146. return DeepseekV4ROCMAiterSparseSWAMetadataBuilder
  147. return DeepseekSparseSWAMetadataBuilder
  148. @dataclass
  149. class DeepseekSparseSWAMetadata:
  150. block_table: torch.Tensor
  151. slot_mapping: torch.Tensor
  152. block_size: int
  153. seq_lens: torch.Tensor | None = None # [num_seqs]
  154. query_start_loc: torch.Tensor | None = None # [num_seqs + 1]
  155. query_start_loc_cpu: torch.Tensor | None = None # [num_seqs + 1]
  156. is_valid_token: torch.Tensor | None = None # [num_tokens]
  157. token_to_req_indices: torch.Tensor | None = None # [num_tokens]
  158. decode_swa_indices: torch.Tensor | None = None # [num_decode_tokens, width]
  159. decode_swa_lens: torch.Tensor | None = None # [num_decode_tokens]
  160. # window_size (causal) or noncausal_index_width (DSpark non-causal).
  161. decode_swa_width: int = 0
  162. # Paged-coordinate prefill SWA indices/lens (FP8 paged-direct prefill).
  163. prefill_swa_indices: torch.Tensor | None = (
  164. None # [num_prefill_tokens, 1, prefill_index_width]
  165. )
  166. prefill_swa_lens: torch.Tensor | None = None # [num_prefill_tokens]
  167. # In-image bidirectional visibility (vision variant): per-token counts of
  168. # extra visible tokens to the left/right inside an image span. Indexed by
  169. # absolute (decode-first) token position; only prefill rows are written.
  170. # None when the model is text-only or the batch has no image spans.
  171. prefill_left_visible: torch.Tensor | None = None
  172. prefill_right_visible: torch.Tensor | None = None
  173. # Number of decode/prefill requests/tokens (batch is reordered: decodes first)
  174. num_decodes: int = 0
  175. num_prefills: int = 0
  176. num_decode_tokens: int = 0
  177. num_prefill_tokens: int = 0
  178. max_decode_query_len: int = 1
  179. # Pre-computed prefill metadata shared across all DeepseekV4 attention layers.
  180. prefill_seq_lens: torch.Tensor | None = None
  181. prefill_seq_lens_cpu: torch.Tensor | None = None
  182. prefill_gather_lens: torch.Tensor | None = None
  183. prefill_query_lens_cpu: torch.Tensor | None = None
  184. prefill_window_size: int = 0
  185. prefill_max_model_len: int = 0
  186. prefill_max_num_batched_tokens: int = 0
  187. # Per-layer-type FlashMLA tile-scheduler metadata. One FlashMLASchedMeta
  188. # per present DeepseekV4 layer type, shared across all ~60 layers of that type
  189. # within a decode step. The first forward call of a given type triggers
  190. # the in-kernel planner (which also allocates tile_scheduler_metadata and
  191. # num_splits via PyTorch's graph-aware allocator); subsequent same-type
  192. # calls skip planning and reuse the plan. Fresh instance per build(), so
  193. # have_initialized is always False at the start of a step and the plan
  194. # is re-derived from current seq_lens / topk_length on replay.
  195. # None for layer types the model does not use (or when num_decode_tokens
  196. # is zero).
  197. tile_sched_swaonly: "FlashMLASchedMeta | None" = None
  198. tile_sched_c4a: "FlashMLASchedMeta | None" = None
  199. tile_sched_c128a: "FlashMLASchedMeta | None" = None
  200. tile_sched_c1a: "FlashMLASchedMeta | None" = None
  201. tile_sched_c2a: "FlashMLASchedMeta | None" = None
  202. flashinfer_sparse_index_cache: dict[str, tuple[torch.Tensor, torch.Tensor]] = field(
  203. default_factory=dict
  204. )
  205. def get_prefill_chunk_plan(
  206. self,
  207. compress_ratio: int,
  208. prefill_chunk_size: int,
  209. has_compressed: bool | None = None,
  210. ) -> list[tuple[int, int, int, int]]:
  211. if self.num_prefills == 0:
  212. return []
  213. assert self.prefill_seq_lens_cpu is not None
  214. assert self.prefill_query_lens_cpu is not None
  215. # Whether the layer gathers a compressed-KV region into the prefill
  216. # workspace. v4.0 callers keep the legacy default (ratio <= 1 means
  217. # SWA-only); v4.1 passes has_compressed explicitly because its
  218. # compress_ratio==1 layers DO have a full-length compressed cache.
  219. if has_compressed is None:
  220. has_compressed = compress_ratio > 1
  221. # query_len <= max_num_batched_tokens and
  222. # gather_len = query_len + min(prefix_len, window_size - 1), so the
  223. # worst-case gathered width is bounded by
  224. # max_num_batched_tokens + window_size - 1. The compressed prefix pool
  225. # is bounded by ceil(max_model_len / compress_ratio).
  226. max_workspace_area = prefill_chunk_size * (
  227. (cdiv(self.prefill_max_model_len, compress_ratio) if has_compressed else 0)
  228. + self.prefill_window_size
  229. + self.prefill_max_num_batched_tokens
  230. )
  231. prefix_lens_cpu = self.prefill_seq_lens_cpu - self.prefill_query_lens_cpu
  232. gather_lens_cpu = self.prefill_query_lens_cpu + torch.clamp(
  233. prefix_lens_cpu, min=0, max=self.prefill_window_size - 1
  234. )
  235. compressed_lens_cpu = (
  236. torch.div(
  237. self.prefill_seq_lens_cpu,
  238. compress_ratio,
  239. rounding_mode="floor",
  240. )
  241. if has_compressed
  242. else torch.zeros_like(self.prefill_seq_lens_cpu)
  243. )
  244. chunk_plan: list[tuple[int, int, int, int]] = []
  245. chunk_start = 0
  246. while chunk_start < self.num_prefills:
  247. chunk_max_compressed = int(compressed_lens_cpu[chunk_start].item())
  248. chunk_max_gather = int(gather_lens_cpu[chunk_start].item())
  249. chunk_end = chunk_start + 1
  250. while chunk_end < self.num_prefills:
  251. candidate_max_compressed = max(
  252. chunk_max_compressed,
  253. int(compressed_lens_cpu[chunk_end].item()),
  254. )
  255. candidate_max_gather = max(
  256. chunk_max_gather,
  257. int(gather_lens_cpu[chunk_end].item()),
  258. )
  259. candidate_width = candidate_max_compressed + candidate_max_gather
  260. candidate_area = (chunk_end - chunk_start + 1) * candidate_width
  261. if candidate_area > max_workspace_area:
  262. break
  263. chunk_max_compressed = candidate_max_compressed
  264. chunk_max_gather = candidate_max_gather
  265. chunk_end += 1
  266. chunk_plan.append(
  267. (
  268. chunk_start,
  269. chunk_end,
  270. chunk_max_compressed,
  271. chunk_max_compressed + chunk_max_gather,
  272. )
  273. )
  274. chunk_start = chunk_end
  275. return chunk_plan
  276. class ComputePrefillMetadataKernel(
  277. VllmTritonJitKernel["ComputePrefillMetadataKernel.CompileKey"]
  278. ):
  279. @dataclass(frozen=True)
  280. class CompileKey:
  281. block_size: int
  282. @staticmethod
  283. @triton.jit(do_not_specialize=["num_prefills", "num_decodes", "window_size"])
  284. def kernel(
  285. # Outputs
  286. prefill_gather_lens_ptr,
  287. # Inputs
  288. seq_lens_ptr,
  289. query_start_loc_ptr,
  290. num_prefills,
  291. num_decodes,
  292. window_size,
  293. BLOCK_SIZE: tl.constexpr,
  294. ):
  295. """Compute prefill gather_lens in a single pass."""
  296. offset = tl.arange(0, BLOCK_SIZE)
  297. mask = offset < num_prefills
  298. # SM12x + Triton 3.6 raises IMA on out-of-bounds address arithmetic for
  299. # masked-off lanes even though the load mask gates the actual read, so
  300. # clamp the offset. Caller guarantees num_prefills > 0.
  301. safe_offset = tl.minimum(offset, num_prefills - 1)
  302. seq_len = tl.load(seq_lens_ptr + num_decodes + safe_offset, mask=mask)
  303. qsl_start = tl.load(query_start_loc_ptr + num_decodes + safe_offset, mask=mask)
  304. qsl_end = tl.load(
  305. query_start_loc_ptr + num_decodes + safe_offset + 1, mask=mask
  306. )
  307. query_len = qsl_end - qsl_start
  308. prefix_len = seq_len - query_len
  309. gather_len = query_len + tl.minimum(prefix_len, window_size - 1)
  310. tl.store(prefill_gather_lens_ptr + offset, gather_len, mask=mask)
  311. def dispatch( # type: ignore[override]
  312. self,
  313. *,
  314. num_prefills: int,
  315. ) -> CompileKey:
  316. return self.CompileKey(
  317. block_size=next_power_of_2(num_prefills),
  318. )
  319. def get_warmup_keys(self, vllm_config: VllmConfig) -> list[CompileKey]:
  320. scheduler_config = vllm_config.scheduler_config
  321. max_prefills = max(
  322. 1,
  323. min(
  324. scheduler_config.max_num_seqs,
  325. scheduler_config.max_num_batched_tokens,
  326. ),
  327. )
  328. return self._trace_dispatch(self.dispatch)(
  329. num_prefills=WarmupIntRange(1, max_prefills + 1),
  330. )
  331. def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
  332. int32_ptr = TritonWarmupTensor(torch.int32)
  333. return dict(
  334. prefill_gather_lens=int32_ptr,
  335. seq_lens=int32_ptr,
  336. query_start_loc=int32_ptr,
  337. num_prefills=compile_key.block_size,
  338. num_decodes=0,
  339. window_size=1,
  340. )
  341. @kernel_launcher
  342. def __call__(
  343. self,
  344. prefill_gather_lens: torch.Tensor,
  345. seq_lens: torch.Tensor,
  346. query_start_loc: torch.Tensor,
  347. num_prefills: int,
  348. num_decodes: int,
  349. window_size: int,
  350. ) -> LaunchSpec:
  351. compile_key = self.dispatch(num_prefills=num_prefills)
  352. return (1,), dict(
  353. BLOCK_SIZE=compile_key.block_size,
  354. )
  355. class DeepseekSparseSWAMetadataBuilder(AttentionMetadataBuilder):
  356. """Builds metadata for DeepseekV4 SWA cache.
  357. Similar to the indexer, this handles mixed batches by:
  358. 1. Using split_decodes_and_prefills() to determine the boundary
  359. 2. Building separate metadata for decode and prefill portions
  360. Supports:
  361. - Mixed decode/prefill batches
  362. - MTP (Multi-Token Prediction) where decode has query_len > 1
  363. - Chunked prefill (aligns with the indexer's chunking)
  364. """
  365. reorder_batch_threshold: int | None = None
  366. _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.UNIFORM_BATCH
  367. supports_draft_decode_metadata_update = True
  368. # DSpark's non-causal multi-token decode only touches the replicated SWA
  369. # caches (no cross-rank combine), so it is supported under DCP.
  370. supports_non_causal_multi_token_dcp: ClassVar[bool] = True
  371. def __init__(self, *args, **kwargs):
  372. super().__init__(*args, **kwargs)
  373. assert isinstance(self.kv_cache_spec, SlidingWindowMLASpec | MLAAttentionSpec)
  374. mla_spec = cast(SlidingWindowMLASpec | MLAAttentionSpec, self.kv_cache_spec)
  375. self.head_size = mla_spec.head_size # Already considered quantization.
  376. assert isinstance(mla_spec.tokens_per_state, int)
  377. self.compress_ratio = mla_spec.tokens_per_state
  378. self.block_size = mla_spec.block_size
  379. self.max_model_len = self.vllm_config.model_config.max_model_len
  380. self.max_num_batched_tokens = (
  381. self.vllm_config.scheduler_config.max_num_batched_tokens
  382. )
  383. # Handle MTP: adjust decode_threshold like the indexer does
  384. spec_config = self.vllm_config.speculative_config
  385. self.num_speculative_tokens = (
  386. spec_config.num_speculative_tokens if spec_config else 0
  387. )
  388. # Decode can have query_len up to
  389. # 1 + (2 if parallel drafting else 1) * num_speculative_tokens.
  390. # sparse_swa has no MQA-vs-dense-MHA routing, so multi-token queries take
  391. # the prefill path and the decode/prefill split stays at that width.
  392. spec_mult = (
  393. 2 if (spec_config is not None and spec_config.parallel_drafting) else 1
  394. )
  395. self.decode_threshold = 1 + spec_mult * self.num_speculative_tokens
  396. self.reorder_batch_threshold = None
  397. hf_config = self.vllm_config.model_config.hf_config
  398. assert hasattr(hf_config, "sliding_window")
  399. self.window_size = hf_config.sliding_window
  400. # Vision variant: image spans (up to vision_max_n_token tokens) are
  401. # visible bidirectionally, so prefill index rows widen from
  402. # window_size to window_size + max_image_tokens. Text-only models keep
  403. # max_image_tokens == 0 and take the original code paths everywhere.
  404. self.max_image_tokens = (
  405. getattr(hf_config, "vision_max_n_token", 0)
  406. if getattr(hf_config, "vision_n_layers", 0) > 0
  407. else 0
  408. )
  409. self.prefill_index_width = self.window_size + self.max_image_tokens
  410. # Detect which DeepseekV4 layer types this model uses so we only build a
  411. # FlashMLA tile-scheduler plan for types that will actually be called.
  412. # Models without compress_ratios (pure SWA) fall back to swaonly.
  413. compress_ratios = getattr(hf_config, "compress_ratios", None) or [1]
  414. self._layer_types: set[str] = set()
  415. for ratio in compress_ratios:
  416. self._layer_types.add(_layer_type_for(int(ratio)))
  417. max_tokens = self.vllm_config.scheduler_config.max_num_batched_tokens
  418. self.token_to_req_indices = torch.zeros(
  419. max_tokens,
  420. dtype=torch.int32,
  421. device=self.device,
  422. )
  423. self.decode_swa_indices = torch.zeros(
  424. max_tokens,
  425. 1,
  426. self.window_size,
  427. dtype=torch.int32,
  428. device=self.device,
  429. )
  430. self.decode_swa_lens = torch.zeros(
  431. max_tokens,
  432. dtype=torch.int32,
  433. device=self.device,
  434. )
  435. # Allocated unconditionally — consumer picks paged-direct vs dequant
  436. # at call time.
  437. self.prefill_swa_indices = torch.zeros(
  438. max_tokens,
  439. 1,
  440. self.prefill_index_width,
  441. dtype=torch.int32,
  442. device=self.device,
  443. )
  444. self.prefill_swa_lens = torch.zeros(
  445. max_tokens,
  446. dtype=torch.int32,
  447. device=self.device,
  448. )
  449. # In-image visibility side buffers (vision variant only). The span
  450. # CSR is uploaded per step from mm_req_doc_ranges; span capacity is
  451. # bounded by max_tokens since each span holds at least one token.
  452. if self.max_image_tokens > 0:
  453. self.left_visible = torch.zeros(
  454. max_tokens, dtype=torch.int32, device=self.device
  455. )
  456. self.right_visible = torch.zeros(
  457. max_tokens, dtype=torch.int32, device=self.device
  458. )
  459. self.span_indptr = torch.zeros(
  460. self.vllm_config.scheduler_config.max_num_seqs + 1,
  461. dtype=torch.int32,
  462. device=self.device,
  463. )
  464. self.span_starts = torch.zeros(
  465. max_tokens, dtype=torch.int32, device=self.device
  466. )
  467. self.span_ends = torch.zeros(
  468. max_tokens, dtype=torch.int32, device=self.device
  469. )
  470. self.is_valid_token = torch.zeros(
  471. max_tokens,
  472. dtype=torch.bool,
  473. device=self.device,
  474. )
  475. # DSpark draft: the block is non-causal (every query attends to the
  476. # trailing window of context PLUS all query tokens, including future ones),
  477. # so its per-token index list is wider than `window_size`. The kernel pads
  478. # the q-head count to B_TOPK. Pad to a kernel-supported width; the logical
  479. # SWA window remains unchanged when the padded matrix is built.
  480. self.is_dspark = spec_config is not None and spec_config.use_dspark()
  481. self.noncausal_index_width = (
  482. get_dspark_swa_index_width(
  483. self.window_size,
  484. self.num_speculative_tokens,
  485. )
  486. if self.is_dspark
  487. else 0
  488. )
  489. self.decode_swa_indices_noncausal: torch.Tensor | None = None
  490. self._max_tokens = max_tokens
  491. def build(
  492. self,
  493. common_prefix_len: int,
  494. common_attn_metadata: CommonAttentionMetadata,
  495. fast_build: bool = False,
  496. ) -> DeepseekSparseSWAMetadata:
  497. """Build SWA metadata for mixed decode/prefill batches.
  498. The batch is assumed to be reordered with decodes first (by vLLM scheduler).
  499. We use split_decodes_and_prefills() to find the boundary, then build
  500. separate window_topk_idxs for each portion.
  501. For prefill, we use chunked prefill to align with the indexer's chunking.
  502. """
  503. seq_lens = common_attn_metadata.seq_lens
  504. seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
  505. query_start_loc = common_attn_metadata.query_start_loc
  506. query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu
  507. block_table = common_attn_metadata.block_table_tensor
  508. slot_mapping = common_attn_metadata.slot_mapping
  509. # Split into decode and prefill portions using configurable threshold
  510. (num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens) = (
  511. split_decodes_and_prefills(
  512. common_attn_metadata, decode_threshold=self.decode_threshold
  513. )
  514. )
  515. # NOTE: Ensure all metadata tensors maintain fixed memory addresses
  516. # for CUDA graph compatibility.
  517. token_to_req_indices = common_attn_metadata.token_to_req_indices(
  518. self.token_to_req_indices
  519. )
  520. is_valid_token = self.is_valid_token[: slot_mapping.shape[0]]
  521. is_valid_token.copy_(slot_mapping >= 0)
  522. non_causal = not common_attn_metadata.causal
  523. decode_swa_width = (
  524. self.noncausal_index_width if non_causal else self.window_size
  525. )
  526. decode_swa_indices = self.decode_swa_indices
  527. if num_decode_tokens > 0:
  528. self.decode_swa_lens[num_decode_tokens:] = 0
  529. if non_causal:
  530. assert self.is_dspark, (
  531. "Non-causal DeepseekV4 SWA is only supported for the DSpark "
  532. "speculation mode, but causal=False was set without DSpark."
  533. )
  534. if self.decode_swa_indices_noncausal is None:
  535. self.decode_swa_indices_noncausal = torch.zeros(
  536. self._max_tokens,
  537. 1,
  538. self.noncausal_index_width,
  539. dtype=torch.int32,
  540. device=self.device,
  541. )
  542. decode_swa_indices = self.decode_swa_indices_noncausal
  543. _COMPUTE_DSPARK_NONCAUSAL_SWA_INDICES_KERNEL(
  544. decode_swa_indices,
  545. self.decode_swa_lens,
  546. self.window_size,
  547. self.noncausal_index_width,
  548. query_start_loc,
  549. seq_lens,
  550. token_to_req_indices,
  551. is_valid_token,
  552. block_table,
  553. self.block_size,
  554. num_tokens=num_decode_tokens,
  555. token_offset=0,
  556. )
  557. else:
  558. _COMPUTE_SWA_INDICES_AND_LENS_KERNEL(
  559. decode_swa_indices,
  560. self.decode_swa_lens,
  561. self.window_size,
  562. decode_swa_indices.shape[-1],
  563. self.decode_swa_lens, # unused (HAS_IMAGE=False)
  564. self.decode_swa_lens, # unused (HAS_IMAGE=False)
  565. query_start_loc,
  566. seq_lens,
  567. token_to_req_indices,
  568. is_valid_token,
  569. block_table,
  570. self.block_size,
  571. num_tokens=num_decode_tokens,
  572. token_offset=0,
  573. )
  574. # Vision variant: per-token in-image visibility for prefill tokens.
  575. # Decode tokens are always past the image spans (spans are prefilled
  576. # atomically), so the decode path above never needs them.
  577. prefill_left_visible: torch.Tensor | None = None
  578. prefill_right_visible: torch.Tensor | None = None
  579. mm_ranges = common_attn_metadata.mm_req_doc_ranges
  580. if (
  581. self.max_image_tokens > 0
  582. and num_prefill_tokens > 0
  583. and mm_ranges
  584. and any(mm_ranges.values())
  585. ):
  586. prefill_left_visible, prefill_right_visible = self._build_image_visibility(
  587. common_attn_metadata.num_reqs,
  588. mm_ranges,
  589. num_decode_tokens,
  590. num_prefill_tokens,
  591. seq_lens,
  592. query_start_loc,
  593. token_to_req_indices,
  594. )
  595. # Prefill SWA indices live in paged coordinates. `token_offset` lets
  596. # the kernel read is_valid_token / token_to_req_indices at absolute
  597. # prefill positions while writing output starting at index 0.
  598. if num_prefill_tokens > 0:
  599. has_image = prefill_left_visible is not None
  600. prefill_swa_indices = self.prefill_swa_indices[:num_prefill_tokens]
  601. prefill_swa_lens = self.prefill_swa_lens[:num_prefill_tokens]
  602. _COMPUTE_SWA_INDICES_AND_LENS_KERNEL(
  603. prefill_swa_indices,
  604. prefill_swa_lens,
  605. self.window_size,
  606. self.prefill_index_width,
  607. prefill_left_visible if has_image else prefill_swa_lens,
  608. prefill_right_visible if has_image else prefill_swa_lens,
  609. query_start_loc,
  610. seq_lens,
  611. token_to_req_indices,
  612. is_valid_token,
  613. block_table,
  614. self.block_size,
  615. num_tokens=num_prefill_tokens,
  616. token_offset=num_decode_tokens,
  617. has_image=has_image,
  618. )
  619. # Pre-compute DeepseekV4 prefill metadata shared across all attention layers.
  620. deepseek_v4_fields = self._build_deepseek_v4_metadata(
  621. num_decodes,
  622. num_prefills,
  623. seq_lens,
  624. seq_lens_cpu,
  625. query_start_loc,
  626. query_start_loc_cpu,
  627. )
  628. # Per-layer-type tile-scheduler plan holders. Empty FlashMLASchedMeta
  629. # per present DeepseekV4 layer type; the first flash_mla_with_kvcache call of
  630. # each type triggers the planner and all same-type layers reuse the
  631. # resulting plan for the rest of the step.
  632. tile_sched = self.build_tile_scheduler(num_decode_tokens)
  633. return DeepseekSparseSWAMetadata(
  634. seq_lens=seq_lens,
  635. query_start_loc=query_start_loc,
  636. query_start_loc_cpu=query_start_loc_cpu,
  637. block_table=block_table,
  638. slot_mapping=slot_mapping,
  639. is_valid_token=is_valid_token,
  640. token_to_req_indices=token_to_req_indices,
  641. decode_swa_indices=decode_swa_indices[:num_decode_tokens],
  642. decode_swa_lens=self.decode_swa_lens[:num_decode_tokens],
  643. decode_swa_width=decode_swa_width,
  644. prefill_swa_indices=(
  645. self.prefill_swa_indices[:num_prefill_tokens]
  646. if num_prefill_tokens > 0
  647. else None
  648. ),
  649. prefill_swa_lens=(
  650. self.prefill_swa_lens[:num_prefill_tokens]
  651. if num_prefill_tokens > 0
  652. else None
  653. ),
  654. prefill_left_visible=prefill_left_visible,
  655. prefill_right_visible=prefill_right_visible,
  656. block_size=self.block_size,
  657. num_decodes=num_decodes,
  658. num_prefills=num_prefills,
  659. num_decode_tokens=num_decode_tokens,
  660. num_prefill_tokens=num_prefill_tokens,
  661. # Upper bound on decode-split rows for the kernel's max_q_len
  662. # hint. common max_query_len bounds every row (scheduled max under
  663. # adaptive verification), clamped to what the split can admit so a
  664. # mixed batch's prefill max does not inflate decode scheduling.
  665. max_decode_query_len=min(
  666. common_attn_metadata.max_query_len, self.decode_threshold
  667. ),
  668. tile_sched_swaonly=tile_sched[_LAYER_TYPE_SWAONLY],
  669. tile_sched_c4a=tile_sched[_LAYER_TYPE_C4A],
  670. tile_sched_c128a=tile_sched[_LAYER_TYPE_C128A],
  671. tile_sched_c1a=tile_sched[_LAYER_TYPE_C1A],
  672. tile_sched_c2a=tile_sched[_LAYER_TYPE_C2A],
  673. **deepseek_v4_fields, # type: ignore[arg-type]
  674. )
  675. def _build_image_visibility(
  676. self,
  677. num_reqs: int,
  678. mm_ranges: dict[int, list[tuple[int, int]]],
  679. num_decode_tokens: int,
  680. num_prefill_tokens: int,
  681. seq_lens: torch.Tensor,
  682. query_start_loc: torch.Tensor,
  683. token_to_req_indices: torch.Tensor,
  684. ) -> tuple[torch.Tensor, torch.Tensor]:
  685. """Upload image spans and compute per-token in-image visibility.
  686. Returns (left, right) int32 tensors indexed by absolute (decode-first)
  687. token position; only prefill rows are meaningful.
  688. """
  689. indptr = [0] * (num_reqs + 1)
  690. starts: list[int] = []
  691. ends: list[int] = []
  692. for req_idx in range(num_reqs):
  693. for span_start, span_end in mm_ranges.get(req_idx, ()):
  694. starts.append(span_start)
  695. ends.append(span_end)
  696. indptr[req_idx + 1] = len(starts)
  697. num_spans = len(starts)
  698. assert num_spans <= self.span_starts.shape[0]
  699. self.span_indptr[: num_reqs + 1].copy_(torch.tensor(indptr, dtype=torch.int32))
  700. if num_spans > 0:
  701. self.span_starts[:num_spans].copy_(torch.tensor(starts, dtype=torch.int32))
  702. self.span_ends[:num_spans].copy_(torch.tensor(ends, dtype=torch.int32))
  703. _compute_image_visibility_kernel[(num_prefill_tokens,)](
  704. self.left_visible,
  705. self.right_visible,
  706. self.span_indptr,
  707. self.span_starts,
  708. self.span_ends,
  709. query_start_loc,
  710. seq_lens,
  711. token_to_req_indices,
  712. self.max_image_tokens,
  713. token_offset=num_decode_tokens,
  714. )
  715. end = num_decode_tokens + num_prefill_tokens
  716. return self.left_visible[:end], self.right_visible[:end]
  717. def update_draft_decode_metadata(
  718. self,
  719. metadata: DeepseekSparseSWAMetadata,
  720. ) -> None:
  721. if metadata.num_decode_tokens == 0:
  722. return
  723. assert metadata.query_start_loc is not None
  724. assert metadata.seq_lens is not None
  725. assert metadata.token_to_req_indices is not None
  726. assert metadata.is_valid_token is not None
  727. assert metadata.decode_swa_indices is not None
  728. assert metadata.decode_swa_lens is not None
  729. _COMPUTE_SWA_INDICES_AND_LENS_KERNEL(
  730. metadata.decode_swa_indices,
  731. metadata.decode_swa_lens,
  732. metadata.decode_swa_indices.shape[-1],
  733. metadata.decode_swa_indices.shape[-1],
  734. metadata.decode_swa_lens, # unused (HAS_IMAGE=False)
  735. metadata.decode_swa_lens, # unused (HAS_IMAGE=False)
  736. metadata.query_start_loc,
  737. metadata.seq_lens,
  738. metadata.token_to_req_indices,
  739. metadata.is_valid_token,
  740. metadata.block_table,
  741. self.block_size,
  742. num_tokens=metadata.num_decode_tokens,
  743. token_offset=0,
  744. )
  745. tile_sched = self.build_tile_scheduler(metadata.num_decode_tokens)
  746. metadata.tile_sched_swaonly = tile_sched[_LAYER_TYPE_SWAONLY]
  747. metadata.tile_sched_c4a = tile_sched[_LAYER_TYPE_C4A]
  748. metadata.tile_sched_c128a = tile_sched[_LAYER_TYPE_C128A]
  749. metadata.tile_sched_c1a = tile_sched[_LAYER_TYPE_C1A]
  750. metadata.tile_sched_c2a = tile_sched[_LAYER_TYPE_C2A]
  751. metadata.flashinfer_sparse_index_cache.clear()
  752. def build_tile_scheduler(
  753. self, num_decode_tokens: int
  754. ) -> dict[str, FlashMLASchedMeta | None]:
  755. """Allocate one empty ``FlashMLASchedMeta`` per present DeepseekV4 layer type.
  756. Returned instances have ``tile_scheduler_metadata`` / ``num_splits``
  757. set to ``None``; the FlashMLA C++ decode path will allocate them and
  758. run the tile-scheduler planner on the first ``flash_mla_with_kvcache``
  759. call of each type. Subsequent same-type calls reuse the plan because
  760. the tensors (and ``have_initialized``) are populated on the struct.
  761. Returns all-``None`` when there are no decode tokens this step, so
  762. ``_forward_decode`` sees a clean sentinel.
  763. """
  764. out: dict[str, FlashMLASchedMeta | None] = {
  765. _LAYER_TYPE_SWAONLY: None,
  766. _LAYER_TYPE_C4A: None,
  767. _LAYER_TYPE_C128A: None,
  768. _LAYER_TYPE_C1A: None,
  769. _LAYER_TYPE_C2A: None,
  770. }
  771. if (
  772. num_decode_tokens == 0
  773. or current_platform.is_rocm()
  774. or current_platform.is_xpu()
  775. or current_platform.is_device_capability_family(120)
  776. ):
  777. return out
  778. for layer_type in self._layer_types:
  779. # get_mla_metadata() is the official FlashMLA entry point that
  780. # returns a fresh empty FlashMLASchedMeta; using it keeps this
  781. # call site aligned with the rest of the vLLM FlashMLA backends
  782. # that already go through the same stub.
  783. out[layer_type] = get_mla_metadata()[0]
  784. return out
  785. def _build_deepseek_v4_metadata(
  786. self,
  787. num_decodes: int,
  788. num_prefills: int,
  789. seq_lens: torch.Tensor,
  790. seq_lens_cpu: torch.Tensor | None,
  791. query_start_loc: torch.Tensor,
  792. query_start_loc_cpu: torch.Tensor,
  793. ) -> dict[str, torch.Tensor | int | None]:
  794. """Pre-compute DeepseekV4 prefill metadata during the metadata build phase.
  795. Returns a dict of keyword arguments to pass to the
  796. DeepseekSparseSWAMetadata constructor.
  797. Note: C128A sparse metadata is computed by the FlashMLASparse builder
  798. (which owns the C128A block_table), not here.
  799. """
  800. result: dict[str, torch.Tensor | int | None] = {}
  801. # --- Prefill query metadata (single Triton kernel + CPU slicing) ---
  802. if num_prefills > 0:
  803. assert seq_lens_cpu is not None
  804. pfx_gather_lens = torch.empty(
  805. num_prefills, dtype=torch.int32, device=seq_lens.device
  806. )
  807. _COMPUTE_PREFILL_METADATA_KERNEL(
  808. pfx_gather_lens,
  809. seq_lens,
  810. query_start_loc,
  811. num_prefills,
  812. num_decodes,
  813. self.window_size,
  814. )
  815. result["prefill_seq_lens"] = seq_lens[num_decodes:]
  816. result["prefill_seq_lens_cpu"] = seq_lens_cpu[num_decodes:]
  817. result["prefill_gather_lens"] = pfx_gather_lens
  818. result["prefill_query_lens_cpu"] = (
  819. query_start_loc_cpu[num_decodes + 1 : num_decodes + num_prefills + 1]
  820. - query_start_loc_cpu[num_decodes : num_decodes + num_prefills]
  821. ).to(dtype=torch.int32)
  822. result["prefill_window_size"] = self.window_size
  823. result["prefill_max_model_len"] = self.max_model_len
  824. result["prefill_max_num_batched_tokens"] = self.max_num_batched_tokens
  825. return result
  826. @triton.jit(do_not_specialize=["token_offset"])
  827. def _compute_image_visibility_kernel(
  828. left_visible_ptr,
  829. right_visible_ptr,
  830. span_indptr_ptr,
  831. span_starts_ptr,
  832. span_ends_ptr,
  833. query_start_loc_ptr,
  834. seq_lens_ptr,
  835. token_to_req_indices_ptr,
  836. max_image_tokens,
  837. token_offset,
  838. ):
  839. """Per-token in-image visible counts (port of `get_image_visible`).
  840. One program per prefill token. A token at position pos inside a span
  841. [span_start, span_end] sees min(pos - span_start, max_image_tokens - 1)
  842. extra tokens to its left and min(span_end - pos, max_image_tokens) to its
  843. right; tokens outside every span get 0/0 (plain causal window).
  844. """
  845. pid = tl.program_id(0)
  846. token_idx = pid + token_offset
  847. req_idx = tl.load(token_to_req_indices_ptr + token_idx)
  848. query_start = tl.load(query_start_loc_ptr + req_idx)
  849. query_end = tl.load(query_start_loc_ptr + req_idx + 1)
  850. seq_len = tl.load(seq_lens_ptr + req_idx)
  851. pos = seq_len - (query_end - query_start) + token_idx - query_start
  852. span_lo = tl.load(span_indptr_ptr + req_idx)
  853. span_hi = tl.load(span_indptr_ptr + req_idx + 1)
  854. left = tl.zeros((), dtype=tl.int32)
  855. right = tl.zeros((), dtype=tl.int32)
  856. for i in range(span_lo, span_hi):
  857. span_start = tl.load(span_starts_ptr + i)
  858. span_end = tl.load(span_ends_ptr + i)
  859. in_span = (pos >= span_start) & (pos <= span_end)
  860. left = tl.where(
  861. in_span, tl.minimum(pos - span_start, max_image_tokens - 1), left
  862. )
  863. right = tl.where(in_span, tl.minimum(span_end - pos, max_image_tokens), right)
  864. tl.store(left_visible_ptr + token_idx, left)
  865. tl.store(right_visible_ptr + token_idx, right)
  866. # TODO(ben): unify this kernel to reduce duplication
  867. @triton.jit(
  868. do_not_specialize=[
  869. "swa_indices_stride",
  870. "block_table_stride",
  871. "token_offset",
  872. ]
  873. )
  874. def _compute_swa_indices_and_lens_kernel(
  875. swa_indices_ptr,
  876. swa_indices_stride,
  877. swa_lens_ptr,
  878. window_size,
  879. index_width,
  880. left_visible_ptr,
  881. right_visible_ptr,
  882. query_start_loc_ptr,
  883. seq_lens_ptr,
  884. token_to_req_indices_ptr,
  885. is_valid_token_ptr,
  886. block_table_ptr,
  887. block_table_stride,
  888. block_size,
  889. token_offset,
  890. HAS_IMAGE: tl.constexpr,
  891. TRITON_BLOCK_SIZE: tl.constexpr,
  892. ):
  893. pid = tl.program_id(0)
  894. token_idx = pid + token_offset
  895. is_valid = tl.load(is_valid_token_ptr + token_idx)
  896. if not is_valid:
  897. tl.store(swa_lens_ptr + pid, 0)
  898. # Clear the row so a padded token cannot gather through stale indices.
  899. for i in range(0, index_width, TRITON_BLOCK_SIZE):
  900. offset = i + tl.arange(0, TRITON_BLOCK_SIZE)
  901. tl.store(
  902. swa_indices_ptr + pid * swa_indices_stride + offset,
  903. -1,
  904. mask=offset < index_width,
  905. )
  906. return
  907. req_idx = tl.load(token_to_req_indices_ptr + token_idx)
  908. query_start = tl.load(query_start_loc_ptr + req_idx)
  909. query_end = tl.load(query_start_loc_ptr + req_idx + 1)
  910. query_len = query_end - query_start
  911. seq_len = tl.load(seq_lens_ptr + req_idx)
  912. prefix_len = seq_len - query_len
  913. pos = prefix_len + token_idx - query_start
  914. if HAS_IMAGE:
  915. # In-image bidirectional visibility widens the window: the window
  916. # starts up to max(left - (window - 1), 0) positions earlier and
  917. # extends `right` positions past the query token.
  918. left = tl.load(left_visible_ptr + token_idx)
  919. right = tl.load(right_visible_ptr + token_idx)
  920. else:
  921. left = 0
  922. right = 0
  923. left_add = tl.maximum(left - (window_size - 1), 0)
  924. start_pos = tl.maximum(pos - (window_size - 1) - left_add, 0)
  925. end_pos = pos + right + 1
  926. swa_len = end_pos - start_pos
  927. tl.store(swa_lens_ptr + pid, swa_len)
  928. for i in range(0, index_width, TRITON_BLOCK_SIZE):
  929. offset = i + tl.arange(0, TRITON_BLOCK_SIZE)
  930. pos_offset = start_pos + offset
  931. block_indices = pos_offset // block_size
  932. block_numbers = tl.load(
  933. block_table_ptr + req_idx * block_table_stride + block_indices,
  934. mask=pos_offset < end_pos,
  935. )
  936. block_offsets = pos_offset % block_size
  937. slot_ids = block_numbers * block_size + block_offsets
  938. slot_ids = tl.where(offset < swa_len, slot_ids, -1)
  939. tl.store(
  940. swa_indices_ptr + pid * swa_indices_stride + offset,
  941. slot_ids,
  942. mask=offset < index_width,
  943. )
  944. class ComputeSWAIndicesAndLensKernel(
  945. VllmTritonJitKernel["ComputeSWAIndicesAndLensKernel.CompileKey"]
  946. ):
  947. @dataclass(frozen=True)
  948. class CompileKey:
  949. window_size: int
  950. index_width: int
  951. has_image: int
  952. block_size: int
  953. triton_block_size: int
  954. kernel = staticmethod(_compute_swa_indices_and_lens_kernel)
  955. def dispatch( # type: ignore[override]
  956. self,
  957. **compile_key_fields: int,
  958. ) -> CompileKey:
  959. return self.CompileKey(
  960. **compile_key_fields,
  961. triton_block_size=1024,
  962. )
  963. def get_warmup_keys(
  964. self,
  965. *,
  966. window_size: int,
  967. block_size: int,
  968. max_image_tokens: int = 0,
  969. ) -> list[CompileKey]:
  970. # Decode rows always use the plain causal window. Vision models widen
  971. # prefill rows by max_image_tokens and additionally need the in-image
  972. # bidirectional variant for batches that contain image spans.
  973. width_has_image = [(window_size, 0)]
  974. if max_image_tokens > 0:
  975. width_has_image += [
  976. (window_size + max_image_tokens, 0),
  977. (window_size + max_image_tokens, 1),
  978. ]
  979. return self._trace_dispatch(self.dispatch)(
  980. zip_inputs(
  981. *(
  982. dict(index_width=index_width, has_image=has_image)
  983. for index_width, has_image in width_has_image
  984. )
  985. ),
  986. window_size=window_size,
  987. block_size=block_size,
  988. )
  989. def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
  990. int32_ptr = TritonWarmupTensor(torch.int32)
  991. return dict(
  992. swa_indices=TritonWarmupTensor(torch.int32, shape=(1, 1), strides=(1, 1)),
  993. swa_lens=int32_ptr,
  994. window_size=compile_key.window_size,
  995. index_width=compile_key.index_width,
  996. left_visible=int32_ptr,
  997. right_visible=int32_ptr,
  998. query_start_loc=int32_ptr,
  999. seq_lens=int32_ptr,
  1000. token_to_req_indices=int32_ptr,
  1001. is_valid_token=TritonWarmupTensor(torch.bool),
  1002. block_table=TritonWarmupTensor(torch.int32, shape=(1, 1), strides=(1, 1)),
  1003. block_size=compile_key.block_size,
  1004. num_tokens=1,
  1005. token_offset=0,
  1006. has_image=bool(compile_key.has_image),
  1007. )
  1008. @kernel_launcher
  1009. def __call__(
  1010. self,
  1011. swa_indices: torch.Tensor,
  1012. swa_lens: torch.Tensor,
  1013. window_size: int,
  1014. index_width: int,
  1015. left_visible: torch.Tensor,
  1016. right_visible: torch.Tensor,
  1017. query_start_loc: torch.Tensor,
  1018. seq_lens: torch.Tensor,
  1019. token_to_req_indices: torch.Tensor,
  1020. is_valid_token: torch.Tensor,
  1021. block_table: torch.Tensor,
  1022. block_size: int,
  1023. *,
  1024. num_tokens: int,
  1025. token_offset: int,
  1026. has_image: bool = False,
  1027. ) -> LaunchSpec:
  1028. return (num_tokens,), dict(
  1029. swa_indices_stride=swa_indices.stride(0),
  1030. block_table_stride=block_table.stride(0),
  1031. HAS_IMAGE=has_image,
  1032. TRITON_BLOCK_SIZE=1024,
  1033. )
  1034. class ComputeDSparkNoncausalSWAIndicesKernel(
  1035. VllmTritonJitKernel["ComputeDSparkNoncausalSWAIndicesKernel.CompileKey"]
  1036. ):
  1037. @dataclass(frozen=True)
  1038. class CompileKey:
  1039. window_size: int
  1040. index_width: int
  1041. block_size: int
  1042. triton_block_size: int
  1043. @staticmethod
  1044. @triton.jit(
  1045. do_not_specialize=[
  1046. "swa_indices_stride",
  1047. "block_table_stride",
  1048. "token_offset",
  1049. ]
  1050. )
  1051. def kernel(
  1052. swa_indices_ptr,
  1053. swa_indices_stride,
  1054. swa_lens_ptr,
  1055. window_size,
  1056. index_width,
  1057. query_start_loc_ptr,
  1058. seq_lens_ptr,
  1059. token_to_req_indices_ptr,
  1060. is_valid_token_ptr,
  1061. block_table_ptr,
  1062. block_table_stride,
  1063. block_size,
  1064. token_offset,
  1065. TRITON_BLOCK_SIZE: tl.constexpr,
  1066. ):
  1067. """Non-causal per-token indices for the DSpark draft block.
  1068. Here, we populate the topk indices with the trailing window of context tokens,
  1069. plus all query tokens (including future ones).
  1070. """
  1071. pid = tl.program_id(0)
  1072. token_idx = pid + token_offset
  1073. is_valid = tl.load(is_valid_token_ptr + token_idx)
  1074. if not is_valid:
  1075. tl.store(swa_lens_ptr + pid, 0)
  1076. # Clear the row so a padded token cannot gather through stale indices.
  1077. for i in range(0, index_width, TRITON_BLOCK_SIZE):
  1078. offset = i + tl.arange(0, TRITON_BLOCK_SIZE)
  1079. tl.store(
  1080. swa_indices_ptr + pid * swa_indices_stride + offset,
  1081. -1,
  1082. mask=offset < index_width,
  1083. )
  1084. return
  1085. req_idx = tl.load(token_to_req_indices_ptr + token_idx)
  1086. query_start = tl.load(query_start_loc_ptr + req_idx)
  1087. query_end = tl.load(query_start_loc_ptr + req_idx + 1)
  1088. query_len = query_end - query_start
  1089. seq_len = tl.load(seq_lens_ptr + req_idx)
  1090. prefix_len = seq_len - query_len
  1091. # Block-anchored window (shared by every token in the block) + full block.
  1092. start_pos = tl.maximum(prefix_len - window_size, 0)
  1093. end_pos = seq_len
  1094. swa_len = end_pos - start_pos
  1095. tl.store(swa_lens_ptr + pid, swa_len)
  1096. for i in range(0, index_width, TRITON_BLOCK_SIZE):
  1097. offset = i + tl.arange(0, TRITON_BLOCK_SIZE)
  1098. pos_offset = start_pos + offset
  1099. block_indices = pos_offset // block_size
  1100. block_numbers = tl.load(
  1101. block_table_ptr + req_idx * block_table_stride + block_indices,
  1102. mask=pos_offset < end_pos,
  1103. )
  1104. block_offsets = pos_offset % block_size
  1105. slot_ids = block_numbers * block_size + block_offsets
  1106. slot_ids = tl.where(offset < swa_len, slot_ids, -1)
  1107. tl.store(
  1108. swa_indices_ptr + pid * swa_indices_stride + offset,
  1109. slot_ids,
  1110. mask=offset < index_width,
  1111. )
  1112. def dispatch( # type: ignore[override]
  1113. self,
  1114. *,
  1115. window_size: int,
  1116. num_speculative_tokens: int,
  1117. block_size: int,
  1118. ) -> CompileKey:
  1119. return self.CompileKey(
  1120. window_size=window_size,
  1121. index_width=get_dspark_swa_index_width(window_size, num_speculative_tokens),
  1122. block_size=block_size,
  1123. triton_block_size=1024,
  1124. )
  1125. def get_warmup_keys(
  1126. self,
  1127. *,
  1128. window_size: int,
  1129. num_speculative_tokens: int,
  1130. block_size: int,
  1131. ) -> list[CompileKey]:
  1132. return self._trace_dispatch(self.dispatch)(
  1133. window_size=window_size,
  1134. num_speculative_tokens=num_speculative_tokens,
  1135. block_size=block_size,
  1136. )
  1137. def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
  1138. int32_ptr = TritonWarmupTensor(torch.int32)
  1139. return dict(
  1140. swa_indices=TritonWarmupTensor(torch.int32, shape=(1, 1), strides=(1, 1)),
  1141. swa_lens=int32_ptr,
  1142. window_size=compile_key.window_size,
  1143. index_width=compile_key.index_width,
  1144. query_start_loc=int32_ptr,
  1145. seq_lens=int32_ptr,
  1146. token_to_req_indices=int32_ptr,
  1147. is_valid_token=TritonWarmupTensor(torch.bool),
  1148. block_table=TritonWarmupTensor(torch.int32, shape=(1, 1), strides=(1, 1)),
  1149. block_size=compile_key.block_size,
  1150. num_tokens=1,
  1151. token_offset=0,
  1152. )
  1153. @kernel_launcher
  1154. def __call__(
  1155. self,
  1156. swa_indices: torch.Tensor,
  1157. swa_lens: torch.Tensor,
  1158. window_size: int,
  1159. index_width: int,
  1160. query_start_loc: torch.Tensor,
  1161. seq_lens: torch.Tensor,
  1162. token_to_req_indices: torch.Tensor,
  1163. is_valid_token: torch.Tensor,
  1164. block_table: torch.Tensor,
  1165. block_size: int,
  1166. *,
  1167. num_tokens: int,
  1168. token_offset: int,
  1169. ) -> LaunchSpec:
  1170. return (num_tokens,), dict(
  1171. swa_indices_stride=swa_indices.stride(0),
  1172. block_table_stride=block_table.stride(0),
  1173. TRITON_BLOCK_SIZE=1024,
  1174. )
  1175. _COMPUTE_PREFILL_METADATA_KERNEL = ComputePrefillMetadataKernel()
  1176. _COMPUTE_SWA_INDICES_AND_LENS_KERNEL = ComputeSWAIndicesAndLensKernel()
  1177. _COMPUTE_DSPARK_NONCAUSAL_SWA_INDICES_KERNEL = ComputeDSparkNoncausalSWAIndicesKernel()