Concepts / Implementing Transformer Blocks in GPT Architecture

Implementing Transformer Blocks in GPT Architecture

A TransformerBlock is a component that processes input data, and in this source's example it is called as output = block(x).

  • Programming

Predicting the Output Shape

This article is about running a TransformerBlock on sample data and checking what comes back. The approach is to look at the code first, predict what it should produce, and then confirm the prediction from the real output. Each claim here can be checked by running the same few lines yourself.

What do you think happens?

You create an input tensor x with shape [2, 4, 768] and pass it through a TransformerBlock with output = block(x). What shape do you expect output to have?

  • The same shape as x, torch.Size([2, 4, 768])

  • A smaller shape, with fewer tokens per sequence

  • A different embedding size than 768

  • A single flat vector with no batch dimension

Reveal answer
Answer:

torch.Size([2, 4, 768])

The source reports that the printed output shape matches the input shape. The TransformerBlock maintains the input shape, so the batch size, the number of tokens, and the embedding dimension all stay the same.

Reading the Three Dimensions

The sample input is built with the line x = torch.rand(2, 4, 768). Its shape is described as [batch_size, num_tokens, emb_dim]. Read in that order, the three numbers mean: 2 sequences in the batch, 4 tokens in each sequence, and 768 values for each token. Each dimension answers a different question about the data.

batch_size2 sequencesnum_tokens4 tokens per sequenceemb_dim768 values per token
Which axis of the input tensor indexes the sample in the batch, which indexes the token position within a sequence, and which holds the embedding vector for each token?

The last dimension is the embedding dimension. This is why the source uses 768 as the embedding size for this configuration. Each of the four token positions in each sequence is represented by one vector of 768 values. If you change only one number, you change only one property of the data: the batch size changes how many sequences you process, the token count changes how long each sequence is, and the embedding size changes how many values describe each token.

DimensionPosition in shapeValue in the sampleWhat it counts
batch_sizeFirst2Sequences in the batch
num_tokensSecond4Token positions in each sequence
emb_dimThird (last)768Values that describe each token

Meaning of each dimension in the sample input shape [2, 4, 768]

Running the Call and Reading Both Shapes

The snippet is short. It creates a random input tensor, passes it through a TransformerBlock, and prints the shape of both the input and the output. The source gives the configuration as GPT_CONFIG_124M and assumes that both the TransformerBlock class and this configuration already exist. This step only uses them.

Code walkthrough
import torch

block = TransformerBlock(GPT_CONFIG_124M)
x = torch.rand(2, 4, 768)
output = block(x)

print("Input shape:", x.shape)
print("Output shape:", output.shape)
  1. 1
    Stage one creates the input: x holds random values with shape 2 by 4 by 768.
  2. 2
    Stage two constructs the block from GPT_CONFIG_124M.
  3. 3
    Stage three calls the block on x and stores the result in output.
  4. 4
    Stage four prints the shape of x and the shape of output, one per line.
  • The import line brings in torch, the library that provides tensors.
  • The block line constructs the TransformerBlock from the configuration. The source assumes this class and GPT_CONFIG_124M already exist.
  • The x line builds the sample input with shape [batch_size, num_tokens, emb_dim] = [2, 4, 768].
  • The output line is the single call that matters: the input x enters block(x) and an output comes back.
  • The two print lines show both shapes so you can compare them directly.
Output
Input shape: torch.Size([2, 4, 768])
Output shape: torch.Size([2, 4, 768])

The random values in x will differ between runs, but the printed shapes are the thing to check. The source reports both shapes as torch.Size([2, 4, 768]), so the printed output confirms that the block keeps the input shape. The source describes the block only as a component that processes input data, so this step does not make claims about what happens inside it.

Shape Before and After the Call

A shape check is a comparison, not a guess. Printing both shapes side by side gives two values you can compare position by position: the first number with the first, the second with the second, and the third with the third. When every position matches, the block has preserved the shape.

x (input)torch.Size([2, 4, 768])outputtorch.Size([2, 4, 768])block(x)
After the block processes x, do the batch_size, num_tokens, and emb_dim values stay the same, and how does the shape before the call compare to the shape after it?

In the source's run, the two boxes carry identical shapes. The batch size is still 2, the token count is still 4, and the embedding dimension is still 768. The diagram shows only the single call and its two shapes; it does not claim anything about the steps between them.

Locating Where a Mismatch Would Appear

