huggingface / huggingface/peft
LoRA + torchao: merging several adapters in one call raises UnboundLocalError
- Dominant language
- Python
- Stars
- 21.7k
- Forks
- 2.5k
- Avg merge
- 4d 16h
- Merged PRs (30d)
- 60
Description
### System Info
- `peft` main at `0e8d0ae8`
- `torchao` 0.18.0, PyTorch 2.11.0, Transformers 5.9.0
- Python 3.13.15, Linux, CPU
### Who can help?
@BenjaminBossan
### Reproduction
Merging more than one adapter in a single call raises `UnboundLocalError` on a torchao quantized layer. `merge_adapter` accepts a list of adapter names, so this is reachable from the public API:
```python
import torch
from torchao.quantization import Int8WeightOnlyConfig
from transformers import AutoModelForCausalLM, TorchAoConfig
from peft import LoraConfig, get_peft_model
model = AutoModelForCausalLM.from_pretrained(
"peft-internal-testing/opt-125m",
quantization_config=TorchAoConfig(Int8WeightOnlyConfig()),
device_map={"": "cpu"},
)
model = get_peft_model(model, LoraConfig(init_lora_weights=False, target_modules=["q_proj", "v_proj"]))
model.add_adapter("other", LoraConfig(init_lora_weights=False, target_modules=["q_proj", "v_proj"]))
model.base_model.merge_adapter(adapter_names=["default", "other"])
```
Current result:
```text
File "/.../src/peft/tuners/tuners_utils.py", line 1321, in merge_adapter
module.merge(adapter_names=adapter_names, safe_merge=safe_merge)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../src/peft/tuners/lora/torchao.py", line 78, in merge
weight = weight.dequantize()
^^^^^^
UnboundLocalError: cannot access local variable 'weight' where it is not associated with a value
```
Merging the two adapters one at a time works, so only the batched call is affected.
### Root cause
`TorchaoLoraLinear.merge` reads the weight once, before the loop, and deletes the local at the end of every iteration:
```python
base_layer = self.get_base_layer()
weight = base_layer.weight
for active_adapter in adapter_names:
weight = weight.dequantize()
...
del base_layer.weight
base_layer.weight = weight
quantize_(base_layer, self.get_apply_tensor_subclass())
del weight # <- the name is gone for the next iteration
```
`unmerge` in the same file does the same work but re-reads `base_layer` and `weight` inside the loop, which is why it survives several adapters. Moving those two lines into the loop in `merge` matches `unmerge` and fixes it.
### Expected behavior
`merge_adapter(adapter_names=[...])` merges every listed adapter, as it does for the plain and bitsandbytes LoRA layers.
Duplicate search before filing: `UnboundLocalError`, `torchao adapter_names`, `merge multiple adapters torchao`, plus the open PRs touching `lora/torchao.py`. Nothing covers this. It is separate from the safe merge work in #3710, although it is in the same function, so I mentioned it there.
I am happy to send the fix with a regression test if you want it as its own PR.
AI assistance: I used Claude Code while investigating this and to help draft the report. I ran the reproduction and checked the code paths myself.
Contributor guide
Assessment
This issue has not been assessed yet.