Concepts / Batching Input Data for Language Models

Batching Input Data for Language Models

CausalAttention extends self-attention with a causal mask and a dropout layer, and it accepts batched inputs of shape (batch, tokens, embedding).

  • Programming

Why Language Models Process Batches

Up to this point, attention has been applied to a single input text. A language model rarely works that way. Training pipelines group many texts together so the hardware can process them in one pass, and the attention code has to accept that grouped input without modification. The class in this article is built with that requirement in mind. It extends the self-attention class from the earlier section by adding two things: a causal mask that hides future tokens, and a dropout layer applied to the attention weights.

What do you think happens?

The batch holds two inputs of six tokens each, every token is a three-dimensional vector, and the layer is built with an output dimension of two. What shape does the result of the attention call have?

  • (2, 6, 3)

  • (2, 6, 2)

  • (2, 6, 6)

  • (6, 2)

Reveal answer
Answer:

(2, 6, 2)

The output keeps the batch and token dimensions and replaces the embedding dimension with the output dimension of two. You can confirm this by tracing every tensor through the forward method, which the next sections do step by step.

From One Text to a Batch of Two

To test batch handling without a real data loader, the source duplicates one tokenized input text and stacks the two copies along a new first dimension. The result is a three-dimensional tensor: two inputs, six tokens each, and every token is a three-dimensional embedding vector. Those three numbers are the key to every shape question later in this article, so it helps to know what each one indexes.

python
Output (expected)
torch.Size([2, 6, 3])
Notes on the code
  • inputs is one tokenized text with shape (6, 3): six tokens, each a three-dimensional vector
  • stack with dim=0 creates a new first dimension holding the two copies
  • the first number indexes the sequence in the batch, not a token
2batch: two sequences6token positions in eachsequence3embedding values per token
Which dimension indexes the sequences in the batch, which indexes token positions, and which holds each token's vector?

Read the shape from left to right. The leading 2 says how many sequences travel together. The 6 says how many token positions each sequence has. The trailing 3 is the embedding size, meaning the number of values in each token's vector. The attention code must keep the first two dimensions meaningful throughout, because the scores are computed for every pair of tokens within each sequence.

Anatomy of the CausalAttention Class

Read the class before running anything. Most lines look familiar from the self-attention version: three linear projections produce queries, keys, and values, and the forward method computes scores, normalizes them, and mixes the values. The two additions are the dropout layer created in the constructor and the mask registered with register_buffer. Each addition appears in a different place in the forward method, so the code itself is the best map of where things happen.

python
Notes on the code
  • The constructor creates the three projections, the dropout layer, and the mask buffer
  • The mask is built from torch.triu applied to a square matrix of ones
  • keys.transpose(1, 2) swaps the token and embedding dimensions while the batch stays in position 0
  • masked_fill_ writes -inf into the blocked score positions in place
  • Dropout is applied to the normalized attention weights, not to the raw scores

The transpose deserves a closer look. Keys are transposed across dimensions 1 and 2 while the batch dimension stays in position 0. That single arrangement lets one matrix multiplication compute scores for every input in the batch at once, rather than looping over the inputs one at a time.

Tracing Shapes Through the Forward Pass

The shape prediction from the opening question works because every tensor can be followed through the forward method. Start with the input of shape 2 by 6 by 3. The three linear projections each map the embedding dimension from 3 to 2, so queries, keys, and values all have shape 2 by 6 by 2. Multiplying queries by the transposed keys produces a score for every pair of tokens within each sequence, giving a 2 by 6 by 6 matrix. The mask, the scaling, and the softmax all operate on that square matrix without changing its shape. Finally, multiplying the weights by the values produces context vectors of shape 2 by 6 by 2.

Worked example

Tracing the shape of a batch of two

Trace the shape of a batch of shape (2, 6, 3) through CausalAttention with d_in = 3 and d_out = 2.

  1. 1
    Input

    x has shape (2, 6, 3): two sequences, six tokens, three values per token.

  2. 2
    Projections

    W_query, W_key, and W_value each map 3 to 2, so queries, keys, and values have shape (2, 6, 2).

  3. 3
    Transposed keys

    keys.transpose(1, 2) has shape (2, 2, 6). The batch stays first and the token and embedding dimensions swap.

  4. 4
    Scores

    queries @ keys transposed gives (2, 6, 6): one score for every pair of tokens within each sequence.

  5. 5
    Mask, scale, softmax

    Blocked positions are set to -inf, the scores are scaled, and softmax runs over the last dimension. The shape stays (2, 6, 6).

  6. 6
    Dropout

    Dropout zeroes some entries of the weights and keeps the shape at (2, 6, 6).

  7. 7
    Context vectors

    weights @ values gives (2, 6, 2). The embedding dimension is now the output dimension.

Result

The output shape is (2, 6, 2).

input(2, 6, 3)queries, keys, values(2, 6, 2)attention scores(2, 6, 6)softmax weights(2, 6, 6)context vectors(2, 6, 2)linear projections 3 to 2queries times transposed keysmask, scale, softmaxweights times values
How does the (batch, tokens, embedding) tensor transform into query, key, and score matrices, and then back into an output of the same batch and token dimensions?

Tracing shapes is also the fastest way to locate a bug. If the printed shape differs from the one you traced, the error is in a step where the last dimension or the token dimension changed unexpectedly. A wrong output dimension usually points to the projection layers, while a wrong square score matrix points to the transpose or to a mismatch between the query and key shapes.

The Causal Mask and Future Tokens

