Skip to content

← Shapes and Strides step 6 of 9

Easy Primitives

Fold the heads into the batch

Jump to the task

Some attention operations expect one batch axis. If activations have shape (batch, heads, length, dim), you can combine batch and head indices:

(batch, heads, length, dim) -> (batch * heads, length, dim)

The new batch index b * heads + h identifies the original pair (b, h). Every head keeps its sequence and feature values in the same order.

When this can share storage

For a contiguous input, merging these leading axes requires no data copy:

import torch

x = torch.arange(120).reshape(2, 3, 4, 5)
y = x.reshape(6, 4, 5)
print(x.stride())  # (60, 20, 5, 1)
print(y.stride())  # (20, 5, 1)
print(torch.equal(y[4], x[1, 1]))  # True
print(y.untyped_storage().data_ptr() == x.untyped_storage().data_ptr())  # True

Each step along the merged axis advances 20 elements. The original batch stride, 60, equals three head strides. This lets a single stride describe the combined axis. A view still needs tensor metadata, but it reuses the existing data buffer.

Adjacent axes are not enough

Strides matter, not just where the dimensions appear in the shape. Starting from the example above:

z = x.permute(0, 2, 1, 3)  # shape (2, 4, 3, 5)
print(z.stride())  # (60, 5, 20, 1)
folded = z.reshape(8, 3, 5)
print(folded.untyped_storage().data_ptr() == x.untyped_storage().data_ptr())  # False

Here the leading axes cannot be merged into a view: moving between batches requires a different spacing from moving between sequence positions. reshape copies to preserve the requested element order. This example uses non-singleton dimensions; layouts with size-one axes can behave differently.

view raises when the requested shape cannot share storage. It does not require every input to be contiguous: some noncontiguous layouts still support a particular view. reshape accepts either case and copies when necessary.

Your task

def fold_heads(x: torch.Tensor) -> torch.Tensor

The harness supplies a contiguous tensor of shape (batch, heads, length, dim) with positive dimensions. Return shape (batch * heads, length, dim) while sharing its storage.

The starter concatenates slices. Its values and shape are right, but it allocates a new data buffer. Replace that step with a view or reshape.