Skip to content

← Broadcasting step 3 of 6

Medium Primitives

Which side may stretch

Broadcasting is symmetric for an ordinary operation. It is not symmetric for an in-place one, and the asymmetry is the rule worth memorising.

a = torch.zeros(3, 4)
b = torch.ones(3, 1)

a + b        # (3, 4). Fine either way round.
a.add_(b)    # (3, 4). Fine: the source stretched.
b.add_(a)    # RuntimeError.
RuntimeError: output with shape [3, 1] doesn't match the
broadcast shape [3, 4]

Why

An in-place op writes into an existing buffer, so the result must fit the buffer it is being written into. The destination’s shape is fixed by the fact that it already exists. The source is free to stretch, because stretching is a stride change on a tensor being read.

So the rule is: the destination’s shape is the answer’s shape. If broadcasting would make the answer bigger than the destination, the operation is refused rather than silently reallocating.

This is a good error, and it is the reason a lot of accumulator bugs surface immediately instead of at the end of training.

Where it bites

Accumulating per-row statistics into a per-row buffer:

totals = torch.zeros(rows, 1)
totals += batch          # batch is (rows, cols). Refused.

The fix is not to reshape the destination; it is to reduce the source to the destination’s shape first, because summing was what you meant:

totals += batch.sum(dim=1, keepdim=True)

Your task

def accumulate(totals: torch.Tensor, batch: torch.Tensor) -> torch.Tensor

totals is (rows, 1); batch is (rows, cols). Add each row of batch into the matching entry of totals, in place, and return totals.

The starter tries to add the batch directly.