Skip to content

Commit b2b34fd

Browse files
FIX Minimal target module optimization bug w/ IA³ (huggingface#2432)
Fixes huggingface#2429 During PEFT model initialization, we have an optimization/compression step where we check the target_modules attribute and, if it's very long, try to find a minimal subset that targets the same modules. If we find it, we reduce the target_modules to that minimal set. This is done mostly to prevent some cases (e.g. in diffusers) that result in hundreds of target_modules being checked against thousands of module names, slowing down initialization. There is an issue with this when using IA³. There, we additionally have the feedforward_modules attribute, which must be subset of target_modules. When target_modules is shrunk, the subset check will fail. This PR fixes this by simply skipping the compression step for IA³. It would be possible to adjust the logic to also shrink feedforward_modules, but it's not quite as forward, since the latter may not be identical to target_modules, so there would have to be extra logic to account for that. At the end of the day, this is too much effort for what's pretty much an edge case, so the simple solution is implemented.
1 parent 7320bb9 commit b2b34fd

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed

src/peft/tuners/tuners_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,13 @@ def inject_adapter(
452452
# quite a lot. See: https://github.com/huggingface/diffusers/issues/9297
453453
# As there is a small chance for undiscovered bugs, we apply this optimization only if the list of
454454
# target_modules is sufficiently big.
455+
# We also exclude IA³ from this optimization. This is because IA³ has both target_modules and
456+
# feedforward_modules, which are coupled (the latter must be a subset). It would be possible to change the logic
457+
# to keep both in sync, but it's not quite trivial and probably not worth the effort. See #2429.
455458
if (
456459
isinstance(peft_config.target_modules, (list, set))
457-
and len(peft_config.target_modules) >= MIN_TARGET_MODULES_FOR_OPTIMIZATION
460+
and (len(peft_config.target_modules) >= MIN_TARGET_MODULES_FOR_OPTIMIZATION)
461+
and (peft_config.peft_type != PeftType.IA3)
458462
):
459463
names_no_target = [
460464
name

tests/test_tuners_utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
IA3Config,
3838
LoHaConfig,
3939
LoraConfig,
40+
PeftModel,
4041
PromptTuningConfig,
4142
VeraConfig,
4243
get_layer_status,
@@ -1502,6 +1503,36 @@ def __init__(self):
15021503
# target modules should *not* be simplified to "query" as that would match "single_transformers_blocks" too
15031504
assert model.peft_config["default"].target_modules != {"query"}
15041505

1506+
def test_find_minimal_target_modules_does_not_error_with_ia3(self, tmp_path):
1507+
# See #2429
1508+
# There is an issue with the compression of the target_modules attribute when using IA³. There, we additionally
1509+
# have the feedforward_modules attribute, which must be subset of target_modules. When target_modules is shrunk,
1510+
# the subset check will fail. This test ensures that this doesn't happen.
1511+
n_layers = MIN_TARGET_MODULES_FOR_OPTIMIZATION + 1
1512+
1513+
class InnerModule(nn.Module):
1514+
def __init__(self):
1515+
super().__init__()
1516+
self.query = nn.Linear(10, 10)
1517+
1518+
class OuterModule(nn.Module):
1519+
def __init__(self):
1520+
super().__init__()
1521+
self.blocks = nn.ModuleList([InnerModule() for _ in range(n_layers)])
1522+
1523+
target_modules = [f"blocks.{i}.query" for i in range(n_layers)]
1524+
feedforward_modules = [f"blocks.{i}.query" for i in range(n_layers)]
1525+
# the subset check happens here
1526+
config = IA3Config(target_modules=target_modules, feedforward_modules=feedforward_modules)
1527+
# the optimization step happens here, after the subset check, so at first we're fine, but we will run into an
1528+
# issue after a save/load roundtrip
1529+
model = get_peft_model(OuterModule(), config)
1530+
model.save_pretrained(tmp_path)
1531+
del model
1532+
1533+
# does not raise
1534+
PeftModel.from_pretrained(OuterModule(), tmp_path)
1535+
15051536

15061537
class TestRankAndAlphaPattern:
15071538
@pytest.fixture

0 commit comments

Comments
 (0)