Failed adapter injection leaves orphaned adapter layers behind
System Info
- Python:
3.12.3 - PEFT: main
279fccce200686af324611d7c49e3adab60485e1 - Accelerate:
1.13.0 - Transformers:
5.3.0
While adding adapters to a small Mamba-like model, I ran into a case where an invalid target caused add_adapter() to fail, but the model was still partially modified.
import torch
from torch import nn
from peft import LoraConfig, get_peft_model
class TinyMambaLike(nn.Module):
def __init__(self):
super().__init__()
self.safe = nn.Linear(4, 4, bias=False)
self.out_proj = nn.Linear(4, 4, bias=False)
self.config = type("Config", (), {"model_type": "mamba"})()
def forward(self, x):
return self.out_proj(self.safe(x))
model = get_peft_model(
TinyMambaLike(),
LoraConfig(target_modules=["safe"], r=2),
adapter_name="default",
)
try:
model.add_adapter(
"other",
LoraConfig(target_modules=["safe", "out_proj"], r=2),
)
except ValueError as error:
print(error)
print(model.peft_config.keys())
print([name for name in model.state_dict() if ".other." in name])The second configuration is rejected because out_proj is not supported for Mamba-based models. However, safe has already been updated before the error is raised.
Observed result:
dict_keys(["default"])
[
"base_model.model.safe.lora_A.other.weight",
"base_model.model.safe.lora_B.other.weight",
]The configuration is rolled back, but the partially injected "other" adapter remains in the model. The same general behavior also occurs during initial injection: an error on a later target can leave earlier targets replaced.
It looks like BaseTuner.inject_adapter() performs module replacement while traversing targets, with compatibility checks occurring as each target is processed. PeftModel.add_adapter() removes the new configuration when injection fails, but does not restore the module structure or tuner state.
I would expect a failed injection to leave the model unchanged, or at least to leave no adapter parameters that are no longer represented in peft_config.
I’d be happy to open a PR with regression coverage and a fix. But I'm not sure right now if we should validate all targets before mutating the model, or implemente a rollback mechanism that restores the module and tuner state if any injection step raises.
Source: huggingface/peft