Skip to content

← Performance step 4 of 7

Medium Primitives

Batch the head projections

Jump to the task

When several matrix multiplications have matching shapes, express them as a batch instead of looping over them in Python. Each batch item can still use its own matrix.

import torch

x = torch.tensor([[[1.0, 2.0]], [[3.0, 4.0]]])
w = torch.tensor([[[10.0], [1.0]], [[1.0], [10.0]]])
print(torch.bmm(x, w).tolist())  # [[[12.0]], [[43.0]]]

The first item computes 1*10 + 2*1; the second computes 3*1 + 4*10. This pairs corresponding items. It does not multiply every input by every weight matrix or share the first matrix across the batch.

Replace the Python loop

The starter indexes each head, multiplies its matrices and stacks the results. torch.bmm(x, w) expresses that work in one tensor operation. This can reduce Python dispatch overhead and give the backend more work to schedule together.

A tensor operation is not a promise of one GPU kernel. The chosen kernels and performance depend on the device, tensor shapes, layout, dtype and library implementation. Batching often helps, but neither a specific speedup nor improved hardware utilization follows from the dispatch count alone.

Layout transformations have costs too. permute creates a view for ordinary strided tensors, while reshape may copy when the existing strides cannot represent the requested shape. Measure the complete path, including any copies required to prepare the batch.

Shape requirements

bmm takes two rank-3 tensors with equal leading batch sizes. Their inner matrix dimensions must match:

(heads, n, d) @ (heads, d, m) -> (heads, n, m)

If different heads need different matrix sizes, they cannot be represented by these two rectangular batches directly. Padding, grouping compatible shapes or using another implementation are options with different costs. Padding is not automatically the best choice.

Your task

def project_heads(x: torch.Tensor, w: torch.Tensor) -> torch.Tensor

The inputs have shapes (heads, n, d) and (heads, d, m). Return shape (heads, n, m), with output head h equal to x[h] @ w[h].

Use at most two tensor operations counted by the harness's TorchDispatchMode. It counts dispatches inside your function, including indexing and stacking, rather than measuring elapsed time or GPU kernels.