- 
                Notifications
    You must be signed in to change notification settings 
- Fork 2.1k
Add Orthogonal Subspace Fine-Tuning (OSF) Tuner for Parameter-Efficient Continual Learning #2685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Orthogonal Subspace Fine-Tuning (OSF) Tuner for Parameter-Efficient Continual Learning #2685
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Thanks for the thorough update, that's a good step forward.
A minor nit: Several files are missing the copyright notice, please make sure to include them in new source files (also make sure that they are not outdated, i.e. include the current year).
I like that you already implemented several (custom) tests, I think that's super helpful. Let's also add some tests to test_decoder_models.py and test_encoder_decoder_models.py similar to the test in test_custom_models.py when you think the implementation can move forward in testing. Let's move the skips for convolutions to testing_common.py, there are already similar exceptions in place.
Two bigger topics:
- ModelWithOSFseems to re-invent PEFT functionality inside PEFT, specifically the layer targeting + replacement portion. Let's streamline OSF with other tuners, i.e. have implementations for specific layers and by implementing- inject_adapter,- _create_new_moduleand- _create_and_replaceto make it easier to branch out to other layer types / quantizations. The LoRA implementation maybe helpful, e.g.- peft.tuners.lora.layers.LoraLayercontains specific layers for- Linearand- Conv*dspecifics (no need to implement Conv now, of course). I can see that this conflicts with using a dict for specifying the top-k ranks per module. How about using- target_modulesand a singular value for the topk rank (e.g.,- config.topk_r) which can default to- None(-> uses 50% of min(shape)). Every targeted module gets that topk rank or an automatic 50% one. We could also add something like- rank_patternfrom LoRA to define exceptions (see- lora.model.py->- _create_and_replace). WDYT?
 Example config:
OSFConfig(
  target_modules='all-linear',
  topk_r=None,
  rank_pattern={
    'q_proj': 10,
  }
)- It's not possible to use more than one adapter of OSF since the base model is modified and we therefore cannot switch between adapters (could be handy in pipeline scenarios where one model is used at several places with different adapters, for example). I left a comment at decompose_weight_matrixto discuss this.
Once we're done with the general implementation I think it'd be super if we could add an experiment to the MetaMathQA comparison suite so that we can compare OSF directly to other implementations.
| 
 Awesome will definitely evaluate our method once the implementation is complete to benchmark OSF against other methods in PEFT. | 
| 
 @githubnemo great suggestion in response to the first bigger topic raised I have implemented the minimal PEFT integration changes: What we implemented: 
 Scope decisions we made: 
 Key files changed: 
 These changes integrate the OSF method modularly into PEFT. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed feedback and your changes.
I think that the re-structuring of OSFModel is almost complete and most of the comments are rather minor. As far as I can see the adhoc ModelWithOSF is replaced by OSFModel and OSFLayer and can be removed - good progress!
I think this is a good time remove outdated code, to merge with main, run make style and run the tests to see if there's still something going horribly wrong.
Let's discuss whether we want to implement the importance score now or leave it up for implementation later. If I'm not mistaken I think that the importance score can technically be added later since it would compute the effective rank of layers based on two new hyper-parameters, so in that sense it is modular. Since it is quite a crucial part of the paper and is touted to improve multi-task learning (arguably one of the big selling points of OSF) I wonder if it should be included from the get-go. What's your opinion on that?
Regardless, I think we can a MetaMathQA experiment rather soon and check if there are major problems with memory consumption or runtime.
| - Complete continual learning scenario with multiple tasks | ||
| - Demonstration of OSF's catastrophic forgetting prevention | ||
| - Configuration examples (target_modules, effective_rank, rank_pattern) | ||
| - Performance comparison with baseline methods | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the performance comparison with baseline methods - at least for single tasks - is best done in the PEFT method comparison (MetaMathQA). Of course, feel free to provide a comparison with methods for support multi-task learning if it fits into the example without too much effort.
        
          
                src/peft/utils/osf_utils.py
              
                Outdated
          
        
      | svd = { | ||
| "U_high": U[:, :k].contiguous().detach().to(device=device_local, dtype=orig_dtype), | ||
| "S_high": S[:k].contiguous().detach().to(device=device_local, dtype=orig_dtype), | ||
| "V_high": Vt[:k, :].contiguous().detach().to(device=device_local, dtype=orig_dtype), | ||
| "U_low": nn.Parameter(U[:, k:].contiguous().detach().to(device=device_local, dtype=orig_dtype)), | ||
| "S_low": nn.Parameter(S[k:].contiguous().detach().to(device=device_local, dtype=orig_dtype)), | ||
| "V_low": nn.Parameter(Vt[k:, :].contiguous().detach().to(device=device_local, dtype=orig_dtype)), | ||
| "rank_high": k, | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the detailed explanation!
The sequential dependency of later added adapters to previous adapters removes a lot of the convenience gained by being able to remove individual adapters, I agree.
I'm OK with not implementing this.
| @githubnemo added MetaMathQA experiment results. OSF achieves the highest accuracy at 55.72% among all PEFT methods in the benchmark! 😊 Top results for comparison: 
 Memory consumption and runtime look okay thus far as well. | 
| @NikhilNayak-debug very nice results! :) Is this ready for review from your side? If so, could you merge main and resolve the merge conflicts? This saves one review cycle. | 
2d435a5    to
    372a375      
    Compare
  
    | @githubnemo thank you. I have rebased the branch on top of the latest upstream main and resolved the conflicts. This is ready for review now. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late reply, I was at a conference.
