Repository navigation
FIX Keep the order of rank_pattern and alpha_pattern keys on save - #3947
Conversation
… the config Co-Authored-By: Claude Opus 5.5 <[email protected]>
chrikrah
left a comment
There was a problem hiding this comment.
@jayzuccarelli 5916eac can go in as is. @BenjaminBossan asked on #3834 whether anything in PEFT depends on the sorted order. You covered the writer there, so I read the other side, every reader of pattern-key order in src/peft. Nothing depends on it.
All 17 get_pattern_key(...) call sites take the first match: LoRA, LoHa, LoKr, DeLoRA, HiRA, WaveFT, EVA, CorDA, ASTRA and utils/hotswap.py. FourierFT, C3A and OSF match their own pattern dicts by hand, and first match wins there too. The only reader that is not first-match is mask_using_rank_pattern in AdaLoRA. It checks the first key for the adapter name, and model.py:312 strips that name from every key at once, so order cannot change the answer.
$ grep -rn "get_pattern_key(" src/peft | grep -v "def get_pattern_key" | wc -l # PR head 5916eac
17
$ grep -rno "next(filter(lambda key: re.match" src/peft/tuners/fourierft/model.py src/peft/tuners/c3a/model.py
src/peft/tuners/fourierft/model.py:70:next(filter(lambda key: re.match
src/peft/tuners/c3a/model.py:66:next(filter(lambda key: re.match
$ grep -n "rank_pattern.items\|break" src/peft/tuners/osf/model.py
94: for pattern, rank in osf_config.rank_pattern.items():
97: break
$ sed -n 507p src/peft/tuners/adalora/layer.py
if self.adapter_name not in next(iter(rank_pattern.keys())):
@BenjaminBossan, does that cover the double-check you asked for, or is there a reader outside PEFT, such as diffusers' adapter loading, that you want checked before this goes in?
|
@jayzuccarelli I haven't reviewed this PR yet, but wondered about a slightly different solution: Right now, you only re-order the top level entries and leave everything else in the existing order. This would lead to changes in the If you think that works, please implement it in a way that makes it easy to add more keys that are exempted from the sorting if ever needed in the future. |
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
@BenjaminBossan that works, and it is the smaller change to the saved file, so I switched to it in 5c17ba6.
For the default config of every config class exported from |
BenjaminBossan
left a comment
There was a problem hiding this comment.
Thanks for implementing the suggestion, I believe this also should help reduce unnecessary diff noise when creating new PEFT checkpoints.
The changes generally look good, I have only a few comments left.
| MIN_EXPECTED_CONFIG_KEYS = {"peft_type"} | ||
|
|
||
| # The adapter_config.json is written with sorted keys, except for the dicts stored under these keys. For those, the | ||
| # order matters: when more than one key matches a module, the first one wins, so the order has to survive a save/load |
There was a problem hiding this comment.
Let's be a bit more explicit in the comment by mentioning rank_pattern and alpha_pattern here. Otherwise, if we later add an item below and don't update the comment, it could state something incorrect.
There was a problem hiding this comment.
Done in 301748f: the comment now names rank_pattern and alpha_pattern as the keys the reason applies to, and asks to extend it when a key is added.
|
|
||
| def test_save_pretrained_sorts_keys_except_pattern_keys(self, tmp_path): | ||
| # The config is written with sorted keys, nested dicts included, but the order of rank_pattern and | ||
| # alpha_pattern is kept, see #3818. |
There was a problem hiding this comment.
Let's quickly mention why it's okay to only test LoRA here.
There was a problem hiding this comment.
Added in 301748f: all configs are saved by the same PeftConfigMixin.save_pretrained, and the methods that support these arguments use the same names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
BenjaminBossan
left a comment
There was a problem hiding this comment.
Thanks for the last update, the PR now LGTM.
Fixes #3818 (approval: #3818 (comment); design per #3834 (comment))
When more than one key in
rank_pattern/alpha_patternmatches a module, the first match in dict order wins.save_pretrainedwrote the config withsort_keys=True, which also sorted the nested pattern dicts, so the winning key could change on reload: a different scaling foralpha_pattern, a size mismatch forrank_pattern.save_pretrainedstill sorts all keys, nested dicts included, except for the dicts stored underrank_patternandalpha_pattern, which keep their order. The exempted keys live inCONFIG_KEYS_WITH_PRESERVED_ORDERinconfig.py, so another key can be added there if needed. Matching is unchanged.Backward compatibility: the load path does not change, so every checkpoint that loads today loads the same way. Checkpoints that were already saved with a changed order are not repaired, since the original order is not on disk. The only difference in new files is the key order inside
rank_patternandalpha_pattern; everything else inadapter_config.jsonis written as before.Tests:
test_overlapping_pattern_keys_order_survives_save_loadinTestRankAndAlphaPattern, forrank_patternandalpha_patternwith keys in non-alphabetical order. Both cases fail on main and pass here.test_save_pretrained_sorts_keys_except_pattern_keysintest_config.py: the pattern keys keep their order, and the rest of the file, including a nestedloftq_config, is the same as withsort_keys=True.pytest tests/test_config.py tests/test_tuners_utils.py tests/test_initialization.py: 2042 passed, 27 skipped, 2 xfailedpytest tests/test_custom_models.py -k "save or hotswap or adalora or pattern": 3185 passed, 343 skippedruff checkandruff format: cleanThat
json.dumpscall is the only place insrc/peftthat writes the config.AI assistance (Claude Code) was used for this PR. I reviewed every changed line and ran the tests above.