Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/peft/tuners/tuners_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1722,7 +1722,10 @@ def check_target_module_exists(config, key: str) -> bool | re.Match[str] | None:
# TODO: It's still unclear how empty layers_pattern (None, [], or "") should behave
# For now, empty layers_pattern means any layer pattern is ok
if layers_pattern is None or len(layers_pattern) == 0:
layer_index = re.match(r".*\.[^.]*\.(\d+)\.", key)
# Use non-greedy .*? to match the FIRST digit group, not the last
# This prevents MoE expert indices from being matched instead of layer indices
# e.g., for "model.layers.1.mlp.experts.0.up_proj", we want to match "1" not "0"
layer_index = re.match(r".*?\.[^.]*\.(\d+)\.", key)
else:
layers_pattern = [layers_pattern] if isinstance(layers_pattern, str) else layers_pattern
for pattern in layers_pattern:
Expand Down
10 changes: 10 additions & 0 deletions tests/test_tuners_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@
("blocks.1.bias", ["weight"], [1], ["blocks"], False),
("mlp.blocks.1.weight", ["weight"], [1], ["blocks"], True),
("mlp.blocks.1.bias", ["weight"], [1], ["blocks"], False),
# MoE models: layers_to_transform should match layer index, not expert index
# For "model.layers.1.mlp.experts.0.up_proj", should match layer 1, not expert 0
("model.layers.1.mlp.experts.0.up_proj", ["up_proj"], [1], ["layers"], True),
("model.layers.1.mlp.experts.0.up_proj", ["up_proj"], [0], ["layers"], False), # expert 0, but layer 1
("model.layers.1.mlp.experts.1.up_proj", ["up_proj"], [1], ["layers"], True), # expert 1, layer 1
("model.layers.2.mlp.experts.1.up_proj", ["up_proj"], [1], ["layers"], False), # layer 2, not 1
Comment on lines +133 to +136
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.

These tests don't really test the mentioned bug. E.g. if the key is "model.layers.1.mlp.experts.0.up_proj", with the layers_pattern being ["layers"], there is just a single match, namely "layers.1". "experts.0" would never be matched in this situation, even with the current code from main. In fact, that code path is never reached, as we land here:

else:
layers_pattern = [layers_pattern] if isinstance(layers_pattern, str) else layers_pattern
for pattern in layers_pattern:
layer_index = re.match(rf".*\.{pattern}\.(\d+)\.", key)
if layer_index is not None:
break

At this stage, we still match the last number, but since there is only one match, it doesn't matter. With the change from this PR, however, I would always expect the first number to match to be consistent with the other code path. Therefore, I would suggest to change that regex too, and to update the examples to really test for this.

# MoE without explicit layers_pattern - should still match layer index, not expert index
("model.layers.1.mlp.experts.0.up_proj", ["up_proj"], [1], None, True),
("model.layers.1.mlp.experts.0.up_proj", ["up_proj"], [0], None, False), # must not match expert 0
("model.layers.2.mlp.experts.1.up_proj", ["up_proj"], [1], None, False), # must not match expert 1
]

MAYBE_INCLUDE_ALL_LINEAR_LAYERS_TEST_CASES = [
Expand Down
Loading