apply.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. """Monkey-patch Anemll DeepseekV4ForCausalLM for Vision-Exp images.
  2. Called at the end of ``vllm/models/deepseek_v4/nvidia/model.py`` after the
  3. stock classes are defined. Video is not registered: the checkpoint has no
  4. video tower.
  5. """
  6. from __future__ import annotations
  7. from typing import Any
  8. import torch
  9. from torch import nn
  10. from .image_processor import (
  11. IMAGE,
  12. is_unregistered_router_bias,
  13. is_vision_exp_weight_name,
  14. vision_args_from_config,
  15. )
  16. from .processor import IMAGE_PLACEHOLDER, register_vision_exp_processor
  17. from .vision import Aligner, ViT
  18. VISION_MAPPER_PREFIXES = {
  19. "vision.": "model.vision.",
  20. "aligner.": "model.aligner.",
  21. "image_start": "model.image_start",
  22. "image_end": "model.image_end",
  23. "image_newline": "model.image_newline",
  24. "image_pad": "model.image_pad",
  25. }
  26. VISION_MAPPER_SUFFIXES = {
  27. ".ffn.gate.bias_vl": ".ffn.gate.e_score_correction_bias_vl",
  28. }
  29. def _as_rows(value: Any) -> list[Any]:
  30. if value is None:
  31. return []
  32. if isinstance(value, (list, tuple)):
  33. return list(value)
  34. if isinstance(value, torch.Tensor):
  35. if value.ndim == 0:
  36. return [value]
  37. return [value[i] for i in range(value.shape[0])]
  38. return [value]
  39. def _scalar(value: Any) -> int:
  40. if hasattr(value, "reshape"):
  41. return int(value.reshape(-1)[0].item())
  42. return int(value)
  43. def _extend_weights_mapper(mapper):
  44. from vllm.model_executor.models.utils import WeightsMapper
  45. extra = WeightsMapper(
  46. orig_to_new_prefix=dict(VISION_MAPPER_PREFIXES),
  47. orig_to_new_suffix=dict(VISION_MAPPER_SUFFIXES),
  48. )
  49. return mapper | extra
  50. def _install_vision_tower(model: nn.Module, config) -> None:
  51. if getattr(model, "vision", None) is not None:
  52. return
  53. args = vision_args_from_config(config)
  54. if args.vision_n_layers <= 0:
  55. model.vision = None
  56. model.aligner = None
  57. return
  58. model.vision = ViT(args)
  59. model.aligner = Aligner(args)
  60. hidden = args.dim
  61. model.image_start = nn.Parameter(torch.empty(hidden))
  62. model.image_end = nn.Parameter(torch.empty(hidden))
  63. model.image_newline = nn.Parameter(torch.empty(hidden))
  64. model.image_pad = nn.Parameter(torch.empty(hidden))
  65. @torch.inference_mode()
  66. def encode_image(model: nn.Module, patches: torch.Tensor, n_vit_h: int, n_vit_w: int):
  67. return model.aligner(model.vision(patches, n_vit_h, n_vit_w), n_vit_h, n_vit_w)
  68. def merge_one_image(
  69. model: nn.Module,
  70. patches: torch.Tensor,
  71. n_vit_h: int,
  72. n_vit_w: int,
  73. types: torch.Tensor,
  74. perm: torch.Tensor,
  75. num_tokens: int,
  76. ) -> torch.Tensor:
  77. types = types[:num_tokens].to(dtype=torch.long)
  78. perm = perm.to(dtype=torch.long)
  79. perm = perm[perm >= 0]
  80. n_patches = int(n_vit_h) * int(n_vit_w)
  81. patches = patches[:n_patches]
  82. device = next(model.vision.parameters()).device
  83. dtype = next(model.aligner.parameters()).dtype
  84. patches = patches.to(device=device, dtype=dtype)
  85. embeds = encode_image(model, patches, int(n_vit_h), int(n_vit_w))[perm.to(device)]
  86. params = torch.stack(
  87. [
  88. model.image_start,
  89. model.image_pad,
  90. model.image_pad,
  91. model.image_newline,
  92. model.image_end,
  93. ]
  94. ).to(device=device, dtype=embeds.dtype)
  95. block = params[types.to(device)]
  96. image_mask = types.to(device) == IMAGE
  97. if int(image_mask.sum()) != int(embeds.shape[0]):
  98. raise RuntimeError(
  99. f"Vision-Exp layout mismatch: {int(image_mask.sum())} IMAGE slots, "
  100. f"{int(embeds.shape[0])} aligner tokens"
  101. )
  102. block = block.clone()
  103. block[image_mask] = embeds.to(block.dtype)
  104. return block
  105. def embed_multimodal(self, **kwargs: object):
  106. pixel_values = kwargs.get("pixel_values")
  107. if pixel_values is None:
  108. return []
  109. pixels = _as_rows(pixel_values)
  110. n_vit_h = _as_rows(kwargs.get("n_vit_h"))
  111. n_vit_w = _as_rows(kwargs.get("n_vit_w"))
  112. types = _as_rows(kwargs.get("types"))
  113. perm = _as_rows(kwargs.get("perm"))
  114. num_tokens = _as_rows(kwargs.get("num_tokens"))
  115. inner = self.model
  116. if getattr(inner, "vision", None) is None:
  117. raise RuntimeError("Vision-Exp tower was not constructed on DeepseekV4Model")
  118. out = []
  119. for i, patches in enumerate(pixels):
  120. out.append(
  121. merge_one_image(
  122. inner,
  123. patches,
  124. _scalar(n_vit_h[i]),
  125. _scalar(n_vit_w[i]),
  126. types[i],
  127. perm[i],
  128. _scalar(num_tokens[i]),
  129. )
  130. )
  131. return out
  132. @classmethod
  133. def get_placeholder_str(cls, modality: str, i: int) -> str | None:
  134. if modality.startswith("image"):
  135. return IMAGE_PLACEHOLDER
  136. raise ValueError(
  137. f"DeepSeek-V4-Flash-Vision-Exp supports images only, got {modality!r}"
  138. )
  139. def apply_vision_exp(
  140. *,
  141. DeepseekV4Model,
  142. DeepseekV4ForCausalLM,
  143. DeepseekV4MoE,
  144. ) -> None:
  145. from vllm.model_executor.models.interfaces import SupportsMultiModal
  146. orig_model_init = DeepseekV4Model.__init__
  147. def model_init(self, *, vllm_config, prefix: str = ""):
  148. config = vllm_config.model_config.hf_config
  149. if int(getattr(config, "vision_n_layers", 0) or 0) > 0:
  150. # b12x fused MHC Gram kernel requires rms_eps == 1e-6 (hard-coded
  151. # in b12x.norm.mhc._impl._supports_fused_mhc_gram). The Vision-Exp
  152. # checkpoint ships rms_norm_eps=1e-20, which fails that guard with
  153. # "b12x_mhc_pre is served only by the fused Gram kernel". Normalize
  154. # to 1e-6: RMSNorm eps is a div-by-zero stabilizer, both are far
  155. # below the activation variance scale (negligible for bf16), and
  156. # it matches the b12x decode config the kernel was built for.
  157. # Guarded on vision_n_layers so text-only 0731 is unchanged.
  158. config.rms_norm_eps = 1.0e-6
  159. orig_model_init(self, vllm_config=vllm_config, prefix=prefix)
  160. _install_vision_tower(self, vllm_config.model_config.hf_config)
  161. DeepseekV4Model.__init__ = model_init
  162. DeepseekV4Model.encode_image = lambda self, patches, n_h, n_w: encode_image(
  163. self, patches, n_h, n_w
  164. )
  165. orig_model_load = DeepseekV4Model.load_weights
  166. def model_load_weights(self, weights):
  167. try:
  168. from vllm.model_executor.model_loader.weight_utils import (
  169. default_weight_loader,
  170. )
  171. except ImportError:
  172. default_weight_loader = None
  173. params_dict = dict(self.named_parameters())
  174. loaded: set[str] = set()
  175. def _filtered():
  176. for name, weight in weights:
  177. if is_unregistered_router_bias(name, params_dict):
  178. continue
  179. if not is_vision_exp_weight_name(name):
  180. yield name, weight
  181. continue
  182. if name not in params_dict:
  183. raise KeyError(
  184. f"Vision-Exp weight {name!r} has no module parameter; "
  185. "the ViT/Aligner tower was not constructed"
  186. )
  187. param = params_dict[name]
  188. loader = getattr(param, "weight_loader", None) or default_weight_loader
  189. if loader is not None:
  190. loader(param, weight)
  191. else:
  192. param.data.copy_(weight)
  193. loaded.add(name)
  194. loaded |= orig_model_load(self, _filtered())
  195. return loaded
  196. DeepseekV4Model.load_weights = model_load_weights
  197. orig_moe_init = DeepseekV4MoE.__init__
  198. def moe_init(self, vllm_config, prefix: str = ""):
  199. orig_moe_init(self, vllm_config, prefix)
  200. config = vllm_config.model_config.hf_config
  201. if getattr(config, "vision_n_layers", 0) > 0:
  202. self.gate.e_score_correction_bias_vl = nn.Parameter(
  203. torch.empty(config.n_routed_experts, dtype=torch.float32),
  204. requires_grad=False,
  205. )
  206. DeepseekV4MoE.__init__ = moe_init
  207. orig_lm_init = DeepseekV4ForCausalLM.__init__
  208. def lm_init(self, *, vllm_config, prefix: str = ""):
  209. orig_lm_init(self, vllm_config=vllm_config, prefix=prefix)
  210. self.hf_to_vllm_mapper = _extend_weights_mapper(self.hf_to_vllm_mapper)
  211. DeepseekV4ForCausalLM.__init__ = lm_init
  212. DeepseekV4ForCausalLM.hf_to_vllm_mapper = _extend_weights_mapper(
  213. DeepseekV4ForCausalLM.hf_to_vllm_mapper
  214. )
  215. orig_lm_embed = DeepseekV4ForCausalLM.embed_input_ids
  216. def embed_input_ids(
  217. self,
  218. input_ids: torch.Tensor,
  219. multimodal_embeddings: Any = None,
  220. *,
  221. is_multimodal: Any = None,
  222. ):
  223. text_embeds = orig_lm_embed(self, input_ids)
  224. if multimodal_embeddings is None:
  225. return text_embeds
  226. try:
  227. empty = len(multimodal_embeddings) == 0
  228. except TypeError:
  229. empty = False
  230. if empty:
  231. return text_embeds
  232. from vllm.model_executor.models.interfaces import _require_is_multimodal
  233. from vllm.model_executor.models.utils import _merge_multimodal_embeddings
  234. return _merge_multimodal_embeddings(
  235. inputs_embeds=text_embeds,
  236. multimodal_embeddings=multimodal_embeddings,
  237. is_multimodal=_require_is_multimodal(is_multimodal),
  238. )
  239. DeepseekV4ForCausalLM.embed_input_ids = embed_input_ids
  240. if SupportsMultiModal not in DeepseekV4ForCausalLM.__mro__:
  241. DeepseekV4ForCausalLM.__bases__ = (
  242. DeepseekV4ForCausalLM.__bases__[0],
  243. SupportsMultiModal,
  244. *DeepseekV4ForCausalLM.__bases__[1:],
  245. )
  246. DeepseekV4ForCausalLM.supports_multimodal = True
  247. DeepseekV4ForCausalLM.supports_multimodal_raw_input_only = False
  248. # Hash MoE (layers 0–2) looks up tid2eid with input_ids. The MM runner
  249. # otherwise sets input_ids=None whenever inputs_embeds is present.
  250. DeepseekV4ForCausalLM.requires_raw_input_tokens = True
  251. DeepseekV4ForCausalLM.get_placeholder_str = get_placeholder_str
  252. DeepseekV4ForCausalLM.embed_multimodal = embed_multimodal
  253. # SupportsMultiModal.get_language_model() returns the first child with
  254. # embed_input_ids (DeepseekV4Model). DSpark/Eagle3 then read `.model` on
  255. # that child. This class *is* the LM wrapper, same as 0731.
  256. def get_language_model(self):
  257. return self
  258. def set_aux_hidden_state_layers(self, layers):
  259. self.model._set_aux_hidden_state_layers(layers)
  260. def get_eagle3_default_aux_hidden_state_layers(self):
  261. num_layers = len(self.model.layers)
  262. return (2, num_layers // 2, num_layers - 3)
  263. DeepseekV4ForCausalLM.get_language_model = get_language_model
  264. DeepseekV4ForCausalLM.set_aux_hidden_state_layers = set_aux_hidden_state_layers
  265. DeepseekV4ForCausalLM.get_eagle3_default_aux_hidden_state_layers = (
  266. get_eagle3_default_aux_hidden_state_layers
  267. )
  268. register_vision_exp_processor(DeepseekV4ForCausalLM)