model.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """Vision-Exp wrapper around vLLM DeepseekV4ForCausalLM.
  2. Loads official `vision.*` / `aligner.*` / image sentinel tensors and merges
  3. ViT embeddings on prefill. Language decode stays on the B12X DeepSeek V4 path.
  4. """
  5. from __future__ import annotations
  6. from types import SimpleNamespace
  7. from typing import Any
  8. import torch
  9. import torch.nn as nn
  10. from vllm.model_executor.models.utils import AutoWeightsLoader
  11. from vllm.models.deepseek_v4 import DeepseekV4ForCausalLM
  12. from .vision import Aligner, ViT
  13. def _vision_args(hf: Any) -> SimpleNamespace:
  14. return SimpleNamespace(
  15. vision_n_layers=int(getattr(hf, "vision_n_layers", 0) or 0),
  16. vision_dim=int(getattr(hf, "vision_dim", 1024)),
  17. vision_n_heads=int(getattr(hf, "vision_n_heads", 16)),
  18. vision_inter_dim=int(getattr(hf, "vision_inter_dim", 2816)),
  19. vision_patch_size=int(getattr(hf, "vision_patch_size", 14)),
  20. vision_rope_theta=float(getattr(hf, "vision_rope_theta", 10000.0)),
  21. vision_downsample_ratio=int(getattr(hf, "vision_downsample_ratio", 3)),
  22. vision_max_n_token=int(getattr(hf, "vision_max_n_token", 384)),
  23. vision_min_pixels=int(getattr(hf, "vision_min_pixels", 147456)),
  24. vision_max_wh_ratio=int(getattr(hf, "vision_max_wh_ratio", 8)),
  25. dim=int(getattr(hf, "hidden_size", 4096)),
  26. num_hash_layers=int(getattr(hf, "num_hash_layers", 0) or 0),
  27. )
  28. class DeepseekV4VisionForCausalLM(DeepseekV4ForCausalLM):
  29. def __init__(self, *, vllm_config, prefix: str = ""):
  30. super().__init__(vllm_config=vllm_config, prefix=prefix)
  31. hf = vllm_config.model_config.hf_config
  32. args = _vision_args(hf)
  33. self._vision_args = args
  34. if args.vision_n_layers <= 0:
  35. self.vision = None
  36. self.aligner = None
  37. return
  38. self.vision = ViT(args)
  39. self.aligner = Aligner(args)
  40. self.image_start = nn.Parameter(torch.empty(args.dim))
  41. self.image_end = nn.Parameter(torch.empty(args.dim))
  42. self.image_newline = nn.Parameter(torch.empty(args.dim))
  43. self.image_pad = nn.Parameter(torch.empty(args.dim))
  44. def encode_image(self, patches: torch.Tensor, n_vit_h: int, n_vit_w: int) -> torch.Tensor:
  45. return self.aligner(self.vision(patches, n_vit_h, n_vit_w), n_vit_h, n_vit_w)
  46. def forward(
  47. self,
  48. input_ids,
  49. positions,
  50. intermediate_tensors=None,
  51. inputs_embeds=None,
  52. **kwargs,
  53. ):
  54. # Spec-decode extra kwargs must not TypeError. lm_head and
  55. # get_mtp_target_hidden_states stay on the parent class.
  56. return super().forward(
  57. input_ids,
  58. positions,
  59. intermediate_tensors,
  60. inputs_embeds,
  61. )
  62. def load_weights(self, weights):
  63. if self.vision is None:
  64. return super().load_weights(weights)
  65. leftover = []
  66. stacked = {
  67. "vision.": self.vision,
  68. "aligner.": self.aligner,
  69. }
  70. params = {
  71. "image_start": self.image_start,
  72. "image_end": self.image_end,
  73. "image_newline": self.image_newline,
  74. "image_pad": self.image_pad,
  75. }
  76. for name, tensor in weights:
  77. if name in params:
  78. params[name].data.copy_(tensor)
  79. continue
  80. routed = False
  81. for prefix, module in stacked.items():
  82. if name.startswith(prefix):
  83. AutoWeightsLoader(module).load_weights(
  84. [(name[len(prefix) :], tensor)]
  85. )
  86. routed = True
  87. break
  88. if not routed:
  89. leftover.append((name, tensor))
  90. leftover = [
  91. pair
  92. for pair in leftover
  93. if not _skip_unmapped_gate(pair[0], self._vision_args)
  94. ]
  95. return super().load_weights(leftover)
  96. def _skip_unmapped_gate(name: str, args: SimpleNamespace) -> bool:
  97. # Hash MoE layers register tid2eid and leave e_score_correction_bias as
  98. # None. The B12X mapper still rewrites ffn.gate.bias onto that missing
  99. # name. bias_vl has no mapper at all.
  100. if name.endswith("ffn.gate.bias_vl"):
  101. return True
  102. if name.endswith("ffn.gate.bias"):
  103. parts = name.split(".")
  104. if (
  105. len(parts) >= 5
  106. and parts[0] == "layers"
  107. and parts[1].isdigit()
  108. and int(parts[1]) < args.num_hash_layers
  109. ):
  110. return True
  111. return False