attention.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. import torch
  4. import torch.nn.functional as F
  5. from torch import nn
  6. from vllm.config import (
  7. CacheConfig,
  8. VllmConfig,
  9. )
  10. from vllm.distributed import (
  11. get_tensor_model_parallel_world_size,
  12. )
  13. from vllm.logger import init_logger
  14. from vllm.model_executor.layers.layernorm import LayerNorm, RMSNorm
  15. from vllm.model_executor.layers.linear import (
  16. ColumnParallelLinear,
  17. MergedColumnParallelLinear,
  18. ReplicatedLinear,
  19. RowParallelLinear,
  20. )
  21. from vllm.model_executor.layers.mla import MLAModules, MultiHeadLatentAttentionWrapper
  22. from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
  23. from vllm.model_executor.layers.rotary_embedding import RotaryEmbedding, get_rope
  24. from vllm.model_executor.models.deepseek_v2 import (
  25. DeepSeekV2FusedQkvAProjLinear,
  26. DeepseekV32IndexerCache,
  27. yarn_get_mscale,
  28. )
  29. from vllm.model_executor.utils import maybe_disable_graph_partition
  30. from vllm.models.glm5next.nvidia.ops.kpool_compress import fwht128_quant_fp8
  31. from vllm.models.glm5next.sparse_indexer import SparseAttnIndexerKpool
  32. from vllm.platforms import current_platform
  33. from vllm.transformers_utils.configs.glm5_next import Glm5NextConfig
  34. from vllm.utils.deep_gemm import PAGED_MQA_PAGE_SIZES
  35. from vllm.utils.math_utils import cdiv, next_power_of_2
  36. from vllm.v1.kv_cache_interface import KpoolTailSpec, MLAAttentionSpec
  37. logger = init_logger(__name__)
  38. # Shared torch.compile config for the indexer's small-kernel leaves. The MLA
  39. # indexer runs under breakable-CG (CompilationMode.NONE), which blocks FX-graph
  40. # fusion of the surrounding eager ops; carving each cluster into its own
  41. # @torch.compile leaf (backend==inductor) still fuses them. Matches the
  42. # grouped_topk / _cast_sigmoid leaf pattern.
  43. _INDEXER_COMPILE = dict(
  44. dynamic=True,
  45. backend=current_platform.simple_compile_backend,
  46. options=maybe_disable_graph_partition(current_platform.simple_compile_backend),
  47. )
  48. @torch.compile(**_INDEXER_COMPILE)
  49. def _fused_indexer_k_norm(
  50. x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, dim: int, eps: float
  51. ) -> torch.Tensor:
  52. # Fuse fp32 cast + layer_norm + cast-back (was 3 kernels) into one.
  53. return F.layer_norm(x.float(), (dim,), weight, bias, eps).type_as(x)
  54. @torch.compile(**_INDEXER_COMPILE)
  55. def _fused_indexer_weight_scale(
  56. weights: torch.Tensor, q_scale: torch.Tensor, scale: float
  57. ) -> torch.Tensor:
  58. # Fuse the weight-scaling muls (was 2 kernels) into one. `scale` folds
  59. # softmax_scale (head_dim**-0.5) and n_head**-0.5 into a single constant.
  60. return (weights.unsqueeze(-1) * q_scale * scale).squeeze(-1)
  61. @torch.compile(**_INDEXER_COMPILE)
  62. def _pad_indexer_heads(x: torch.Tensor, pad: int) -> torch.Tensor:
  63. # DeepGEMM MQA-logits needs num_heads in {32,64}; zero-pad the head dim.
  64. # Fuse new_zeros + cat (was 2 kernels) into one. Pad values are zero (exact
  65. # in fp8 e4m3 and zero-weight in the logits sum), so numerically a no-op.
  66. return torch.cat([x, x.new_zeros(x.shape[0], pad, *x.shape[2:])], dim=1)
  67. class Glm5NextIndexerCache(DeepseekV32IndexerCache):
  68. """Indexer K cache that stores kpool-compressed entries.
  69. Setting ``tokens_per_state = index_kpool`` on the KV cache spec makes vLLM's
  70. indexer metadata builder emit pool-granular ``slot_mapping`` /
  71. ``seq_lens`` / ``cu_seq_lens`` / ``page_table`` for free, and shrinks the
  72. cache allocation store one state per ``index_kpool`` tokens. The pool
  73. *content* (softmax-weighted sum vs keep-every-Nth) is computed by the
  74. kpool compress kernel inside the indexer op — the cache only provides the
  75. addressing, which is identical for both schemes.
  76. The indexer shares one block with the co-located MLA (a single
  77. ``MLAAttentionSpec`` / block_table), so ``block_size`` is the model-wide
  78. ``cache_config.block_size``. DeepGEMM's paged-MQA kernel
  79. (``csrc/apis/attention.hpp``) requires ``block_kv`` to be exactly 32 or
  80. 64, so the storage block is virtually split into pool pages of the
  81. largest such size that tiles it (``storage_kernel_block_size``); this
  82. needs ``block_size`` to be a multiple of ``index_kpool * 32`` (512 for
  83. ``index_kpool = 16``). A smaller block (e.g. the default 64) silently
  84. collapses ``storage_block_size`` (64 // 16 = 4) and only fails later at
  85. the opaque C++ assert; ``get_kv_cache_spec`` guards this up front
  86. instead.
  87. """
  88. def __init__(
  89. self,
  90. *,
  91. head_dim: int,
  92. dtype: torch.dtype,
  93. prefix: str,
  94. cache_config,
  95. index_kpool: int,
  96. ):
  97. super().__init__(
  98. head_dim=head_dim, dtype=dtype, prefix=prefix, cache_config=cache_config
  99. )
  100. assert index_kpool > 1, "Glm5NextIndexerCache expects index_kpool > 1"
  101. # Keep chunked-prefill boundaries aligned to complete pools.
  102. assert cache_config.block_size % index_kpool == 0, (
  103. "Glm5NextIndexerCache: cache_config.block_size "
  104. f"({cache_config.block_size}) must be a multiple of index_kpool "
  105. f"({index_kpool}) so chunked-prefill boundaries stay pool-aligned."
  106. )
  107. self._index_kpool = index_kpool
  108. def get_kv_cache_spec(self, vllm_config: VllmConfig):
  109. from dataclasses import replace
  110. spec = super().get_kv_cache_spec(vllm_config)
  111. # ``tokens_per_state`` is the KV-spec representation of kpool
  112. # compression in the current cache-layout API.
  113. assert isinstance(spec, MLAAttentionSpec)
  114. spec = replace(spec, tokens_per_state=self._index_kpool)
  115. # DeepGEMM paged-MQA takes block_kv in {32, 64}; the storage block
  116. # (= block_size // index_kpool) is virtually split into pool pages of
  117. # the largest such size that tiles it, so it must be a multiple of 32.
  118. storage_block_size = spec.block_size // self._index_kpool
  119. assert (
  120. spec.block_size % self._index_kpool == 0 and storage_block_size % 32 == 0
  121. ), (
  122. "Glm5NextIndexerCache: kpool indexer requires cache block_size to "
  123. f"be a multiple of index_kpool * 32 ({self._index_kpool * 32}) so "
  124. "that DeepGEMM paged-MQA pool pages (32 or 64 entries) tile the "
  125. f"storage block, got block_size={spec.block_size} -> "
  126. f"storage_block_size={storage_block_size}."
  127. )
  128. max_page_size = max(PAGED_MQA_PAGE_SIZES)
  129. min_page_size = min(PAGED_MQA_PAGE_SIZES)
  130. if storage_block_size <= max_page_size:
  131. page_size = storage_block_size
  132. elif storage_block_size % max_page_size == 0:
  133. page_size = max_page_size
  134. else:
  135. page_size = min_page_size
  136. return replace(
  137. spec,
  138. storage_block_size=page_size * self._index_kpool,
  139. )
  140. class Glm5NextTailCache(DeepseekV32IndexerCache):
  141. """Paged circular buffer for the kpool indexer's in-progress (tail) pool.
  142. Holds the trailing incomplete pool's raw K + gate score: one block of
  143. ``ring`` slots per request, overwritten in place by ``pos % ring``
  144. as decode/spec-decode advances. Prefill seeds it (instead of discarding the
  145. tail raw K+gate); the connector transfers it across PD; decode reads it to
  146. compress the boundary pool correctly. ``KpoolTailSpec`` /
  147. ``KpoolTailManager`` provide the no-prune, 1-block/req allocation that lets
  148. the in-progress pool survive across steps and across transfer.
  149. Stores raw bf16 K (``head_dim``) as the "K" half of each block and the
  150. bf16 gate score (``head_dim``) as the "V" half -- not the fp8-compressed
  151. entry, which lives in ``Glm5NextIndexerCache``.
  152. """
  153. def __init__(
  154. self,
  155. *,
  156. head_dim: int,
  157. dtype: torch.dtype,
  158. prefix: str,
  159. cache_config,
  160. index_kpool: int,
  161. ):
  162. super().__init__(
  163. head_dim=head_dim, dtype=dtype, prefix=prefix, cache_config=cache_config
  164. )
  165. assert index_kpool > 1, "Glm5NextTailCache expects index_kpool > 1"
  166. self._index_kpool = index_kpool
  167. def get_kv_cache_spec(self, vllm_config: VllmConfig):
  168. # The two head slots form [K, gate score] in the generic
  169. # [block, head, state, content] cache view.
  170. # Drafts are stashed before acceptance. With a one-pool ring, the
  171. # drafts behind a rejected pool-completing draft overwrite the keys
  172. # read by its redo.
  173. span = self._index_kpool + vllm_config.num_speculative_tokens
  174. ring = self._index_kpool * next_power_of_2(cdiv(span, self._index_kpool))
  175. # ring must divide the attention block size (a multiple of 128).
  176. assert self.cache_config.block_size % ring == 0, (
  177. f"Glm5NextTailCache: cache_config.block_size "
  178. f"({self.cache_config.block_size}) must be a multiple of the "
  179. f"tail ring ({ring})"
  180. )
  181. return KpoolTailSpec(
  182. block_size=ring,
  183. num_kv_heads=2,
  184. head_size=self.head_dim,
  185. head_size_v=0,
  186. dtype=torch.bfloat16,
  187. sliding_window=ring,
  188. )
  189. def get_attn_backend(self):
  190. from vllm.v1.attention.backends.mla.indexer import KpoolTailBackend
  191. return KpoolTailBackend
  192. class Indexer(nn.Module):
  193. def __init__(
  194. self,
  195. vllm_config: VllmConfig,
  196. config: Glm5NextConfig,
  197. hidden_size: int,
  198. q_lora_rank: int,
  199. quant_config: QuantizationConfig | None,
  200. cache_config: CacheConfig | None,
  201. topk_indices_buffer: torch.Tensor | None,
  202. prefix: str = "",
  203. ):
  204. super().__init__()
  205. self.vllm_config = vllm_config
  206. self.config = config
  207. self.quant_config = quant_config
  208. # self.indexer_cfg = config.attn_module_list_cfg[0]["attn_index"]
  209. # Indexer is only constructed for v32 configs, where these sparse-indexer
  210. # fields are guaranteed populated; narrow away the `int | None` declared
  211. # on Glm5NextConfig for the optional-indexer case.
  212. assert config.index_topk is not None
  213. assert config.index_n_heads is not None
  214. assert config.index_head_dim is not None
  215. assert config.index_kpool is not None
  216. self.topk_tokens = config.index_topk
  217. self.n_head = config.index_n_heads # 64
  218. self.head_dim = config.index_head_dim # 128
  219. self.rope_dim = config.qk_rope_head_dim # 64
  220. self.index_kpool = config.index_kpool
  221. self.q_lora_rank = q_lora_rank # 1536
  222. # kpool
  223. self.index_kpool_compress_ape = nn.Parameter(
  224. torch.zeros(self.index_kpool, self.head_dim, dtype=torch.float32)
  225. )
  226. # Keep the checkpoint name ``index_kpool_compress_gate`` without a
  227. # ``.weight`` suffix. F.linear consumes its [head_dim, hidden_size] shape.
  228. self.index_kpool_compress_gate = nn.Parameter(
  229. torch.empty(self.head_dim, hidden_size, dtype=torch.bfloat16)
  230. )
  231. # no tensor parallel, just replicated
  232. self.wq_b = ReplicatedLinear(
  233. self.q_lora_rank,
  234. self.head_dim * self.n_head,
  235. bias=False,
  236. quant_config=quant_config,
  237. prefix=f"{prefix}.wq_b",
  238. )
  239. # Fused wk + weights_proj: single GEMM producing [head_dim + n_head].
  240. # FP8 wk weights are upcasted to BF16 during loading to maintain fusion.
  241. self.wk_weights_proj = MergedColumnParallelLinear(
  242. hidden_size,
  243. [self.head_dim, self.n_head],
  244. bias=False,
  245. quant_config=None,
  246. disable_tp=True,
  247. prefix=f"{prefix}.wk_weights_proj",
  248. )
  249. self.k_norm = LayerNorm(self.head_dim, eps=1e-6)
  250. self.softmax_scale = self.head_dim**-0.5
  251. # Hadamard-128 rotation of the indexer query is fused with the FP8
  252. # quant (see forward: fwht128_quant_fp8) -- no precomputed matrix.
  253. self.scale_fmt = "ue8m0"
  254. self.quant_block_size = 128 # TODO: get from config
  255. self.topk_indices_buffer = topk_indices_buffer
  256. self._wp_fp32: torch.Tensor | None = None
  257. # NOTE: (zyongye) we use fp8 naive cache,
  258. # where we store value in fp8 and scale in fp32
  259. # per self.quant_block_size element
  260. self.k_cache = Glm5NextIndexerCache(
  261. head_dim=self.head_dim + self.head_dim // self.quant_block_size * 4,
  262. dtype=torch.uint8,
  263. prefix=f"{prefix}.k_cache",
  264. cache_config=cache_config,
  265. index_kpool=self.index_kpool,
  266. )
  267. # Paged tail cache (in-progress pool's raw K + gate score). Written by
  268. # prefill (seeds the boundary pool) and decode (per-step stash); read by
  269. # the decode kernel to compress the boundary pool. Transferred across PD
  270. # so the decode side sees the prefill tail. See KpoolTailSpec/Manager.
  271. self.tail_cache = Glm5NextTailCache(
  272. head_dim=self.head_dim,
  273. dtype=torch.bfloat16,
  274. prefix=f"{prefix}.tail_cache",
  275. cache_config=cache_config,
  276. index_kpool=self.index_kpool,
  277. )
  278. self.max_pool_len = vllm_config.model_config.max_model_len // self.index_kpool
  279. self.prefix = prefix
  280. from vllm.v1.attention.backends.mla.indexer import get_max_prefill_buffer_size
  281. self.max_total_seq_len = get_max_prefill_buffer_size(vllm_config)
  282. self.indexer_op = SparseAttnIndexerKpool(
  283. self.k_cache,
  284. self.quant_block_size,
  285. self.scale_fmt,
  286. self.topk_tokens,
  287. self.head_dim,
  288. self.max_pool_len,
  289. self.max_total_seq_len,
  290. self.topk_indices_buffer,
  291. tail_cache=self.tail_cache,
  292. )
  293. def forward(
  294. self, hidden_states: torch.Tensor, qr: torch.Tensor, positions, rotary_emb
  295. ) -> torch.Tensor:
  296. q, _ = self.wq_b(qr)
  297. q = q.view(-1, self.n_head, self.head_dim)
  298. # Compute the head gate in fp32; bf16 error can change near-tie pool
  299. # rankings on long-context tasks. Cache it after weights are loaded.
  300. kw, _ = self.wk_weights_proj(hidden_states)
  301. k = kw[:, : self.head_dim]
  302. # GB10 fix: bf16 x bf16 with fp32 accumulation and output. The inputs
  303. # are exact in bf16, so this matches the fp32 GEMM up to summation
  304. # order, and cuBLAS's fp32 small-N kernel took ~69 us here per layer.
  305. if self._wp_fp32 is None:
  306. self._wp_fp32 = self.wk_weights_proj.weight.data[self.head_dim :, :].t()
  307. weights = torch.mm(hidden_states, self._wp_fp32, out_dtype=torch.float32)
  308. k = _fused_indexer_k_norm(
  309. k, self.k_norm.weight, self.k_norm.bias, self.head_dim, self.k_norm.eps
  310. )
  311. if self.rope_dim > 0:
  312. q_pe, q_nope = torch.split(
  313. q, [self.rope_dim, self.head_dim - self.rope_dim], dim=-1
  314. )
  315. k_pe, k_nope = torch.split(
  316. k, [self.rope_dim, self.head_dim - self.rope_dim], dim=-1
  317. )
  318. q_pe, k_pe = rotary_emb(positions, q_pe, k_pe.unsqueeze(1))
  319. # Note: RoPE (NeoX) can introduce extra leading dimensions during
  320. # compilation so we need to reshape back to token-flattened shapes
  321. q_pe = q_pe.reshape(-1, self.n_head, self.rope_dim)
  322. k_pe = k_pe.reshape(-1, 1, self.rope_dim)
  323. # `rotary_emb` is shape-preserving; `q_pe` is already
  324. # [num_tokens, n_head, rope_dim].
  325. q = torch.cat([q_pe, q_nope], dim=-1)
  326. # `k_pe` is [num_tokens, 1, rope_dim] (MQA).
  327. k = torch.cat([k_pe.squeeze(-2), k_nope], dim=-1)
  328. # else: qk_rope_head_dim=0 — no rope component. q is already
  329. # [num_tokens, n_head, head_dim] and k is [num_tokens, head_dim] (all
  330. # nope), so skip the rope split / rotary / cat entirely; otherwise the
  331. # split/reshape would build 0-element tensors (breaks dynamo tracing).
  332. # Rotate Q into the cached K basis before computing fp8 MQA logits.
  333. # Fusing the fp32 FWHT and quantization avoids an intermediate HBM
  334. # round-trip and bf16 matrix-rounding bias.
  335. assert self.head_dim == 128 and self.quant_block_size == 128
  336. assert self.scale_fmt == "ue8m0"
  337. q = q.view(-1, self.head_dim)
  338. q_fp8, q_scale = fwht128_quant_fp8(q)
  339. q_fp8 = q_fp8.view(-1, self.n_head, self.head_dim)
  340. q_scale = q_scale.view(-1, self.n_head, 1)
  341. weights = _fused_indexer_weight_scale(
  342. weights, q_scale, self.softmax_scale * self.n_head**-0.5
  343. )
  344. # kpool: per-token gate score driving the softmax-weighted pool. Computed
  345. # from the same hidden_states that produced `k`, so it stays token-aligned.
  346. # F.linear(x, gate) = x @ gate.T with gate [head_dim, hidden_size].
  347. gate_score = F.linear(hidden_states, self.index_kpool_compress_gate)
  348. # DeepGEMM's MQA-logits kernels (fp8_mqa_logits /
  349. # fp8_fp4_paged_mqa_logits) require num_heads in {32, 64}; this
  350. # checkpoint uses index_n_heads=16. Zero-pad q and the per-head
  351. # weights: logits are a weights-weighted sum over heads, so
  352. # zero-weight padded heads contribute exactly nothing.
  353. if self.n_head < 32:
  354. pad = 32 - self.n_head
  355. q_fp8 = _pad_indexer_heads(q_fp8, pad)
  356. weights = _pad_indexer_heads(weights, pad)
  357. return self.indexer_op(
  358. hidden_states,
  359. q_fp8,
  360. k,
  361. weights,
  362. gate_score=gate_score,
  363. compress_ape=self.index_kpool_compress_ape,
  364. index_kpool=self.index_kpool,
  365. positions=positions,
  366. )
  367. class Glm5NextMLAAttention(nn.Module):
  368. def __init__(
  369. self,
  370. vllm_config: VllmConfig,
  371. config: Glm5NextConfig,
  372. hidden_size: int,
  373. num_heads: int,
  374. qk_nope_head_dim: int,
  375. qk_rope_head_dim: int,
  376. v_head_dim: int,
  377. q_lora_rank: int | None,
  378. kv_lora_rank: int,
  379. max_position_embeddings: int = 8192,
  380. cache_config: CacheConfig | None = None,
  381. quant_config: QuantizationConfig | None = None,
  382. prefix: str = "",
  383. topk_indices_buffer: torch.Tensor | None = None,
  384. input_size: int | None = None,
  385. skip_rope: bool | None = False,
  386. ) -> None:
  387. super().__init__()
  388. self.hidden_size = hidden_size
  389. self.qk_nope_head_dim = qk_nope_head_dim
  390. self.qk_rope_head_dim = qk_rope_head_dim
  391. self.qk_head_dim = qk_nope_head_dim + qk_rope_head_dim
  392. self.v_head_dim = v_head_dim
  393. self.q_lora_rank = q_lora_rank
  394. self.kv_lora_rank = kv_lora_rank
  395. self.num_heads = num_heads
  396. tp_size = get_tensor_model_parallel_world_size()
  397. assert num_heads % tp_size == 0
  398. self.num_local_heads = num_heads // tp_size
  399. self.scaling = self.qk_head_dim**-0.5
  400. self.max_position_embeddings = max_position_embeddings
  401. # Use input_size for projection input dimensions if provided,
  402. # otherwise default to hidden_size (used in Eagle3 Deepseek with MLA)
  403. proj_input_size = input_size if input_size is not None else self.hidden_size
  404. if self.q_lora_rank is not None:
  405. self.fused_qkv_a_proj = DeepSeekV2FusedQkvAProjLinear(
  406. proj_input_size,
  407. [self.q_lora_rank, self.kv_lora_rank + self.qk_rope_head_dim],
  408. quant_config=quant_config,
  409. prefix=f"{prefix}.fused_qkv_a_proj",
  410. )
  411. else:
  412. self.kv_a_proj_with_mqa = ReplicatedLinear(
  413. proj_input_size,
  414. self.kv_lora_rank + self.qk_rope_head_dim,
  415. bias=False,
  416. quant_config=quant_config,
  417. prefix=f"{prefix}.kv_a_proj_with_mqa",
  418. )
  419. if self.q_lora_rank is not None:
  420. self.q_a_layernorm = RMSNorm(self.q_lora_rank, eps=config.rms_norm_eps)
  421. self.q_b_proj = ColumnParallelLinear(
  422. self.q_lora_rank,
  423. self.num_heads * self.qk_head_dim,
  424. bias=False,
  425. quant_config=quant_config,
  426. prefix=f"{prefix}.q_b_proj",
  427. )
  428. else:
  429. self.q_proj = ColumnParallelLinear(
  430. proj_input_size,
  431. self.num_heads * self.qk_head_dim,
  432. bias=False,
  433. quant_config=quant_config,
  434. prefix=f"{prefix}.q_proj",
  435. )
  436. self.kv_a_layernorm = RMSNorm(self.kv_lora_rank, eps=config.rms_norm_eps)
  437. self.kv_b_proj = ColumnParallelLinear(
  438. self.kv_lora_rank,
  439. self.num_heads * (self.qk_nope_head_dim + self.v_head_dim),
  440. bias=False,
  441. quant_config=quant_config,
  442. prefix=f"{prefix}.kv_b_proj",
  443. )
  444. self.o_proj = RowParallelLinear(
  445. self.num_heads * self.v_head_dim,
  446. self.hidden_size,
  447. bias=False,
  448. quant_config=quant_config,
  449. prefix=f"{prefix}.o_proj",
  450. )
  451. if not skip_rope:
  452. assert config.rope_parameters is not None
  453. if config.rope_parameters["rope_type"] != "default":
  454. config.rope_parameters["rope_type"] = (
  455. "deepseek_llama_scaling"
  456. if config.rope_parameters.get("attention_factor") == 1.0
  457. else "deepseek_yarn"
  458. )
  459. self.rotary_emb: RotaryEmbedding | None = get_rope(
  460. qk_rope_head_dim,
  461. max_position=max_position_embeddings,
  462. rope_parameters=config.rope_parameters,
  463. is_neox_style=False,
  464. )
  465. if (
  466. config.rope_parameters["rope_type"] != "default"
  467. and config.rope_parameters["rope_type"] == "deepseek_yarn"
  468. ):
  469. mscale_all_dim = config.rope_parameters.get("mscale_all_dim", False)
  470. scaling_factor = config.rope_parameters["factor"]
  471. mscale = yarn_get_mscale(scaling_factor, float(mscale_all_dim))
  472. self.scaling = self.scaling * mscale * mscale
  473. else:
  474. self.rotary_emb = None
  475. self.is_v32 = config.index_topk is not None
  476. if self.is_v32:
  477. self.indexer_rope_emb: RotaryEmbedding | None = get_rope(
  478. qk_rope_head_dim,
  479. max_position=max_position_embeddings,
  480. rope_parameters=config.rope_parameters,
  481. is_neox_style=not config.indexer_rope_interleave,
  482. )
  483. # The sparse indexer projects from the MLA q-lora rank, which is
  484. # always set for v32 MLA configs; narrow away the `int | None`.
  485. assert q_lora_rank is not None
  486. self.indexer: Indexer | None = Indexer(
  487. vllm_config,
  488. config,
  489. hidden_size,
  490. q_lora_rank,
  491. quant_config,
  492. cache_config,
  493. topk_indices_buffer,
  494. f"{prefix}.indexer",
  495. )
  496. else:
  497. self.indexer_rope_emb = None
  498. self.indexer = None
  499. mla_modules = MLAModules(
  500. kv_a_layernorm=self.kv_a_layernorm,
  501. kv_b_proj=self.kv_b_proj,
  502. rotary_emb=self.rotary_emb,
  503. o_proj=self.o_proj,
  504. fused_qkv_a_proj=self.fused_qkv_a_proj
  505. if self.q_lora_rank is not None
  506. else None,
  507. kv_a_proj_with_mqa=self.kv_a_proj_with_mqa
  508. if self.q_lora_rank is None
  509. else None,
  510. q_a_layernorm=self.q_a_layernorm if self.q_lora_rank is not None else None,
  511. q_b_proj=self.q_b_proj if self.q_lora_rank is not None else None,
  512. q_proj=self.q_proj if self.q_lora_rank is None else None,
  513. indexer=self.indexer,
  514. indexer_rotary_emb=self.indexer_rope_emb,
  515. is_sparse=self.is_v32,
  516. topk_indices_buffer=topk_indices_buffer,
  517. )
  518. self.mla_attn = MultiHeadLatentAttentionWrapper(
  519. self.hidden_size,
  520. self.num_local_heads,
  521. self.scaling,
  522. self.qk_nope_head_dim,
  523. self.qk_rope_head_dim,
  524. self.v_head_dim,
  525. self.q_lora_rank,
  526. self.kv_lora_rank,
  527. mla_modules,
  528. cache_config,
  529. quant_config,
  530. prefix,
  531. skip_topk=False,
  532. fuse_qkv_rmsnorm=True,
  533. )
  534. def forward(
  535. self, hidden_states: torch.Tensor, positions: torch.Tensor
  536. ) -> torch.Tensor:
  537. # The wrapper also runs the sparse indexer before MLA attention.
  538. return self.mla_attn(positions, hidden_states)