A causal mask enforces a simple rule: a token may attend to itself and to earlier tokens, but never to tokens that come later in the sequence. This restriction prevents data leakage in autoregressive models, which generate text one token at a time and must not peek at the words they have not yet produced.

key 0visiblekey 1visiblekey 2visiblekey 3blocked, -infkey 4blocked, -infkey 5blocked, -inf
Which key positions can query token 2 attend to, and which are blocked by the causal mask?

The class builds the mask with torch.triu applied to a square matrix of ones. Passing diagonal=1 zeroes the main diagonal and everything below it, so the surviving ones sit strictly above the diagonal and mark exactly the future positions. Read the mask row for query token 2 above: keys 0, 1, and 2 are earlier or current tokens and remain visible, while keys 3, 4, and 5 lie in the future and are blocked.

Before softmax, blocked positions are filled with -inf. Because the exponential of negative infinity is zero, those positions receive zero weight after normalization, and the remaining weights in each row still sum to one. Masking happens on the scores, so the mask does not change the shape of the score matrix.

python
Output (expected)
tensor([[0., 1., 1., 1., 1., 1.],
        [0., 0., 1., 1., 1., 1.],
        [0., 0., 0., 1., 1., 1.],
        [0., 0., 0., 0., 1., 1.],
        [0., 0., 0., 0., 0., 1.],
        [0., 0., 0., 0., 0., 0.]])
Notes on the code
  • A 1 marks a future position that will be blocked
  • Row i shows which keys query token i must ignore

Why the Mask Is a Registered Buffer

The mask is a fixed tensor. It holds no learnable weights, so it is not a model parameter. It still has to live on the same device as the model's parameters, because the masked fill operates on scores that are computed on that device. The register_buffer call exists for this case. A buffer is stored with the model but is not treated as a trainable parameter, and the source notes that buffers move automatically to the appropriate device, CPU or GPU, together with the model.

PropertyMask stored as a bufferLearnable weight (parameter)
Holds learnable weightsNoYes
Treated as a trainable parameterNoYes
Moves with the model to CPU or GPUYes, automaticallyMoved together with the model

Where the mask differs from a trainable weight, based on the source's description of register_buffer

Dropout After the Softmax

Dropout is a regularization technique that randomly sets a fraction of tensor elements to zero, which helps prevent overfitting. In this class it is created as nn.Dropout(dropout) in the constructor and applied to the attention weights after the softmax. The source applies dropout to the normalized weights rather than to the raw scores or the input embeddings, so each entry in the weight matrix can be zeroed independently during training.

The order of operations is the important part. Masking comes first, so blocked future positions are already -inf and get zero weight from the softmax. Softmax then normalizes each row. Dropout comes last and only perturbs the finished weights. The context vectors are produced from those dropped-out weights and the values, and the shape is still (2, 6, 2) at the end.

Mistakes When Reading Batched Attention Code

  • Common mistake

    Treating the first dimension as a token index

    What it looks like

    Reading shape (2, 6, 3) as six sequences of three tokens

    Why it fails

    The leading dimension indexes the batch. The 6 is the number of tokens in each sequence.

    Do this instead

    Name the dimensions as (batch, tokens, embedding) and keep that order in every shape check.

  • Common mistake

    Expecting the mask to change the shape of the scores

    What it looks like

    Assuming masking turns the (2, 6, 6) scores into a smaller matrix

    Why it fails

    Masking only writes -inf into blocked positions. The mask, scaling, and softmax all keep the square score matrix at the same shape.

    Do this instead

    Track the shape across masking and check that only the values, not the dimensions, have changed.

  • Common mistake

    Moving the mask to the device by hand

    Why it fails

    Manual moves are where device mismatch errors tend to appear once the class is part of a larger model.

    Do this instead

    Register the mask with register_buffer so it follows the model to CPU or GPU automatically.

  • Common mistake

    Applying dropout to the raw scores

    Why it fails

    The source applies dropout to the normalized weights after the softmax, so each weight can be zeroed independently.

    Do this instead

    Keep dropout after the softmax, directly on attn_weights.

  • Common mistake

    Slicing the mask with the wrong length, which misaligns the blocked positions with the actual tokens

Predict and Verify the Output Shape

python
Output (expected)
torch.Size([2, 6, 2])
Notes on the code
  • The class is built with d_in = 3 and d_out = 2
  • The batch from earlier is passed through the layer in one call
  • The printed shape should match the trace you wrote before running the code
EASY

Before running the code, write down the shape you expect from ca(batch) when the batch is (2, 6, 3) and d_out is 2. Then change the batch to three inputs and predict the new output shape. Compare your answers with the printed shapes, and if they differ, trace the forward pass again to find the step where a dimension changed unexpectedly.

Hints
  • Only the embedding dimension changes from 3 to d_out
  • The batch and token dimensions pass through the layer unchanged
  • With three inputs the expected output is (3, 6, 2)

Key Takeaways

  • CausalAttention extends self-attention with a causal mask and a dropout layer, and it accepts batched inputs of shape (batch, tokens, embedding).
  • A causal mask built with torch.triu and diagonal=1 marks future positions, and masked_fill_ writes -inf into them before the softmax so that tokens cannot attend to later tokens.
  • The mask is stored with register_buffer so it moves to the correct device automatically with the model, and it is not a trainable parameter.
  • Dropout is applied to the normalized attention weights after the softmax, and tracing tensor shapes through the forward pass is the most reliable way to locate a bug.

Related concepts

More concepts starting with B