43. Collate Function for Variable-Length Sequence Batching
Problem
Real training batches never arrive as neat, equal-length tensors. A DataLoader pulls a list of individually-tokenized examples of different lengths, and it is the collate_fn's job to turn that list into a single padded batch tensor plus an attention mask, so the model can be told which positions are real tokens and which are padding to ignore in attention and loss.
Given a list of variable-length 1D LongTensors (already-tokenized
sequences), pad them on the right to the batch's max length with a given
pad_id, and produce a boolean attention mask that is True
for real tokens and False for padding.
Source: src/43_collate_variable_length_batch.py
def collate_variable_length(
sequences: list[torch.Tensor], pad_id: int = 0,
) -> tuple[torch.Tensor, torch.Tensor]: ...
>>> seqs = [torch.tensor([5, 6, 7]), torch.tensor([9, 2])]
>>> padded, mask = collate_variable_length(seqs, pad_id=0)
>>> padded
tensor([[5, 6, 7],
[9, 2, 0]])
>>> mask
tensor([[ True, True, True],
[ True, True, False]])
Step-by-Step Approach
- Find
max_len, the length of the longest sequence in the batch -- every row of the output will be padded out to this length. - Pre-allocate the full
(batch, max_len)output tensor filled entirely withpad_id, using the first sequence's dtype so the result staysint64rather than silently becoming a float tensor. - Pre-allocate a same-shaped boolean mask tensor, initialized to
Falseeverywhere (padding is the default; real tokens are the exception you write in). - For each sequence, copy it into the corresponding row's first
lengthcolumns of the padded tensor. - Set that same row's first
lengthcolumns of the mask toTrue-- everything else in that row staysFalsefrom the pre-allocation. - Return both tensors together; a model consuming this batch uses
maskto zero out attention to padding and to exclude padded positions from the loss.
The key insight is pre-allocating both output tensors up front and writing into
fixed-size slices, rather than building each row with per-sequence
torch.cat/F.pad calls and stacking them afterward -- it
avoids one extra tensor allocation per sequence and keeps the whole function to a
single pass over the list.
Reference solution
def collate_variable_length(
sequences: list[torch.Tensor],
pad_id: int = 0,
) -> tuple[torch.Tensor, torch.Tensor]:
batch_size = len(sequences)
lengths = [seq.shape[0] for seq in sequences]
max_len = max(lengths)
# Pre-allocate the full (batch, max_len) tensor filled with pad_id, then
# copy each sequence into its row -- avoids per-row torch.cat/pad calls
# and one dtype mismatch surprise (new_full keeps sequences[0]'s dtype).
padded = sequences[0].new_full((batch_size, max_len), pad_id)
mask = torch.zeros((batch_size, max_len), dtype=torch.bool)
for i, seq in enumerate(sequences):
length = lengths[i]
padded[i, :length] = seq
mask[i, :length] = True # real tokens only; padding stays False
return padded, mask
Key Functions & Tricks
tensor.new_full(shape, value)— allocates a new tensor with the same dtype and device as an existing tensor, avoiding an accidental dtype mismatch (e.g. padding an int64 sequence tensor with a Python float).torch.zeros(shape, dtype=torch.bool)— a clean, explicit way to build a boolean mask that defaults toFalse.- Slice-assignment (
padded[i, :length] = seq) — writes into a pre-allocated tensor in place, which is cheaper than building each row separately and concatenating. seq.shape[0]— reads a 1D tensor's length without a Pythonlen()call that only works because 1D tensors happen to support it.
How to Recognize This Pattern
Signal words: "collate_fn," "pad a batch of sequences," "variable-length
batching," "attention mask for padding." The tell is a DataLoader
or custom batching step receiving a list of differently-shaped tensors that has
to become one regular tensor plus a mask before it can hit a model. Common
variations: left-padding instead of right-padding (needed for some
generation-time batching setups where the last real token must stay at a fixed
offset from the end); padding along a sequence dimension of 2D token-id
sequences with an extra feature dimension; or building the mask as a float
additive mask (0 / -inf) instead of a boolean, ready to add directly to
attention scores. A common pitfall is forgetting to also return the mask at
all -- padding alone silently corrupts attention and loss computations unless
the model is explicitly told which positions to ignore.