Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
88beb3b
Enable fused linear layers to load themselves
hmellor Jun 4, 2026
41f4584
Enable GPTQ extra bias skipping in AutoWeightsLoader
hmellor Jun 4, 2026
fcd151b
Try it on a couple of simple weight loaders
hmellor Jun 4, 2026
a9788ab
Fix LoRA loading for these two models
hmellor Jun 5, 2026
cca665e
Delete some more load_weights methods
hmellor Jun 5, 2026
e316238
Add patterns from `maybe_remap_kv_scale_name` to `QuantizationConfig.…
hmellor Jun 5, 2026
665ca0c
Use new mappings in `AutoWeightsLoader`
hmellor Jun 5, 2026
edf67e6
Remove some more load_weights methods
hmellor Jun 5, 2026
698d2a6
Merge remote-tracking branch 'upstream/main' into remove-simple-load-…
hmellor Jun 11, 2026
b1f1c9d
Fix `load_weights` methods for fused case
hmellor Jun 11, 2026
b5a7191
Fix BaiChuan tests that depend on old behaviour
hmellor Jun 11, 2026
f5383aa
Handle MergedColumnParallelLinear for LoRA too
hmellor Jun 11, 2026
82a7a64
Delete some more load_weights methods
hmellor Jun 11, 2026
c3a316a
Add debug logs while loading
hmellor Jun 11, 2026
c802faa
Fix late initialised biases
hmellor Jun 11, 2026
88de67d
Fix GPTQ tests
hmellor Jun 11, 2026
4069aae
fix bnb
hmellor Jun 11, 2026
9d69ba6
Merge branch 'main' into remove-simple-load-weights
hmellor Jun 13, 2026
872ff37
Make `vllm.model_executor.utils.get_packed_modules_mapping` check `hf…
hmellor Jun 13, 2026
3b73687
Fix `WeightsMapper.get_packed_modules_mapping`
hmellor Jun 13, 2026
c657d7d
Better `SupportsQuant._maybe_apply_model_mapping`
hmellor Jun 13, 2026
f2d548b
`BitsAndBytesModelLoader` can be simpler now
hmellor Jun 13, 2026
41e3a9e
Use `get_packed_modules_mapping` for `get_supported_lora_modules`
hmellor Jun 13, 2026
68085a0
Fix test
hmellor Jun 13, 2026
2f56a42
tweaks
hmellor Jun 13, 2026
d051813
typo
hmellor Jun 13, 2026
5c2a354
Mapper must present both shard id and weight name as supported packings
hmellor Jun 13, 2026
b5bdb58
Fix test
hmellor Jun 13, 2026
dbf02b1
`AutoWeightsLoader` inject packed mappings from mapper at load time
hmellor Jun 13, 2026
a97a1a1
revert lora test changes
hmellor Jun 13, 2026
a33e90e
Revert quant/lora hacks; `get_packed_modules_mapping` -> `get_unfused…
hmellor Jun 13, 2026
4e5c1e3
Add `packed_modules_mapping` attrs back to models
hmellor Jun 13, 2026
735d725
Revert now unused LoRA things
hmellor Jun 13, 2026
a7a65a9
Revert lora test
hmellor Jun 13, 2026
2aadace
tweak diff
hmellor Jun 13, 2026
ed3b43c
More accurate comment
hmellor Jun 13, 2026
4a53e8b
Merge branch 'main' into remove-simple-load-weights
hmellor Jun 19, 2026
f7d1268
Be explicit about which mappings are for stacking
hmellor Jun 19, 2026
342b77c
Always ignore unexpected bias
hmellor Jun 19, 2026
5df5d58
Fix loading submodules for quant schemes
hmellor Jun 19, 2026
280726c
Merge branch 'main' into remove-simple-load-weights
hmellor Jun 26, 2026
f6babd8
remove unnecessary `loaded_shard_id: tuple[int, ...]` loading
hmellor Jun 26, 2026
88cc90c
Merge branch 'main' into remove-simple-load-weights
hmellor Jun 26, 2026
38388b6
Revert "remove unnecessary `loaded_shard_id: tuple[int, ...]` loading"
hmellor Jun 26, 2026
b5ee2e2
`MergedColumnParallelLinear.load_weights` handle tuple shard id
hmellor Jun 26, 2026
d7619d2
Make `validate_shard_id` narrow type properly
hmellor Jun 27, 2026
1f62b5b
Pass shard_id as a param of the weight instead of str hacks
hmellor Jun 27, 2026
4ab92e2
Apply to models
hmellor Jun 27, 2026
5fe0c6e
revert whisper formatting change
hmellor Jun 27, 2026
0e78b7c
Remove child scan in bnb loading
hmellor Jun 27, 2026
48fae6b
Bring GLMV4 mapper to the top class
hmellor Jun 27, 2026
2426f76
propagate mapper from Qwen2Model
hmellor Jun 27, 2026
d36e06e
Add Gemma3
hmellor Jun 27, 2026
69f7415
propagate up for internlm2 too
hmellor Jun 27, 2026
29c0149
supports quant and propagation for other models too
hmellor Jun 27, 2026
c3491ea
Merge branch 'main' into remove-simple-load-weights
hmellor Jun 27, 2026
0e292fe
Merge branch 'main' into remove-simple-load-weights
hmellor Jun 29, 2026
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
121 changes: 121 additions & 0 deletions tests/model_executor/test_weight_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,5 +160,126 @@ def test_missing_target_returns_none(self):
assert result is None


class TestKvCacheScaleMapper:
"""The `WeightsMapper` returned by `get_cache_scale_mapper` replaces the
per-model `maybe_remap_kv_scale_name` calls. It must remap the same set of
checkpoint formats (the non-`params_dict`-dependent ones) and be idempotent
so it composes safely with a model's own qkv/gate_up `hf_to_vllm_mapper`."""

def _mapper(self):
# `get_cache_scale_mapper` does not use `self`; call it on the base
# class to get the default (non-config-specific) mapper.
from vllm.model_executor.layers.quantization.base_config import (
QuantizationConfig,
)

return QuantizationConfig.get_cache_scale_mapper()

def _map(self, name: str) -> str | None:
return self._mapper()._map_name(name)

@pytest.mark.parametrize(
"name,expected",
[
# Qwen3-MoE / llm-compressor fused qkv_proj
(
"model.layers.0.self_attn.qkv_proj.k_scale",
"model.layers.0.self_attn.attn.k_scale",
),
(
"model.layers.0.self_attn.qkv_proj.v_scale",
"model.layers.0.self_attn.attn.v_scale",
),
# ModelOpt / NVFP4 k_proj/v_proj
(
"model.layers.0.self_attn.k_proj.k_scale",
"model.layers.0.self_attn.attn.k_scale",
),
(
"model.layers.0.self_attn.v_proj.v_scale",
"model.layers.0.self_attn.attn.v_scale",
),
# deprecated fused kv_scale and bare scales
(
"model.layers.0.self_attn.kv_scale",
"model.layers.0.self_attn.attn.k_scale",
),
(
"model.layers.0.self_attn.k_scale",
"model.layers.0.self_attn.attn.k_scale",
),
# NemotronH mixer
(
"model.layers.0.mixer.k_proj.k_scale",
"model.layers.0.mixer.attn.k_scale",
),
# already in vLLM form -> unchanged (idempotent)
(
"model.layers.0.self_attn.attn.k_scale",
"model.layers.0.self_attn.attn.k_scale",
),
# non-kv scales must not be touched
(
"model.layers.0.self_attn.k_proj.weight_scale",
"model.layers.0.self_attn.k_proj.weight_scale",
),
(
"model.layers.0.self_attn.k_proj.input_scale",
"model.layers.0.self_attn.k_proj.input_scale",
),
# regular weights untouched
(
"model.layers.0.self_attn.q_proj.weight",
"model.layers.0.self_attn.q_proj.weight",
),
],
)
def test_remap(self, name, expected):
assert self._map(name) == expected

@pytest.mark.parametrize(
"name",
[
"model.layers.0.self_attn.k_scale",
"model.layers.0.self_attn.k_proj.k_scale",
"model.layers.0.self_attn.qkv_proj.v_scale",
"model.layers.0.mixer.k_proj.k_scale",
],
)
def test_idempotent(self, name):
once = self._map(name)
assert once is not None
assert self._map(once) == once

def test_composes_with_qkv_mapper(self):
"""Applied together with a model's qkv/gate_up mapper, the regex scale
rules run before the substr rename, so scales are normalized to `.attn.`
and regular projections are still fused correctly."""
from vllm.model_executor.models.utils import WeightsMapper

model_mapper = WeightsMapper(
orig_to_new_substr={
".q_proj": ".qkv_proj.q",
".k_proj": ".qkv_proj.k",
".v_proj": ".qkv_proj.v",
}
)
# AutoWeightsLoader does `mapper |= cache_scale_mapper`
combined = model_mapper | self._mapper()

assert (
combined._map_name("model.layers.0.self_attn.q_proj.weight")
== "model.layers.0.self_attn.qkv_proj.q.weight"
)
assert (
combined._map_name("model.layers.0.self_attn.k_proj.k_scale")
== "model.layers.0.self_attn.attn.k_scale"
)
assert (
combined._map_name("model.layers.0.self_attn.k_scale")
== "model.layers.0.self_attn.attn.k_scale"
)


if __name__ == "__main__":
test_download_weights_from_hf()
6 changes: 5 additions & 1 deletion vllm/lora/worker_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,13 @@ def _load_adapter(self, lora_request: LoRARequest) -> LoRAModel:
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.
# 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.
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()

# Get model-defined prefixes to skip during LoRA loading.
lora_skip_prefixes = getattr(model, "lora_skip_prefixes", None)
Expand Down
122 changes: 85 additions & 37 deletions vllm/model_executor/layers/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@

import itertools
from abc import abstractmethod
from collections.abc import Iterable
from typing import Any

import torch
from torch.nn.parameter import Parameter
from typing_extensions import TypeIs

import vllm.envs as envs
from vllm.distributed import (
Expand Down Expand Up @@ -632,31 +635,31 @@ def __init__(
disable_tp=disable_tp,
)

def validate_shard_id(self, loaded_shard_id: int | tuple[int, ...] | None):
if loaded_shard_id is None:
return
if isinstance(loaded_shard_id, tuple):
for idx in loaded_shard_id:
def validate_shard_id(self, shard_id: Any) -> TypeIs[int | tuple[int, ...] | None]:
if isinstance(shard_id, int):
if shard_id < 0 or shard_id >= len(self.output_sizes):
raise ValueError(
f"Shard id should be between 0 and {len(self.output_sizes) - 1}. "
f"Got shard id {shard_id}."
)
return True
if shard_id is None:
return True
if isinstance(shard_id, tuple):
for idx in shard_id:
if not (0 <= idx < len(self.output_sizes)):
raise ValueError(
f"Shard id index {idx} should be between 0 and "
f"{len(self.output_sizes) - 1}. Got shard id {loaded_shard_id}."
f"{len(self.output_sizes) - 1}. Got shard id {shard_id}."
)
if len(loaded_shard_id) > 1 and any(
b - a != 1 for a, b in zip(loaded_shard_id[:-1], loaded_shard_id[1:])
if len(shard_id) > 1 and any(
b - a != 1 for a, b in zip(shard_id[:-1], shard_id[1:])
):
raise ValueError(
"Shard id with multiple indices should be consecutive. "
f"Got shard id {loaded_shard_id}."
f"Got shard id {shard_id}."
)
return
elif isinstance(loaded_shard_id, int):
if loaded_shard_id < 0 or loaded_shard_id >= len(self.output_sizes):
raise ValueError(
f"Shard id should be between 0 and {len(self.output_sizes) - 1}. "
f"Got shard id {loaded_shard_id}."
)
return
return True
raise ValueError("This line should not be reached")

def weight_loader(
Expand Down Expand Up @@ -910,6 +913,31 @@ def weight_loader_v2(
tp_rank=self.tp_rank,
)

def load_weights(
self, weights: Iterable[tuple[str, torch.Tensor]]
) -> Iterable[str]:
for name, loaded_weight in weights:
shard_id = getattr(loaded_weight, "shard_id", None)
self.validate_shard_id(shard_id)
# Load into self if name is not an attr of self or its submodules
param: Parameter
if "." in name:
submodule, _, attr = name.rpartition(".")
param = getattr(self.get_submodule(submodule), attr, self)
else:
param = getattr(self, name, self)
if param is None and name == "bias":
continue
Comment thread
hmellor marked this conversation as resolved.
param.weight_loader(param, loaded_weight, shard_id)
logger.debug(
"Loaded shard %s with shape %s into %s.%s",
shard_id,
loaded_weight.shape,
self.prefix,
name,
)
yield name


class QKVParallelLinear(ColumnParallelLinear):
"""Linear layers for the attention's QKV transformation.
Expand Down Expand Up @@ -996,17 +1024,13 @@ def __init__(
disable_tp=disable_tp,
)

def validate_shard_id(self, loaded_shard_id: str | None):
if loaded_shard_id is None:
return
if isinstance(loaded_shard_id, str):
if loaded_shard_id not in ["q", "k", "v"]:
raise ValueError(
"Shard id for QKVParallelLinear should be 'q', 'k', or 'v', "
f"got shard id {loaded_shard_id}."
)
return
raise ValueError("This line should not be reached")
def validate_shard_id(self, shard_id: Any) -> TypeIs[str | None]:
if shard_id in {"q", "k", "v"} or shard_id is None:
return True
raise ValueError(
"Shard id for QKVParallelLinear should be 'q', 'k', or 'v', "
f"got shard id {shard_id}."
)

def _get_shard_offset_mapping(self, loaded_shard_id: str):
shard_offset_mapping = {
Expand Down Expand Up @@ -1302,6 +1326,31 @@ def weight_loader(
assert param_data.shape == loaded_weight.shape
param_data.copy_(loaded_weight)

def load_weights(
self, weights: Iterable[tuple[str, torch.Tensor]]
) -> Iterable[str]:
for name, loaded_weight in weights:
shard_id = getattr(loaded_weight, "shard_id", None)
self.validate_shard_id(shard_id)
# Load into self if name is not an attr of self or its submodules
param: Parameter
if "." in name:
submodule, _, attr = name.rpartition(".")
param = getattr(self.get_submodule(submodule), attr, self)
else:
param = getattr(self, name, self)
if param is None and name == "bias":
continue
param.weight_loader(param, loaded_weight, shard_id)
logger.debug(
"Loaded shard %s with shape %s into %s.%s",
shard_id,
loaded_weight.shape,
self.prefix,
name,
)
yield name


class MinimaxM3QKVParallelLinearWithIndexer(QKVParallelLinear):
"""QKV projection fused with a lightning-indexer's index_q/index_k.
Expand Down Expand Up @@ -1387,15 +1436,14 @@ def __init__(
prefix=prefix,
)

def validate_shard_id(self, loaded_shard_id: str | None) -> None:
if loaded_shard_id is None:
return
if loaded_shard_id not in ("q", "k", "v", "index_q", "index_k"):
raise ValueError(
"Shard id for MinimaxM3QKVParallelLinearWithIndexer must be one of "
"'q', 'k', 'v', 'index_q', 'index_k'; got "
f"{loaded_shard_id}."
)
def validate_shard_id(self, shard_id: Any) -> TypeIs[str | None]:
if shard_id in {"q", "k", "v", "index_q", "index_k"} or shard_id is None:
return True
raise ValueError(
"Shard id for MinimaxM3QKVParallelLinearWithIndexer must be one of "
"'q', 'k', 'v', 'index_q', 'index_k'; got "
f"{shard_id}."
)

def _get_shard_offset_mapping(self, loaded_shard_id: str) -> int | None:
h = self.head_size
Expand Down
Loading
Loading