When does a PEFT config actually need `modules_to_save` set, and what goes in it?
modules_to_save trains listed modules fully alongside the LoRA adapter, typically embed_tokens and lm_head when the tokenizer gains new tokens whose random rows LoRA cannot reach.
Imagine your model is a giant library with a fixed catalogue of book topics. LoRA is a small set of margin notes you can scribble on existing pages to adjust how they read. Then one day you add brand new book categories to the library, categories the original catalogue never had. Those new categories need fresh full pages in the index, not margin notes. modules_to_save is the list of pages you mark as needing fresh full pages instead of margin notes. The most common case is adding new words to the model's vocabulary; the input lookup table and the output prediction layer both need new full pages for the new words to make sense.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Most LoRA fine-tuning configs have one field everyone touches and several fields that sit at defaults forever. modules_to_save is one of the latter, until you try to extend the tokenizer and discover that the new tokens stubbornly refuse to learn. Then it becomes the single most important field in the config.
The question is asking when you actually need this field set and what goes in it. The answer is vocabulary extension, with embed_tokens and lm_head as the contents, and the reasoning is mechanical: new token rows are random, LoRA cannot rescue random initialization with a low-rank update, and only full rank training of the affected modules can teach the new tokens.
This deep dive walks through what modules_to_save semantically does, why vocabulary extension specifically triggers the need for it, why LoRA fundamentally cannot train new token rows, how the field relates to target_modules in the same config, the tied weights situation, and the memory cost of using it carelessly. By the end the field should not look like a mystery default but like a precise tool for a specific capability extension scenario.
What modules_to_save actually does
The PEFT library wraps a base model with adapter modules controlled by a LoraConfig (or similar config for other adapter types). Two top-level fields determine what happens to each module in the base model.
target_modules
This list names modules to wrap with LoRA adapters. For each named module, PEFT replaces the original linear with a LoRA-wrapped version that holds the original weight frozen and adds the trainable adapter pair A and B. Forward pass computes the base matmul plus the scaled adapter product. Backward pass updates only A and B.
For a typical Llama-family model, target_modules might be ["q_proj", "k_proj", "v_proj", "o_proj"] or include the MLP linears too. The exact choice affects which weight matrices receive a low-rank trainable correction.
modules_to_save
This list names modules to train fully, with no adapter wrapping and no frozen base. For each named module, PEFT keeps the original module in place and unfreezes its weights so the optimizer can update them directly at full rank and full precision.
The two lists are disjoint by purpose. A module is either wrapped with LoRA (target_modules) or trained fully (modules_to_save), but not both. The bulk of the model stays frozen; LoRA-wrapped modules get a low-rank trainable correction; modules_to_save modules get full rank trainable updates.
How the trainer treats the lists
At training time, the optimizer's parameter list includes the LoRA adapter parameters (from target_modules) and the full weights of modules_to_save modules. Gradient flow is handled automatically by PyTorch's autograd: any parameter marked requires_grad=True gets gradients, and modules_to_save's job is to flip that flag on the listed modules.
At save time, the resulting adapter checkpoint contains both the LoRA A and B matrices for each wrapped module and the full weights of each modules_to_save module. This is why loading a PEFT checkpoint with modules_to_save entries requires the same base architecture; the saved full weights replace the base's defaults for those modules.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- Hugging Face PEFT LoraConfig exposes modules_to_save as a top-level field used in Llama 4 fine-tuning recipes when adding domain markers to the tokenizer.
- Axolotl recipes for multilingual continued training of Mistral list embed_tokens and lm_head in modules_to_save when extending the tokenizer for new scripts.
What an interviewer would ask next. Try answering before peeking at the approach.
QWhy is LoRA fundamentally unable to teach new vocabulary tokens added after pretraining?
Walk through how LoRA adds a low-rank delta to the original weight; the new token rows are random, and a low-rank correction on random noise produces noise, not a useful representation.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Treating modules_to_save as a list of LoRA target modules. It is the opposite: these modules bypass LoRA entirely and train at full precision in full rank.
60 second bullets to scan on the way to the call.
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.