[LoRA] feat: Support LoRA for DeepSeek V4 - #52986
HollowMan6 wants to merge 2 commits into
Conversation
Add LoRA support for DeepSeek V4 (DSV4). DSV4 is a MoE model with quantized (fp8/mxfp4) experts, MLA, and an MTP draft head. Three issues blocked merge=False (live-adapter) LoRA, causing train/inference inconsistency: 1. **Non-LoRA params on a LoRA-wrapped module** (e.g. `gate.tid2eid`, `gate.e_score_correction_bias`) live at `<head>.base_layer.<leaf>` in the live namespace, but the checkpoint name is plain. The DSV4 `load_weights` strict `params_dict[name]` lookup KeyError'd / orphaned these on the initial profile load. 2. **Expert mapping `param_name` vs `weight_name` prefix confusion.** `RoutedExperts.make_expert_params_mapping` applied a single `lora_base_layer_prefix` to both sides. But `get_expert_mapping` resolves `param_name` via `getattr` against this layer's bare `w13_weight`/`w2_weight` (no prefix), while `make_expert_params_mapping` indexes the model-wide `params_dict` (prefix included). One prefix for both sides silently dropped per-expert weights. 3. **`weight_scale_inv` / scale not forwarded through the LoRA wrapper.** Custom kernels read `weight_scale_inv` through the wrapper; without `__getattr__` forwarding the attribute lookup failed. This PR adds `SupportsLoRA` to `DeepseekV4ForCausalLM`, declares `packed_modules_mapping` and `lora_skip_prefixes` (the MTP draft head is not LoRA-adapted), adds `.base_layer.`-namespace variants to the weights mapper scale regexes, reconciles non-LoRA params in the inner `load_weights`, splits the expert mapping prefix into checkpoint-side (`weight_name`) and live-side (`param_name`), and forwards public attribute misses in `BaseLayerWithLoRA` to `base_layer`. Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
|
|
||
|
|
||
| class BaseLayerWithLoRA(nn.Module): | ||
| def __getattr__(self, name): |
There was a problem hiding this comment.
QQ: could you please explain this in detail?
There was a problem hiding this comment.
This transparently forwards attribute misses to the wrapped base_layer. When LoRA is applied, vLLM wraps the original layer in a LoRA shim (FusedMoEWithLoRA/BaseLayerWithLoRA) and demotes the original into a .base_layer. submodule. The weight loader resolves params via recursive getattr (AutoWeightsLoader). Without getattr, getattr(shim, "weight") raises AttributeError, So getattr makes shim.weight resolve to shim.base_layer.weight.
Mechanically: it checks own _parameters/_buffers/modules first, then for public names forwards to base_layer; private (-prefixed) names stay local.
| num_redundant_experts: int = 0, | ||
| routed_experts_prefix: str = "routed_experts", | ||
| lora_base_layer_prefix: str = "", | ||
| lora_base_layer_prefix_on_param_name: str = "", |
There was a problem hiding this comment.
They must be independent because the two resolution paths use different namespaces: get_expert_mapping resolves param_name via getattr against bare w13_weight/w2_weight (no prefix), while make_expert_params_mapping indexes the model-wide params_dict (prefixed). One shared arg would either drop the ckpt prefix (Fp8 experts not found) or add a unneeded prefix to the getattr path (attribute not found).
|
Sorry, get this closed accidentally, just reopened at #53361 |
Purpose
Add LoRA support for DeepSeek V4 (DSV4). DSV4 is a MoE model with quantized (fp8/mxfp4) experts and an MTP draft head. Three issues blocked merge=False (live-adapter) LoRA, causing train/inference inconsistency:
Non-LoRA params on a LoRA-wrapped module (e.g.
gate.tid2eid,gate.e_score_correction_bias) live at<head>.base_layer.<leaf>in the live namespace, but the checkpoint name is plain. The DSV4load_weightsstrictparams_dict[name]lookup KeyError'd / orphaned these on the initial profile load.Expert mapping
param_namevsweight_nameprefix confusion.RoutedExperts.make_expert_params_mappingapplied a singlelora_base_layer_prefixto both sides. Butget_expert_mappingresolvesparam_nameviagetattragainst this layer's barew13_weight/w2_weight(no prefix), whilemake_expert_params_mappingindexes the model-wideparams_dict(prefix included). One prefix for both sides silently dropped per-expert weights.weight_scale_inv/ scale not forwarded through the LoRA wrapper. Custom kernels readweight_scale_invthrough the wrapper; without__getattr__forwarding the attribute lookup failed.This PR adds
SupportsLoRAtoDeepseekV4ForCausalLM, declarespacked_modules_mappingandlora_skip_prefixes(the MTP draft head is not LoRA-adapted), adds.base_layer.-namespace variants to the weights mapper scale regexes, reconciles non-LoRA params in the innerload_weights, splits the expert mapping prefix into checkpoint-side (weight_name) and live-side (param_name), and forwards public attribute misses inBaseLayerWithLoRAtobase_layer.Test Plan
Test with verl E2E under verl-project/verl#7483
Test Result
DSV4 merge=False LoRA GRPO, 2-node (Megatron backend):
Before this PR, merge=False diverged (pearson ~0.43) due to dropped per-expert weights and orphaned non-LoRA params; after, it aligns with the merge=True baseline to within noise.
Details
BaseLayerWithLoRA.__getattr__(vllm/lora/layers/base.py)Forwards public attribute misses to
base_layer(e.g.weight_scale_invread by custom kernels through the wrapper). Private (_-prefixed) names are framework bookkeeping and stay local.RoutedExpertsdual-prefix mapping (vllm/model_executor/layers/fused_moe/routed_experts.py)build_expert_params_mappingnow passes thebase_layer.prefix to bothlora_base_layer_prefix(checkpointweight_nameside) andlora_base_layer_prefix_on_param_name(liveparam_nameside).make_expert_params_mappinggainslora_base_layer_prefix_on_param_nameand applies it to thew13/w2param names, whileweight_namekeeps the existinglora_base_layer_prefix. This matches the fact thatget_expert_mappingresolvesparam_nameviagetattron barew13_weight/w2_weight, whereasmake_expert_params_mappingindexes model-wideparams_dict(prefix included).DeepseekV4Model.load_weightsreconcile (vllm/models/deepseek_v4/nvidia/model.py)nameis missing fromparams_dict, try<head>.base_layer.<leaf>and use it if live. Covers non-LoRA params on a wrapped module during the initial checkpoint load (profile_run), guarded so weight-sync (where the suffix is already present) is a no-op._make_deepseek_v4_weights_mapper: adds.base_layer.-namespace variants of thew[123].scale→weight_scale/weight_scale_invregexes for LoRA-wrapped experts.DeepseekV4ForCausalLM: addsSupportsLoRA,packed_modules_mapping(gate_up_proj,fused_wqa_wkv,compressor.fused_wkv_wgate), andlora_skip_prefixes = ["mtp."].Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.