ml-models / Mathematical foundations / Vectors and dot products intro

Vectors and dot products

A vector is a list of numbers you can read as an arrow. The dot product turns two arrows into one number that says how much they point the same way. That single operation is the score inside attention, the ranking inside search and recommendation, and every output of a linear layer. This page builds it from the geometry up, with every number worked and every PyTorch function linked at v2.15.0.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. Vectors as points and directions
  5. Norms: the length of a vector
  6. The dot product
  7. Projection
  8. Cosine similarity and orthogonality
  9. Why attention and retrieval are dot products
  10. Worked example
  11. Implementation
  12. Tradeoffs
  13. Check yourself
  14. Connections
  15. Read first
  16. Read next
  17. References

Problem

Models represent words, images, users and products as vectors of numbers called embeddings. Once everything is a vector, the questions a model asks become geometric: is this document about the same thing as this query? Which earlier word should this word pay attention to? How much of this input points in the direction a neuron is looking for?

We need a cheap, differentiable way to compare two vectors, one that a GPU can do billions of times a second. The dot product is that tool, and knowing exactly what it measures (and what it doesn't) explains choices like scaling attention scores or normalizing embeddings before search.

Intuition

Read a vector [x, y] as an arrow from the origin to the point (x, y). It has a direction and a length. The dot product of two arrows is large and positive when they point the same way, zero when they are at right angles, and negative when they point apart. It also grows with the lengths of both arrows, which is the detail that matters most in practice.

A query vector and four document vectors in the plane A 2-D grid with the origin left of center. The query q = [1, 2] is drawn as a blue arrow. Four documents are green arrows: d1 = [2, 4] points exactly along q but is twice as long; d2 = [2, 1] is 37 degrees away; d3 = [-2, 1] is at a right angle to q; d4 = [6, 0] is long and 63 degrees away. A table beside the figure lists each one's dot product and cosine with q. d1 [2, 4] d2 [2, 1] d3 [-2, 1] d4 [6, 0] q [1, 2] doc q·d cos d1 10 1.00 d2 4 0.80 d3 0 0.00 d4 6 0.45

d1 and q point the same way, so their cosine is 1. d4 is far off-direction but long, so its dot product (6) beats d2's (4) even though d2 is the closer match in direction.

Mechanics

Vectors as points and directions

A vector v in ℝn is n numbers. In PyTorch it is a rank-1 tensor of shape [n], and a batch of them is [batch, n] (see Tensors, shapes and broadcasting). Adding vectors adds arrows tip to tail; multiplying by a scalar stretches an arrow without turning it. Embedding sizes are 64 to several thousand, but every idea on this page is the same in 2-D.

Norms: the length of a vector

‖v‖p = (Σi |vi|p)1/p

The L2 norm (p = 2) is ordinary length: ‖[3, 4]‖2 = 5. The L1 norm sums absolute values (‖[3, −4]‖1 = 7), and the L∞ norm takes the largest (‖[3, −4]‖∞ = 4). L2 shows up in distances and weight decay, L1 in sparsity penalties, L∞ in gradient clipping by value and adversarial bounds (torch.linalg.vector_norm). Dividing a vector by its L2 norm gives a unit vector: same direction, length 1.

The dot product

u · v = Σi ui vi = ‖u‖ ‖v‖ cos θ

The first form is how it is computed: multiply matching entries and add. The second is what it means: lengths times the cosine of the angle θ between them. The two agree in any number of dimensions, which is why "similar direction" makes sense for 4096-wide embeddings. torch.dot takes exactly two 1-D tensors of the same length and, unlike NumPy's dot, nothing else (torch.dot); its docstring's own example is [2, 3] · [2, 1] = 7. For complex vectors torch.vdot conjugates the first argument (vdot), and torch.inner generalizes to the last axis of any two tensors (inner).

Projection

The dot product with a unit vector is the length of the shadow one vector casts on the other's line. Dividing by ‖a‖² instead of ‖a‖ gives the shadow as a vector:

proja(b) = (a · b / a · a) a

What's left over, b − proja(b), is orthogonal to a. That split is how Gram–Schmidt builds orthogonal bases and how least squares finds the closest point on a line, which the Linear regression page uses.

Projecting b = [2, 2] onto a = [3, 1] The vector a = [3, 1] in blue and b = [2, 2] in green, from a shared origin. A dashed line drops from the tip of b to a's line, meeting it at a right angle at [2.4, 0.8], the projection, drawn in amber along a. The dashed segment from [2.4, 0.8] to [2, 2] is the residual [-0.4, 1.2], orthogonal to a. The scale factor is a·b / a·a = 8 / 10 = 0.8. a [3, 1] b [2, 2] proj = [2.4, 0.8] residual [-0.4, 1.2] a · b = 8 a · a = 10 scale = 0.8 residual · a = 0

Cosine similarity and orthogonality

Divide the dot product by both lengths and only the angle is left:

cos(u, v) = u · v / (‖u‖ ‖v‖) ∈ [−1, 1]

1 means same direction, 0 means orthogonal (at right angles, sharing no direction), −1 means opposite. In high dimensions random vectors are nearly orthogonal, so a cosine of 0.3 between two embeddings can already be a strong signal. F.cosine_similarity clamps each norm separately to at least eps = 1e-8 before dividing, so a zero vector gives 0 instead of NaN (F.cosine_similarity); the module wrapper only stores dim and eps and calls it (nn.CosineSimilarity). Note that the module's docstring writes the clamp around the product of the norms, while the function it calls clamps each norm; the function is what runs.

Why attention and retrieval are dot products

  • A neuron is a dot product. A linear layer's output j is wj · x + bj: how much the input points along the weight row the neuron has learned. A whole layer is many dot products at once, which is matrix multiplication, the next page.
  • Attention scores are dot products. Each query is dotted with every key, the scores are scaled by 1/√dk and softmaxed. The scaling exists precisely because dot products grow with dimension (see The Transformer).
  • Retrieval is a dot product. Encode the query and every document, then return the documents with the highest q · d. Normalize everything first and that is cosine similarity; vector indexes exist to find the top scores without computing all of them.
TermShape/ParamsMeaningSource
Dot product [n], [n] → scalar Sum of elementwise products; ‖u‖‖v‖cos θ. torch.dot
Inner product […, n], […, n] → outer shapes joined Dot product over the last axis of each input. torch.inner
Outer product [m], [n] → [m, n] Every pair's product; the opposite of collapsing to a scalar. torch.outer
Vector norm ord (default 2), dim Length of a vector, or of each vector along dim. linalg.vector_norm
Cosine similarity dim=1, eps=1e-8; drops dim Dot product of the two directions; ignores length. F.cosine_similarity, nn.CosineSimilarity
Pairwise distance p=2, eps=1e-6 ‖x1 − x2 + eps‖p row by row. nn.PairwiseDistance
All-pairs distance [P, M], [R, M] → [P, R] Distance between every row of one set and every row of the other. torch.cdist

Worked example

Ranking four documents for one query. The query is q = [1, 2], with ‖q‖ = √5 ≈ 2.236. For d2 = [2, 1]: q · d2 = 1·2 + 2·1 = 4, ‖d2‖ ≈ 2.236, so cos = 4 / (2.236 · 2.236) = 0.800, an angle of 36.9°.

DocVectorq · d‖d‖cos‖q − d‖
d1 [2, 4] 10 4.472 1.000 2.236
d2 [2, 1] 4 2.236 0.800 1.414
d3 [-2, 1] 0 2.236 0.000 3.162
d4 [6, 0] 6 6.000 0.447 5.385

The three scores rank the documents differently:

  • by dot product: d1 > d4 > d2 > d3. Long vectors win.
  • by cosine: d1 > d2 > d4 > d3. Only direction counts.
  • by distance (smaller is better): d2 < d1 < d3 < d4. Being near the query's point counts, so d1, twice as long as q, loses to d2.

d3 = [−2, 1] scores 0 on both dot and cosine: 1·(−2) + 2·1 = 0, so it is orthogonal to the query. If all four vectors had length 1, the dot and cosine rankings would be identical, and distance would agree too, since ‖q − d‖² = 2 − 2 q·d for unit vectors.

Implementation

import torch
import torch.nn.functional as F

q = torch.tensor([1., 2.])
docs = torch.tensor([[2., 4.], [2., 1.], [-2., 1.], [6., 0.]])

torch.dot(q, docs[1])                       # tensor(4.)
docs @ q                                    # tensor([10.,  4.,  0.,  6.]): all four at once
torch.linalg.vector_norm(docs, dim=1)       # lengths of each row
F.cosine_similarity(q.unsqueeze(0), docs)   # q broadcasts against every row
torch.cdist(q.unsqueeze(0), docs)           # [1, 4] distances

unit = F.normalize(docs, dim=1)             # divide each row by its L2 norm
(unit @ F.normalize(q, dim=0)).topk(2)      # cosine top-2: the core of vector search

a, b = torch.tensor([3., 1.]), torch.tensor([2., 2.])
along = (a @ b) / (a @ a) * a               # tensor([2.4000, 0.8000])
(b - along) @ a                             # tensor(0.): the residual is orthogonal

From scratch, in plain Python:

def dot(u, v):
    return sum(a * b for a, b in zip(u, v))

def norm(v):
    return dot(v, v) ** 0.5

def cosine(u, v, eps=1e-8):
    return dot(u, v) / (max(norm(u), eps) * max(norm(v), eps))

Tradeoffs

  • Dot product versus cosine. The raw dot product lets length carry meaning: a recommender may want popular items, whose embeddings grow long, to score higher. Cosine removes length, which is fairer across documents of different sizes but throws that signal away. Many retrieval models train with normalized embeddings so the two coincide.
  • Cosine versus Euclidean distance. For unit vectors they rank identically. For raw vectors, distance treats "near the same point" as similar, which suits clustering in a space where magnitude means something.
  • Scale grows with dimension. The dot product of two random vectors with unit-variance entries has variance n. Attention divides by √dk to undo this; forget it and softmax saturates.
  • Precision. In float16, a long dot product can overflow or lose small terms. PyTorch kernels accumulate in higher precision, but hand-written loops in low precision may not.
  • The eps clamp. It prevents NaN for zero vectors but means a tiny, nearly-zero vector gets a meaningless cosine. Filter degenerate embeddings before search.

Check yourself

What is the cosine similarity of [1, 0] and [0, 3]?

0. The dot product is 0, so they are orthogonal (90°); length doesn't matter.

Doubling a document vector changes which scores?

Its dot product with the query doubles; its cosine is unchanged; its distance to the query changes in a way that depends on where it was.

Why does attention divide q · k by √dk?

With unit-variance entries, q · k has variance dk. Dividing by √dk brings the standard deviation back to 1, keeping softmax away from a near one-hot output with tiny gradients.

What does docs @ q compute, and what shape is it?

The dot product of q with each row of docs: shape [4]. That is a matrix–vector product, the bridge to the next page.

Connections

References

  • Strang. Introduction to Linear Algebra, 6th ed., chapter 1 (vectors, lengths and dot products). Wellesley-Cambridge Press, 2023.
  • Vaswani et al. Attention Is All You Need. 2017, §3.2.1 on scaled dot-product attention. arXiv:1706.03762
  • Karpukhin et al. Dense Passage Retrieval for Open-Domain Question Answering. 2020. arXiv:2004.04906. Retrieval as a dot product between query and passage encoders.
  • PyTorch v2.15.0: F.cosine_similarity and torch/nn/modules/distance.py.