huggingface / huggingface/peft

LoRA + torchao: merging several adapters in one call raises UnboundLocalError

Open
#3,728 1 comment 0 reactions 1 assignee Claimed by @BenjaminBossan View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.