Skip to content

Commit 06fdc63

Browse files
committed
Give mixed-rank LoRAs without alpha keys their intended scale
1 parent e504b04 commit 06fdc63

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

src/diffusers/utils/peft_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def get_peft_kwargs(
156156
rank_pattern = {}
157157
alpha_pattern = {}
158158
r = lora_alpha = list(rank_dict.values())[0]
159+
has_alphas = network_alpha_dict is not None and len(network_alpha_dict) > 0
159160

160161
if len(set(rank_dict.values())) > 1:
161162
# get the rank occurring the most number of times
@@ -165,6 +166,13 @@ def get_peft_kwargs(
165166
rank_pattern = dict(filter(lambda x: x[1] != r, rank_dict.items()))
166167
rank_pattern = {k.split(".lora_B.")[0]: v for k, v in rank_pattern.items()}
167168

169+
if not has_alphas:
170+
# No alpha data in the checkpoint: the diffusers/PEFT convention is
171+
# `W_eff = W + lora_B @ lora_A`, i.e. alpha == rank per module (scale 1.0).
172+
# Mirror the ranks into the alphas so every module keeps scale 1.0.
173+
lora_alpha = r
174+
alpha_pattern = dict(rank_pattern)
175+
168176
if network_alpha_dict is not None and len(network_alpha_dict) > 0:
169177
if len(set(network_alpha_dict.values())) > 1:
170178
# get the alpha occurring the most number of times

tests/lora/test_peft_utils.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2026 HuggingFace Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from diffusers.utils.peft_utils import get_peft_kwargs
16+
17+
18+
def _rank_dict(module_ranks):
19+
return {f"{module}.lora_B.weight": rank for module, rank in module_ranks.items()}
20+
21+
22+
def _peft_state_dict(module_ranks):
23+
state_dict = {}
24+
for module in module_ranks:
25+
state_dict[f"{module}.lora_A.weight"] = None
26+
state_dict[f"{module}.lora_B.weight"] = None
27+
return state_dict
28+
29+
30+
def _effective_scale(kwargs, module):
31+
alpha = kwargs["alpha_pattern"].get(module, kwargs["lora_alpha"])
32+
rank = kwargs["rank_pattern"].get(module, kwargs["r"])
33+
return alpha / rank
34+
35+
36+
def test_mixed_ranks_without_alphas_apply_at_scale_one():
37+
# An adapter with per-module ranks and no alpha keys means `W_eff = W + lora_B @ lora_A`,
38+
# i.e. alpha == rank, so every module must come out at scale 1.0.
39+
module_ranks = {"blocks.0.adaln": 16, "blocks.0.to_q": 64, "blocks.0.to_v": 64}
40+
kwargs = get_peft_kwargs(_rank_dict(module_ranks), None, _peft_state_dict(module_ranks))
41+
for module in module_ranks:
42+
assert _effective_scale(kwargs, module) == 1.0
43+
44+
45+
def test_mixed_ranks_without_alphas_are_order_independent():
46+
# The scale must not depend on which module happens to come first in the state dict.
47+
module_ranks = {"blocks.0.to_q": 64, "blocks.0.to_v": 64, "blocks.0.adaln": 16}
48+
reordered = dict(reversed(module_ranks.items()))
49+
for ranks in (module_ranks, reordered):
50+
kwargs = get_peft_kwargs(_rank_dict(ranks), None, _peft_state_dict(ranks))
51+
for module in ranks:
52+
assert _effective_scale(kwargs, module) == 1.0
53+
54+
55+
def test_uniform_rank_without_alphas_unchanged():
56+
module_ranks = {"blocks.0.to_q": 32, "blocks.0.to_v": 32}
57+
kwargs = get_peft_kwargs(_rank_dict(module_ranks), None, _peft_state_dict(module_ranks))
58+
assert kwargs["r"] == kwargs["lora_alpha"] == 32
59+
assert kwargs["rank_pattern"] == {}
60+
assert kwargs["alpha_pattern"] == {}
61+
62+
63+
def test_mixed_ranks_with_uniform_alpha_keep_declared_alpha():
64+
# A declared alpha must win over the alpha == rank convention: scale is alpha / rank per module.
65+
module_ranks = {"blocks.0.adaln": 16, "blocks.0.to_q": 64}
66+
network_alphas = {f"{module}.alpha": 32 for module in module_ranks}
67+
kwargs = get_peft_kwargs(_rank_dict(module_ranks), network_alphas, _peft_state_dict(module_ranks))
68+
assert kwargs["lora_alpha"] == 32
69+
assert kwargs["alpha_pattern"] == {}
70+
assert _effective_scale(kwargs, "blocks.0.adaln") == 2.0
71+
assert _effective_scale(kwargs, "blocks.0.to_q") == 0.5
72+
73+
74+
def test_mixed_ranks_with_per_module_alphas_unchanged():
75+
module_ranks = {"blocks.0.adaln": 16, "blocks.0.to_q": 64}
76+
network_alphas = {"blocks.0.adaln.alpha": 16, "blocks.0.to_q.alpha": 64}
77+
kwargs = get_peft_kwargs(_rank_dict(module_ranks), network_alphas, _peft_state_dict(module_ranks))
78+
for module in module_ranks:
79+
assert _effective_scale(kwargs, module) == 1.0

0 commit comments

Comments
 (0)