Skip to content

← Broadcasting step 5 of 6

Easy Primitives

Every pair of vector entries

Jump to the task

An outer product multiplies every value in one vector by every value in another. For a of length m and b of length n, the result has shape (m, n) with entry [i, j] = a[i] * b[j].

Give each vector its own axis

import torch

a = torch.tensor([1., 2., 3.])
b = torch.tensor([10., 20.])
out = a[:, None] * b[None, :]
print(list(out.shape))  # [3, 2]
print(out.tolist())  # [[10.0, 20.0], [20.0, 40.0], [30.0, 60.0]]
a[:, None]: (m, 1)
b[None, :]: (1, n)
result:     (m, n)

The first vector varies down the rows and is reused across columns. The second varies across columns and is reused down the rows. Inserting these singleton axes creates views; multiplying them constructs the result matrix.

The pattern also works for comparisons

Replacing multiplication with subtraction gives all pairwise differences. A comparison gives a Boolean matrix. For example, comparing query positions with key positions creates a lower-triangular allowed-position mask:

positions = torch.arange(3)
allowed = positions[:, None] >= positions[None, :]
print(allowed.tolist())  # [[True, False, False], [True, True, False], [True, True, True]]

Here rows are queries, columns are keys, and True includes the current position and earlier positions. Check the receiving attention API's mask convention: some Boolean masks use True to exclude rather than allow. This is one way to construct a causal mask, not a requirement for every attention implementation.

Check the orientation

a * b does not form all pairs. It either multiplies aligned elements, broadcasts a singleton vector, or raises when lengths are incompatible. a[None, :] * b[:, None] forms all pairs in the reverse orientation, with shape (n, m). Even equal shapes do not establish correctness; compare values using different vectors.

torch.outer(a, b) is a direct API for the outer product of two vectors. For this exercise, use broadcasting instead to practice axis alignment.

Your task

def outer(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor

Given nonempty 1-D floating-point tensors a and b, return their (m, n) outer product without calling torch.outer. Use at most three dispatched operations on the harness inputs. The two axis insertions and multiplication meet this budget in the tested execution path. Dispatch count is not a measurement of GPU kernels or runtime.

The starter computes each output entry in Python. Replace the loop with a broadcasted expression.