The changes look very good! There was quite a large PR merged in the mean time that refactored a good portion of the BaseTuner infrastructure (#2771) which means that you need a lot less code now - I hope I highlighted all occurrences.
I'm currently in the process of reproducing the MetaMathQA results you posted. One thing I noticed is that there are more layers targeted and the default effective rank (min(shape) // 2) is used which is using way more parameters than other methods. While it is certainly good to see that OSF is better than full fine-tuning it would be a fairer comparison to match the trainable parameter counts of the other methods.
372a375    to
    00073fe      
    Compare
  
    | 
 @githubnemo no worries thank you so much for the comments. I have rebased and simplified the code as you suggested. I also reran the experiments using rank 128 applied to the MLP and attention matrices, the results are available in  We do need a higher rank with OSF because the most important directions are intentionally fixed to reduce forgetting. Since learning occurs in the lower-singular-value subspace, a higher trainable rank is required to capture meaningful updates. The upside is that this approach retains performance on previous tasks, unlike LoRA for example which updates those high importance directions and tends to cause more forgetting. | 
| Thanks for the updated experiments and the fixes! I think this can be merged after these few nits are fixed. It would be good to have a runnable example but if you want we can merge this first and do the example in a separate PR, whichever suits you best. It could also be a chance to showcase the continual learning benefits of OSF to contrast the weaker parameter efficiency. Before merging, let's remove the MetaMathQA results since we're running them on our runner after merging anyway. Don't forget to run  | 
| 
 @githubnemo thanks! I will add a small runnable OSF continual-learning example in a follow‑up PR (sequential tasks, increasing effective_rank using model.unload(), and with a Full Finetuning baseline for contrast). For this PR: I removed the MetaMathQA results and the large MetaMathQA experiment as requested, keeping only the rank 128 experiment. | 
| @githubnemo this is ready for review again. Let me know if there are any other changes that need to be made! | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice :) Only a few comments left.
Please check the CI as there seem to be some errors left.
rebasing to make use of simplified basetuner implementation and adding more experiment results fixing style, quality, etc in the code Make style fixing CI and other test cases
89c3113    to
    2418375      
    Compare
  
    | @githubnemo this is ready for review. There are 36 test cases failing, but they are also failing in the upstream main branch so they are not related to the OSF changes. All CI related issues should be addressed now. | 
| 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. | 
| @githubnemo could you start the workflow again, fixed a couple of remaining test case errors! | 
| Sure thing. You can also run this using  | 
| 
 @githubnemo okay yeah OSF related tests are passing locally and I don't see any remaining failing tests in the CI. The one failing check seems to be unrelated. How do we proceed from here? | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! This looks good to me :)
Let's merge this and work on the example in a new PR. I think this is a nice addition, thanks again for your continued efforts!
Summary
This PR adds a new parameter-efficient fine-tuning method called Orthogonal Subspace Fine-Tuning (OSF) to the PEFT library. OSF enables continual learning in LLMs by freezing the high-rank subspace of weight matrices and fine-tuning only the low-rank directions. This approach constrains updates to be orthogonal to previously important directions, thereby mitigating catastrophic forgetting without increasing parameter count.
Issue for this PR on PEFT repository
Tracked in PEFT Issue #2648
Key Features
Implements a new
OSFConfig,OSFModel, and tuner class undersrc/peft/tuners/osf/following PEFT's standard APIIntegrates seamlessly with the
get_peft_modelAPI:Adds utility functions for:
Automatically enforces orthogonality constraints during training without requiring optimizer wrapping
Will include tests for saving, loading, and applying the OSF adapter in
tests/test_custom_models.pyExports relevant modules at the package level for easier use with other PEFT components
Notes
Background
This implementation is based on the method described in our paper:
Sculpting Subspaces: Constrained Full Fine-Tuning in LLMs for Continual Learning
Paper on arXiv · Project Repository