Concepts / Applying LoRA to GPT Models

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.

  • Programming

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

Definition

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.

Original parameterrequires_grad TrueOriginal parameterrequires_grad FalseNew LoRA parameterrequires_grad Truefreeze loop
What changes in each original parameter's trainable state before and after the freeze, and which parameters remain updatable?

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.

python
Notes on the code
  • 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
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.

Worked example

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?

  1. 1
    Replace 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.

  2. 2
    Run 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.

  3. 3
    Check 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.

Result

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.

LinearWithLoRAupgraded layerlinearoriginal Linear,requires_grad FalseloraLoRALayer, trainablecontainscontains
Which child holds the frozen original weights, and which child holds the trainable LoRA 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 like

    Replacing the Linear layers first, then running the freeze loop over model.parameters().

    Why it fails

    The loop also reaches the new LoRA parameters, which are meant to be trained, so none remain trainable.

    Do this instead

    Run 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 like

    Expecting one assignment on the model to stop all updates.

    Why it fails

    The flag belongs to each parameter, so the freeze is applied by walking through model.parameters().

    Do this instead

    Loop over the parameters and set requires_grad on each one.

  • Common mistake

    Mixing up the two trainable counts

    What it looks like

    Reading the count after the replacement as if it described the original model.

    Why it fails

    The first count is about the original model, while the count after the replacement is about the new LoRA parameters.

    Do this instead

    Label 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.

Count trainableparameterscheckpoint 1Set requires_grad toFalseloop overmodel.parameters()Count trainableparameterscheckpoint 2, expected 0Replace Linear withLinearWithLoRArank and alpha setCount trainableparameterscheckpoint 3, LoRAparametersMatches expected?Continue tofine-tuningInspect the last stepfreeze or replacementyesno
At which step does the observed trainable count diverge from the expected count, pointing to the faulty freeze or replacement?

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

MEDIUM

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_grad flag 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_grad to False on each original parameter through a loop over model.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.

Related concepts

More concepts starting with A