Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/models/inkling/test_moe_weight_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def test_inkling_mapper_maps_modelopt_exclusions() -> None:
)

quant_config.apply_vllm_mapper(
_TmlForCausalLMBase.hf_to_vllm_mapper.get_unstacked_mapper()
_TmlForCausalLMBase.hf_to_vllm_mapper.get_rename_mapper()
)

assert quant_config.is_layer_excluded("model.layers.2.mlp.experts")
Expand Down
8 changes: 3 additions & 5 deletions tests/models/test_dspark_mla.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,9 @@ def test_dspark_mla_checkpoint_weight_mapping(checkpoint_name, runtime_name, sha
def test_dspark_mla_shares_frozen_target_weights_and_skips_training_head():
assert not K3DSparkForCausalLM.has_own_embed_tokens
assert not K3DSparkForCausalLM.has_own_lm_head
assert set(K3DSparkForCausalLM.checkpoint_skip_substrs) == {
"confidence_head",
"embed_tokens",
"lm_head",
}
mapper = K3DSparkForCausalLM.hf_to_vllm_mapper
for name in ("confidence_head.weight", "embed_tokens.weight", "lm_head.weight"):
assert mapper._map_name(name) is None


@pytest.mark.cpu_test
Expand Down
104 changes: 28 additions & 76 deletions tests/models/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project

import pytest
import regex as re
import torch

from vllm.model_executor.layers.vocab_parallel_embedding import (
Expand All @@ -10,6 +11,7 @@
)
from vllm.model_executor.models.utils import (
AutoWeightsLoader,
WeightsMapper,
_merge_multimodal_embeddings,
)
from vllm.platforms import current_platform
Expand Down Expand Up @@ -93,82 +95,6 @@ def weight_generator():
assert new_mod.nested_mod.bn.num_batches_tracked.item() == 1


@pytest.mark.cpu_test
def test_module_skip_prefix():
"""Ensure the auto weight loader can skip prefix."""
mod = ModuleWithNestedBatchNorm()
# Run some data through the module with batchnorm
mod(torch.Tensor([[1, 2], [3, 4]]))

# Try to load the weights to a new instance
def weight_generator():
# weights needed to be filtered out
redundant_weights = {
"prefix.bn.weight": torch.Tensor([1, 2]),
"prefix.bn.bias": torch.Tensor([3, 4]),
}
yield from (mod.state_dict() | redundant_weights).items()

new_mod = ModuleWithNestedBatchNorm()

assert not torch.all(
new_mod.nested_mod.bn.running_mean == mod.nested_mod.bn.running_mean
)
assert not torch.all(
new_mod.nested_mod.bn.running_var == mod.nested_mod.bn.running_var
)
assert new_mod.nested_mod.bn.num_batches_tracked.item() == 0

loader = AutoWeightsLoader(new_mod, skip_prefixes=["prefix."])
loader.load_weights(weight_generator())

# Ensure the stats are updated
assert torch.all(
new_mod.nested_mod.bn.running_mean == mod.nested_mod.bn.running_mean
)
assert torch.all(new_mod.nested_mod.bn.running_var == mod.nested_mod.bn.running_var)
assert new_mod.nested_mod.bn.num_batches_tracked.item() == 1


@pytest.mark.cpu_test
def test_module_skip_substr():
"""Ensure the auto weight loader can skip prefix."""
mod = ModuleWithNestedBatchNorm()
# Run some data through the module with batchnorm
mod(torch.Tensor([[1, 2], [3, 4]]))

# Try to load the weights to a new instance
def weight_generator():
# weights needed to be filtered out
redundant_weights = {
"nested_mod.0.substr.weight": torch.Tensor([1, 2]),
"nested_mod.0.substr.bias": torch.Tensor([3, 4]),
"nested_mod.substr.weight": torch.Tensor([1, 2]),
"nested_mod.substr.bias": torch.Tensor([3, 4]),
}
yield from (mod.state_dict() | redundant_weights).items()

new_mod = ModuleWithNestedBatchNorm()

assert not torch.all(
new_mod.nested_mod.bn.running_mean == mod.nested_mod.bn.running_mean
)
assert not torch.all(
new_mod.nested_mod.bn.running_var == mod.nested_mod.bn.running_var
)
assert new_mod.nested_mod.bn.num_batches_tracked.item() == 0

loader = AutoWeightsLoader(new_mod, skip_substrs=["substr."])
loader.load_weights(weight_generator())

# Ensure the stats are updated
assert torch.all(
new_mod.nested_mod.bn.running_mean == mod.nested_mod.bn.running_mean
)
assert torch.all(new_mod.nested_mod.bn.running_var == mod.nested_mod.bn.running_var)
assert new_mod.nested_mod.bn.num_batches_tracked.item() == 1


VOCAB_SIZE = 16
HIDDEN_SIZE = 2

Expand Down Expand Up @@ -265,3 +191,29 @@ def test_merge_multimodal_embeddings_no_sync():
_merge_multimodal_embeddings(
inputs_embeds, multimodal_embeddings, is_multimodal
)


@pytest.mark.cpu_test
def test_get_rename_mapper_keeps_only_renames():
"""`None` means "do not load", which is meaningless to the consumers of
this mapper (LoRA name parsing, quantization config layer lists), and
applying it would silently shrink their lists."""
mapper = WeightsMapper(
orig_to_new_regex={re.compile(r"^drop_regex\."): None},
orig_to_new_substr={"drop_substr": None, "keep_substr": "kept"},
orig_to_new_stacked={".q_proj": (".qkv_proj", "q")},
orig_to_new_prefix={"drop_prefix.": None, "keep_prefix.": "kept."},
orig_to_new_suffix={".drop_suffix": None},
)
renames = mapper.get_rename_mapper()

assert renames.orig_to_new_regex == {}
assert renames.orig_to_new_substr == {"keep_substr": "kept"}
assert renames.orig_to_new_stacked == {}
assert renames.orig_to_new_prefix == {"keep_prefix.": "kept."}
assert renames.orig_to_new_suffix == {}

# Names the full mapper drops now survive unchanged.
for name in ("drop_regex.w", "drop_substr.w", "drop_prefix.w", "w.drop_suffix"):
assert mapper._map_name(name) is None
assert renames._map_name(name) == name
4 changes: 2 additions & 2 deletions tests/quantization/test_modelopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ def test_modelopt_mixed_precision_composes_gemma4_mappers():
)

