【Bug已解决】Add LoRA support for Gemma 4 (Gemma4ClippableLinear) 解决方案
【Bug已解决】Add LoRA support for Gemma 4 (Gemma4ClippableLinear) 解决方案一、现象长什么样给Gemma 4模型加 LoRA 时发现 PEFT 没能把 adapter 挂到模型里的线性层或挂上后训练报错TypeError: ... Gemma4ClippableLinear object has no attribute weight (expected by LoRA)或ValueError: Target module Gemma4ClippableLinear is not supported by LoRA根因是Gemma 4 用了自定义的Gemma4ClippableLinear一种支持“可裁剪/可切换”的线性层可能内部把权重包在子模块或用了不同的属性名而 PEFT 的 LoRA 实现默认只认标准nn.Linearmodule.weight。遇到这个自定义类PEFT 要么不识别漏挂要么按标准 Linear 假设属性名而失败。本文讲清如何给 Gemma 4 的Gemma4ClippableLinear加 LoRA 支持。二、背景“ClippableLinear”可裁剪线性层是某些新模型如 Gemma 4 为了支持剪枝/专家切换引入的自定义层它可能在内部持有self.linear nn.Linear(...)真正的权重在子模块里对外暴露weight作为 property或者self.weight是一个nn.Parameter但额外有clip/select逻辑或者它把权重存在self.w而非self.weight。PEFT 的 LoRA 在 inject 时默认逻辑是“找到nn.Linear实例把它替换成LoraLinear(base原linear)”。如果Gemma4ClippableLinear不是nn.Linear的子类而是自己实现的nn.ModulePEFT 的isinstance(module, nn.Linear)检查会失败导致target_modules命中了Gemma4ClippableLinear的命名但 PEFT 发现它不是标准 Linear跳过或报错即使强行替换LoRA 层期望base_layer.weight存在而自定义层属性名不同前向失败。要让 LoRA 支持它需要让 PEFT 能“识别并适配”这个自定义类——通常是注册一个针对Gemma4ClippableLinear的适配映射或让它被当作标准 Linear 处理。三、根因根因 AGemma4ClippableLinear不是nn.Linear子类最直接。PEFT 用isinstance(m, nn.Linear)决定能否挂 LoRA自定义类不是就漏挂/报错。根因 B权重属性名/结构不同自定义层把权重放在self.linear.weight或self.wPEFT 假设module.weight直接存在假设失败。根因 CPEFT 的 target 匹配只认已知类型PEFT 内部的TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES没有 Gemma 4 的Gemma4ClippableLinear映射默认的 Linear 适配层不覆盖它。根因 D前向逻辑不兼容LoRA 包装层的前向假设 base 是标准 Linear自定义层的前向含 clip 逻辑若被包进 LoRA 会改变行为。根因小结Gemma 4 的Gemma4ClippableLinear是自定义层PEFT 默认只认nn.Linear修复注册自定义类到 PEFT 的 Linear 适配映射/子类检查让其权重可被 LoRA 接管需保证属性名、前向语义兼容。四、最小可运行复现下面脚本模拟“自定义 ClippableLinear 不被 PEFT 识别”与适配方案import torch import torch.nn as nn class Gemma4ClippableLinear(nn.Module): 模拟 Gemma 4 的自定义可裁剪线性层不是 nn.Linear 子类 def __init__(self, in_f, out_f): super().__init__() self.linear nn.Linear(in_f, out_f) # 权重在子模块 self.clip_ratio 1.0 def forward(self, x): return self.linear(x) def peft_recognizes(module): # PEFT 默认只认 nn.Linear return isinstance(module, nn.Linear) class LoraWrapper(nn.Module): 适配把自定义层包成带 LoRA 的层 def __init__(self, clippable, r4): super().__init__() self.base_layer clippable.linear # 用内部标准 Linear 作 base in_f, out_f clippable.linear.in_features, clippable.linear.out_features self.A nn.Parameter(torch.zeros(r, in_f)) self.B nn.Parameter(torch.zeros(out_f, r)) self.scaling 2.0 self._clip clippable.clip_ratio def forward(self, x): out self.base_layer(x) delta (x self.A.t()) self.B.t() * self.scaling return out delta def demo(): layer Gemma4ClippableLinear(16, 8) print(PEFT 默认识别?, peft_recognizes(layer)) # False - 漏挂 # 适配把内部 linear 当 base 包 LoRA wrapped LoraWrapper(layer, r4) x torch.randn(2, 16) out wrapped(x) print(适配后前向形状:, tuple(out.shape)) if __name__ __main__: demo()运行后默认 PEFT 不识别自定义层False适配方案用内部标准 Linear 作 base 成功挂 LoRA。五、解决方案第一层最小直接修复让 PEFT 把Gemma4ClippableLinear当作可挂 LoRA 的层利用它内部的self.linear标准nn.Linear作为 base。# 方法1target_modules 指向内部 linear用子模块路径 config LoraConfig( r4, target_modules[layers.0.self_attn.q_proj.linear], # 指向内部标准 Linear ) # 方法2注册自定义类到 PEFT 的 Linear 适配 from peft.tuners.lora import LoraLayer # 让 PEFT 在 inject 时把 Gemma4ClippableLinear 当成 Linear用其内部 weight # 通常需要扩展 PEFT 的 _get_submodules / 类型注册若你维护 PEFT加一个针对 Gemma 4 的映射# peft/utils/constants.py 或对应文件 from your_model_file import Gemma4ClippableLinear # 让 inject 时把 Gemma4ClippableLinear 视同 Linear LINEAR_MAPPING.update({Gemma4ClippableLinear: linear}) # 指明内部标准 Linear 属性名六、解决方案第二层结构性改进6.1 让Gemma4ClippableLinear继承nn.Linearclass Gemma4ClippableLinear(nn.Linear): def __init__(self, in_f, out_f): super().__init__(in_f, out_f) self.clip_ratio 1.0 def forward(self, x): return super().forward(x) # 仍走标准 Linear 前向PEFT 直接认这样 PEFT 的isinstance(m, nn.Linear)通过LoRA 自动支持。6.2 给 PEFT 提 PR注册 Gemma 4 target mapping社区修复在 PEFT 的 transformers 模型 target 映射里加 Gemma 4 的Gemma4ClippableLinear支持使target_modules能直接命中。6.3 升级 transformers/peftpip install -U transformers peft新版可能已内置 Gemma 4 支持。七、解决方案第三层断言 / CI 守护import torch import pytest import torch.nn as nn def test_clippable_is_linear_compatible(layer_factory): layer layer_factory(16, 8) # 应能被当作 Linear有 weight、可挂 LoRA assert hasattr(layer, weight) or hasattr(layer, linear) assert isinstance(getattr(layer, linear, layer), nn.Linear) def test_lora_attaches_to_clippable(model_gemma4): cfg LoraConfig(r4, target_modules[q_proj, v_proj]) m get_peft_model(model_gemma4, cfg) assert any(lora in n for n, _ in m.named_parameters()), LoRA 应挂上 Gemma4ClippableLinear def test_clippable_lora_forward(model_gemma4): m get_peft_model(model_gemma4, LoraConfig(r4, target_modules[q_proj])) out m(torch.randint(0, 100, (1, 8))) assert out is not None def test_no_weight_attribute_error(model_gemma4): # 守护不应报 Gemma4ClippableLinear has no attribute weight try: m get_peft_model(model_gemma4, LoraConfig(r4, target_modules[q_proj])) except AttributeError as e: pytest.fail(f仍报 weight 属性错误: {e})CI 跑这四条Gemma 4 LoRA 支持被守住。八、排查清单给 Gemma 4 的Gemma4ClippableLinear加 LoRA 时查它是nn.Linear子类吗不是则 PEFT 默认不识别。权重在哪在self.linear.weight还是self.wPEFT 假设module.weight。target_modules 命中内部 linear 了吗用子模块路径指向标准 Linear。注册到 PEFT 映射了吗让Gemma4ClippableLinear视同 Linear。升级 transformers/peft 了吗新版可能内置支持。前向语义兼容吗LoRA 包装不破坏 clip 逻辑。CI 测了 Gemma 4 LoRA 吗加前向/挂载断言防回归。九、小结“Add LoRA support for Gemma 4 (Gemma4ClippableLinear)” 是给Gemma 4 的自定义Gemma4ClippableLinear层加 LoRA 支持根因Gemma4ClippableLinear不是nn.Linear子类或权重在子模块PEFT 默认只认nn.Linear导致漏挂或has no attribute weight第一层target_modules指向内部标准linear或把内部linear当 base 包 LoRA第二层让Gemma4ClippableLinear继承nn.Linear、提 PR 注册 Gemma 4 target 映射、升级 transformers/peft第三层pytest 守护“可被当作 Linear LoRA 挂上 前向通过 无 weight 属性错误”。一句话Gemma 4 的Gemma4ClippableLinear因不是标准nn.Linear而 LoRA 不识别让它继承nn.Linear或target_modules指向其内部linear、注册到 PEFT 映射即可支持 LoRA。