Skip to content

← Matmul and einsum step 4 of 6

Easy Primitives

Elementwise or matrix multiplication?

Jump to the task

* multiplies matching elements, with broadcasting where needed. @ performs matrix multiplication: each output entry sums products from one row of the left matrix and one column of the right.

import torch

a = torch.tensor([[1., 2.], [3., 4.]])
b = torch.tensor([[5., 6.], [7., 8.]])
print((a * b).tolist())  # [[5.0, 12.0], [21.0, 32.0]]
print((a @ b).tolist())  # [[19.0, 22.0], [43.0, 50.0]]

Both outputs have shape (2, 2). A shape check alone would not catch an accidental substitution. Test the values too, and include rectangular inputs to expose assumptions hidden by equal axis lengths.

Choose the operation

Operation Meaning
torch.mul(a, b) or a * b Elementwise multiplication with broadcasting
torch.mm(a, b) Two-dimensional matrix multiplication
torch.bmm(a, b) Three-dimensional batched matrix multiplication with equal batch sizes
torch.matmul(a, b) or a @ b Vector, matrix or batched matrix multiplication; compatible batch axes broadcast

matmul requires both operands to have at least one dimension. For two vectors of equal length it returns their dot product. For two matrices, (n, d) @ (d, m) returns (n, m).

Compare every pair of rows

For a real matrix x with shape (n, d), a Gram matrix stores the dot product of each pair of rows. Entry [i, j] is:

sum(x[i, t] * x[j, t] for t in range(d))

Transposing x puts each original row into a column. Multiplying x @ x.t() then produces all row-pair dot products with shape (n, n). For strided tensors, the transpose shares storage; it does not copy the values. Creating a view and using its layout can still have a cost.

Your task

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

Given a real tensor x of shape (n, d), return its (n, n) Gram matrix. The starter uses elementwise multiplication. With square inputs its shape looks right, but its values are generally wrong. Rectangular inputs may raise a shape error or broadcast to an unintended shape.