Skip to content

Commit 45949b9

Browse files
committed
[Bugfix] Fix HYV3 shared_mlp prefix for compressed-tensors ignore matching
HYV3MoEFused built its shared expert (shared_mlp) as an HYV3FeedForward with `prefix=f"{prefix}"`, omitting the `.shared_mlp` segment. The Linear layers inside it (gate_up_proj, down_proj) register their parameters under `...mlp.shared_mlp.<proj>.*` (the attribute name is appended by nn.Module regardless of the prefix argument), but CompressedTensorsConfig ignores Linears by matching the *prefix* against its `ignore` list. With the missing segment the ignore check saw `...mlp.down_proj` instead of `...mlp.shared_mlp.down_proj`, so a `re:.*shared_mlp.*` ignore entry failed to match. Consequence: the shared expert was built as a quantized Linear expecting weight_packed/scale/shape, while real W4A16 hy_v3 checkpoints (produced by llm-compressor) correctly keep shared_mlp in BF16 as `.weight`. This raised `KeyError: '...mlp.shared_mlp.down_proj.weight'` at load time. Fix: pass `prefix=f"{prefix}.shared_mlp"` so the ignore check matches the real parameter registration path and the shared expert is built unquantized (UnquantizedLinearMethod), aligning with the BF16 checkpoint. Parameter registration names are unchanged. Adds a regression test that builds HYV3MoEFused under a compressed-tensors config whose ignore list contains `re:.*shared_mlp.*` and asserts the shared expert Linears are unquantized, plus a sanity check that they are quantized when shared_mlp is not ignored. Co-authored-by: xu_xiaoyi@hotmail.com
1 parent 31be872 commit 45949b9

2 files changed