config.apply_vllm_mapper(
Gemma4ForConditionalGeneration.hf_to_vllm_mapper.get_unstacked_mapper()
Gemma4ForConditionalGeneration.hf_to_vllm_mapper.get_rename_mapper()
)
config.apply_vllm_mapper(Gemma4ForCausalLM.hf_to_vllm_mapper.get_unstacked_mapper())
config.apply_vllm_mapper(Gemma4ForCausalLM.hf_to_vllm_mapper.get_rename_mapper())

expected_prefix = "language_model.model.layers.0.moe.experts"
assert set(config.quantized_layers) == {
Expand Down
9 changes: 4 additions & 5 deletions vllm/lora/worker_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,13 @@ def _load_adapter(self, lora_request: LoRARequest) -> LoRAModel:
# loading weights, throwing an exception if validation fails.
peft_helper.validate_legal(self.lora_config)

# For some models like Qwen2VL, we need to use hf_to_vllm_mapper
# to ensure correct loading of lora weights. Drop the QKV/MLP fusion
# substr maps so constituent names (e.g. `q_proj`) survive for the
# LoRA manager to pack, while keeping genuine renames/prefixes.
# For some models like Qwen2VL, we need to use hf_to_vllm_mapper to ensure
# correct loading of lora weights. We only need to know about renames for
# this, so we use get_rename_mapper() to ignore stacking and deletions.
model = self._adapter_manager.model
hf_to_vllm_mapper = getattr(model, "hf_to_vllm_mapper", None)
if hf_to_vllm_mapper is not None:
hf_to_vllm_mapper = hf_to_vllm_mapper.get_unstacked_mapper()
hf_to_vllm_mapper = hf_to_vllm_mapper.get_rename_mapper()

# Get model-defined prefixes to skip during LoRA loading.
lora_skip_prefixes = getattr(model, "lora_skip_prefixes", None)
Expand Down
2 changes: 1 addition & 1 deletion vllm/model_executor/model_loader/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,6 @@ def configure_quant_config(

# pass mappings by reference to quant_config
if hf_to_vllm_mapper is not None:
quant_config.apply_vllm_mapper(hf_to_vllm_mapper.get_unstacked_mapper())
quant_config.apply_vllm_mapper(hf_to_vllm_mapper.get_rename_mapper())
if packed_mapping is not None:
quant_config.packed_modules_mapping = packed_mapping
13 changes: 8 additions & 5 deletions vllm/model_executor/models/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ def _load_pooling_model_weights(

def default_load_weights(weights):
loader = AutoWeightsLoader(self)
return loader.load_weights(weights)
mapper = getattr(self, "hf_to_vllm_mapper", None)
return loader.load_weights(weights, mapper=mapper)

load_weights = getattr(super(), "load_weights", default_load_weights)
return load_weights(mapped_weights)
Expand Down Expand Up @@ -584,10 +585,11 @@ def load_weights_using_from_2_way_softmax(
)
loaded_weights.add(score_weight_name)

lm_head_name = "lm_head.weight"
lm_head_name: str | None = "lm_head.weight"
if hf_to_vllm_mapper := getattr(model, "hf_to_vllm_mapper", None):
lm_head_name = hf_to_vllm_mapper._map_name(lm_head_name)
loaded_weights.discard(lm_head_name)
if lm_head_name is not None:
loaded_weights.discard(lm_head_name)
return loaded_weights


Expand Down Expand Up @@ -649,10 +651,11 @@ def load_weights_no_post_processing(model, weights: Iterable[tuple[str, torch.Te
)
loaded_weights.add(score_weight_name)

lm_head_name = "lm_head.weight"
lm_head_name: str | None = "lm_head.weight"
if hf_to_vllm_mapper := getattr(model, "hf_to_vllm_mapper", None):
lm_head_name = hf_to_vllm_mapper._map_name(lm_head_name)
loaded_weights.discard(lm_head_name)
if lm_head_name is not None:
loaded_weights.discard(lm_head_name)
return loaded_weights


Expand Down
15 changes: 6 additions & 9 deletions vllm/model_executor/models/aimv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ def __init__(
require_post_norm=require_post_norm,
prefix=f"{prefix}.trunk",
)
# post_trunk_norm is optional (absent for clip-skip backbones).
if self.trunk.post_trunk_norm is None:
self.hf_to_vllm_mapper = self.hf_to_vllm_mapper | WeightsMapper(
orig_to_new_prefix={"trunk.post_trunk_norm.": None}
)

def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
x = self.preprocessor(pixel_values)
Expand All @@ -227,13 +232,5 @@ def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
return x

def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
loader = AutoWeightsLoader(
self,
# post_trunk_norm is optional (absent for clip-skip backbones).
skip_prefixes=(
["trunk.post_trunk_norm."]
if self.trunk.post_trunk_norm is None
else None
),
)
loader = AutoWeightsLoader(self)
return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
7 changes: 5 additions & 2 deletions vllm/model_executor/models/arcee.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,10 @@ class ArceeForCausalLM(
".q_proj": (".qkv_proj", "q"),
".k_proj": (".qkv_proj", "k"),
".v_proj": (".qkv_proj", "v"),
}
},
orig_to_new_substr={
"gate_proj": None,
},
)
# Map fused module names to their submodule components
# (for quantization and LoRA)
Expand Down Expand Up @@ -356,7 +359,7 @@ def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
"""Load weights into the model (delegates to inner model and handles
tied embeddings)."""
loader = AutoWeightsLoader(self, skip_substrs=["gate_proj"])
loader = AutoWeightsLoader(self)
# AutoWeightLoader handles weight name remapping, including fusing
# separate q_proj, k_proj, v_proj into qkv_proj
return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
22 changes: 7 additions & 15 deletions vllm/model_executor/models/aria.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,13 @@
from vllm.config.multimodal import BaseDummyOptions, ImageDummyOptions
from vllm.inputs import MultiModalDataDict
from vllm.model_executor.layers.activation import get_act_fn
from vllm.model_executor.layers.fused_moe import (
FusedMoEFactory,
)
from vllm.model_executor.layers.fused_moe import FusedMoEFactory
from vllm.model_executor.layers.linear import ColumnParallelLinear, RowParallelLinear
from vllm.model_executor.layers.logits_processor import LogitsProcessor
from vllm.model_executor.layers.quantization import QuantizationConfig
from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
from vllm.multimodal import MULTIMODAL_REGISTRY
from vllm.multimodal.inputs import (
MultiModalFieldConfig,
MultiModalKwargsItems,
)
from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargsItems
from vllm.multimodal.parse import MultiModalDataItems
from vllm.multimodal.processing import (
BaseDummyInputsBuilder,
Expand All @@ -42,11 +37,7 @@
)
from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsQuant
from .llama import LlamaDecoderLayer, LlamaMLP, LlamaModel
from .utils import (
AutoWeightsLoader,
WeightsMapper,
maybe_prefix,
)
from .utils import AutoWeightsLoader, WeightsMapper, maybe_prefix


class AriaImagePixelInputs(TensorSchema):
Expand Down Expand Up @@ -88,16 +79,17 @@ def __init__(
self.post_layernorm = nn.Identity()

hf_to_vllm_mapper = WeightsMapper(
# NOTE: post_layernorm is not used in Aria.
orig_to_new_substr={"post_layernorm": None},
orig_to_new_stacked={
".q_proj": (".qkv_proj", "q"),
".k_proj": (".qkv_proj", "k"),
".v_proj": (".qkv_proj", "v"),
}
},
)

def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
# NOTE: post_layernorm is not used in Aria.
loader = AutoWeightsLoader(self, skip_substrs=["post_layernorm"])
loader = AutoWeightsLoader(self)
return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)


Expand Down
12 changes: 3 additions & 9 deletions vllm/model_executor/models/bagel.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,9 @@ class BagelForConditionalGeneration(
The image generation part is not supported in vLLM.
"""

# Weight mapping from HF to vLLM
# pos_embed is handled by the PositionEmbedding module
hf_to_vllm_mapper = WeightsMapper(
orig_to_new_prefix={
"language_model.": "language_model.",
"vit_model.": "vit_model.",
"connector.": "connector.",
"vit_pos_embed.": "vit_pos_embed.",
}
orig_to_new_prefix={"vit_pos_embed.pos_embed": None}
)

@classmethod
Expand Down Expand Up @@ -580,6 +575,5 @@ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:

filtered_weights.append((name, tensor))

# Skip vit_pos_embed.pos_embed as it's handled by PositionEmbedding module
loader = AutoWeightsLoader(self, skip_prefixes=["vit_pos_embed.pos_embed"])
loader = AutoWeightsLoader(self)
return loader.load_weights(filtered_weights, mapper=self.hf_to_vllm_mapper)
Loading
Loading