Sitelet https://github.com/huggingface/peft/pull/3590
Skip to content

Refuse to hot-swap adapters while they are merged into the base weights - #3590

Open
Sravanjangam wants to merge 1 commit into
huggingface:mainfrom
Sravanjangam:fix/hotswap-guard-merged-state
Open

Refuse to hot-swap adapters while they are merged into the base weights#3590
Sravanjangam wants to merge 1 commit into
huggingface:mainfrom
Sravanjangam:fix/hotswap-guard-merged-state

Conversation

@Sravanjangam

Copy link
Copy Markdown

hotswap_adapter replaces only the adapter weights, but a merged adapter has its delta already folded into the base weights. Swapping in that state left the old adapter effectively active (merged forward skips adapter weights), and a later unmerge subtracted the new delta from a base containing the old delta — silently corrupting the model. This was case 1 from the linked issue.

Fixes #3581

Details

The low-level hotswap_adapter_from_state_dict now scans for BaseTunerLayers with non-empty merge state and raises a ValueError pointing users to unmerge_adapter() first. Raising is deliberately conservative — auto-unmerging would be an alternative semantic, and we'd be glad to switch if maintainers prefer that.

Tests

  • New TestHotswapMergedGuard in tests/test_initialization.py: swap-while-merged raises; the same swap on an unmerged model still works end-to-end including a forward pass (guard test fails before, passes after).

Test environment: Python 3.12, torch 2.13.0+cpu, transformers 5.15.1, peft @ 5d602fd, ruff 0.16.4 (check + format clean).

hotswap_adapter only replaces the adapter weights, but a merged adapter has
its delta already folded into the base weights. Swapping in that state left
the old adapter effectively active (merged forward skips adapter weights),
and a later unmerge subtracted the NEW delta from a base containing the OLD
delta, silently corrupting the model.

Raise a ValueError pointing users to unmerge_adapter() first. Auto-unmerging
would be an alternative semantics; raising is the conservative choice and can
be revisited.

Fixes huggingface#3581 (case 1)

@BenjaminBossan BenjaminBossan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this fix to prevent hotswapping when there are merged layers. Raising an error instead of unmerging is the right call.

I have a few comments on this PR, please check.

Note that you should always await approval from the maintainers on the corresponding issue before opening the PR. We may close future PRs if there is no prior approval if we get overwhelmed with agent PRs.

assert len(model.tinylora_v["b"]) == len(model_control.tinylora_v["b"]) == 1


class TestHotswapMergedGuard:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the tests into the already existing TestHotSwapping class and follow the conventions of the tests there.

Comment thread src/peft/utils/hotswap.py
# (see #3581). Refuse to run instead.
from peft.tuners.tuners_utils import BaseTunerLayer

merged_layers = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's sufficient to check if any layer has merged modules. This breaks the loop early if there is a match. There is no need to report which layer exactly has the merged layer.

Comment thread src/peft/utils/hotswap.py
from peft.tuners.tuners_utils import BaseTunerLayer

merged_layers = [
name for name, module in model.named_modules() if isinstance(module, BaseTunerLayer) and module.merged

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically, if adapter "foo" is merged but we hotswap adapter "bar", we should be good. As is, we still raise an error though. We should check if the adapter that is being swapped out is the one being merged. Please update the tests accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants