Skip to content

← Matmul and einsum step 2 of 6

Medium Primitives

Queries against keys

Jump to the task

Scaled dot-product attention compares each query with every key in the same batch item. The feature dimension is shared, but the query and key sequence lengths can differ:

q:      (batch, q_len, d)
k:      (batch, k_len, d)
scores: (batch, q_len, k_len)

scores[b, i, j] = dot(q[b, i], k[b, j]) / sqrt(d)

Each output entry is a similarity score for one query/key pair. These are scores before masking or softmax, not probabilities.

Align the feature axes

For these matrix operands, @ sums over the last axis of the left operand and the second-to-last axis of the right. Transpose the last two key axes so both contracted dimensions have size d:

import torch

q = torch.tensor([[[1.0, 0.0], [0.0, 2.0]]])
k = torch.tensor([[[1.0, 1.0], [2.0, 0.0], [0.0, 3.0]]])
dots = q @ k.transpose(-2, -1)
print(dots.tolist())  # [[[1.0, 2.0, 0.0], [2.0, 0.0, 6.0]]]
print(list(dots.shape))  # [1, 2, 3]

For strided tensors, this transpose shares storage rather than copying key values. It still creates a view, and the resulting layout can affect the following multiplication's implementation and cost.

Negative axis numbers name the matrix axes even if an additional batch or head axis is inserted before them. Those leading dimensions must still be compatible for the intended multiplication.

Why divide by the square root of d?

Under a simple model where query and key components are independent, zero-mean and unit-variance, their dot product has variance d. Dividing by sqrt(d) gives variance one under those assumptions.

This scaling helps prevent larger feature dimensions from producing increasingly spread-out scores solely through summation. Large score gaps can make softmax highly concentrated and reduce some gradients, but saturation is not inevitable. Learned queries and keys need not satisfy the assumptions, so scaling does not guarantee a fixed score variance throughout training.

Your task

def scores(q: torch.Tensor, k: torch.Tensor) -> torch.Tensor

Return scaled dot-product scores of shape (batch, q_len, k_len) for inputs with shapes (batch, q_len, d) and (batch, k_len, d), where d is positive. Do not apply a mask or softmax.

The starter omits the transpose. It runs when k_len == d, regardless of q_len, but then multiplies the wrong key axes. Matching dimensions can hide the error rather than make the calculation correct.