Concepts / Freezing Model Layers for Fine-Tuning

Freezing Model Layers for Fine-Tuning

Replacing model.out_head changes the final projection from the vocabulary size of 50,257 to a size set by the task, here num_classes equal to 2.

  • Programming

One Swapped Layer in a Pre-Trained Model

This lesson looks at a single assignment that changes what a pre-trained GPT-2 model is for. The model keeps almost all of its structure. Its internal layers, which build representations of the input text, stay as they are. Only the final layer, the one that turns those internal representations into outputs, is replaced with a new layer built for a different task.

The swap is a single assignment, model.out_head = followed by a constructor call. Nothing else in the model is rewritten. That means every question about what changes comes down to two things: what the new object is, and how it is sized. The most useful habit here is to know which line changed the model and what the model looked like just before that line ran.

What the Original Output Layer Computes

Before you can judge the replacement, you need to know what it replaces. The original model.out_head takes the internal vector produced for each position and maps it to 50,257 values. That number is the size of the vocabulary, which means there is one output value for every token the model can produce.

This makes the original layer a vocabulary projection. It answers the question of which token comes next by scoring every entry in the vocabulary. Each position gets its own vector of 50,257 scores, and the highest-scoring entry is the model's candidate for the next token.

A classification task does not need a score for every vocabulary token. If the goal is to choose between two categories, the model only needs two outputs. The replacement is the change that matches the output size to the task instead of to the vocabulary.

Hidden vector768 values per positionOriginal out_headvocabulary projection50,257 vocabularyscoresone per tokenHidden vector768 values per positionNew out_headLinear from 768 to 22 class scoresone per classprojectsoutputsprojectsoutputs
Before and after the replacement, what is the output layer's size, and which part of the model produces each output? Everything before the output layer is identical in both states.

Read the diagram from the top down. The hidden vector on the left and the hidden vector on the right are the same kind of object, because the layers before the output layer are untouched. Only the last projection and the number of scores it produces have changed. The original projection emits a score for every vocabulary token, while the new projection emits one score for each class.

Reading the Replacement Line

The replacement creates a new torch.nn.Linear layer and stores it in the model's out_head attribute, overwriting the original. The call takes two named arguments. Reading the line means understanding what each argument controls, so the constructor call is the place to start.

python
Notes on the code
  • num_classes is set to 2 just before the replacement
  • model.out_head is the attribute being overwritten
  • in_features takes its value from the embedding dimension in the config
  • out_features takes the number of classes
model.out_headattribute being overwritten=stores the new layer theretorch.nn.Linearconstructor for the newlayerin_features =BASE_CONFIG emb_diminput width, 768 forgpt2-smallout_features =num_classesnumber of outputs, 2 hereargumentargument
Which part of the line sets the input width taken from the configuration, and which part sets the number of output classes?

Predicting the New Weight Shape

A Linear layer stores its weight matrix with one row per output feature and one column per input feature. Use that rule and the two arguments you just read to predict the shape of model.out_head.weight. Then check your answer against the reveal.

What do you think happens?

What is the shape of model.out_head.weight after the replacement, given in_features equal to 768 and out_features equal to 2?

  • 768 by 2, with one row per input feature

  • 2 by 768, with one row per output feature

  • 50,257 by 768, the same as the original layer

  • 2 by 2, because both arguments are small

Reveal answer
Answer:

2 by 768: two rows, one per output feature (num_classes), and 768 columns, one per input feature (the embedding dimension).

The weight matrix has one row per output feature and one column per input feature. Because out_features is 2 and in_features is 768, the matrix has 2 rows and 768 columns. The original layer, with 50,257 outputs and 768 inputs, had a weight of shape 50,257 by 768. Only the row count changed.

row 0weights for class 0columns 0 to 767one column per embeddingvaluerow 1weights for class 1768 entries768 entries
Which dimension of the weight matrix matches the number of outputs, and which matches the incoming embedding size?
Worked example

Sizing the Replacement Layer

The embedding dimension for gpt2-small is 768 and num_classes is 2. Work out the shape of the new weight and the shape of the output for a batch of one sequence with 5 positions.

  1. 1
    Identify in_features

    in_features comes from BASE_CONFIG['emb_dim'], which is 768 for gpt2-small.

  2. 2
    Identify out_features

    out_features is num_classes, which is set to 2.

  3. 3
    Apply the weight rule

    The weight has one row per output feature and one column per input feature, so the shape is 2 rows by 768 columns, written as W in R2 x 768.

  4. 4
    Apply the output rule

    The layer is applied to the vector at each position. Five positions each produce 2 outputs, so the output has one row of 2 values per position.

Result

