From dadc4bf8db7e1ee8331e65f3b5e9f95dc4538e93 Mon Sep 17 00:00:00 2001 From: Chessing234 <134995372+Chessing234@users.noreply.github.com> Date: Tue, 5 May 2026 05:00:38 +0530 Subject: [PATCH 1/2] fix(tuners): guard child.weight access in _replace_module for non-linear modules When modules_to_save contains non-linear modules (e.g. encoder blocks without weight attributes), _replace_module unconditionally assigns child.weight, raising AttributeError. Add hasattr guard to match the existing pattern used for bias. --- src/peft/tuners/tuners_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/peft/tuners/tuners_utils.py b/src/peft/tuners/tuners_utils.py index a6be1a8e84..01a4888d9d 100644 --- a/src/peft/tuners/tuners_utils.py +++ b/src/peft/tuners/tuners_utils.py @@ -1167,7 +1167,8 @@ def _replace_module(self, parent, child_name, new_module, child) -> None: child = child.base_layer if not hasattr(new_module, "base_layer"): - new_module.weight = child.weight + if hasattr(child, "weight"): + new_module.weight = child.weight if hasattr(child, "bias"): new_module.bias = child.bias From 76b27380a449cb80fad6c910562e9684b30239ff Mon Sep 17 00:00:00 2001 From: Taksh Date: Thu, 28 May 2026 12:01:23 +0530 Subject: [PATCH 2/2] test: add test for merge_and_unload with ModulesToSaveWrapper on modules without weight --- tests/test_other.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_other.py b/tests/test_other.py index 693399a257..755b6b29bf 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -274,6 +274,37 @@ def test_transient_attribute_access_non_existing_adapter(self, mlp): model.lin1.weight +class TestModulesToSaveMergeAndUnload: + def test_merge_and_unload_without_weight(self): + # Regression test for #3213: merge_and_unload should not crash when modules_to_save + # targets a module without a weight attribute (e.g., a transformer encoder block) + class ModuleWithoutWeight(nn.Module): + def __init__(self): + super().__init__() + self.foo = nn.Linear(1, 1) + + def forward(self, x): + return self.foo(x) + + class Model(nn.Module): + def __init__(self): + super().__init__() + self.lin0 = nn.Linear(1, 2) + self.mod1 = ModuleWithoutWeight() + + def forward(self, x): + return self.mod1(self.lin0(x)) + + from peft import LoraConfig, get_peft_model + config = LoraConfig(target_modules=["lin0"], modules_to_save=["mod1"]) + model = get_peft_model(Model(), config) + + # This should not raise an AttributeError + merged = model.merge_and_unload() + assert not hasattr(merged.mod1, "weight") + + + class TestModulesToSaveKwargsOnlyForward: """Regression test for #3191: modules listed in `modules_to_save` whose parent calls them with keyword arguments only (e.g. Gemma's `vision_tower(pixel_values=...)`) used to crash with `TypeError: forward() missing 1 required