【Bug已解决】[Bug]: Gemma4 (probably other models with missing v_proj) breaks with mixed quantization 解决方
【Bug已解决】[Bug]: Gemma4 (probably other models with missing v_proj) breaks with mixed quantization 解决方案一、现象长什么样用混合量化mixed quantization只量化部分模块/层其余保持 fp16加载 Gemma4 这类模型时启动或权重加载报错KeyError: v_proj not found in quantization target modules或ValueError: module v_proj listed in quant_modules but absent from model几个典型表征只在混合量化且在 Gemma4或类似结构上出现全量化或 fp16 正常说明问题在按模块名指定要量化的集合与模型实际模块名对不上。报错围绕v_proj缺失Gemma4 的注意力实现里value 投影没有被命名成标准的v_proj——它可能是融合进qkv_proj、或者由另一个名字如q_proj一并承担、或k_proj/v_proj合并表示。于是混合量化配置里写quant_modules[q_proj,k_proj,v_proj]时v_proj在模型里根本不存在。其它模型也可能有同样特征任何注意力里没有独立 v_proj的架构如某些用qkv_proj融合投影、或o_proj之外没有单独 value 投影的变体都会踩这个坑。这不是量化算法坏了而是混合量化的目标模块名集合假设了标准q_proj/k_proj/v_proj命名而模型实际模块名不符。下面给出定位与修复。二、背景混合量化通过quantization_config或 CLI 指定哪些模块要量化例如quantization_config: { method: gptq, quant_modules: [q_proj, k_proj, v_proj, o_proj] }vLLM 加载时遍历模型named_modules()把名字命中的模块替换成量化版。问题在于模块名来自模型的真实实现。Gemma4 的注意力可能把 Q/K/V 融合成一个qkv_proj一个nn.Linear输出维度 q_dimk_dimv_dim此时named_modules()里只有qkv_proj没有q_proj/k_proj/v_proj混合量化配置按标准命名去匹配三个投影名全 missv_proj这种必须存在的强假设直接KeyError。更微妙的是有些实现把 V 投影共享/折叠进别的层导致v_proj名字彻底不存在。根因是量化目标模块名与模型实际模块名之间的映射缺失。修复就是加载前探测模型真实模块名把量化配置里的标准名翻译成模型实际名或回退到融合模块。下面用可运行代码复现并修复。三、根因拆成两条根因量化目标模块名假设了标准命名混合量化配置写死[q_proj,k_proj,v_proj]但模型用qkv_proj融合投影名字对不上强假设v_proj存在就KeyError。根因是量化模块名集合没有按模型实际命名适配。融合投影qkv_proj没有被当作量化目标即使知道是qkv_proj现有量化逻辑可能只对单投影做量化不支持一个融合 Linear 整体量化或需要把融合层整体量化而非拆 Q/K/V。根因是量化器不支持融合投影作为整体目标。修复方向加载前用named_modules()探测真实模块名建立标准名 → 实际名的映射表如v_proj映射到qkv_proj的 value 段对融合投影支持整体量化或按子段切片量化缺失的模块名给出清晰警告而非崩溃。四、最小可运行复现下面复现量化目标名 v_proj 在融合投影模型里不存在import torch.nn as nn class GemmaAttention(nn.Module): Gemma 风格Q/K/V 融合成一个 qkv_proj无独立 v_proj。 def __init__(self, h, n): super().__init__() self.qkv_proj nn.Linear(h, 3 * n) # 融合 self.o_proj nn.Linear(n, h) def naive_quant_targets(model, targets): 现状按标准名逐个匹配缺失就 KeyError。 names {n for n, _ in model.named_modules()} for t in targets: if t not in names: raise KeyError(f{t} not found in quantization target modules) return True m GemmaAttention(16, 8) try: naive_quant_targets(m, [q_proj, k_proj, v_proj, o_proj]) except KeyError as e: print(复现:, e) # v_proj not found复现: v_proj not found即复现。下面加上标准名 → 实际名映射与融合投影处理。五、解决方案第一层最小直接修复最小修复加载前探测真实模块名建立标准名映射v_proj不存在时回退到融合qkv_proj整体量化而不是崩溃。def resolve_quant_targets(model, standard_targets): 把标准模块名解析成模型里真实存在的目标含融合投影回退。 real_names {n for n, _ in model.named_modules()} resolved [] for t in standard_targets: if t in real_names: resolved.append(t) # 标准名直接命中 continue # 回退v_proj/k_proj/q_proj 缺失 → 尝试融合 qkv_proj if t in (q_proj, k_proj, v_proj) and qkv_proj in real_names: if qkv_proj not in resolved: resolved.append(qkv_proj) # 融合层整体量化 continue # 既无标准名也无融合层警告并跳过而非崩溃 print(f[warn] 量化目标 {t} 在模型中不存在已跳过) return resolved # 复现修复 m GemmaAttention(16, 8) targets resolve_quant_targets(m, [q_proj, k_proj, v_proj, o_proj]) print(解析后量化目标:, targets) # [qkv_proj, o_proj]这一层改动让v_proj缺失时自动回退到qkv_proj融合层整体量化缺失的模块只警告不崩混合量化得以在 Gemma4 上跑。六、解决方案第二层结构化改进把量化目标解析做成结构化组件支持标准名 → 实际名映射表、融合投影的整体量化与按子段切片量化若量化器支持并在加载期产出清晰的解析报告。from dataclasses import dataclass, field from typing import Dict, List # 标准名 → 融合层回退按模型家族可扩展 FUSION_FALLBACK { q_proj: qkv_proj, k_proj: qkv_proj, v_proj: qkv_proj, } dataclass class QuantResolution: resolved: List[str] field(default_factorylist) skipped: List[str] field(default_factorylist) fused_as_whole: List[str] field(default_factorylist) class QuantTargetResolver: def __init__(self, model): self.real {n for n, _ in model.named_modules()} def resolve(self, standard_targets: List[str]) - QuantResolution: res QuantResolution() for t in standard_targets: if t in self.real: res.resolved.append(t) elif t in FUSION_FALLBACK and FUSION_FALLBACK[t] in self.real: fused FUSION_FALLBACK[t] if fused not in res.fused_as_whole: res.fused_as_whole.append(fused) res.resolved.append(fused) else: res.skipped.append(t) return res def report(self): print(量化目标解析:) print( 直接命中:, self.resolved) print( 融合层整体量化:, self.fused_as_whole) print( 跳过(不存在):, self.skipped) # 用法 r QuantTargetResolver(m).resolve([q_proj, k_proj, v_proj, o_proj]) QuantTargetResolver(m).report() if False else None print(r)QuantTargetResolver把命名适配集中管理新增模型家族只需扩展FUSION_FALLBACK文档也能从它生成哪些模型回退到融合层。七、解决方案第三层断言 / CI 守护混合量化最怕换模型又 KeyError。用断言守两条不变量def check_quant_target_invariants(model, standard_targets): res QuantTargetResolver(model).resolve(standard_targets) # 不变量 1解析后必须有实际量化目标不能全跳过 assert len(res.resolved) 0, 量化目标全部缺失混合量化无效 # 不变量 2任何标准目标要么命中、要么回退融合、要么被显式跳过不得 KeyError total len(res.resolved) len(res.skipped) assert total len(standard_targets), 解析结果数量与输入不符 return True def test_gemma4_mixed_quant(): m GemmaAttention(16, 8) check_quant_target_invariants(m, [q_proj, k_proj, v_proj, o_proj]) # 标准模型也应正常有独立 v_proj 时直接命中 class StdAttn(nn.Module): def __init__(self, h, n): super().__init__() self.q_proj nn.Linear(h, n) self.k_proj nn.Linear(h, n) self.v_proj nn.Linear(h, n) self.o_proj nn.Linear(n, h) check_quant_target_invariants(StdAttn(16, 8), [q_proj, k_proj, v_proj, o_proj]) print(OK: Gemma4 混合量化目标解析不变量通过) if __name__ __main__: test_gemma4_mixed_quant()把test_gemma4_mixed_quant接进 CI任何又对缺失 v_proj 直接 KeyError的改动都会立即红。八、排查清单Gemma4 混合量化报v_proj not found按序查先打印模型真实模块名[n for n,_ in model.named_modules() if proj in n]。若看到qkv_proj而无v_proj就是融合投影问题。确认是融合投影Gemma 系常把 Q/K/V 融进qkv_proj。混合量化配置按标准q_proj/k_proj/v_proj匹配自然全 miss。用resolve_quant_targets做命名适配v_proj不存在时回退到qkv_proj整体量化缺失模块只警告跳过不崩。融合层整体量化 vs 子段切片整体量化简单把qkv_proj当一个 Linear 量化若需只量化 V 段则要对qkv_proj的输出维按 q/k/v 段切片分别量化更复杂看量化器是否支持。混合量化的 quant_modules 配置写配置时优先用模型实际名如[qkv_proj,o_proj]而非假设标准名避免依赖运行期回退。其它类似模型任何无独立 v_proj的架构如某些 MoE/共享注意力变体同样适用FUSION_FALLBACK回退逻辑。CI 接test_gemma4_mixed_quant覆盖融合投影模型 标准模型两类锁死命名解析防止回归 KeyError。九、小结Gemma4及无独立 v_proj 的模型混合量化报v_proj not found的根因是混合量化的目标模块名假设了标准q_proj/k_proj/v_proj命名而模型把 Q/K/V 融合进qkv_proj导致标准名全 miss、强假设v_proj存在就崩。三层修复第一层resolve_quant_targets探测真实模块名v_proj缺失时回退到qkv_proj整体量化缺失模块只警告不崩第二层QuantTargetResolverFUSION_FALLBACK集中管理标准名 → 融合层映射支持整体量化与解析报告新增模型家族只需扩展映射表第三层CI 断言守住解析后必有实际目标 / 不得 KeyError任何回退失效立即红。落实后Gemma4 的混合量化要么正确量化qkv_proj融合层整体要么清晰告知哪些目标被跳过而不是因v_proj缺失崩溃。