qwen3_dflash.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. import io
  4. from collections.abc import Iterable
  5. import torch
  6. import torch.nn.functional as F
  7. from torch import nn
  8. from transformers import Qwen3Config
  9. from vllm import _custom_ops as ops
  10. from vllm.compilation.decorators import support_torch_compile
  11. from vllm.config import CacheConfig, VllmConfig, get_current_vllm_config
  12. from vllm.distributed import (
  13. get_tensor_model_parallel_rank,
  14. get_tensor_model_parallel_world_size,
  15. )
  16. from vllm.logger import init_logger
  17. from vllm.model_executor.layers.attention import Attention
  18. from vllm.model_executor.layers.layernorm import RMSNorm
  19. from vllm.model_executor.layers.linear import (
  20. QKVParallelLinear,
  21. ReplicatedLinear,
  22. RowParallelLinear,
  23. )
  24. from vllm.model_executor.layers.logits_processor import LogitsProcessor
  25. from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
  26. from vllm.model_executor.layers.rotary_embedding import get_rope
  27. from vllm.model_executor.layers.vocab_parallel_embedding import (
  28. ParallelLMHead,
  29. VocabParallelEmbedding,
  30. )
  31. from vllm.multimodal.inputs import NestedTensors
  32. from vllm.transformers_utils.config import set_default_rope_theta
  33. from vllm.transformers_utils.repo_utils import get_hf_file_bytes
  34. from vllm.v1.attention.backend import AttentionType
  35. from vllm.v1.worker.gpu.spec_decode.eagle.eagle3_utils import (
  36. get_eagle3_aux_layers_from_config,
  37. )
  38. from .qwen2 import Qwen2MLP as Qwen3MLP
  39. from .qwen3 import Qwen3ForCausalLM
  40. from .utils import (
  41. AutoWeightsLoader,
  42. WeightsMapper,
  43. get_draft_quant_config,
  44. maybe_prefix,
  45. process_eagle_weight,
  46. )
  47. logger = init_logger(__name__)
  48. _SLIDING_ATTENTION = "sliding_attention"
  49. def _dflash_layer_causal(config: Qwen3Config, layer_idx: int) -> bool:
  50. """Resolve explicit causality before falling back to legacy layer defaults."""
  51. is_causal = getattr(config, "is_causal", None)
  52. if is_causal is not None:
  53. return bool(is_causal)
  54. override = (getattr(config, "dflash_config", None) or {}).get("causal")
  55. if override is not None:
  56. return bool(override)
  57. layer_types = getattr(config, "layer_types", None)
  58. return bool(layer_types) and layer_types[layer_idx] == _SLIDING_ATTENTION
  59. def dflash_has_any_non_causal(config: Qwen3Config) -> bool:
  60. """Whether the draft needs a non-causal-capable backend, resolved from config
  61. (config mirror of the model's ``get_draft_attn_causal``, usable pre-build)."""
  62. return not all(
  63. _dflash_layer_causal(config, i) for i in range(config.num_hidden_layers)
  64. )
  65. def dflash_target_rope_is_neox_style(target_model: nn.Module) -> bool | None:
  66. """The target's RoPE layout, from its first attention layer.
  67. A DFlash head must rotate Q/K the way the target it was distilled against
  68. does, and a mismatch is silent — acceptance collapses but nothing errors and
  69. the output stays correct. Draft checkpoints do not carry this, so take it
  70. from the target. None if the target uses no RoPE.
  71. """
  72. language_model = (
  73. target_model.get_language_model()
  74. if hasattr(target_model, "get_language_model")
  75. else target_model
  76. )
  77. for module in language_model.modules():
  78. style = getattr(module, "is_neox_style", None)
  79. if isinstance(style, bool):
  80. return style
  81. return None
  82. def _get_dflash_fc_input_size(vllm_config: VllmConfig) -> int:
  83. spec_config = vllm_config.speculative_config
  84. config = spec_config.draft_model_config.hf_config
  85. aux_layers = get_eagle3_aux_layers_from_config(spec_config)
  86. num_features_to_use = len(aux_layers) if aux_layers else config.num_hidden_layers
  87. target_hidden_size = (
  88. getattr(config, "target_hidden_size", None) or config.hidden_size
  89. )
  90. return target_hidden_size * num_features_to_use
  91. def _resolve_layer_attention(
  92. config: Qwen3Config, layer_idx: int
  93. ) -> tuple[int | None, bool]:
  94. """Resolve ``(sliding_window, causal)`` for one DFlash draft layer.
  95. +----------------------+-------------------------+--------------------------------+
  96. | Config | ``layer_type`` | *``causal`` |
  97. +======================+=========================+================================+
  98. | ``layer_types`` | SWA if ``use_swa`` | True if ``layer_types[i]=SWA`` |
  99. | | else ``layer_types[i]`` | else False |
  100. +----------------------+-------------------------+--------------------------------+
  101. | ``layer_types=None`` | SWA | False |
  102. | + ``use_swa=True`` | | |
  103. +----------------------+-------------------------+--------------------------------+
  104. | ``layer_types=None`` | Full | False |
  105. | + ``use_swa=False`` | | |
  106. +----------------------+-------------------------+--------------------------------+
  107. * If ``dflash_config.causal`` is set, its value overrides ``causal`` for all layers.
  108. This is to support a varied ecosystem of checkpoints, including:
  109. - XiaomiMiMo/MiMo-V2.5-Pro-FP4-DFlash (sets "use_swa", assumes non-causal)
  110. - z-lab/gemma-4-31B-it-DFlash (has mixed layer types, assumes causal only for SWA)
  111. - z-lab/Qwen3.5-9B-DFlash ("standard" DFlash, all full attn, assumes non-causal)
  112. """
  113. dflash_config = getattr(config, "dflash_config", None) or {}
  114. layer_types = getattr(config, "layer_types", None)
  115. use_swa = dflash_config.get("use_swa", False)
  116. any_sliding = False
  117. if layer_types is not None:
  118. num_sliding = sum(lt == _SLIDING_ATTENTION for lt in layer_types)
  119. any_sliding = num_sliding > 0
  120. # Mixed sliding/full attention needs multiple KV groups (V2 runner only).
  121. if (
  122. 0 < num_sliding < len(layer_types)
  123. and not get_current_vllm_config().use_v2_model_runner
  124. ):
  125. raise NotImplementedError(
  126. "DFlash drafters with mixed sliding/full attention require "
  127. "the V2 model runner; relaunch with "
  128. "VLLM_USE_V2_MODEL_RUNNER=1."
  129. )
  130. # ``use_swa`` forces SWA on every layer, even an all-full ``layer_types``.
  131. if layer_types is None or (use_swa and not any_sliding):
  132. is_sliding = use_swa
  133. else:
  134. is_sliding = layer_types[layer_idx] == _SLIDING_ATTENTION
  135. sliding_window = None
  136. if is_sliding:
  137. sliding_window = dflash_config.get(
  138. "swa_window_size", getattr(config, "sliding_window", None)
  139. )
  140. if sliding_window is None:
  141. raise ValueError(
  142. "DFlash sliding attention requires a window size configured in "
  143. "dflash_config.swa_window_size or the top-level sliding_window."
  144. )
  145. return sliding_window, _dflash_layer_causal(config, layer_idx)
  146. class DFlashQwen3Attention(nn.Module):
  147. """Attention for DFlash speculative decoding.
  148. Context KVs are pre-inserted into the KV cache before the forward pass.
  149. This layer handles only query tokens via standard attention.
  150. Adapted from Qwen3Attention."""
  151. def __init__(
  152. self,
  153. hidden_size: int,
  154. num_heads: int,
  155. num_kv_heads: int,
  156. rope_parameters: dict,
  157. max_position: int = 4096 * 32,
  158. head_dim: int | None = None,
  159. rms_norm_eps: float = 1e-06,
  160. attention_bias: bool = False,
  161. add_swa_attention_sink_bias: bool = False,
  162. sliding_window: int | None = None,
  163. causal: bool = False,
  164. is_neox_style: bool = True,
  165. cache_config: CacheConfig | None = None,
  166. quant_config: QuantizationConfig | None = None,
  167. prefix: str = "",
  168. attn_type: str = AttentionType.DECODER,
  169. ) -> None:
  170. super().__init__()
  171. self.layer_name = prefix
  172. self.hidden_size = hidden_size
  173. tp_size = get_tensor_model_parallel_world_size()
  174. self.total_num_heads = num_heads
  175. assert self.total_num_heads % tp_size == 0
  176. self.num_heads = self.total_num_heads // tp_size
  177. self.total_num_kv_heads = num_kv_heads
  178. if self.total_num_kv_heads >= tp_size:
  179. assert self.total_num_kv_heads % tp_size == 0
  180. else:
  181. assert tp_size % self.total_num_kv_heads == 0
  182. self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
  183. self.head_dim = head_dim or hidden_size // self.total_num_heads
  184. self.q_size = self.num_heads * self.head_dim
  185. self.kv_size = self.num_kv_heads * self.head_dim
  186. self.scaling = self.head_dim**-0.5
  187. self.qkv_proj = QKVParallelLinear(
  188. hidden_size,
  189. self.head_dim,
  190. self.total_num_heads,
  191. self.total_num_kv_heads,
  192. bias=attention_bias,
  193. quant_config=quant_config,
  194. prefix=f"{prefix}.qkv_proj",
  195. )
  196. self.o_proj = RowParallelLinear(
  197. self.total_num_heads * self.head_dim,
  198. hidden_size,
  199. bias=attention_bias, # DFlash has o_proj bias when using attention bias
  200. quant_config=quant_config,
  201. prefix=f"{prefix}.o_proj",
  202. )
  203. self.rotary_emb = get_rope(
  204. self.head_dim,
  205. max_position=max_position,
  206. is_neox_style=is_neox_style,
  207. rope_parameters=rope_parameters,
  208. )
  209. self.attention_sink_bias = (
  210. torch.nn.Parameter(torch.empty(self.num_heads), requires_grad=False)
  211. if add_swa_attention_sink_bias
  212. else None
  213. )
  214. self.sliding_window = sliding_window
  215. self.attn = Attention(
  216. self.num_heads,
  217. self.head_dim,
  218. self.scaling,
  219. num_kv_heads=self.num_kv_heads,
  220. cache_config=cache_config,
  221. quant_config=quant_config,
  222. per_layer_sliding_window=sliding_window,
  223. prefix=f"{prefix}.attn",
  224. attn_type=attn_type,
  225. sinks=self.attention_sink_bias,
  226. )
  227. self.causal = causal
  228. self.q_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
  229. self.k_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
  230. def forward(
  231. self,
  232. positions: torch.Tensor,
  233. hidden_states: torch.Tensor,
  234. ) -> torch.Tensor:
  235. """DFlash attention assumes that the KV cache is already populated
  236. with the context K/V from the target model's hidden states. This forward op
  237. computes attention for the query tokens only.
  238. See also: precompute_and_store_context_kv"""
  239. qkv, _ = self.qkv_proj(hidden_states)
  240. q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
  241. # Per-head RMSNorm
  242. q_shape, k_shape = q.shape, k.shape
  243. q = self.q_norm(
  244. q.view(*q_shape[:-1], q_shape[-1] // self.head_dim, self.head_dim)
  245. ).view(q_shape)
  246. k = self.k_norm(
  247. k.view(*k_shape[:-1], k_shape[-1] // self.head_dim, self.head_dim)
  248. ).view(k_shape)
  249. q, k = self.rotary_emb(positions, q, k)
  250. attn_output = self.attn(q, k, v)
  251. output, _ = self.o_proj(attn_output)
  252. return output
  253. class DFlashQwen3DecoderLayer(nn.Module):
  254. def __init__(
  255. self,
  256. vllm_config: VllmConfig,
  257. *,
  258. config: Qwen3Config,
  259. layer_idx: int,
  260. cache_config: CacheConfig | None = None,
  261. quant_config: QuantizationConfig | None = None,
  262. prefix: str = "",
  263. ) -> None:
  264. super().__init__()
  265. self.hidden_size = config.hidden_size
  266. set_default_rope_theta(config, default_theta=1000000)
  267. attn_type = AttentionType.DECODER
  268. # DFlash drafts store the sink-bias flag inside dflash_config; fall back
  269. # to the top-level attribute used by other (e.g. MiMo) configs.
  270. dflash_config = getattr(config, "dflash_config", None) or {}
  271. add_swa_attention_sink_bias = dflash_config.get(
  272. "attention_sink_bias",
  273. getattr(config, "add_swa_attention_sink_bias", False),
  274. )
  275. # Resolve this layer's attention mode (full vs sliding window, causal vs
  276. # non-causal) from the draft config.
  277. sliding_window, causal = _resolve_layer_attention(config, layer_idx)
  278. # RoPE layout, copied off the target at load time by the draft loader
  279. # (see `dflash_target_rope_is_neox_style`). Checkpoints do not carry it:
  280. # a head distilled from an interleaved-RoPE target must rotate the way
  281. # that target does, or every drafted Q/K is wrong and acceptance
  282. # collapses with no error raised.
  283. is_neox_style = getattr(config, "is_neox_style", True)
  284. self.self_attn = DFlashQwen3Attention(
  285. hidden_size=self.hidden_size,
  286. num_heads=config.num_attention_heads,
  287. max_position=config.max_position_embeddings,
  288. num_kv_heads=config.num_key_value_heads,
  289. rms_norm_eps=config.rms_norm_eps,
  290. attention_bias=getattr(config, "attention_bias", False),
  291. add_swa_attention_sink_bias=add_swa_attention_sink_bias,
  292. sliding_window=sliding_window,
  293. causal=causal,
  294. is_neox_style=is_neox_style,
  295. head_dim=getattr(config, "head_dim", None),
  296. cache_config=cache_config,
  297. quant_config=quant_config,
  298. rope_parameters=config.rope_parameters,
  299. prefix=f"{prefix}.self_attn",
  300. attn_type=attn_type,
  301. )
  302. self.mlp = Qwen3MLP(
  303. hidden_size=self.hidden_size,
  304. intermediate_size=config.intermediate_size,
  305. hidden_act=config.hidden_act,
  306. quant_config=quant_config,
  307. prefix=f"{prefix}.mlp",
  308. )
  309. self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
  310. self.post_attention_layernorm = RMSNorm(
  311. config.hidden_size, eps=config.rms_norm_eps
  312. )
  313. def forward(
  314. self,
  315. positions: torch.Tensor,
  316. hidden_states: torch.Tensor,
  317. residual: torch.Tensor | None,
  318. ) -> tuple[torch.Tensor, torch.Tensor]:
  319. if residual is not None:
  320. hidden_states, residual = self.input_layernorm(hidden_states, residual)
  321. else:
  322. residual = hidden_states
  323. hidden_states = self.input_layernorm(hidden_states)
  324. hidden_states = self.self_attn(
  325. positions=positions,
  326. hidden_states=hidden_states,
  327. )
  328. hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
  329. hidden_states = self.mlp(hidden_states)
  330. return hidden_states, residual
  331. @support_torch_compile
  332. class DFlashQwen3Model(nn.Module):
  333. decoder_layer_cls = DFlashQwen3DecoderLayer
  334. hf_to_vllm_mapper = WeightsMapper(
  335. orig_to_new_substr={
  336. "midlayer.": "layers.0.",
  337. # Muse-Glimmer-30B-assistant names the aux-hidden-state encoder
  338. # `encoder.fc` / `encoder.output_norm_enc`; this head calls them
  339. # `fc` / `hidden_norm`. Same tensors and shapes, different names.
  340. "encoder.output_norm_enc.": "hidden_norm.",
  341. "encoder.fc.": "fc.",
  342. },
  343. orig_to_new_stacked={
  344. ".q_proj": (".qkv_proj", "q"),
  345. ".k_proj": (".qkv_proj", "k"),
  346. ".v_proj": (".qkv_proj", "v"),
  347. ".gate_proj": (".gate_up_proj", 0),
  348. ".up_proj": (".gate_up_proj", 1),
  349. },
  350. )
  351. def __init__(
  352. self,
  353. *,
  354. vllm_config: VllmConfig,
  355. start_layer_id: int = 0,
  356. prefix: str = "",
  357. ) -> None:
  358. super().__init__()
  359. self.config = vllm_config.speculative_config.draft_model_config.hf_config
  360. self.vocab_size = self.config.vocab_size
  361. self.quant_config = get_draft_quant_config(vllm_config)
  362. drafter_config = getattr(self.config, "eagle_config", {})
  363. drafter_config.update(getattr(self.config, "dflash_config", {}))
  364. self.use_aux_hidden_state = drafter_config.get(
  365. "use_aux_hidden_state",
  366. getattr(self.config, "use_aux_hidden_state", True),
  367. )
  368. current_vllm_config = get_current_vllm_config()
  369. self.embed_tokens = VocabParallelEmbedding(
  370. self.config.vocab_size,
  371. self.config.hidden_size,
  372. prefix=maybe_prefix(prefix, "embed_tokens"),
  373. )
  374. # Masked query slots are fed to the draft as `mask_token_id`. Most DFlash
  375. # checkpoints will have the mask embedding in the vocabulary embedding table
  376. # at that slot id. Some checkpoints (XiaomiMiMo/MiMo-V2.5-Pro-FP4-DFlash) ship
  377. # with a separate mask embedding tensor to use instead. When present, we load it
  378. # and substitute it for embed_tokens[mask_token_id] when computing embeddings.
  379. self.mask_token_id = drafter_config.get(
  380. "mask_token_id", getattr(self.config, "mask_token_id", None)
  381. )
  382. self.mask_embedding = nn.Parameter(
  383. torch.zeros(self.config.hidden_size, dtype=vllm_config.model_config.dtype),
  384. requires_grad=False,
  385. )
  386. self.has_separate_mask_embedding = False
  387. self.layers = nn.ModuleList(
  388. [
  389. self.decoder_layer_cls(
  390. current_vllm_config,
  391. config=self.config,
  392. layer_idx=layer_idx,
  393. cache_config=current_vllm_config.cache_config,
  394. quant_config=self.quant_config,
  395. prefix=maybe_prefix(prefix, f"layers.{layer_idx + start_layer_id}"),
  396. )
  397. for layer_idx in range(self.config.num_hidden_layers)
  398. ]
  399. )
  400. if self.use_aux_hidden_state:
  401. self.fc = ReplicatedLinear(
  402. input_size=_get_dflash_fc_input_size(
  403. vllm_config,
  404. ),
  405. output_size=self.config.hidden_size,
  406. bias=False,
  407. params_dtype=vllm_config.model_config.dtype,
  408. quant_config=self.quant_config,
  409. prefix=maybe_prefix(prefix, "fc"),
  410. return_bias=False,
  411. )
  412. self.hidden_norm = RMSNorm(
  413. self.config.hidden_size,
  414. eps=self.config.rms_norm_eps,
  415. )
  416. self.norm = RMSNorm(
  417. self.config.hidden_size,
  418. eps=self.config.rms_norm_eps,
  419. )
  420. # The context projection concatenates K/V weights from every draft
  421. # layer. It is not a LinearBase module because its output layout is a
  422. # DFlash-specific fusion. Serialized MXFP8 checkpoints retain an
  423. # independently packed copy in this parameter container.
  424. self._fused_kv_linear = nn.Module()
  425. self._fused_kv_quant_method = None
  426. self._fused_kv_weight: torch.Tensor | None = None
  427. self._fused_kv_weight_scale: torch.Tensor | None = None
  428. def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
  429. embeds = self.embed_tokens(input_ids)
  430. if self.has_separate_mask_embedding and self.mask_token_id is not None:
  431. # Replace masked slots with the dedicated mask embedding.
  432. is_mask = (input_ids == self.mask_token_id).unsqueeze(-1)
  433. embeds = torch.where(is_mask, self.mask_embedding.to(embeds.dtype), embeds)
  434. return embeds
  435. def _build_context_kv_buffers(
  436. self,
  437. layers_attn: list[nn.Module],
  438. has_bias: bool,
  439. ) -> None:
  440. from vllm.model_executor.layers.quantization.modelopt import (
  441. ModelOptMxFp8LinearMethod,
  442. )
  443. quant_methods = [a.qkv_proj.quant_method for a in layers_attn]
  444. uses_mxfp8 = [
  445. isinstance(method, ModelOptMxFp8LinearMethod) for method in quant_methods
  446. ]
  447. if any(uses_mxfp8) and not all(uses_mxfp8):
  448. raise ValueError(
  449. "Every DFlash attention layer must use the same MXFP8 "
  450. "linear format for the fused context K/V projection."
  451. )
  452. self._hidden_norm_weight = self.hidden_norm.weight.data
  453. # KV projection weights: [num_layers * 2 * kv_size, hidden_size]
  454. kv_weights = [a.qkv_proj.weight[a.q_size :] for a in layers_attn]
  455. self._fused_kv_weight = torch.cat(kv_weights, dim=0)
  456. if all(uses_mxfp8):
  457. kv_scales = [a.qkv_proj.weight_scale[a.q_size :] for a in layers_attn]
  458. self._fused_kv_weight_scale = torch.cat(kv_scales, dim=0)
  459. else:
  460. self._fused_kv_weight_scale = None
  461. if has_bias:
  462. kv_biases = [a.qkv_proj.bias[a.q_size :] for a in layers_attn]
  463. self._fused_kv_bias: torch.Tensor | None = torch.cat(kv_biases, dim=0)
  464. else:
  465. self._fused_kv_bias = None
  466. # K-norm weights stacked into one contiguous [num_layers, head_dim]
  467. # tensor so the per-layer K-norm runs as a single grouped kernel.
  468. self._k_norm_weights = torch.stack(
  469. [a.k_norm.weight.data for a in layers_attn], dim=0
  470. ).contiguous()
  471. def _build_fused_kv_buffers(self) -> None:
  472. """Build fused weight buffers for precompute_and_store_context_kv.
  473. Must be called after weights are loaded. Stacks the KV-projection
  474. weights, K-norm weights, and RoPE parameters from every attention
  475. layer so that precompute_and_store_context_kv can run one fused
  476. GEMM for all layers at once. Also aliases the weight of the hidden_norm.
  477. """
  478. layers_attn = [layer.self_attn for layer in self.layers]
  479. attn0 = layers_attn[0]
  480. has_bias = attn0.qkv_proj.bias is not None
  481. self._build_context_kv_buffers(layers_attn, has_bias)
  482. # RoPE parameters
  483. self._rope_head_size = attn0.rotary_emb.head_size
  484. self._rope_cos_sin_cache = attn0.rotary_emb.cos_sin_cache
  485. self._rope_is_neox = attn0.rotary_emb.is_neox_style
  486. # Validation that RoPE params are the same across all layers
  487. for attn in layers_attn[1:]:
  488. assert (
  489. attn.rotary_emb.head_size == self._rope_head_size
  490. and attn.rotary_emb.is_neox_style == self._rope_is_neox
  491. ), "All layers must have the same RoPE parameters for DFlash precomputation"
  492. # Layer metadata
  493. self._num_attn_layers = len(layers_attn)
  494. self._kv_size = attn0.kv_size
  495. self._head_dim = attn0.head_dim
  496. self._num_kv_heads = attn0.num_kv_heads
  497. self._rms_norm_eps = attn0.q_norm.variance_epsilon
  498. # Validation that all layers have the same attention config
  499. for attn in layers_attn[1:]:
  500. assert (
  501. attn.kv_size == self._kv_size
  502. and attn.head_dim == self._head_dim
  503. and attn.num_kv_heads == self._num_kv_heads
  504. and attn.q_norm.variance_epsilon == self._rms_norm_eps
  505. ), "All layers must have the same attn config for DFlash precomputation"
  506. # References to inner Attention layers for direct cache writes
  507. self._attn_layers = [layer.self_attn.attn for layer in self.layers]
  508. def process_weights_after_loading(self) -> None:
  509. """Pack the serialized MXFP8 context projection for its GEMM backend."""
  510. from vllm.model_executor.layers.quantization.modelopt import (
  511. ModelOptMxFp8LinearMethod,
  512. )
  513. quant_method = self.layers[0].self_attn.qkv_proj.quant_method
  514. if not isinstance(quant_method, ModelOptMxFp8LinearMethod):
  515. return
  516. if self._fused_kv_weight is None or self._fused_kv_weight_scale is None:
  517. raise RuntimeError(
  518. "The DFlash MXFP8 context projection requires serialized K/V "
  519. "weights and block scales to be fused during checkpoint loading."
  520. )
  521. output_size, input_size = self._fused_kv_weight.shape
  522. self._fused_kv_linear.input_size_per_partition = input_size
  523. self._fused_kv_linear.output_size_per_partition = output_size
  524. self._fused_kv_linear.logical_widths = [output_size]
  525. self._fused_kv_linear.register_parameter(
  526. "weight", nn.Parameter(self._fused_kv_weight, requires_grad=False)
  527. )
  528. self._fused_kv_linear.register_parameter(
  529. "weight_scale",
  530. nn.Parameter(self._fused_kv_weight_scale, requires_grad=False),
  531. )
  532. quant_method.process_weights_after_loading(self._fused_kv_linear)
  533. self._fused_kv_quant_method = quant_method
  534. self._fused_kv_weight = None
  535. self._fused_kv_weight_scale = None
  536. logger.info_once(
  537. "Using %s for the fused DFlash context K/V projection.",
  538. type(quant_method.kernel).__name__,
  539. )
  540. def _project_context_kv(
  541. self,
  542. context_states: torch.Tensor,
  543. num_ctx: int,
  544. num_layers: int,
  545. num_kv_heads: int,
  546. head_dim: int,
  547. ) -> tuple[torch.Tensor, torch.Tensor]:
  548. # --- Fused KV projection (one GEMM for all layers) ---
  549. normed_context_states = torch.empty_like(context_states)
  550. ops.rms_norm(
  551. normed_context_states,
  552. context_states,
  553. self._hidden_norm_weight,
  554. self._rms_norm_eps,
  555. )
  556. if self._fused_kv_quant_method is None:
  557. if self._fused_kv_weight is None:
  558. raise RuntimeError("DFlash context K/V projection is not initialized.")
  559. all_kv_flat = F.linear(
  560. normed_context_states, self._fused_kv_weight, self._fused_kv_bias
  561. )
  562. else:
  563. all_kv_flat = self._fused_kv_quant_method.apply(
  564. self._fused_kv_linear,
  565. normed_context_states,
  566. self._fused_kv_bias,
  567. )
  568. # Single contiguous copy that separates K/V and transposes to
  569. # layer-major layout. Result: [2, L, num_ctx, nkv, hd] contiguous.
  570. # Indexing dim-0 gives contiguous [L, num_ctx, nkv, hd] for K and V.
  571. all_kv = (
  572. all_kv_flat.view(num_ctx, num_layers, 2, num_kv_heads, head_dim)
  573. .permute(2, 1, 0, 3, 4)
  574. .contiguous()
  575. )
  576. all_k = all_kv[0] # [L, num_ctx, nkv, hd], contiguous
  577. all_v = all_kv[1] # [L, num_ctx, nkv, hd], contiguous
  578. return all_k, all_v
  579. def _normalize_context_k(self, all_k: torch.Tensor) -> torch.Tensor:
  580. # --- Grouped RMSNorm K across all layers ([L, num_ctx, nkv, hd]) ---
  581. # The weight is selected per layer by the outermost (layer) index.
  582. all_k_normed = torch.empty_like(all_k)
  583. ops.rms_norm(
  584. all_k_normed,
  585. all_k,
  586. self._k_norm_weights,
  587. self._rms_norm_eps,
  588. )
  589. return all_k_normed
  590. def precompute_and_store_context_kv(
  591. self,
  592. context_states: torch.Tensor,
  593. context_positions: torch.Tensor,
  594. context_slot_mapping: torch.Tensor | list[torch.Tensor | None] | None = None,
  595. ) -> None:
  596. """Precompute K/V for context states write them into each layer's KV cache.
  597. Input context states are projected to K/V, normed, and have RoPE applied.
  598. Since the context shape is different than the query shape, we can't rely on the
  599. regular forward pass to apply torch.compile and CUDA graphs to this section.
  600. As such, this function is optimized to minimize the number of torch ops present:
  601. we use fused vLLM kernels for RMSNorm and RoPE, fuse the GEMM into one
  602. large projection, and avoid cloning buffers (with .contiguous()) where possible.
  603. When context_slot_mapping is None (e.g. during dummy_run) only
  604. the computation runs, and no K/V is written to cache.
  605. """
  606. if not hasattr(self, "_num_attn_layers"):
  607. logger.warning_once(
  608. "DFlash buffer initialization was skipped. If dummy weights are not "
  609. "in use, this may indicate an error in weight loading."
  610. )
  611. self._build_fused_kv_buffers()
  612. num_ctx = context_states.shape[0]
  613. L = self._num_attn_layers
  614. kv = self._kv_size
  615. hd = self._head_dim
  616. nkv = self._num_kv_heads
  617. all_k, all_v = self._project_context_kv(context_states, num_ctx, L, nkv, hd)
  618. all_k_normed = self._normalize_context_k(all_k)
  619. # --- Fused RoPE across all layers ---
  620. # View as [L * num_ctx, kv] so RoPE sees one big batch (no copy).
  621. # In-place RoPE: pass K as the "query" arg with key=None.
  622. all_k_flat = all_k_normed.view(L * num_ctx, kv)
  623. positions_repeated = context_positions.repeat(L)
  624. cos_sin_cache = self._rope_cos_sin_cache
  625. if cos_sin_cache.dtype != all_k_flat.dtype:
  626. cos_sin_cache = cos_sin_cache.to(dtype=all_k_flat.dtype)
  627. ops.rotary_embedding(
  628. positions_repeated,
  629. all_k_flat,
  630. None,
  631. self._rope_head_size,
  632. cos_sin_cache,
  633. self._rope_is_neox,
  634. )
  635. if context_slot_mapping is None:
  636. return
  637. # --- Per-layer cache insert ---
  638. all_k_final = all_k_flat.view(L, num_ctx, nkv, hd)
  639. per_layer = isinstance(context_slot_mapping, (list, tuple))
  640. for i in range(L):
  641. slot_mapping = (
  642. context_slot_mapping[i] if per_layer else context_slot_mapping
  643. )
  644. if slot_mapping is None:
  645. continue # dummy run: skip cache ops
  646. attn = self._attn_layers[i]
  647. kv_cache = attn.kv_cache
  648. attn.impl.do_kv_cache_update(
  649. attn,
  650. all_k_final[i],
  651. all_v[i],
  652. kv_cache,
  653. slot_mapping,
  654. )
  655. def forward(
  656. self,
  657. input_ids: torch.Tensor,
  658. positions: torch.Tensor,
  659. input_embeds: torch.Tensor | None = None,
  660. ) -> torch.Tensor:
  661. if input_embeds is None:
  662. input_embeds = self.embed_input_ids(input_ids)
  663. hidden_states = input_embeds
  664. residual = None
  665. for layer in self.layers:
  666. hidden_states, residual = layer(
  667. positions=positions,
  668. hidden_states=hidden_states,
  669. residual=residual,
  670. )
  671. hidden_states, _ = self.norm(hidden_states, residual)
  672. return hidden_states
  673. def _preprocess(
  674. self, weights: Iterable[tuple[str, torch.Tensor]]
  675. ) -> Iterable[tuple[str, torch.Tensor]]:
  676. tp_size = get_tensor_model_parallel_world_size()
  677. tp_rank = get_tensor_model_parallel_rank()
  678. for name, loaded_weight in weights:
  679. if "attention_sink_bias" in name:
  680. # Sink bias is per-head; shard it across TP ranks like the
  681. # attention heads themselves.
  682. heads_per_rank = loaded_weight.shape[0] // tp_size
  683. loaded_weight = loaded_weight.narrow(
  684. 0, tp_rank * heads_per_rank, heads_per_rank
  685. )
  686. yield name, loaded_weight
  687. def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
  688. loader = AutoWeightsLoader(self)
  689. return loader.load_weights(
  690. self._preprocess(weights), mapper=self.hf_to_vllm_mapper
  691. )
  692. class DFlashQwen3ForCausalLM(Qwen3ForCausalLM):
  693. model_cls = DFlashQwen3Model
  694. def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
  695. nn.Module.__init__(self)
  696. self.draft_model_config = vllm_config.speculative_config.draft_model_config
  697. self.config = self.draft_model_config.hf_config
  698. if getattr(self.config, "draft_vocab_size", None) is None:
  699. self.config.draft_vocab_size = getattr(self.config, "vocab_size", None)
  700. target_layer_num = vllm_config.model_config.get_num_layers(
  701. vllm_config.parallel_config
  702. )
  703. self.model = self.model_cls(
  704. vllm_config=vllm_config,
  705. prefix=maybe_prefix(prefix, "model"),
  706. start_layer_id=target_layer_num,
  707. )
  708. logit_scale = getattr(self.config, "logit_scale", 1.0)
  709. self.lm_head = ParallelLMHead(
  710. self.config.draft_vocab_size,
  711. self.config.hidden_size,
  712. prefix=maybe_prefix(prefix, "lm_head"),
  713. )
  714. self.logits_processor = LogitsProcessor(
  715. self.config.draft_vocab_size, scale=logit_scale
  716. )
  717. target_vocab_size = vllm_config.model_config.get_vocab_size()
  718. if self.config.draft_vocab_size != target_vocab_size:
  719. self.draft_id_to_target_id = nn.Parameter(
  720. torch.zeros(self.config.draft_vocab_size, dtype=torch.long),
  721. requires_grad=False,
  722. )
  723. else:
  724. self.draft_id_to_target_id = None
  725. def embed_input_ids(
  726. self,
  727. input_ids: torch.Tensor,
  728. multimodal_embeddings: NestedTensors | None = None,
  729. is_multimodal: torch.Tensor | None = None,
  730. ) -> torch.Tensor:
  731. return self.model.embed_input_ids(input_ids)
  732. def forward(
  733. self,
  734. input_ids: torch.Tensor,
  735. positions: torch.Tensor,
  736. inputs_embeds: torch.Tensor | None = None,
  737. ) -> torch.Tensor:
  738. return self.model(input_ids, positions, inputs_embeds)
  739. def get_draft_kv_cache_layer_names(self) -> list[str]:
  740. return [layer.self_attn.attn.layer_name for layer in self.model.layers]
  741. def get_draft_attn_causal(self) -> list[bool]:
  742. """Per-layer attention causality, aligned with
  743. get_draft_kv_cache_layer_names."""
  744. return [layer.self_attn.causal for layer in self.model.layers]
  745. def compute_logits(
  746. self,
  747. hidden_states: torch.Tensor,
  748. ) -> torch.Tensor | None:
  749. logits = self.logits_processor(self.lm_head, hidden_states)
  750. if self.draft_id_to_target_id is None:
  751. return logits
  752. base = torch.arange(self.config.draft_vocab_size, device=logits.device)
  753. targets = base + self.draft_id_to_target_id
  754. logits_new = logits.new_full(
  755. (logits.shape[0], self.config.vocab_size),
  756. float("-inf"),
  757. )
  758. logits_new[:, targets] = logits
  759. return logits_new
  760. def precompute_and_store_context_kv(
  761. self,
  762. context_states: torch.Tensor,
  763. context_positions: torch.Tensor,
  764. context_slot_mapping: torch.Tensor | list[torch.Tensor | None] | None = None,
  765. ) -> None:
  766. """Precompute projected + RoPE'd K/V and write to cache."""
  767. self.model.precompute_and_store_context_kv(
  768. context_states, context_positions, context_slot_mapping
  769. )
  770. def process_weights_after_loading(self) -> None:
  771. """Finalize the DFlash fused context projection after linear packing."""
  772. self.model.process_weights_after_loading()
  773. def combine_hidden_states(
  774. self,
  775. hidden_states: torch.Tensor,
  776. ) -> torch.Tensor:
  777. if not self.model.use_aux_hidden_state:
  778. return hidden_states
  779. needs_squeeze = hidden_states.dim() == 1
  780. if needs_squeeze:
  781. hidden_states = hidden_states.unsqueeze(0)
  782. expected = self.model.fc.input_size
  783. if hidden_states.shape[-1] != expected:
  784. raise ValueError(
  785. f"DFlash drafter expects {expected} concatenated aux hidden "
  786. f"features but received {hidden_states.shape[-1]}. This usually "
  787. "means the draft model's target_layer_ids reference layers that "
  788. "do not exist in the target model (incompatible draft/target pair)."
  789. )
  790. result = self.model.fc(hidden_states)
  791. if needs_squeeze:
  792. result = result.squeeze(0)
  793. return result
  794. def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
  795. model_weights = {}
  796. includes_draft_id_mapping = False
  797. includes_embed_tokens = False
  798. for name, loaded_weight in weights:
  799. assert "mask_hidden" not in name, (
  800. "DFlash embeds masked slots via mask_token_id (optionally "
  801. "overridden by a mask_embedding.pt file); it should not ship a "
  802. "mask_hidden weight."
  803. )
  804. if "t2d" in name:
  805. continue
  806. if "d2t" in name:
  807. name = name.replace("d2t", "draft_id_to_target_id")
  808. includes_draft_id_mapping = True
  809. elif "lm_head" not in name:
  810. name = "model." + name
  811. if "embed_tokens" in name:
  812. includes_embed_tokens = True
  813. model_weights[name] = loaded_weight
  814. process_eagle_weight(self, name)
  815. # Route the separately-trained mask embedding (if shipped) through the
  816. # standard weight loader alongside the rest of the draft weights.
  817. mask_embedding = self._read_mask_embedding()
  818. if mask_embedding is not None:
  819. model_weights["model.mask_embedding"] = mask_embedding
  820. self.model.has_separate_mask_embedding = True
  821. orig_to_new_substr = {}
  822. if not includes_draft_id_mapping:
  823. orig_to_new_substr["draft_id_to_target_id"] = None
  824. if not includes_embed_tokens:
  825. orig_to_new_substr["embed_tokens"] = None
  826. if not self.model.use_aux_hidden_state:
  827. orig_to_new_substr["fc."] = None
  828. if not self.model.has_separate_mask_embedding:
  829. orig_to_new_substr["mask_embedding"] = None
  830. mapper = WeightsMapper(orig_to_new_substr=orig_to_new_substr)
  831. loader = AutoWeightsLoader(self)
  832. loader.load_weights(model_weights.items(), mapper=mapper)
  833. self.model._build_fused_kv_buffers()
  834. def _read_mask_embedding(self) -> torch.Tensor | None:
  835. """Checks for an override mask embedding in `mask_embedding.pt` and returns it.
  836. Some checkpoints ship a separately-trained mask embedding for the mask token,
  837. which we use to overwrite the embedding for `mask_token_id`. This helper
  838. checks for the file, loads the pytorch tensor, and returns the embedding to use.
  839. Returns None if the override file is not present.
  840. """
  841. mask_token_id = self.model.mask_token_id
  842. if mask_token_id is None:
  843. return None
  844. MASK_EMBEDDING_FILENAME = "mask_embedding.pt"
  845. data = get_hf_file_bytes(
  846. MASK_EMBEDDING_FILENAME,
  847. self.draft_model_config.model,
  848. self.draft_model_config.revision,
  849. )
  850. if data is None:
  851. return None
  852. state = torch.load(io.BytesIO(data), weights_only=True)
  853. if isinstance(state, dict):
  854. if state.get("mask_token_id", mask_token_id) != mask_token_id:
  855. raise ValueError(
  856. f"{MASK_EMBEDDING_FILENAME} mask_token_id does not match "
  857. f"dflash_config.mask_token_id ({mask_token_id}). "
  858. f"Got {state.get('mask_token_id')}."
  859. )
  860. state = state["embedding"]
  861. logger.info(
  862. "Loaded DFlash mask embedding for mask_token_id %s from %s",
  863. mask_token_id,
  864. MASK_EMBEDDING_FILENAME,
  865. )
  866. return state.reshape(-1)