The current state of the PEFT docs is not one of structure and I was constantly annoyed that whenever I wanted to change something there were several places that needed touching and they all felt disconnected. So this is my attempt at structuring the docs. Some of these ideas are quite old (discussed in 01/2025) but are still valid. I've removed most of the code guides without replacement. That's not ideal, I think we should have code examples but I'm think they should be method-focused. Maybe one general example of a training workflow is sufficient because most methods follow the same scheme. All details from the method guides (prompting, lora, oft/boft, etc.) are now integrated into the respective method pages instead. I would have hesitated to do this if these guides would have integrated information about the adapters but they didn't. I think it makes a lot more sense to have one place for each method to gather examples/tips/recommendations and that is now the `package_refernce/<method>` page. This page now also hosts a small space that shows the MetaMathQA (and potentially other) benchmark results highlighted for that method. I've moved the LoRA initializations to `package_reference/lora#Initialization` and converted the init methods to `<hfoption>`-tags. This collapses them to a list but may reduce searchability through the document - at least firefox is not able to search 'through' the option tabs. This also doesn't make them appear in the ToC and people specifically searching for, say, PiSSA won't find it directly. I think that's OK though, since the search is able to locate it. The quicktour is a bit more detailed about what happens under the hood (quick doesn't have to mean simplistic) and includes some new visualizations. I hope that we can integrate more visualizations in the future where it makes sense. * Remove PEFT method space + front page buttons The space was not that useful anymore since most methods are compatible with most models. The front page buttons are, at least temporarily, with the exception of the quicktour and method overview buttons. I like the visuals but there should only be elements that are useful. --------- Co-authored-by: Benjamin Bossan <BenjaminBossan@users.noreply.github.com> Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
5.4 KiB
Memory Efficient Training
🤗 PEFT makes fine-tuning parameter efficient, but not automatically memory efficient. This overview collects tips for cutting training memory and links to the detailed guides.
Tip
Always consider the basics of choosing a smaller base model, smaller batch size or shorter sequence length to lower your memory usage.
Training memory overview
Let's dissect the distribution of training memory so we can reason about potential countermeasures. We will use a large language-model trained with the Adam optimizer as an example. When doing full fine-tuning, we will have the following positions taking up memory:
- base model parameters: the memory consumption highly depends on the chosen
dtype. The less bits per parameter (16 for float16), the less memory this will take up. A 1B model in float16 (16 bit/2 byte per parameter) will roughly take1e9 × 2 byte = 1.863 GiBof memory. - all trainable base model parameters ×3 for gradients (1×) and Adam optimizer states (2×), therefore 5.59GB of memory
- memory for the intermediate activations between layers, these are hard to predict but mostly depend on the used compute dtype and sequence length / batch size.
A smaller base model or a using a smaller compute dtype will reduce all points while using shorter sequences or smaller batches mainly affects gradients and activation memory. Employing PEFT methods will reduce the number of trainable parameters and therefore significantly reduce both gradients and optimizer state, saving a lot of memory.
Choosing the right method
Not every PEFT method is built equally and some formulations are easier to build in a memory efficient manner. If you are on a memory budget it makes sense to check out the PEFT method comparison suite and filter for maximum accelerator memory usage. Average accelerator memory usage can be fairly equal across methods but not every method scales equally with activations and sequence length; some methods are more prone to memory spikes than others.
Consider using trainable tokens when targeting large layers like language modeling heads or embedding layers to fine-tune specific tokens.
Quantization
Quantization is one of the best ways to reduce memory consumption of the base model and will, depending on the employed quantization, also reduce activation memory. Since the PEFT methods will only take up a small portion of the total number of parameters, PEFT defaults to use a higher precision than the base model. This can also have the effect that adapters can mitigate some of the quality loss incurred by quantization methods. Read the PEFT quantization guide.
Compilation
The models we train are composed of operations like matrix multiplications, sums and assignments where each operation produces a new result and, subsequently, needs to take up memory. If those intermediate results are not needed we can fuse these operations and save up on memory. This is just one of many optimizations that torch.compile can do for you, so check out the PEFT torch.compile guide.
Gradient Checkpointing
You can trade memory with computation by only saving every nth gradient between layers and computing the rest on the fly. Check out the gradient checkpointing documentation of Transformers to learn more.
Note
When not using Diffusers or Transformers you may need to implement your own gradient checkpointing logic, depending on the training framework that you are using.
Chunked NLL loss
Using NLLLoss is very common when training language models (or classification tasks). You allocate a matrix of size batch × sequence × vocabulary. With particularly long sequences or vocabularies this can get expensive fast.
When using TRL you can either use the Liger kernel integration or use Chunked NLLLoss. The latter will split the sequence in chunks of size 256 to keep the maximum memory consumption constant.
In case the default chunk size is not optimal for your setting, look in the original TRL PR for more information on how to tune the chunk size.
