[XPU][LoRA] Support LoRA for DeepSeek V4 on XPU (#53689)

Signed-off-by: Chaojun Zhang <chaojun.zhang@intel.com>

Chaojun Zhang committed Sep 7, 2026 at 17:44 UTC 86484465153f08f4523d8be964fd6309146eab09
1 file changed +31 -1
vllm/models/deepseek_v4/xpu/model.py
+31 -1
@@ -46,6 +46,7 @@ from vllm.model_executor.model_loader.weight_utils import default_weight_loader
46 from vllm.model_executor.models.interfaces import (
47 EagleModelMixin,
48 SupportsEagle3,
49 + SupportsLoRA,
50 SupportsPP,
51 )
52 from vllm.model_executor.models.utils import (
@@ -1170,6 +1171,11 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
1171
1172 if is_pp_missing_parameter(name, self):
1173 break
1174 + if name not in params_dict:
1175 + head, _, leaf = name.rpartition(".")
1176 + suffixed = f"{head}.base_layer.{leaf}"
1177 + if suffixed in params_dict:
1178 + name = suffixed
1179 param = params_dict[name]
1180 weight_loader = param.weight_loader
1181 weight_loader(param, loaded_weight, shard_id)
@@ -1224,6 +1230,13 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
1230 else:
1231 if is_pp_missing_parameter(name, self):
1232 continue
1233 + # Non-LoRA params on a LoRA-wrapped module live at
1234 + # ``<head>.base_layer.<leaf>``; the checkpoint is plain.
1235 + if name not in params_dict:
1236 + head, _, leaf = name.rpartition(".")
1237 + suffixed = f"{head}.base_layer.{leaf}"
1238 + if suffixed in params_dict:
1239 + name = suffixed
1240 param = params_dict[name]
1241 weight_loader = getattr(
1242 param, "weight_loader", default_weight_loader
@@ -1260,6 +1273,10 @@ def _make_deepseek_v4_weights_mapper(expert_dtype: str) -> WeightsMapper:
1273 # shared experts use Fp8LinearMethod's block scales, which
1274 # register as ``weight_scale_inv``.
1275 scale_regex = {
1276 + # ``.base_layer.``-namespace variant (LoRA-wrapped experts).
1277 + re.compile(
1278 + r"(\.experts\.\d+\.w[123]\.base_layer)\.scale$"
1279 + ): r"\1.weight_scale",
1280 re.compile(r"(\.experts\.\d+\.w[123])\.scale$"): r"\1.weight_scale",
1281 re.compile(r"\.scale$"): ".weight_scale_inv",
1282 }
@@ -1268,6 +1285,10 @@ def _make_deepseek_v4_weights_mapper(expert_dtype: str) -> WeightsMapper:
1285 # scales as ``w{13,2}_weight_scale_inv``. Map all ``.scale`` keys
1286 # there.
1287 scale_regex = {
1288 + # ``.base_layer.``-namespace variant of the above.
1289 + re.compile(
1290 + r"(\.experts\.\d+\.w[123]\.base_layer)\.scale$"
1291 + ): r"\1.weight_scale_inv",
1292 re.compile(r"\.scale$"): ".weight_scale_inv",
1293 }
1294 return WeightsMapper(
@@ -1291,13 +1312,22 @@ def _make_deepseek_v4_weights_mapper(expert_dtype: str) -> WeightsMapper:
1312 )
1313
1314
1294 -class DeepseekV4ForCausalLM(nn.Module, SupportsPP, SupportsEagle3):
1315 +class DeepseekV4ForCausalLM(nn.Module, SupportsPP, SupportsEagle3, SupportsLoRA):
1316 model_cls = DeepseekV4Model
1317
1318 # Default mapper assumes the original FP4-expert checkpoint layout.
1319 # Overridden per-instance in __init__ when expert_dtype != "fp4".
1320 hf_to_vllm_mapper = _make_deepseek_v4_weights_mapper("fp4")
1321
1322 + packed_modules_mapping = {
1323 + "gate_up_proj": ["w1", "w3"],
1324 + "fused_wqa_wkv": ["wq_a", "wkv"],
1325 + "fused_wkv_wgate": ["wkv", "wgate"],
1326 + }
1327 +
1328 + # The MTP draft head is not LoRA-adapted.
1329 + lora_skip_prefixes = ["mtp."]
1330 +
1331 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1332 super().__init__()
1333