The weight shape is 2 by 768. The output for one sequence of 5 positions has shape 5 by 2, with 2 class scores per position.

python
Output (expected)
torch.Size([2, 768])
Notes on the code
  • The first dimension is out_features, the number of classes
  • The second dimension is in_features, the embedding width

Keeping the Width in the Configuration

The number 768 could have been typed directly into the call. The source instead reads it from BASE_CONFIG['emb_dim']. The stated reason is generality: the same replacement line then works for larger GPT-2 variants, because each variant's embedding dimension is stored in the configuration rather than written into the line itself.

python
Notes on the code
  • The config-based version reads the width from the settings for whichever model is loaded
  • The hardcoded version fixes the width to the value for gpt2-small

The hardcoded version is not wrong for gpt2-small. It becomes wrong only when the same code is pointed at a model with a different embedding dimension. The config-based version keeps the replacement line unchanged across model sizes, and the size-specific information lives in one place.

Finding Where a Mismatch Shows Up

When the output layer is replaced incorrectly, the error usually does not appear on the replacement line itself. The line is accepted, because creating a Linear layer with those arguments is valid. The problem shows up later, when data first passes through the new layer or when a shape is inspected. The useful debugging question is therefore not only what the line says but where its result is first used.

Trace from the replacement to the first place a shape is checked, and compare what you see there with the shape rule you derived earlier. If the input width of the new layer does not match the width of the vector arriving from the earlier layers, the first mismatch appears at the first forward pass through the new layer. If the output has a size you did not expect, the mismatch appears when you inspect the output or the weight shape.

  • Common mistake

    Swapping in_features and out_features

    What it looks like
    torch.nn.Linear(in_features=num_classes, out_features=BASE_CONFIG['emb_dim'])
    Why it fails

    The input width must match the embedding vector coming out of the earlier layers, and the output width must match the number of classes. Swapping them makes the input width wrong, so the mismatch appears on the first forward pass.

    Do this instead

    Keep in_features set to the embedding dimension and out_features set to num_classes.

  • Common mistake

    Expecting the replacement line to raise the error

    What it looks like

    Running the replacement line, seeing no error, and assuming the shapes are correct

    Why it fails

    The constructor accepts the arguments without checking them against the data that will later flow through the layer.

    Do this instead

    Pass a sample input through the model and check the output shape, then confirm the weight shape matches the rule.

  • Common mistake

    Keeping the vocabulary size as the output width

    What it looks like

    Leaving out_features at 50,257 when the task has two classes

    Why it fails

    The original layer scored every vocabulary token. A classifier that needs two categories does not need that many outputs, so the output width no longer matches the task.

    Do this instead

    Set out_features to num_classes so the number of outputs matches the task.

  • Common mistake

    Hardcoding the embedding width

    What it looks like
    in_features=768 written directly into the replacement line
    Why it fails

    The line then only works for gpt2-small. A larger GPT-2 variant has a different embedding dimension stored in the configuration, and the hardcoded value no longer matches it.

    Do this instead

    Read the width from BASE_CONFIG['emb_dim'] so the line matches whichever model is loaded.

Practice: Sizing a New Output Head

EASY

A pre-trained model's configuration sets emb_dim to 768. You want to adapt the model to a task with four categories. Write the replacement line for model.out_head using BASE_CONFIG['emb_dim'] for the input width, then state the shape of the new weight matrix and where the first mismatch would appear if the input width were wrong.

Hints
  • in_features should be the embedding dimension, read from BASE_CONFIG['emb_dim'].
  • out_features should be the number of categories, so set num_classes to 4 before the replacement.
  • A Linear weight has one row per output feature and one column per input feature.
MEDIUM

Without changing the replacement line, explain what happens to the new weight's shape if the configuration is switched to a model whose emb_dim is different from 768, and identify the first place in the forward pass where a shape check would fail if the input width no longer matched.

Hints
  • Think about what in_features is set to when the line reads from the config.
  • Think about which layer receives the vector first after the earlier layers run.

Key Takeaways

  • The swap replaces only model.out_head, a single assignment that creates a new torch.nn.Linear layer, leaving the rest of the network unchanged.
  • The original output layer maps each position's hidden vector to 50,257 scores, one per vocabulary token. The replacement maps it to num_classes scores, which is 2 in this example.
  • in_features is the embedding dimension read from BASE_CONFIG['emb_dim'] (768 for gpt2-small), and out_features is num_classes. The weight has shape 2 by 768, with one row per output and one column per input.
  • Reading the width from the configuration keeps the same line usable across GPT-2 sizes. Shape errors usually surface at the first forward pass through the new layer, not on the replacement line, and the new layer must be trained for the task.

Related concepts

More concepts starting with F