When a shape check does not match what you expect, the useful question is where in the code the result diverged. In this snippet there are four stages: the input is created, the block is constructed, the block is called, and the shapes are printed. The printed lines are the last stage, so a mismatch that shows up between the input and output lines points to the call output = block(x) or to the block itself, not to the print statements.

The pipeline shows the order of the four stages. The shape check is most informative at the last stage, where both shapes are printed. Use the printed values to narrow the search before you change any code. If the two lines differ, the call output = block(x) is the first place to look, because that single line is where the block receives x and returns output.

Mistakes Beginners Make When Checking Shapes

  • Common mistake

    Reading the shape numbers in the wrong order

    What it looks like

    Treating 768 as the number of sequences in the batch.

    Why it fails

    The source describes the shape as [batch_size, num_tokens, emb_dim], so the first number is the batch size and the last number is the embedding dimension.

    Do this instead

    Read the shape left to right: 2 sequences, 4 tokens each, 768 values per token.

  • Common mistake

    Printing only the output shape

    What it looks like

    Printing output.shape alone and assuming it matches the input.

    Why it fails

    A single printed value gives nothing to compare against. The check depends on two values side by side.

    Do this instead

    Print both x.shape and output.shape, then compare them position by position.

  • Common mistake

    Assuming the random values determine the shape

    What it looks like

    Expecting the shape to change because torch.rand produces different numbers on each run.

    Why it fails

    The source's check is about the shape, and the shape is set by the arguments 2, 4, and 768.

    Do this instead

    Compare the shape values, not the individual numbers inside the tensor.

  • Common mistake

    Blaming the print statements for a mismatch

    What it looks like

    Rewriting the print lines after the two shapes disagree.

    Why it fails

    The source locates a mismatch between the input and output lines in the call output = block(x) or in the block itself.

    Do this instead

    Inspect the call and the block construction first, since the printing stage only reports values.

Working Through a Shape Mismatch

Worked example

Checking whether a block preserved the shape

You run the source's snippet and see the two printed lines below. Does the block preserve the shape, and if not, which stage should you investigate?

  1. 1
    Read the input shape

    The input line reads torch.Size([2, 4, 768]): 2 sequences, 4 tokens each, 768 values per token.

  2. 2
    Read the output shape

    Suppose the output line reads torch.Size([2, 4, 512]). The batch size and token count match, but the last number differs.

  3. 3
    Compare position by position

    Position one: 2 and 2 match. Position two: 4 and 4 match. Position three: 768 and 512 do not match.

  4. 4
    Locate the stage

    The mismatch sits in the emb_dim position, and the pipeline points to the call output = block(x) or the block itself, since the input stage and the printing stage are not the source of a changed dimension.

Result

The block did not preserve the shape in this hypothetical run. The next step is to inspect the call and the block's construction, not the print statements. The source's own run, by contrast, reports torch.Size([2, 4, 768]) for both lines, so the shape is preserved there.

Checking Your Own Run

EASY

Run the snippet with the input x = torch.rand(2, 4, 768) and the block call output = block(x). Print both shapes. Then change only the batch size in x to 3, keeping the token count and embedding size the same. Predict the printed output shape before you run the code, and say which of the three dimensions you changed.

Hints
  • Read the shape as [batch_size, num_tokens, emb_dim] and change only the first number.
  • Compare the printed input and output shapes position by position after the run.
  • If the output shape keeps the same three numbers as the input, the block has preserved the shape.

Summary of the Shape Check

  1. 1
    The sample input has shape [batch_size, num_tokens, emb_dim], given in the source as [2, 4, 768].
  2. 2
    The source's run reports torch.Size([2, 4, 768]) for both the input and the output, so the TransformerBlock maintains the input shape.
  3. 3
    To verify a shape, print both the input and output shapes and compare them position by position.
  4. 4
    If the shapes differ, the mismatch points to the call output = block(x) or to the block itself, not to the print statements.
  5. 5
    The source describes the block only as a component that processes input data, so the check confirms the shape without claiming anything about the internal steps.

At a glance

  • The sample input has shape [batch_size, num_tokens, emb_dim], given in the source as [2, 4, 768]: 2 sequences, 4 tokens each, 768 values per token.
  • The source's run prints torch.Size([2, 4, 768]) for both the input and the output, so the TransformerBlock maintains the input shape.
  • To verify a shape, print both the input and output shapes and compare them position by position.
  • A mismatch between the input and output lines points to the call output = block(x) or to the block itself, not to the print statements.

More concepts starting with I