Lines changed: 181 additions & 1 deletion

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
"""Regression test for HYV3 shared expert (shared_mlp) prefix under
4+
compressed-tensors quantization.
5+
6+
Background:
7+
HYV3MoEFused builds its shared expert as HYV3FeedForward. The Linear layers
8+
inside it (gate_up_proj, down_proj) register their parameters under the
9+
module tree as ``...mlp.shared_mlp.<proj>.*`` (the ``shared_mlp`` attribute
10+
name is appended by nn.Module, independent of the ``prefix`` argument).
11+
12+
CompressedTensorsConfig decides whether a Linear is quantized via
13+
``get_quant_method(layer, prefix=...)``: it ignores layers whose ``prefix``
14+
matches the config's ``ignore`` list. A real W4A16 hy_v3 checkpoint keeps
15+
``shared_mlp`` in BF16 (it is in the ignore list, e.g. ``re:.*shared_mlp.*``).
16+
17+
Bug: HYV3MoEFused previously passed ``prefix=f"{prefix}"`` (without
18+
``.shared_mlp``) to HYV3FeedForward, so the ignore check saw
19+
``...mlp.down_proj`` and did NOT match ``re:.*shared_mlp.*``. The shared
20+
expert was then built as a quantized Linear (expecting
21+
weight_packed/scale/shape) while the checkpoint stores BF16 ``.weight``,
22+
raising KeyError at load time.
23+
24+
Fix: pass ``prefix=f"{prefix}.shared_mlp"`` so the ignore check matches and
25+
the shared expert is built unquantized.
26+
27+
This test builds a single HYV3MoEFused layer with a compressed-tensors config
28+
whose ignore list contains ``re:.*shared_mlp.*`` and asserts the shared
29+
expert's Linear layers are unquantized.
30+
"""
31+
32+
import pytest
33+
import torch
34+
from compressed_tensors.quantization import (
35+
QuantizationArgs,
36+
QuantizationScheme,
37+
QuantizationStrategy,
38+
QuantizationType,
39+
)
40+
41+
from vllm.model_executor.layers.linear import UnquantizedLinearMethod
42+
from vllm.model_executor.layers.quantization.compressed_tensors import (
43+
CompressedTensorsConfig,
44+
CompressedTensorsLinearMethod,
45+
)
46+
from vllm.model_executor.models.hy_v3 import HYV3MoEFused
47+
from vllm.transformers_utils.configs.hy_v3 import HYV3Config
48+
49+
50+
def _make_hy_v3_config() -> HYV3Config:
51+
# Minimal MoE config: tiny dims, a few experts, one shared expert.
52+
return HYV3Config(
53+
vocab_size=16,
54+
hidden_size=8,
55+
intermediate_size=16,
56+
num_hidden_layers=1,
57+
num_attention_heads=2,
58+
num_key_value_heads=2,
59+
head_dim=4,
60+
num_experts=2,
61+
num_experts_per_tok=1,
62+
num_shared_experts=1,
63+
expert_hidden_dim=8,
64+
first_k_dense_replace=0,
65+
route_norm=True,
66+
)
67+
68+
69+
def _make_w4a16_ct_config_with_shared_mlp_ignore() -> CompressedTensorsConfig:
70+
# W4A16 pack-quantized scheme targeting all Linears, but shared_mlp ignored
71+
# (matches real hy_v3 W4A16 checkpoints produced by llm-compressor).
72+
scheme = QuantizationScheme(
73+
targets=["Linear"],
74+
weights=QuantizationArgs(
75+
num_bits=4,
76+
type=QuantizationType.INT,
77+
strategy=QuantizationStrategy.GROUP,
78+
group_size=128,
79+
symmetric=True,
80+
dynamic=False,
81+
),
82+
)
83+
# Reuse the public from_config path so the ignore list is parsed the same
84+
# way as when vLLM loads a real model.
85+
config_dict = {
86+
"format": "pack-quantized",
87+
"quant_method": "compressed-tensors",
88+
"ignore": ["re:.*shared_mlp.*"],
89+
"config_groups": {
90+
"config_group_0": {
91+
"targets": ["Linear"],
92+
"weights": {
93+
"num_bits": 4,
94+
"type": "int",
95+
"strategy": "group",
96+
"group_size": 128,
97+
"symmetric": True,
98+
"dynamic": False,
99+
},
100+
"input_activations": None,
101+
}
102+
},
103+
}
104+
del scheme # build from dict to exercise the real parsing path
105+
return CompressedTensorsConfig.from_config(config_dict)
106+
107+
108+
@pytest.mark.skipif(not torch.cuda.is_available(), reason="Needs CUDA for FusedMoE")
109+
def test_hy_v3_shared_mlp_ignored_under_compressed_tensors(
110+
dist_init, default_vllm_config
111+
):
112+
"""shared_mlp Linears must be unquantized when shared_mlp is in the ignore
113+
list. Without the prefix fix they would be built as CompressedTensorsLinear
114+
Method and fail to load BF16 weights."""
115+
hy_config = _make_hy_v3_config()
116+
quant_config = _make_w4a16_ct_config_with_shared_mlp_ignore()
117+
118+
with torch.device("cuda:0"):
119+
moe = HYV3MoEFused(
120+
config=hy_config,
121+
quant_config=quant_config,
122+
prefix="model.layers.0.mlp",
123+
)
124+
125+
assert moe.shared_mlp is not None
126+
127+
# down_proj (RowParallelLinear) must be unquantized (ignored).
128+
assert isinstance(
129+
moe.shared_mlp.down_proj.quant_method, UnquantizedLinearMethod
130+
), "shared_mlp.down_proj should be ignored (unquantized)"
131+
132+
# gate_up_proj (MergedColumnParallelLinear) must be unquantized (ignored).
133+
assert isinstance(
134+
moe.shared_mlp.gate_up_proj.quant_method, UnquantizedLinearMethod
135+
), "shared_mlp.gate_up_proj should be ignored (unquantized)"
136+
137+
138+
@pytest.mark.skipif(not torch.cuda.is_available(), reason="Needs CUDA for FusedMoE")
139+
def test_hy_v3_shared_mlp_quantized_when_not_ignored(
140+
dist_init, default_vllm_config
141+
):
142+
"""Sanity check: without the ignore entry, shared_mlp Linears ARE quantized.
143+
This confirms the test above is actually exercising the ignore path (and is
144+
not passing for some other reason)."""
145+
hy_config = _make_hy_v3_config()
146+
147+
config_dict = _make_w4a16_ct_config_with_shared_mlp_ignore().__dict__
148+
# Drop the ignore entry so shared_mlp is no longer ignored.
149+
config_dict = {
150+
"format": "pack-quantized",
151+
"quant_method": "compressed-tensors",
152+
"ignore": [],
153+
"config_groups": {
154+
"config_group_0": {
155+
"targets": ["Linear"],
156+
"weights": {
157+
"num_bits": 4,
158+
"type": "int",
159+
"strategy": "group",
160+
"group_size": 128,
161+
"symmetric": True,
162+
"dynamic": False,
163+
},
164+
"input_activations": None,
165+
}
166+
},
167+
}
168+
quant_config = CompressedTensorsConfig.from_config(config_dict)
169+
170+
with torch.device("cuda:0"):
171+
moe = HYV3MoEFused(
172+
config=hy_config,
173+
quant_config=quant_config,
174+
prefix="model.layers.0.mlp",
175+
)
176+
177+
assert moe.shared_mlp is not None
178+
assert isinstance(
179+
moe.shared_mlp.down_proj.quant_method, CompressedTensorsLinearMethod
180+
), "shared_mlp.down_proj should be quantized when NOT ignored"

vllm/model_executor/models/hy_v3.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def __init__(
171171
intermediate_size=config.expert_hidden_dim * config.num_shared_experts,
172172
hidden_act=config.hidden_act,
173173
quant_config=quant_config,
174-
prefix=f"{prefix}",
174+
prefix=f"{prefix}.shared_mlp",
175175
reduce_results=False,
176176
)
177177
else:

0 commit comments

Comments
 (0)