gdn_attn.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. """Backend for GatedDeltaNet attention."""
  4. from dataclasses import dataclass, replace
  5. from typing import Literal
  6. import os
  7. import torch
  8. from vllm.config import VllmConfig
  9. from vllm.utils.torch_utils import async_tensor_h2d
  10. from vllm.v1.attention.backend import (
  11. AttentionBackend,
  12. AttentionCGSupport,
  13. AttentionMetadataBuilder,
  14. CommonAttentionMetadata,
  15. )
  16. from vllm.v1.attention.backends.utils import (
  17. NULL_BLOCK_ID,
  18. compute_causal_conv1d_metadata,
  19. mamba_get_block_table_tensor,
  20. split_decodes_and_prefills,
  21. )
  22. from vllm.v1.kv_cache_interface import MambaSpec
  23. class GDNAttentionBackend(AttentionBackend):
  24. @staticmethod
  25. def get_name() -> str:
  26. return "GDN_ATTN"
  27. @staticmethod
  28. def get_builder_cls() -> type["GDNAttentionMetadataBuilder"]:
  29. return GDNAttentionMetadataBuilder
  30. @classmethod
  31. def is_ssm(cls) -> bool:
  32. return True
  33. @dataclass
  34. class GDNAttentionMetadata:
  35. num_prefills: int
  36. num_prefill_tokens: int
  37. num_decodes: int
  38. num_decode_tokens: int
  39. num_spec_decodes: int
  40. num_spec_decode_tokens: int
  41. num_actual_tokens: int
  42. has_initial_state: torch.Tensor | None = None
  43. spec_query_start_loc: torch.Tensor | None = None # shape: [num_spec_decodes + 1,]
  44. non_spec_query_start_loc: torch.Tensor | None = (
  45. None # shape: [batch - num_spec_decodes + 1,]
  46. )
  47. spec_state_indices_tensor: torch.Tensor | None = None # shape: [batch, num_spec]
  48. non_spec_state_indices_tensor: torch.Tensor | None = (
  49. None # shape: [batch - num_spec_decodes,]
  50. )
  51. spec_sequence_masks: torch.Tensor | None = None # shape: [batch,]
  52. spec_sequence_masks_cpu: torch.Tensor | None = None # shape: [batch,]
  53. spec_token_indx: torch.Tensor | None = None
  54. non_spec_token_indx: torch.Tensor | None = None
  55. num_accepted_tokens: torch.Tensor | None = None # shape: [batch,]
  56. uniform_spec_sequence_length: int | None = None # None for ragged batches
  57. # Pre-computed FLA chunk metadata (avoids GPU->CPU sync in prepare_chunk_indices)
  58. chunk_indices: torch.Tensor | None = None
  59. chunk_offsets: torch.Tensor | None = None
  60. # Chunk-kernel inputs for prefill
  61. prefill_query_start_loc: torch.Tensor | None = None
  62. prefill_state_indices: torch.Tensor | None = None
  63. prefill_has_initial_state: torch.Tensor | None = None
  64. # The following attributes are for triton implementation of causal_conv1d
  65. nums_dict: dict | None = None
  66. batch_ptr: torch.Tensor | None = None
  67. token_chunk_offset_ptr: torch.Tensor | None = None
  68. # GB10: a decode step builds this metadata once per KDA KV-cache group (four
  69. # on GLM-5.3-Flash) from the same step inputs. The pure speculative-decode case
  70. # computes the group-independent parts once per step; each builder still reads
  71. # its own block table and copies into its own CUDA-graph buffers.
  72. _spec_step_cache: dict = {}
  73. # Diagnostic: while this file exists, every builder computes everything itself.
  74. _CACHE_OFF_FLAG = os.environ.get("VLLM_GDN_STEP_CACHE_OFF_FLAG", "/root/.cache/gdn-step-cache-off")
  75. class GDNAttentionMetadataBuilder(AttentionMetadataBuilder[GDNAttentionMetadata]):
  76. kv_cache_spec: MambaSpec
  77. _cudagraph_support = AttentionCGSupport.UNIFORM_BATCH
  78. reorder_batch_threshold: int = 1
  79. def __init__(
  80. self,
  81. kv_cache_spec: MambaSpec,
  82. layer_names: list[str],
  83. vllm_config: VllmConfig,
  84. device: torch.device,
  85. ):
  86. super().__init__(kv_cache_spec, layer_names, vllm_config, device)
  87. self.compilation_config = vllm_config.compilation_config
  88. self.speculative_config = vllm_config.speculative_config
  89. from vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn import (
  90. _resolve_gdn_prefill_backend,
  91. )
  92. self.gdn_prefill_backend: Literal["triton", "flashinfer", "cutedsl"]
  93. _, self.gdn_prefill_backend = _resolve_gdn_prefill_backend(vllm_config)
  94. if self.speculative_config:
  95. assert self.speculative_config.num_speculative_tokens is not None
  96. self.num_spec: int = self.speculative_config.num_speculative_tokens
  97. else:
  98. self.num_spec = 0
  99. self.use_spec_decode: bool = self.num_spec > 0
  100. self._init_reorder_batch_threshold(1, self.use_spec_decode)
  101. self.use_full_cuda_graph: bool = (
  102. self.compilation_config.cudagraph_mode.has_full_cudagraphs()
  103. )
  104. # update_block_table() keeps the source group's batch-level FULL graph
  105. # buffers, so only MRV2, which also reuses metadata at capture, may use
  106. # it.
  107. self.supports_update_block_table = (
  108. vllm_config.use_v2_model_runner
  109. and device.type == "cuda"
  110. # Not isinstance: KDA's RecoverSSM/checkpoint metadata is per group.
  111. and type(self) is GDNAttentionMetadataBuilder
  112. )
  113. if self.supports_update_block_table:
  114. # Opts into MRV2's CUDA-only aligned-index precompute.
  115. self.mamba_aligned_state_indices: torch.Tensor | None = None
  116. self.decode_cudagraph_max_bs: int = (
  117. self.vllm_config.scheduler_config.max_num_seqs * (self.num_spec + 1)
  118. )
  119. if self.compilation_config.max_cudagraph_capture_size is not None:
  120. self.decode_cudagraph_max_bs = min(
  121. self.decode_cudagraph_max_bs,
  122. self.compilation_config.max_cudagraph_capture_size,
  123. )
  124. self.spec_state_indices_tensor: torch.Tensor = torch.empty(
  125. (self.decode_cudagraph_max_bs, self.num_spec + 1),
  126. dtype=torch.int32,
  127. device=device,
  128. )
  129. self.non_spec_state_indices_tensor: torch.Tensor = torch.empty(
  130. (self.decode_cudagraph_max_bs,),
  131. dtype=torch.int32,
  132. device=device,
  133. )
  134. self.spec_sequence_masks: torch.Tensor = torch.empty(
  135. (self.decode_cudagraph_max_bs,),
  136. dtype=torch.bool,
  137. device=device,
  138. )
  139. self.spec_token_indx: torch.Tensor = torch.empty(
  140. (self.decode_cudagraph_max_bs * (self.num_spec + 1),),
  141. dtype=torch.int32,
  142. device=device,
  143. )
  144. self.non_spec_token_indx: torch.Tensor = torch.empty(
  145. (self.decode_cudagraph_max_bs * (self.num_spec + 1),),
  146. dtype=torch.int32,
  147. device=device,
  148. )
  149. self.spec_query_start_loc: torch.Tensor = torch.empty(
  150. (self.decode_cudagraph_max_bs + 1,),
  151. dtype=torch.int32,
  152. device=device,
  153. )
  154. self.non_spec_query_start_loc: torch.Tensor = torch.empty(
  155. (self.decode_cudagraph_max_bs + 1,),
  156. dtype=torch.int32,
  157. device=device,
  158. )
  159. self.num_accepted_tokens: torch.Tensor = torch.empty(
  160. (self.decode_cudagraph_max_bs,),
  161. dtype=torch.int32,
  162. device=device,
  163. )
  164. def _build_chunk_metadata(
  165. self,
  166. prefill_query_start_loc: torch.Tensor,
  167. prefill_query_start_loc_cpu: torch.Tensor,
  168. device: torch.device,
  169. ) -> tuple[torch.Tensor, torch.Tensor]:
  170. from vllm.third_party.flash_linear_attention.ops.utils import FLA_CHUNK_SIZE
  171. if self.gdn_prefill_backend == "cutedsl":
  172. from vllm.model_executor.layers.mamba.ops.gdn_chunk_cutedsl import (
  173. prepare_metadata_cutedsl,
  174. )
  175. assert prefill_query_start_loc is not None
  176. assert prefill_query_start_loc_cpu is not None
  177. total_tokens = int(prefill_query_start_loc_cpu[-1].item())
  178. return prepare_metadata_cutedsl(
  179. prefill_query_start_loc,
  180. total_tokens,
  181. FLA_CHUNK_SIZE,
  182. )
  183. # Only prefill batches use FLA chunk ops.
  184. # Pre-compute on CPU and async-copy to GPU to avoid
  185. # GPU→CPU sync (.tolist()) in prepare_chunk_indices.
  186. from vllm.third_party.flash_linear_attention.ops.index import (
  187. prepare_chunk_indices,
  188. prepare_chunk_offsets,
  189. )
  190. assert prefill_query_start_loc_cpu is not None
  191. return (
  192. async_tensor_h2d(
  193. prepare_chunk_indices(prefill_query_start_loc_cpu, FLA_CHUNK_SIZE),
  194. device=device,
  195. ),
  196. async_tensor_h2d(
  197. prepare_chunk_offsets(prefill_query_start_loc_cpu, FLA_CHUNK_SIZE),
  198. device=device,
  199. ),
  200. )
  201. def build( # type: ignore[override]
  202. self,
  203. common_prefix_len: int,
  204. common_attn_metadata: CommonAttentionMetadata,
  205. num_accepted_tokens: torch.Tensor | None = None,
  206. num_decode_draft_tokens_cpu: torch.Tensor | None = None,
  207. fast_build: bool = False,
  208. ) -> GDNAttentionMetadata:
  209. m = common_attn_metadata
  210. num_accepted_tokens_in = num_accepted_tokens
  211. c = _spec_step_cache
  212. # A builder that already used this entry is on the next step: recompute.
  213. if (
  214. c
  215. and not os.path.exists(_CACHE_OFF_FLAG)
  216. and id(self) not in c["served"]
  217. and c["num_spec"] == self.num_spec
  218. and num_decode_draft_tokens_cpu is not None
  219. and num_accepted_tokens is not None
  220. and c["accepted"] == (num_accepted_tokens.data_ptr(), tuple(num_accepted_tokens.shape))
  221. and torch.equal(c["qsl_cpu"], m.query_start_loc_cpu)
  222. and torch.equal(c["drafts"], num_decode_draft_tokens_cpu)
  223. ):
  224. c["served"].add(id(self))
  225. return self._build_spec_from_cache(m, c)
  226. _spec_step_cache.clear()
  227. query_start_loc = m.query_start_loc
  228. query_start_loc_cpu = m.query_start_loc_cpu
  229. nums_dict, batch_ptr, token_chunk_offset_ptr = None, None, None
  230. aligned_state_indices = getattr(self, "mamba_aligned_state_indices", None)
  231. if aligned_state_indices is not None:
  232. block_table_tensor = aligned_state_indices[: m.num_reqs]
  233. else:
  234. block_table_tensor = mamba_get_block_table_tensor(
  235. m.block_table_tensor,
  236. m.seq_lens,
  237. self.kv_cache_spec,
  238. self.vllm_config.cache_config.mamba_cache_mode,
  239. )
  240. uniform_spec_sequence_length = None
  241. spec_sequence_masks_cpu: torch.Tensor | None = None
  242. if not self.use_spec_decode or num_decode_draft_tokens_cpu is None:
  243. spec_sequence_masks = None
  244. num_spec_decodes = 0
  245. else:
  246. spec_sequence_masks_cpu = num_decode_draft_tokens_cpu >= 0
  247. num_spec_decodes = spec_sequence_masks_cpu.sum().item()
  248. if (
  249. num_spec_decodes == 0
  250. or num_decode_draft_tokens_cpu[spec_sequence_masks_cpu].sum().item()
  251. == 0
  252. ):
  253. num_spec_decodes = 0
  254. spec_sequence_masks = None
  255. spec_sequence_masks_cpu = None
  256. else:
  257. spec_sequence_masks = async_tensor_h2d(
  258. spec_sequence_masks_cpu, device=query_start_loc.device
  259. )
  260. if spec_sequence_masks is None:
  261. # V2 already excludes prefills from full decode graphs via
  262. # has_prefill. Classify first chunks as prefills to mask recycled
  263. # state; resumed one-token chunks can still use the decode kernels.
  264. assert m.seq_lens_cpu_upper_bound is not None
  265. query_lens_cpu = query_start_loc_cpu.diff()
  266. no_prior_state = (query_lens_cpu > 0) & (
  267. m.seq_lens_cpu_upper_bound <= query_lens_cpu
  268. )
  269. # Capture batches also have seq_len == query_len, but are not
  270. # prefills.
  271. if m.is_prefilling is not None:
  272. no_prior_state &= m.is_prefilling
  273. else:
  274. no_prior_state = torch.zeros_like(no_prior_state)
  275. num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
  276. split_decodes_and_prefills(
  277. m.replace(is_prefilling=no_prior_state),
  278. decode_threshold=1,
  279. treat_short_extends_as_decodes=False,
  280. )
  281. )
  282. # Exclude trailing padding from both prefill counts.
  283. if num_prefills:
  284. num_prefills -= int((query_lens_cpu[num_decodes:] == 0).sum())
  285. num_prefill_tokens = (
  286. int(query_start_loc_cpu[num_decodes + num_prefills])
  287. - num_decode_tokens
  288. )
  289. num_spec_decode_tokens = 0
  290. spec_token_indx = None
  291. non_spec_token_indx = None
  292. spec_state_indices_tensor = None
  293. non_spec_state_indices_tensor = block_table_tensor[:, 0]
  294. spec_query_start_loc = None
  295. non_spec_query_start_loc = query_start_loc
  296. non_spec_query_start_loc_cpu = query_start_loc_cpu
  297. num_accepted_tokens = None
  298. else:
  299. query_lens = query_start_loc[1:] - query_start_loc[:-1]
  300. assert spec_sequence_masks_cpu is not None
  301. non_spec_sequence_masks_cpu = ~spec_sequence_masks_cpu
  302. query_lens_cpu = query_start_loc_cpu[1:] - query_start_loc_cpu[:-1]
  303. spec_query_lens_cpu = query_lens_cpu[spec_sequence_masks_cpu]
  304. if spec_query_lens_cpu.numel() > 0:
  305. first_spec_sequence_length = int(spec_query_lens_cpu[0])
  306. if first_spec_sequence_length > 0 and bool(
  307. torch.all(spec_query_lens_cpu == first_spec_sequence_length)
  308. ):
  309. uniform_spec_sequence_length = first_spec_sequence_length
  310. # Use CPU tensors to avoid CPU-GPU sync
  311. non_spec_query_lens_cpu = query_lens_cpu[non_spec_sequence_masks_cpu]
  312. num_decodes = (non_spec_query_lens_cpu == 1).sum().item()
  313. # Exclude zero-length padded sequences from prefill count.
  314. num_zero_len = (non_spec_query_lens_cpu == 0).sum().item()
  315. num_prefills = non_spec_query_lens_cpu.size(0) - num_decodes - num_zero_len
  316. num_decode_tokens = num_decodes
  317. num_prefill_tokens = (
  318. non_spec_query_lens_cpu.sum().item() - num_decode_tokens
  319. )
  320. num_spec_decode_tokens = (
  321. query_lens_cpu.sum().item() - num_prefill_tokens - num_decode_tokens
  322. )
  323. # num_decodes and num_spec_decodes are mutually exclusive.
  324. # Reclassify non-spec decodes as prefills when spec decodes
  325. # exist — the prefill kernel handles 1-token sequences with
  326. # initial state correctly, producing identical results.
  327. if num_decodes > 0 and num_spec_decodes > 0:
  328. num_prefills += num_decodes
  329. num_prefill_tokens += num_decode_tokens
  330. num_decodes = 0
  331. num_decode_tokens = 0
  332. if num_prefills == 0 and num_decodes == 0:
  333. spec_token_size = min(
  334. num_spec_decodes * (self.num_spec + 1),
  335. query_start_loc_cpu[-1].item(),
  336. )
  337. spec_token_indx = torch.arange(
  338. spec_token_size,
  339. dtype=torch.int32,
  340. device=query_start_loc.device,
  341. )
  342. non_spec_token_indx = torch.empty(
  343. 0, dtype=torch.int32, device=query_start_loc.device
  344. )
  345. # Filter by spec_sequence_masks to exclude padded sequences
  346. spec_state_indices_tensor = block_table_tensor[
  347. spec_sequence_masks_cpu, : self.num_spec + 1
  348. ]
  349. non_spec_state_indices_tensor = None
  350. # Padded sequences are always at the back, so the first
  351. # num_spec_decodes + 1 entries of query_start_loc already
  352. # contain the correct cumulative token counts.
  353. spec_query_start_loc = query_start_loc[: num_spec_decodes + 1]
  354. non_spec_query_start_loc = None
  355. non_spec_query_start_loc_cpu = None
  356. else:
  357. spec_token_masks = torch.repeat_interleave(
  358. spec_sequence_masks,
  359. query_lens,
  360. output_size=query_start_loc_cpu[-1].item(),
  361. )
  362. index = torch.argsort(spec_token_masks, stable=True)
  363. num_non_spec_tokens = num_prefill_tokens + num_decode_tokens
  364. non_spec_token_indx = index[:num_non_spec_tokens]
  365. spec_token_indx = index[num_non_spec_tokens:]
  366. spec_state_indices_tensor = block_table_tensor[
  367. spec_sequence_masks_cpu, : self.num_spec + 1
  368. ]
  369. non_spec_state_indices_tensor = block_table_tensor[
  370. non_spec_sequence_masks_cpu, 0
  371. ]
  372. spec_query_start_loc = torch.zeros(
  373. num_spec_decodes + 1,
  374. dtype=torch.int32,
  375. device=query_start_loc.device,
  376. )
  377. torch.cumsum(
  378. query_lens[spec_sequence_masks_cpu],
  379. dim=0,
  380. out=spec_query_start_loc[1:],
  381. )
  382. non_spec_query_start_loc = torch.zeros(
  383. query_lens.size(0) - num_spec_decodes + 1,
  384. dtype=torch.int32,
  385. device=query_start_loc.device,
  386. )
  387. torch.cumsum(
  388. query_lens[non_spec_sequence_masks_cpu],
  389. dim=0,
  390. out=non_spec_query_start_loc[1:],
  391. )
  392. non_spec_query_start_loc_cpu = torch.zeros(
  393. query_lens_cpu.size(0) - num_spec_decodes + 1,
  394. dtype=torch.int32,
  395. )
  396. torch.cumsum(
  397. query_lens_cpu[non_spec_sequence_masks_cpu],
  398. dim=0,
  399. out=non_spec_query_start_loc_cpu[1:],
  400. )
  401. assert num_accepted_tokens is not None
  402. num_accepted_tokens = num_accepted_tokens[spec_sequence_masks_cpu]
  403. chunk_indices: torch.Tensor | None = None
  404. chunk_offsets: torch.Tensor | None = None
  405. prefill_query_start_loc: torch.Tensor | None = None
  406. prefill_state_indices: torch.Tensor | None = None
  407. prefill_has_initial_state: torch.Tensor | None = None
  408. if num_prefills > 0:
  409. # In a mixed non-spec batch, decodes are peeled off to the recurrent
  410. # kernel (decode-first front slice), so build chunk metadata from the
  411. # rebased prefill-only cu_seqlens; otherwise use the full non-spec one.
  412. # _forward_core keys off the same condition, so they agree.
  413. if spec_sequence_masks is None and num_decodes > 0:
  414. assert non_spec_query_start_loc is not None
  415. assert non_spec_query_start_loc_cpu is not None
  416. assert non_spec_state_indices_tensor is not None
  417. prefill_query_start_loc = (
  418. non_spec_query_start_loc[num_decodes:] - num_decode_tokens
  419. )
  420. prefill_query_start_loc_cpu = (
  421. non_spec_query_start_loc_cpu[num_decodes:] - num_decode_tokens
  422. )
  423. prefill_state_indices = non_spec_state_indices_tensor[num_decodes:]
  424. else:
  425. prefill_query_start_loc = non_spec_query_start_loc
  426. prefill_query_start_loc_cpu = non_spec_query_start_loc_cpu
  427. prefill_state_indices = non_spec_state_indices_tensor
  428. chunk_indices, chunk_offsets = self._build_chunk_metadata(
  429. prefill_query_start_loc,
  430. prefill_query_start_loc_cpu,
  431. query_start_loc.device,
  432. )
  433. if num_prefills > 0:
  434. context_lens_tensor = m.compute_num_computed_tokens()
  435. has_initial_state = context_lens_tensor > 0
  436. if spec_sequence_masks_cpu is not None:
  437. has_initial_state = has_initial_state[~spec_sequence_masks_cpu]
  438. assert non_spec_query_start_loc_cpu is not None
  439. nums_dict, batch_ptr, token_chunk_offset_ptr = (
  440. compute_causal_conv1d_metadata(
  441. non_spec_query_start_loc_cpu,
  442. device=query_start_loc.device,
  443. )
  444. )
  445. if spec_sequence_masks is None and num_decodes > 0:
  446. prefill_has_initial_state = has_initial_state[num_decodes:]
  447. else:
  448. prefill_has_initial_state = has_initial_state
  449. else:
  450. has_initial_state = None
  451. # Function code counted on either presency non-spec decode or spec decode,
  452. # but not both.
  453. assert not (num_decodes > 0 and num_spec_decodes > 0), (
  454. f"num_decodes: {num_decodes}, num_spec_decodes: {num_spec_decodes}"
  455. )
  456. # Prepare per-request tensors for cudagraph. m.num_actual_tokens is
  457. # token-padded for FULL graph replay, but the GDN state/query/accepted
  458. # metadata below is indexed by request.
  459. batch_size = m.num_reqs
  460. if self._stage_spec_decode(
  461. num_prefills, num_decodes, num_spec_decodes, num_spec_decode_tokens
  462. ):
  463. assert spec_sequence_masks is not None
  464. _spec_step_cache.clear()
  465. _spec_step_cache.update(
  466. qsl_cpu=m.query_start_loc_cpu.clone(), drafts=num_decode_draft_tokens_cpu.clone(),
  467. accepted=(num_accepted_tokens_in.data_ptr(), tuple(num_accepted_tokens_in.shape)),
  468. num_spec=self.num_spec,
  469. masks_cpu=spec_sequence_masks_cpu, masks=spec_sequence_masks,
  470. spec_token_indx=spec_token_indx, non_spec_token_indx=non_spec_token_indx,
  471. spec_query_start_loc=spec_query_start_loc, num_accepted_tokens=num_accepted_tokens,
  472. num_spec_decodes=num_spec_decodes, num_spec_decode_tokens=num_spec_decode_tokens,
  473. num_prefill_tokens=num_prefill_tokens, num_decode_tokens=num_decode_tokens,
  474. uniform=uniform_spec_sequence_length, served={id(self)})
  475. self.spec_state_indices_tensor[:num_spec_decodes].copy_(
  476. spec_state_indices_tensor, non_blocking=True
  477. )
  478. spec_state_indices_tensor = self.spec_state_indices_tensor[:batch_size]
  479. spec_state_indices_tensor[num_spec_decodes:].fill_(NULL_BLOCK_ID)
  480. self.spec_sequence_masks[:num_spec_decodes].copy_(
  481. spec_sequence_masks[:num_spec_decodes], non_blocking=True
  482. )
  483. spec_sequence_masks = self.spec_sequence_masks[:batch_size]
  484. spec_sequence_masks[num_spec_decodes:].fill_(False)
  485. assert non_spec_token_indx is not None and spec_token_indx is not None
  486. self.non_spec_token_indx[: non_spec_token_indx.size(0)].copy_(
  487. non_spec_token_indx, non_blocking=True
  488. )
  489. non_spec_token_indx = self.non_spec_token_indx[
  490. : non_spec_token_indx.size(0)
  491. ]
  492. self.spec_token_indx[: spec_token_indx.size(0)].copy_(
  493. spec_token_indx, non_blocking=True
  494. )
  495. spec_token_indx = self.spec_token_indx[: spec_token_indx.size(0)]
  496. self.spec_query_start_loc[: num_spec_decodes + 1].copy_(
  497. spec_query_start_loc, non_blocking=True
  498. )
  499. spec_num_query_tokens = spec_query_start_loc[-1] # type: ignore[index]
  500. spec_query_start_loc = self.spec_query_start_loc[: batch_size + 1]
  501. spec_query_start_loc[num_spec_decodes + 1 :].fill_(spec_num_query_tokens)
  502. self.num_accepted_tokens[:num_spec_decodes].copy_(
  503. num_accepted_tokens, non_blocking=True
  504. )
  505. num_accepted_tokens = self.num_accepted_tokens[:batch_size]
  506. num_accepted_tokens[num_spec_decodes:].fill_(1)
  507. if self._stage_decode(num_prefills, num_decodes, num_spec_decodes):
  508. self.non_spec_state_indices_tensor[:num_decodes].copy_(
  509. non_spec_state_indices_tensor, non_blocking=True
  510. )
  511. non_spec_state_indices_tensor = self.non_spec_state_indices_tensor[
  512. :batch_size
  513. ]
  514. non_spec_state_indices_tensor[num_decodes:].fill_(NULL_BLOCK_ID)
  515. self.non_spec_query_start_loc[: num_decodes + 1].copy_(
  516. non_spec_query_start_loc, non_blocking=True
  517. )
  518. non_spec_num_query_tokens = non_spec_query_start_loc[-1] # type: ignore[index]
  519. non_spec_query_start_loc = self.non_spec_query_start_loc[: batch_size + 1]
  520. non_spec_query_start_loc[num_decodes + 1 :].fill_(non_spec_num_query_tokens)
  521. attn_metadata = GDNAttentionMetadata(
  522. num_prefills=num_prefills,
  523. num_prefill_tokens=num_prefill_tokens,
  524. num_decodes=num_decodes,
  525. num_decode_tokens=num_decode_tokens,
  526. num_spec_decodes=num_spec_decodes,
  527. num_spec_decode_tokens=num_spec_decode_tokens,
  528. num_actual_tokens=m.num_actual_tokens,
  529. has_initial_state=has_initial_state,
  530. chunk_indices=chunk_indices,
  531. chunk_offsets=chunk_offsets,
  532. prefill_query_start_loc=prefill_query_start_loc,
  533. prefill_state_indices=prefill_state_indices,
  534. prefill_has_initial_state=prefill_has_initial_state,
  535. spec_query_start_loc=spec_query_start_loc,
  536. non_spec_query_start_loc=non_spec_query_start_loc,
  537. spec_state_indices_tensor=spec_state_indices_tensor,
  538. non_spec_state_indices_tensor=non_spec_state_indices_tensor,
  539. spec_sequence_masks=spec_sequence_masks,
  540. spec_sequence_masks_cpu=spec_sequence_masks_cpu,
  541. spec_token_indx=spec_token_indx,
  542. non_spec_token_indx=non_spec_token_indx,
  543. num_accepted_tokens=num_accepted_tokens,
  544. uniform_spec_sequence_length=uniform_spec_sequence_length,
  545. nums_dict=nums_dict,
  546. batch_ptr=batch_ptr,
  547. token_chunk_offset_ptr=token_chunk_offset_ptr,
  548. )
  549. return attn_metadata
  550. def _stage_spec_decode(
  551. self,
  552. num_prefills: int,
  553. num_decodes: int,
  554. num_spec_decodes: int,
  555. num_spec_decode_tokens: int,
  556. ) -> bool:
  557. """Whether spec-decode metadata goes into the FULL cudagraph buffers."""
  558. return (
  559. self.use_full_cuda_graph
  560. and num_prefills == 0
  561. and num_decodes == 0
  562. and num_spec_decodes <= self.decode_cudagraph_max_bs
  563. and num_spec_decode_tokens <= self.decode_cudagraph_max_bs
  564. )
  565. def _stage_decode(
  566. self, num_prefills: int, num_decodes: int, num_spec_decodes: int
  567. ) -> bool:
  568. """Whether decode metadata goes into the FULL cudagraph buffers."""
  569. return (
  570. self.use_full_cuda_graph
  571. and num_prefills == 0
  572. and num_spec_decodes == 0
  573. and num_decodes <= self.decode_cudagraph_max_bs
  574. )
  575. def update_block_table(
  576. self,
  577. metadata: GDNAttentionMetadata,
  578. blk_table: torch.Tensor,
  579. slot_mapping: torch.Tensor,
  580. ) -> GDNAttentionMetadata:
  581. """Re-gather this group's state indices. The other fields are
  582. batch-level and stay shared with ``metadata``."""
  583. m = metadata
  584. if self.vllm_config.cache_config.mamba_cache_mode == "align":
  585. assert self.mamba_aligned_state_indices is not None
  586. blk_table = self.mamba_aligned_state_indices
  587. masks = m.spec_sequence_masks_cpu
  588. spec_indices = non_spec_indices = prefill_indices = None
  589. if masks is None:
  590. non_spec_indices = blk_table[:, 0]
  591. if m.num_prefills > 0:
  592. prefill_indices = non_spec_indices[m.num_decodes :]
  593. else:
  594. spec_indices = blk_table[masks, : self.num_spec + 1]
  595. if m.num_prefills > 0:
  596. non_spec_indices = prefill_indices = blk_table[~masks, 0]
  597. if self._stage_spec_decode(
  598. m.num_prefills, m.num_decodes, m.num_spec_decodes, m.num_spec_decode_tokens
  599. ):
  600. assert m.spec_state_indices_tensor is not None
  601. batch_size = m.spec_state_indices_tensor.shape[0]
  602. self.spec_state_indices_tensor[: m.num_spec_decodes].copy_(
  603. spec_indices, non_blocking=True
  604. )
  605. spec_indices = self.spec_state_indices_tensor[:batch_size]
  606. spec_indices[m.num_spec_decodes :].fill_(NULL_BLOCK_ID)
  607. if self._stage_decode(m.num_prefills, m.num_decodes, m.num_spec_decodes):
  608. assert m.non_spec_state_indices_tensor is not None
  609. batch_size = m.non_spec_state_indices_tensor.shape[0]
  610. self.non_spec_state_indices_tensor[: m.num_decodes].copy_(
  611. non_spec_indices, non_blocking=True
  612. )
  613. non_spec_indices = self.non_spec_state_indices_tensor[:batch_size]
  614. non_spec_indices[m.num_decodes :].fill_(NULL_BLOCK_ID)
  615. return replace(
  616. m,
  617. spec_state_indices_tensor=spec_indices,
  618. non_spec_state_indices_tensor=non_spec_indices,
  619. prefill_state_indices=prefill_indices,
  620. )
  621. def _build_spec_from_cache(self, m: CommonAttentionMetadata, c: dict) -> GDNAttentionMetadata:
  622. """The pure spec-decode full-graph case of build(), from this step's cache."""
  623. block_table_tensor = mamba_get_block_table_tensor(
  624. m.block_table_tensor,
  625. m.seq_lens,
  626. self.kv_cache_spec,
  627. self.vllm_config.cache_config.mamba_cache_mode,
  628. )
  629. n, batch_size = c["num_spec_decodes"], m.num_reqs
  630. self.spec_state_indices_tensor[:n].copy_(
  631. block_table_tensor[c["masks_cpu"], : self.num_spec + 1], non_blocking=True
  632. )
  633. spec_state_indices_tensor = self.spec_state_indices_tensor[:batch_size]
  634. spec_state_indices_tensor[n:].fill_(NULL_BLOCK_ID)
  635. self.spec_sequence_masks[:n].copy_(c["masks"][:n], non_blocking=True)
  636. spec_sequence_masks = self.spec_sequence_masks[:batch_size]
  637. spec_sequence_masks[n:].fill_(False)
  638. nsi, si = c["non_spec_token_indx"], c["spec_token_indx"]
  639. self.non_spec_token_indx[: nsi.size(0)].copy_(nsi, non_blocking=True)
  640. self.spec_token_indx[: si.size(0)].copy_(si, non_blocking=True)
  641. self.spec_query_start_loc[: n + 1].copy_(c["spec_query_start_loc"], non_blocking=True)
  642. spec_query_start_loc = self.spec_query_start_loc[: batch_size + 1]
  643. spec_query_start_loc[n + 1 :].fill_(c["spec_query_start_loc"][-1])
  644. self.num_accepted_tokens[:n].copy_(c["num_accepted_tokens"], non_blocking=True)
  645. num_accepted_tokens = self.num_accepted_tokens[:batch_size]
  646. num_accepted_tokens[n:].fill_(1)
  647. return GDNAttentionMetadata(
  648. num_prefills=0,
  649. num_prefill_tokens=c["num_prefill_tokens"],
  650. num_decodes=0,
  651. num_decode_tokens=c["num_decode_tokens"],
  652. num_spec_decodes=n,
  653. num_spec_decode_tokens=c["num_spec_decode_tokens"],
  654. num_actual_tokens=m.num_actual_tokens,
  655. has_initial_state=None,
  656. chunk_indices=None,
  657. chunk_offsets=None,
  658. prefill_query_start_loc=None,
  659. prefill_state_indices=None,
  660. prefill_has_initial_state=None,
  661. spec_query_start_loc=spec_query_start_loc,
  662. non_spec_query_start_loc=None,
  663. spec_state_indices_tensor=spec_state_indices_tensor,
  664. non_spec_state_indices_tensor=None,
  665. spec_sequence_masks=spec_sequence_masks,
  666. spec_token_indx=self.spec_token_indx[: si.size(0)],
  667. non_spec_token_indx=self.non_spec_token_indx[: nsi.size(0)],
  668. num_accepted_tokens=num_accepted_tokens,
  669. uniform_spec_sequence_length=c["uniform"],
  670. nums_dict=None,
  671. batch_ptr=None,
  672. token_chunk_offset_ptr=None,
  673. )
  674. def build_for_cudagraph_capture(
  675. self, common_attn_metadata: CommonAttentionMetadata
  676. ):
  677. """This method builds the metadata for full cudagraph capture.
  678. Currently, only decode is supported for full cudagraphs with Mamba.
  679. """
  680. m = common_attn_metadata
  681. assert (
  682. m.num_reqs <= self.decode_cudagraph_max_bs
  683. and m.num_actual_tokens <= self.decode_cudagraph_max_bs
  684. ), (
  685. f"GDN only supports decode-only full CUDAGraph capture. "
  686. f"Make sure batch size ({m.num_reqs}) <= "
  687. f"cudagraph capture sizes ({self.decode_cudagraph_max_bs}), "
  688. f"and number of tokens ({m.num_actual_tokens}) <= "
  689. f"cudagraph capture sizes ({self.decode_cudagraph_max_bs})."
  690. )
  691. num_accepted_tokens = torch.diff(m.query_start_loc)
  692. num_decode_draft_tokens_cpu = torch.diff(m.query_start_loc_cpu).sub_(1)
  693. assert num_decode_draft_tokens_cpu.shape == num_accepted_tokens.shape
  694. return self.build(0, m, num_accepted_tokens, num_decode_draft_tokens_cpu)