sparse_swa.py 49 KB

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