sparse_swa.py 49 KB

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