kda.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. """GLM-5.3-Flash KDA layer with separate convolutions and a bounded safe gate."""
  4. import os
  5. from dataclasses import replace
  6. import torch
  7. from torch import nn
  8. from vllm.compilation.breakable_cudagraph import eager_break_during_capture
  9. from vllm.config import VllmConfig, get_current_vllm_config
  10. from vllm.distributed import divide
  11. from vllm.forward_context import get_forward_context
  12. from vllm.model_executor.layers.linear import (
  13. ColumnParallelLinear,
  14. MergedColumnParallelLinear,
  15. RowParallelLinear,
  16. )
  17. from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention
  18. from vllm.model_executor.layers.mamba.mamba_utils import (
  19. MambaStateDtypeCalculator,
  20. MambaStateShapeCalculator,
  21. is_conv_state_dim_first,
  22. )
  23. from vllm.model_executor.layers.mamba.ops.causal_conv1d import (
  24. causal_conv1d_fn,
  25. causal_conv1d_update,
  26. )
  27. from vllm.model_executor.layers.mamba.ops.gather_initial_states import (
  28. gather_initial_states,
  29. )
  30. from vllm.model_executor.layers.mamba.ops.scatter_states import scatter_states
  31. from vllm.model_executor.model_loader.weight_utils import sharded_weight_loader
  32. from vllm.model_executor.utils import (
  33. maybe_disable_graph_partition,
  34. set_weight_attrs,
  35. )
  36. from vllm.platforms import current_platform
  37. from vllm.third_party.flash_linear_attention.ops.kda import FusedRMSNormGated
  38. from vllm.transformers_utils.configs.glm5_next import Glm5NextConfig
  39. from vllm.v1.attention.backend import AttentionBackend
  40. from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata
  41. from vllm.v1.kv_cache_interface import KVCacheSpec
  42. from vllm.v1.worker.workspace import current_workspace_manager
  43. # Spec verify from one checkpoint state per request (recoverssm.py).
  44. _RECOVERSSM = os.environ.get("VLLM_GLM5NEXT_RECOVERSSM") == "1"
  45. if _RECOVERSSM:
  46. from vllm.models.glm5next.common.recoverssm import (
  47. Glm5NextRecoverSSMBackend,
  48. kda_recoverssm_verify,
  49. )
  50. if current_platform.is_rocm():
  51. from vllm.models.glm5next.amd.ops.third_party.kda import (
  52. chunk_kda_with_fused_gate,
  53. fused_recurrent_kda,
  54. )
  55. else:
  56. from vllm.models.glm5next.nvidia.ops.third_party.kda import (
  57. chunk_kda_with_fused_gate,
  58. fused_recurrent_kda,
  59. )
  60. class _Glm5NextMergedColumnParallelLinear(MergedColumnParallelLinear):
  61. """Merged projection with multiple replicated output shards.
  62. Extends K3's ``_KimiGDNMergedColumnParallelLinear`` to support two
  63. replicated shards (f_a, g_a) instead of one. Pre-multiplies each
  64. replicated entry's output_size by tp_size so the per-rank shard
  65. divides back to the full size, and forces tp_rank=0 during weight
  66. loading for replicated shards.
  67. """
  68. def __init__(
  69. self,
  70. input_size: int,
  71. output_sizes: list[int],
  72. replicated_shard_ids: tuple[int, ...],
  73. tp_size: int,
  74. **kwargs,
  75. ) -> None:
  76. self.replicated_shard_ids = set(replicated_shard_ids)
  77. output_sizes = output_sizes.copy()
  78. for sid in self.replicated_shard_ids:
  79. output_sizes[sid] *= tp_size
  80. super().__init__(input_size, output_sizes, **kwargs)
  81. def weight_loader(
  82. self,
  83. param: nn.Parameter,
  84. loaded_weight: torch.Tensor,
  85. loaded_shard_id: tuple[int, ...] | int | None = None,
  86. ) -> None:
  87. tp_rank = self.tp_rank
  88. param_tp_rank = getattr(param, "tp_rank", None)
  89. if loaded_shard_id in self.replicated_shard_ids:
  90. self.tp_rank = 0
  91. if param_tp_rank is not None:
  92. param.tp_rank = 0
  93. try:
  94. super().weight_loader(param, loaded_weight, loaded_shard_id)
  95. finally:
  96. self.tp_rank = tp_rank
  97. if param_tp_rank is not None:
  98. param.tp_rank = param_tp_rank
  99. def weight_loader_v2(
  100. self,
  101. param: nn.Parameter,
  102. loaded_weight: torch.Tensor,
  103. loaded_shard_id: tuple[int, ...] | int | None = None,
  104. ) -> None:
  105. tp_rank = self.tp_rank
  106. param_tp_rank = getattr(param, "tp_rank", None)
  107. if loaded_shard_id in self.replicated_shard_ids:
  108. self.tp_rank = 0
  109. if param_tp_rank is not None:
  110. param.tp_rank = 0
  111. try:
  112. super().weight_loader_v2(param, loaded_weight, loaded_shard_id)
  113. finally:
  114. self.tp_rank = tp_rank
  115. if param_tp_rank is not None:
  116. param.tp_rank = param_tp_rank
  117. @torch.compile(
  118. dynamic=True,
  119. backend=current_platform.simple_compile_backend,
  120. options=maybe_disable_graph_partition(current_platform.simple_compile_backend),
  121. )
  122. def _cast_sigmoid(x: torch.Tensor) -> torch.Tensor:
  123. """Fuse the fp32 cast + sigmoid into one Inductor kernel."""
  124. return x.float().sigmoid()
  125. def _resolve_kda_prefill_backend(
  126. backend: str, head_dim: int, dtype: torch.dtype, lower_bound: float | None
  127. ) -> str:
  128. """Pick the chunked-prefill kernel: FlashKDA (fused CUDA, ~2-4x faster on
  129. SM90/SM10x/SM12x for bf16, head_dim 128 and a bounded gate) or the Triton
  130. ``chunk_kda_with_fused_gate`` path. ``backend`` comes from
  131. ``additional_config.kda_prefill_backend`` (auto / triton / flashkda)."""
  132. if backend not in ("auto", "triton", "flashkda"):
  133. raise ValueError(f"Unsupported KDA prefill backend: {backend}")
  134. capability = current_platform.get_device_capability()
  135. supported = (
  136. current_platform.is_cuda()
  137. and capability is not None
  138. and capability.major in (9, 10, 12)
  139. and head_dim == 128
  140. and dtype == torch.bfloat16
  141. and lower_bound is not None
  142. )
  143. if backend == "flashkda" and not supported:
  144. raise RuntimeError(
  145. "FlashKDA requires CUDA SM90/SM10x/SM12x, bfloat16, head_dim=128 "
  146. "and a bounded KDA gate."
  147. )
  148. return "flashkda" if supported and backend != "triton" else "triton"
  149. class Glm5NextLinearAttention(GatedDeltaNetAttention):
  150. head_dim: int
  151. num_heads: int
  152. conv_size: int
  153. def get_state_dtype(
  154. self,
  155. ) -> tuple[torch.dtype, torch.dtype]:
  156. if self.model_config is None or self.cache_config is None:
  157. raise ValueError("model_config and cache_config must be set")
  158. return MambaStateDtypeCalculator.kda_state_dtype(
  159. self.model_config.dtype, self.cache_config.mamba_cache_dtype
  160. )
  161. def get_state_shape(
  162. self,
  163. ) -> tuple[tuple[int, ...], tuple[int, ...]]:
  164. # conv_state width must include num_spec so the spec-decode conv update
  165. # (causal_conv1d_update with num_accepted_tokens + max_query_len) can
  166. # slide the window across the draft-verify tokens without reading past
  167. # the allocated width. Matches qwen_gdn_linear_attn.get_state_shape.
  168. return MambaStateShapeCalculator.kda_state_shape(
  169. self.tp_size,
  170. self.num_heads,
  171. self.head_dim,
  172. conv_kernel_size=self.conv_size,
  173. num_spec=self.num_spec,
  174. )
  175. def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec | None:
  176. spec = super().get_kv_cache_spec(vllm_config)
  177. if self._recoverssm:
  178. # One checkpoint block per request. The commit writes only the final state.
  179. spec = replace(spec, num_speculative_blocks=0)
  180. return spec
  181. def get_attn_backend(self) -> type[AttentionBackend]:
  182. if self._recoverssm:
  183. return Glm5NextRecoverSSMBackend
  184. return super().get_attn_backend()
  185. def __init__(
  186. self,
  187. config: Glm5NextConfig,
  188. vllm_config: VllmConfig,
  189. prefix: str = "",
  190. ) -> None:
  191. # KDA projections remain BF16 because fp8 checkpoints omit their scales.
  192. saved_quant_config = vllm_config.quant_config
  193. try:
  194. vllm_config.quant_config = None
  195. super().__init__(config, vllm_config, prefix)
  196. finally:
  197. vllm_config.quant_config = saved_quant_config
  198. self._recoverssm = _RECOVERSSM and self.num_spec > 0
  199. # The RecoverSSM metadata builder allocates these (recoverssm.Records).
  200. self.recoverssm_records = None
  201. self.head_dim = config.linear_head_dim
  202. self.num_heads = config.linear_num_heads
  203. self.conv_size = config.linear_conv_kernel_dim
  204. assert self.num_heads % self.tp_size == 0
  205. self.local_num_heads = divide(self.num_heads, self.tp_size)
  206. projection_size = self.head_dim * self.num_heads
  207. self.local_projection_size = divide(projection_size, self.tp_size)
  208. _, recurrent_state_shape = self.get_state_shape()
  209. _, recurrent_state_dtype = self.get_state_dtype()
  210. scatter_states.register_warmup(
  211. state_shape=recurrent_state_shape,
  212. state_dtype=recurrent_state_dtype,
  213. indices_dtype=torch.int32,
  214. max_num_tokens=vllm_config.scheduler_config.max_num_seqs,
  215. )
  216. # Merge q, k, v, b, f_a, g_a projections into one GEMM (6→1 launches).
  217. # Order matches checkpoint's fused_qkvbfg_a_proj convention.
  218. # Shards 4 (f_a) and 5 (g_a) are replicated across TP ranks.
  219. self.in_proj_qkvbfg_a = _Glm5NextMergedColumnParallelLinear(
  220. self.hidden_size,
  221. [
  222. projection_size, # q (shard 0)
  223. projection_size, # k (shard 1)
  224. projection_size, # v (shard 2)
  225. self.num_heads, # b (shard 3)
  226. self.head_dim, # f_a (shard 4, replicated)
  227. self.head_dim, # g_a (shard 5, replicated)
  228. ],
  229. replicated_shard_ids=(4, 5),
  230. tp_size=self.tp_size,
  231. bias=False,
  232. quant_config=self.quant_config,
  233. prefix=f"{prefix}.in_proj_qkvbfg_a",
  234. )
  235. self.f_b_proj = ColumnParallelLinear(
  236. self.head_dim,
  237. projection_size,
  238. bias=False,
  239. quant_config=self.quant_config,
  240. prefix=f"{prefix}.f_b_proj",
  241. )
  242. self.dt_bias = nn.Parameter(
  243. torch.empty(divide(projection_size, self.tp_size), dtype=torch.float32)
  244. )
  245. set_weight_attrs(self.dt_bias, {"weight_loader": sharded_weight_loader(0)})
  246. self.q_conv1d = ColumnParallelLinear(
  247. input_size=self.conv_size,
  248. output_size=projection_size,
  249. bias=False,
  250. params_dtype=torch.float32,
  251. prefix=f"{prefix}.q_conv1d",
  252. )
  253. self.k_conv1d = ColumnParallelLinear(
  254. input_size=self.conv_size,
  255. output_size=projection_size,
  256. bias=False,
  257. params_dtype=torch.float32,
  258. prefix=f"{prefix}.k_conv1d",
  259. )
  260. self.v_conv1d = ColumnParallelLinear(
  261. input_size=self.conv_size,
  262. output_size=projection_size,
  263. bias=False,
  264. params_dtype=torch.float32,
  265. prefix=f"{prefix}.v_conv1d",
  266. )
  267. # unsqueeze to fit conv1d weights shape into the linear weights shape.
  268. # Can't do this in `weight_loader` since it already exists in
  269. # `ColumnParallelLinear` and `set_weight_attrs`
  270. # doesn't allow to override it
  271. self.q_conv1d.weight.data = self.q_conv1d.weight.data.unsqueeze(1)
  272. self.k_conv1d.weight.data = self.k_conv1d.weight.data.unsqueeze(1)
  273. self.v_conv1d.weight.data = self.v_conv1d.weight.data.unsqueeze(1)
  274. # Lazily-built merged q|k|v conv weight (built on first forward, after
  275. # weights are loaded). See _forward.
  276. self._merged_conv_weight: torch.Tensor | None = None
  277. self.A_log = nn.Parameter(
  278. torch.empty(1, 1, self.local_num_heads, 1, dtype=torch.float32)
  279. )
  280. set_weight_attrs(self.A_log, {"weight_loader": sharded_weight_loader(2)})
  281. self.g_b_proj = ColumnParallelLinear(
  282. self.head_dim,
  283. projection_size,
  284. bias=False,
  285. quant_config=self.quant_config,
  286. prefix=f"{prefix}.g_b_proj",
  287. )
  288. self.o_norm = FusedRMSNormGated(self.head_dim, activation="sigmoid")
  289. self.o_proj = RowParallelLinear(
  290. projection_size,
  291. self.hidden_size,
  292. bias=False,
  293. quant_config=self.quant_config,
  294. prefix=f"{prefix}.o_proj",
  295. )
  296. compilation_config = get_current_vllm_config().compilation_config
  297. if prefix in compilation_config.static_forward_context:
  298. raise ValueError(f"Duplicate layer name: {prefix}")
  299. compilation_config.static_forward_context[prefix] = self
  300. # Checkpoints store A_log as 1-D; the model parameter is 4-D.
  301. def _a_log_weight_loader(param, loaded_weight):
  302. if loaded_weight.dim() == 1:
  303. loaded_weight = loaded_weight.view([1, 1, -1, 1])
  304. return sharded_weight_loader(2)(param, loaded_weight)
  305. self.A_log.weight_loader = _a_log_weight_loader
  306. # GLM-5.3-Flash uses a bounded sigmoid gate instead of the default
  307. # unbounded softplus gate.
  308. self.kda_safe_gate = True
  309. self.kda_lower_bound = config.linear_lower_bound
  310. # Process-global conv-state layout, resolved once here instead of on
  311. # every _forward call (it reads an env-derived flag each time).
  312. self._conv_state_dim_first = is_conv_state_dim_first()
  313. additional_config = vllm_config.additional_config
  314. self.kda_prefill_backend = _resolve_kda_prefill_backend(
  315. additional_config.get("kda_prefill_backend", "auto")
  316. if isinstance(additional_config, dict)
  317. else "auto",
  318. self.head_dim,
  319. vllm_config.model_config.dtype,
  320. self.kda_lower_bound,
  321. )
  322. self._flashkda_buffer_specs: (
  323. tuple[tuple[tuple[int, ...], torch.dtype], ...] | None
  324. ) = None
  325. if self.kda_prefill_backend == "flashkda":
  326. import vllm._flashkda_C # noqa: F401
  327. max_tokens = vllm_config.scheduler_config.max_num_batched_tokens
  328. max_seqs = vllm_config.scheduler_config.max_num_seqs
  329. workspace_size = torch.ops._flashkda_C.get_workspace_size(
  330. max_tokens, self.local_num_heads, max_seqs
  331. )
  332. self._flashkda_buffer_specs = (
  333. (
  334. (max_seqs, self.local_num_heads, self.head_dim, self.head_dim),
  335. self.get_state_dtype()[1],
  336. ),
  337. ((workspace_size,), torch.uint8),
  338. # Output buffer for steps that also carry spec-decode tokens:
  339. # the non-spec tokens are then scattered by non_spec_token_indx.
  340. (
  341. (1, max_tokens, self.local_num_heads, self.head_dim),
  342. vllm_config.model_config.dtype,
  343. ),
  344. )
  345. def _flashkda_prefill(
  346. self,
  347. q: torch.Tensor,
  348. k: torch.Tensor,
  349. v: torch.Tensor,
  350. g: torch.Tensor,
  351. beta: torch.Tensor,
  352. initial_state: torch.Tensor,
  353. cu_seqlens: torch.Tensor,
  354. out: torch.Tensor | None,
  355. ) -> tuple[torch.Tensor, torch.Tensor]:
  356. """Fused KDA chunked prefill (FlashKDA). Takes the raw gate logits ``g``
  357. and raw ``beta`` logits, l2-normalizes q/k in-kernel and applies the
  358. bounded gate ``lower_bound * sigmoid(exp(A_log) * (g + dt_bias))``,
  359. matching ``chunk_kda_with_fused_gate(..., safe_gate=True)``. Writes the
  360. attention output into ``out`` (a workspace buffer when ``None``) and
  361. returns ``(out, final_state)``."""
  362. assert self._flashkda_buffer_specs is not None
  363. final_state, workspace, workspace_out = (
  364. current_workspace_manager().get_simultaneous(*self._flashkda_buffer_specs)
  365. )
  366. final_state = final_state[: initial_state.shape[0]]
  367. if out is None:
  368. out = workspace_out[:, : q.shape[1]]
  369. # FlashKDA hardcodes dense q/k/v/g strides; beta may be row-strided.
  370. torch.ops._flashkda_C.fwd(
  371. q.contiguous(),
  372. k.contiguous(),
  373. v.contiguous(),
  374. g.contiguous(),
  375. beta,
  376. self.head_dim**-0.5,
  377. out,
  378. workspace,
  379. self.A_log.view(-1),
  380. self.dt_bias.view(-1, self.head_dim),
  381. self.kda_lower_bound,
  382. initial_state.contiguous(),
  383. final_state,
  384. cu_seqlens.contiguous(),
  385. None,
  386. None,
  387. )
  388. return out, final_state
  389. def forward(
  390. self,
  391. hidden_states: torch.Tensor,
  392. positions: torch.Tensor,
  393. ) -> torch.Tensor:
  394. num_tokens = hidden_states.size(0)
  395. # One merged GEMM for q, k, v, b, f_a, g_a (replaces 6 separate GEMMs).
  396. projected = self.in_proj_qkvbfg_a(hidden_states)[0]
  397. qkv, beta_raw, f_a, g_a = projected.split(
  398. [
  399. 3 * self.local_projection_size,
  400. self.local_num_heads,
  401. self.head_dim,
  402. self.head_dim,
  403. ],
  404. dim=-1,
  405. )
  406. # Beta stays raw (bf16) here: the recurrent kernel sigmoids it in fp32
  407. # at load (SIGMOID_BETA), and only the chunked prefill path needs the
  408. # pre-computed fp32 sigmoid — computed lazily in _forward. Pure decode
  409. # / spec-verify steps then skip the _cast_sigmoid kernel and its fp32
  410. # intermediate entirely.
  411. beta = beta_raw.unsqueeze(0)
  412. g1 = self.f_b_proj(f_a)[0]
  413. g1 = g1.reshape(1, -1, self.local_num_heads, self.head_dim)
  414. g_proj_states = self.g_b_proj(g_a)[0]
  415. # Must stay 3D: rms_norm_gated reads H from g.shape[-2].
  416. g2 = g_proj_states.reshape(-1, self.local_num_heads, self.head_dim)
  417. core_attn_out = torch.empty(
  418. (1, num_tokens, self.local_num_heads, self.head_dim),
  419. dtype=hidden_states.dtype,
  420. device=hidden_states.device,
  421. )
  422. # Call the decorated eager break directly so host-side prefill branches
  423. # are not captured by PIECEWISE CUDA graphs.
  424. self._forward(
  425. qkv_proj_states=qkv,
  426. g1=g1,
  427. beta=beta,
  428. core_attn_out=core_attn_out,
  429. )
  430. core_attn_out = self.o_norm(core_attn_out, g2)
  431. core_attn_out = core_attn_out.reshape(core_attn_out.size(1), -1)
  432. return self.o_proj(core_attn_out)[0]
  433. @eager_break_during_capture
  434. def _forward(
  435. self,
  436. qkv_proj_states: torch.Tensor,
  437. g1: torch.Tensor,
  438. beta: torch.Tensor,
  439. core_attn_out: torch.Tensor,
  440. ) -> None:
  441. forward_context = get_forward_context()
  442. attn_metadata_raw = forward_context.attn_metadata
  443. if attn_metadata_raw is None:
  444. return
  445. assert isinstance(attn_metadata_raw, dict)
  446. attn_metadata_narrowed = attn_metadata_raw.get(self.prefix)
  447. if attn_metadata_narrowed is None:
  448. # Profile/warmup dummy runs may omit mamba-family metadata.
  449. return
  450. assert isinstance(attn_metadata_narrowed, GDNAttentionMetadata)
  451. has_initial_state = attn_metadata_narrowed.has_initial_state
  452. non_spec_query_start_loc = attn_metadata_narrowed.non_spec_query_start_loc
  453. non_spec_state_indices_tensor = (
  454. attn_metadata_narrowed.non_spec_state_indices_tensor
  455. ) # noqa: E501
  456. num_actual_tokens = attn_metadata_narrowed.num_actual_tokens
  457. # Spec-decode metadata (all None when speculative decoding is disabled).
  458. spec_sequence_masks = attn_metadata_narrowed.spec_sequence_masks
  459. spec_query_start_loc = attn_metadata_narrowed.spec_query_start_loc
  460. spec_state_indices_tensor = attn_metadata_narrowed.spec_state_indices_tensor
  461. spec_token_indx = attn_metadata_narrowed.spec_token_indx
  462. non_spec_token_indx = attn_metadata_narrowed.non_spec_token_indx
  463. num_accepted_tokens = attn_metadata_narrowed.num_accepted_tokens
  464. num_spec_decodes = attn_metadata_narrowed.num_spec_decodes
  465. # RecoverSSM metadata leaves the mask None. num_spec_decodes marks spec rows.
  466. use_spec = num_spec_decodes > 0 and (
  467. spec_sequence_masks is not None or self._recoverssm
  468. )
  469. # Safe-gate checkpoints use the bounded sigmoid variant.
  470. safe_gate = self.kda_safe_gate
  471. lower_bound = self.kda_lower_bound
  472. constant_caches = self.kv_cache
  473. qkv_proj_states = qkv_proj_states[:num_actual_tokens]
  474. g1 = g1[:, :num_actual_tokens]
  475. beta = beta[:, :num_actual_tokens]
  476. (conv_state, recurrent_state) = constant_caches
  477. # conv_state must be (..., dim, width-1) for the conv kernels.
  478. # DS layout stores it that way directly; SD layout needs a transpose.
  479. # Layout is process-global and resolved once at init (see __init__).
  480. if not self._conv_state_dim_first:
  481. conv_state = conv_state.transpose(-1, -2)
  482. # One merged short-conv over q|k|v instead of three separate calls. The
  483. # 1D conv is independent per channel, so concatenating q/k/v along the
  484. # channel dim and running a single causal_conv1d is bit-identical to
  485. # three calls. The merged weight is q|k|v conv weights concatenated;
  486. # built once and cached (params are fixed after load). conv_state is
  487. # already stored as the merged q|k|v state, so it is used directly.
  488. if self._merged_conv_weight is None:
  489. def _w(m):
  490. return m.weight.view(m.weight.size(0), m.weight.size(2))
  491. self._merged_conv_weight = torch.cat(
  492. [_w(self.q_conv1d), _w(self.k_conv1d), _w(self.v_conv1d)],
  493. dim=0,
  494. ).contiguous()
  495. conv_weights = self._merged_conv_weight
  496. conv_bias = self.q_conv1d.bias
  497. # Split projections / gating into spec (draft-verify) and non-spec token
  498. # groups when speculative decoding is active. Spec tokens carry
  499. # num_spec+1 recurrent-state columns each and are advanced with
  500. # num_accepted_tokens for rejection-sampling rollback; non-spec tokens
  501. # are one-per-request. Mirrors olmo_gdn_linear_attn.py. Projections are
  502. # [n, *] (token dim 0); g1/beta are [1, n, h, d] (token dim 1).
  503. if use_spec:
  504. # In a pure spec-verify step (no non-spec tokens) the metadata
  505. # builder sets spec_token_indx = arange(num_actual_tokens), making
  506. # the index_select calls below identity copies. Skip them on this
  507. # steady-state decode hot path. The outputs alias the inputs here;
  508. # the downstream conv/recurrent kernels read them without mutating
  509. # in place, so the aliasing is safe.
  510. if non_spec_token_indx is None or non_spec_token_indx.numel() == 0:
  511. qkv_spec = qkv_proj_states
  512. g1_spec = g1
  513. beta_spec = beta
  514. else:
  515. qkv_spec = qkv_proj_states.index_select(0, spec_token_indx)
  516. g1_spec = g1.index_select(1, spec_token_indx)
  517. beta_spec = beta.index_select(1, spec_token_indx)
  518. if non_spec_token_indx is not None and non_spec_token_indx.numel() > 0:
  519. qkv_ns = qkv_proj_states.index_select(0, non_spec_token_indx)
  520. g1_ns = g1.index_select(1, non_spec_token_indx)
  521. beta_ns = beta.index_select(1, non_spec_token_indx)
  522. else:
  523. qkv_ns = g1_ns = beta_ns = None
  524. else:
  525. qkv_spec = g1_spec = beta_spec = None
  526. qkv_ns, g1_ns, beta_ns = qkv_proj_states, g1, beta
  527. # --- causal conv1d: spec (draft-verify) path ---
  528. if use_spec:
  529. assert spec_state_indices_tensor is not None
  530. assert num_accepted_tokens is not None
  531. conv_idx = spec_state_indices_tensor[:, 0][:num_spec_decodes]
  532. # RecoverSSM keeps one state slot, but the window is still k + 1.
  533. conv_mql = (
  534. self.num_spec + 1
  535. if self._recoverssm
  536. else spec_state_indices_tensor.size(-1)
  537. )
  538. qkv_spec = causal_conv1d_update(
  539. qkv_spec,
  540. conv_state,
  541. conv_weights,
  542. conv_bias,
  543. activation="silu",
  544. conv_state_indices=conv_idx,
  545. num_accepted_tokens=num_accepted_tokens,
  546. query_start_loc=spec_query_start_loc,
  547. max_query_len=conv_mql,
  548. )
  549. q_spec, k_spec, v_spec = qkv_spec.split(self.local_projection_size, dim=-1)
  550. # --- causal conv1d: non-spec path (prefill or plain decode) ---
  551. q_ns = k_ns = v_ns = None
  552. if attn_metadata_narrowed.num_prefills > 0:
  553. assert qkv_ns is not None
  554. # q, k and v come out as separate dense tensors, which FlashKDA
  555. # needs; split from one [T, 3 * P] output they had to be copied.
  556. q_ns, k_ns, v_ns = causal_conv1d_fn(
  557. qkv_ns.transpose(0, 1),
  558. conv_weights,
  559. conv_bias,
  560. activation="silu",
  561. conv_states=conv_state,
  562. has_initial_state=has_initial_state,
  563. cache_indices=non_spec_state_indices_tensor,
  564. query_start_loc=non_spec_query_start_loc,
  565. metadata=attn_metadata_narrowed,
  566. out_group=self.local_projection_size,
  567. ).unbind(0)
  568. elif attn_metadata_narrowed.num_decodes > 0:
  569. assert non_spec_state_indices_tensor is not None
  570. decode_conv_indices = non_spec_state_indices_tensor[
  571. : attn_metadata_narrowed.num_decodes
  572. ]
  573. qkv_ns = causal_conv1d_update(
  574. qkv_ns,
  575. conv_state,
  576. conv_weights,
  577. conv_bias,
  578. activation="silu",
  579. conv_state_indices=decode_conv_indices,
  580. )
  581. q_ns, k_ns, v_ns = qkv_ns.split(self.local_projection_size, dim=-1)
  582. def _rearr(x):
  583. return x.reshape(1, -1, self.local_num_heads, self.head_dim)
  584. # --- core attention: spec (draft-verify) path ---
  585. core_attn_out_spec = None
  586. # In a pure spec-verify step (no non-spec tokens) the recurrent kernel
  587. # can write straight into the layer output buffer, skipping the
  588. # fresh allocation + copy below. Mixed steps must scatter via
  589. # spec_token_indx, so they keep the kernel-managed output.
  590. spec_out = (
  591. core_attn_out[0, :num_actual_tokens].unsqueeze(0)
  592. if non_spec_token_indx is None or non_spec_token_indx.numel() == 0
  593. else None
  594. )
  595. if use_spec and self._recoverssm:
  596. assert spec_state_indices_tensor is not None
  597. assert spec_query_start_loc is not None
  598. assert self.recoverssm_records is not None
  599. core_attn_out_spec = kda_recoverssm_verify(
  600. q=_rearr(q_spec),
  601. k=_rearr(k_spec),
  602. v=_rearr(v_spec),
  603. g=g1_spec,
  604. beta=beta_spec,
  605. a_log=self.A_log,
  606. g_bias=self.dt_bias,
  607. lower_bound=lower_bound,
  608. checkpoint_state=recurrent_state,
  609. records=self.recoverssm_records,
  610. query_start_loc=spec_query_start_loc[: num_spec_decodes + 1],
  611. state_indices=spec_state_indices_tensor[:num_spec_decodes, 0],
  612. out=spec_out,
  613. )
  614. elif use_spec:
  615. assert spec_state_indices_tensor is not None
  616. assert num_accepted_tokens is not None
  617. assert spec_query_start_loc is not None
  618. # Gate computed inside the recurrent kernel (COMPUTE_GATE) from
  619. # raw g1 — replicates fused_kda_gate's arithmetic bit-for-bit and
  620. # skips its launch + fp32 [n, H, D] intermediate per layer.
  621. core_attn_out_spec, _ = fused_recurrent_kda(
  622. q=_rearr(q_spec),
  623. k=_rearr(k_spec),
  624. v=_rearr(v_spec),
  625. g=g1_spec,
  626. beta=beta_spec,
  627. initial_state=recurrent_state,
  628. use_qk_l2norm_in_kernel=True,
  629. cu_seqlens=spec_query_start_loc[: num_spec_decodes + 1],
  630. ssm_state_indices=spec_state_indices_tensor,
  631. num_accepted_tokens=num_accepted_tokens,
  632. out=spec_out,
  633. sigmoid_beta=True,
  634. a_log=self.A_log,
  635. g_bias=self.dt_bias,
  636. compute_gate=True,
  637. lower_bound=lower_bound,
  638. )
  639. # --- core attention: non-spec path (prefill or plain decode) ---
  640. core_attn_out_non_spec = None
  641. # Only the plain-decode recurrent kernel can write straight into the
  642. # layer output buffer; the chunked prefill kernel cannot, so this
  643. # stays None there and the merge copy below runs as before.
  644. ns_out = None
  645. if attn_metadata_narrowed.num_prefills > 0:
  646. assert q_ns is not None
  647. assert non_spec_state_indices_tensor is not None
  648. assert has_initial_state is not None
  649. initial_state = gather_initial_states(
  650. recurrent_state, non_spec_state_indices_tensor, has_initial_state
  651. )
  652. if self.kda_prefill_backend == "flashkda":
  653. # Non-spec step: write straight into the layer output buffer
  654. # (dense token order, no merge copy). Step with spec-decode
  655. # tokens: write to the workspace buffer and scatter below.
  656. ns_out = None if use_spec else core_attn_out[:, :num_actual_tokens]
  657. core_attn_out_non_spec, last_recurrent_state = self._flashkda_prefill(
  658. q=_rearr(q_ns),
  659. k=_rearr(k_ns),
  660. v=_rearr(v_ns),
  661. g=g1_ns,
  662. beta=beta_ns,
  663. initial_state=initial_state,
  664. cu_seqlens=non_spec_query_start_loc,
  665. out=ns_out,
  666. )
  667. else:
  668. (
  669. core_attn_out_non_spec,
  670. last_recurrent_state,
  671. ) = chunk_kda_with_fused_gate(
  672. q=_rearr(q_ns),
  673. k=_rearr(k_ns),
  674. v=_rearr(v_ns),
  675. raw_g=g1_ns,
  676. # Chunk path wants the pre-sigmoided fp32 beta (its
  677. # kernels don't sigmoid); beta_ns is raw bf16 from forward.
  678. beta=_cast_sigmoid(beta_ns.squeeze(0)).unsqueeze(0),
  679. A_log=self.A_log,
  680. g_bias=self.dt_bias,
  681. initial_state=initial_state,
  682. output_final_state=True,
  683. use_qk_l2norm_in_kernel=True,
  684. cu_seqlens=non_spec_query_start_loc,
  685. safe_gate=safe_gate,
  686. lower_bound=lower_bound,
  687. )
  688. # Init cache
  689. scatter_states(
  690. recurrent_state,
  691. last_recurrent_state,
  692. non_spec_state_indices_tensor,
  693. )
  694. elif attn_metadata_narrowed.num_decodes > 0:
  695. assert non_spec_query_start_loc is not None
  696. assert non_spec_state_indices_tensor is not None
  697. # Plain decode step (no spec tokens): token order is dense, so the
  698. # kernel can write straight into the layer output buffer. A mixed
  699. # step scatters non-spec output via non_spec_token_indx instead.
  700. # Gate computed in-kernel (COMPUTE_GATE), beta sigmoided in-kernel.
  701. if not use_spec:
  702. ns_out = spec_out
  703. core_attn_out_non_spec, _ = fused_recurrent_kda(
  704. q=_rearr(q_ns),
  705. k=_rearr(k_ns),
  706. v=_rearr(v_ns),
  707. g=g1_ns,
  708. beta=beta_ns,
  709. initial_state=recurrent_state,
  710. use_qk_l2norm_in_kernel=True,
  711. cu_seqlens=non_spec_query_start_loc[
  712. : attn_metadata_narrowed.num_decodes + 1
  713. ],
  714. ssm_state_indices=non_spec_state_indices_tensor,
  715. out=ns_out,
  716. sigmoid_beta=True,
  717. a_log=self.A_log,
  718. g_bias=self.dt_bias,
  719. compute_gate=True,
  720. lower_bound=lower_bound,
  721. )
  722. # --- merge spec / non-spec outputs back into token order ---
  723. if use_spec and core_attn_out_non_spec is not None:
  724. assert core_attn_out_spec is not None
  725. core_attn_out.index_copy_(1, spec_token_indx, core_attn_out_spec)
  726. core_attn_out.index_copy_(1, non_spec_token_indx, core_attn_out_non_spec)
  727. elif use_spec:
  728. assert core_attn_out_spec is not None
  729. if spec_out is None:
  730. core_attn_out[0, :num_actual_tokens] = core_attn_out_spec.squeeze(0)
  731. else:
  732. assert core_attn_out_non_spec is not None
  733. if ns_out is None:
  734. core_attn_out[0, :num_actual_tokens] = core_attn_out_non_spec[
  735. 0, :num_actual_tokens
  736. ]