sparse_swa.py 48 KB

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