Applying LoRA to GPT Models
LoRA is designed so that most of those original values stay exactly as they were, while a small number of new values are learned on top of them.
Deciding Which Numbers Can Change
Fine-tuning with LoRA does not begin by changing the pretrained model. It begins by deciding which numbers are allowed to change. A pretrained GPT model already holds a large set of learned parameters. LoRA is designed so that most of those original values stay exactly as they were, while a small number of new values are learned on top of them.
This article follows that decision in order. You will see how the original parameters are frozen, why the freeze must happen before any LoRA layers exist, what a LinearWithLoRA layer is made of, and how to use trainable counts as checkpoints. Those checkpoints let you find the exact step where something went wrong.
Freezing as a Per-Parameter Flag
Freezing sets requires_grad to False on every original parameter, so none of the pretrained weights are updated during fine-tuning.
The important detail is that the flag belongs to each individual parameter, not to the model as a whole. The model is simply a collection of parameters, and freezing is a per-parameter setting that you apply by walking through that collection. This is why the freezing step is a loop over model.parameters() rather than a single switch on the model object.
The result you want is a clear, measurable checkpoint. Before freezing, you count how many parameters are trainable. You freeze them. Then you count again. The difference between those two counts is the visible effect of the freeze.
The diagram shows the key split. The original parameters move from trainable to frozen. The LoRA parameters are not part of the original model, so they are created later and remain trainable. The freeze only affects the parameters that exist when the loop runs.
The Freeze Loop in Practice
The lesson trace does three things in order. It counts the trainable parameters, runs a loop that sets requires_grad to False on every parameter, and counts again. The sketch below follows that structure. It is an illustrative reconstruction of the steps described in the lesson, not a verbatim copy of the trace.
- The first count measures the original model before any change.
- The loop visits each parameter individually, which is why the flag is set per parameter.
- The second count is the checkpoint that should show no trainable parameters from the original model.
What do you think happens?
Before you read further, predict: after the loop sets requires_grad to False on every parameter of the original model, how many parameters are still trainable?
Reveal answer
Zero.
The loop reaches every parameter of the original model and sets its requires_grad flag to False. No LoRA layers have been added yet, so there are no other trainable parameters to count at this checkpoint.
Notice what the prediction is asking. The counting line looks for parameters whose flag is True. The loop changes each parameter's flag. Reading both lines together tells you what the trace is measuring at each point.
Why the Freeze Comes First
The order of the two steps is deliberate. The freeze has to come before the LinearWithLoRA replacement, because the new LoRA layers are meant to be trained and must not be frozen. The source describes the reasoning this way: freeze the original weights first, then add the small trainable pieces.
Tracing a freeze that runs after the replacement
A model first has its Linear layers replaced with LinearWithLoRA layers. Then the freeze loop runs over model.parameters(). What remains trainable at the end?
- 1Replace the layers
Each targeted Linear layer becomes a LinearWithLoRA wrapper containing a linear child with the original weights and a lora child that is a LoRALayer.
- 2Run the freeze loop
The loop visits every parameter in the collection. That collection now includes the parameters inside the new lora children, so those flags are also set to False.
- 3Check the result
Compare this with the correct order. There the freeze ran first, so the LoRA parameters were created afterward with their flags still True.
No parameters remain trainable. Fine-tuning would have nothing to update, which is why the freeze must be applied before the LoRA layers are added.
The two counts in the trace measure different things. The first count is about the original model. The count after the replacement is about the new LoRA parameters. Mixing up these two measurements is one of the most common ways to misread the output.
Anatomy of a LinearWithLoRA Layer
A LinearWithLoRA layer is not a brand new kind of layer that replaces the original one in a single step. It is a wrapper with two parts. The first part is the original Linear layer, which has been set to nontrainable. The second part is a new LoRA layer, which is trainable and is the piece that fine-tuning will update. Together, the two parts make the upgraded layer.
When you look at the printed model architecture after the replacement, each upgraded entry shows the same structure. Every LinearWithLoRA entry has a linear child that holds the original weights and a lora child that is a LoRALayer. This printout is the second place to check when something seems wrong.
- The output projection inside the attention block appears as LinearWithLoRA.
- The two linear layers of the feed-forward block appear as LinearWithLoRA.
- The output head appears as LinearWithLoRA.
- The GELU activation, the dropout layers, and the layer norms are printed without a LoRA child, because they were not replaced.
Rank and Alpha in the Replacement
The replacement call takes two settings, rank and alpha. The source uses a rank of 16 and an alpha of 16, and it describes both values as good default choices. The rank controls the size of the LoRA layers. Raising the rank increases the number of trainable parameters, because the LoRA layers grow with it.
Alpha is commonly set to half the rank, double the rank, or equal to the rank. Changing alpha is a different kind of choice from changing rank, and it is not the setting that most directly controls how many parameters are trainable.
Common Mistakes With the Freeze and Replacement
Common mistake Freezing the model after the LinearWithLoRA replacement
What it looks likeReplacing the Linear layers first, then running the freeze loop over model.parameters().
Why it failsThe loop also reaches the new LoRA parameters, which are meant to be trained, so none remain trainable.
Do this insteadRun the freeze first, then add the LinearWithLoRA layers so the LoRA parameters are created afterward.
Common mistake Treating the freeze as a single switch on the model object
What it looks likeExpecting one assignment on the model to stop all updates.
Why it failsThe flag belongs to each parameter, so the freeze is applied by walking through
model.parameters().Do this insteadLoop over the parameters and set requires_grad on each one.
Common mistake Mixing up the two trainable counts
What it looks likeReading the count after the replacement as if it described the original model.
Why it failsThe first count is about the original model, while the count after the replacement is about the new LoRA parameters.
Do this insteadLabel each checkpoint with what it measures before comparing numbers.
Common mistake Assuming every layer in the model is replaced; GELU, dropout, and layer norms appear without a LoRA child in the printed architecture
Locating a Faulty Checkpoint
Verifying a freeze means treating each count as a checkpoint. The first checkpoint is taken before the loop. The second is taken after the freeze and before the replacement. The third is taken after the replacement. If one checkpoint disagrees with what you expected, the step just before it is the place to look.
Suppose the second checkpoint shows parameters still trainable after the loop. The problem lies in the freeze step, since the loop did not set every original parameter to False. Suppose instead the second checkpoint is zero but the third shows no trainable parameters at all. The replacement then either happened before the freeze or the LoRA layers were frozen afterward. Comparing each observed count with its expected value narrows the search to one step.
Diagnosing a Run from Its Counts
A run shows a nonzero trainable count at the second checkpoint, and the third checkpoint's count matches only the LoRA parameters. Which step is the most likely cause, and why does the third count still look correct?
Hints
- Ask which checkpoint measures the original model and which measures the new LoRA parameters.
- The freeze loop is the step that changes the
requires_gradflag on original parameters. - If the third count is correct, the LoRA layers were created and left trainable, so the problem is confined to the original parameters.
Key Takeaways
- Freezing sets
requires_gradto False on each original parameter through a loop overmodel.parameters(), so the pretrained weights are not updated during fine-tuning. - The freeze must run before the LinearWithLoRA replacement, because running it afterward would also freeze the new LoRA parameters that are meant to be trained.
- A LinearWithLoRA layer wraps a frozen original Linear layer together with a trainable LoRA layer, and the printed architecture shows this structure.
- Trainable counts act as checkpoints: comparing each observed count with its expected value shows which step, the freeze or the replacement, went wrong.