Dot Product in Machine Learning: The Engine Behind Similarity, Attention, and Neural Networks
From cosine similarity to scaled dot-product attention — how one operation powers modern AI
Abstract AlgorithmsIntermediate
For developers with some experience. Builds on fundamentals.
Estimated read time: 21 min
AI-assisted content. This post may have been written or enhanced with AI tools. Please verify critical information independently.
TLDR: The dot product multiplies corresponding elements of two vectors and sums the results. In machine learning it does three critical jobs: it scores semantic similarity between embeddings, computes every activation in a fully connected layer, and generates the Q·Kᵀ score matrix that transformer attention uses to decide what to focus on. Normalising by vector magnitude turns it into cosine similarity; dividing by √d_k in attention prevents softmax saturation. Master this one operation and the internals of neural networks, transformers, and vector search all click into place.
📖 The Operation Your Model Runs a Billion Times Before Breakfast
Imagine you are building a semantic search engine for a product catalogue with 500,000 items. A user types "wireless noise-cancelling headphones". Your model encodes the query into a 768-dimensional vector. Now it needs to rank every item in the catalogue by how semantically close it is to that vector — not by keyword match, but by meaning. And it needs to do this in under 100 milliseconds.
The operation that makes this possible is not a complex neural circuit. It is a dot product: multiply each pair of corresponding numbers in two vectors, then add all the results. That single formula, repeated 500,000 times in a tight matrix multiplication, produces a ranked list of similarity scores in milliseconds on any modern GPU.
The same operation appears at every layer of a neural network — every neuron computes a dot product between its weight vector and the input before applying an activation function. In transformer models, it appears again as the Q·Kᵀ matrix that determines which tokens attend to which. In embedding databases, it is the inner product search at the core of approximate nearest-neighbour retrieval. The dot product is not a footnote in linear algebra homework — it is the single most executed mathematical operation in all of modern deep learning.
This guide explains what the dot product computes geometrically, why that geometry translates to similarity, how it powers neural network layers and transformer attention, when to normalise it into cosine similarity, and how production frameworks like PyTorch and FAISS implement it at scale.
🔍 Geometry Before Algebra: What a Dot Product Actually Measures
Given two vectors a = [a₁, a₂, …, aₙ] and b = [b₁, b₂, …, bₙ], their dot product is:
a · b = a₁b₁ + a₂b₂ + … + aₙbₙ
That is the algebraic definition. The geometric meaning is more useful for intuition:
a · b = |a| × |b| × cos(θ)
where |a| is the magnitude (length) of a, |b| is the magnitude of b, and θ is the angle between them.
This geometric form reveals exactly what the dot product measures: the projection of one vector onto the other, scaled by the magnitude of both. Think of it as answering the question: "How much does a point in the same direction as b?"
Three cases make the intuition concrete:
| Angle between vectors | cos(θ) | Dot product | Meaning |
| 0° (perfectly aligned) | 1.0 | Large positive | Same direction — maximum similarity |
| 90° (perpendicular) | 0.0 | 0 | Orthogonal — completely unrelated |
| 180° (opposite) | −1.0 | Large negative | Opposite direction — maximum dissimilarity |
This is why training a sentence encoder on similar/dissimilar pairs works: the model learns to rotate vectors so that semantically related sentences point in similar directions in the embedding space. Once they do, a dot product between any two vectors instantly produces a similarity score.
One subtlety: the dot product mixes magnitude and direction. A very long vector will produce a high dot product with almost anything, even vectors that point in moderately different directions. This is fine when all vectors are normalised to unit length (magnitude = 1), but it becomes a problem when vectors have varying magnitudes — something addressed in the cosine similarity section below.
⚙️ How the Dot Product Drives Every Neural Network Layer
Every neuron in a fully connected neural network computes exactly one dot product per forward pass. Given input vector x ∈ ℝᵈ and weight vector w ∈ ℝᵈ for a single neuron, the pre-activation output is:
z = w · x + b = w₁x₁ + w₂x₂ + … + wᵈxᵈ + b
The neuron is asking: "How much does this input pattern align with the pattern I have learned to detect?" High alignment → high z → strong activation after ReLU or sigmoid. The weight vector w defines the "template" the neuron is tuned for, and the dot product scores how closely any input matches that template.
For a full layer of H neurons receiving input x, the computation is a matrix–vector product:
z = W**x + b**
where W ∈ ℝᴴˣᵈ stacks H weight vectors as rows. This is one large dot product operation: each row of W takes its dot product with x, producing one element of z. In practice, for a batch of N inputs stacked as X ∈ ℝᴺˣᵈ, this becomes a full matrix multiplication X**Wᵀ** — a batched dot product computed in a single GPU kernel.
The diagram below shows how a single forward pass through a linear layer is entirely a dot product operation, from input to pre-activation:
flowchart LR
X["Input Vector x"] --> WX["Dot Product: W times x"]
W["Weight Matrix W"] --> WX
WX --> B["Add Bias b"]
B --> ACT["Activation Function"]
ACT --> Y["Layer Output y"]
The diagram traces one forward pass through a fully connected layer. The weight matrix W defines what patterns each neuron detects; the dot product scores how strongly each pattern appears in the input x; bias shifts the threshold; and the activation function introduces non-linearity. Without the dot product, the layer cannot learn directional preference over input features.
Training adjusts W via gradient descent so that the dot products score relevant patterns higher. The gradient of the loss with respect to W is itself computed via a dot product (the outer product of the upstream gradient and the input), which means dot products dominate both the forward and backward passes of every dense layer in a neural network.
🧠 Deep Dive: Scaled Dot-Product Attention in Transformers
The transformer's self-attention mechanism is where the dot product achieves its most elegant and impactful role. Every token in the input sequence is simultaneously a query, a key, and a value — and dot products between queries and keys determine the entire attention pattern.
The Internal Mechanics of Q·Kᵀ Token Scoring
Given input sequence X ∈ ℝᴺˣᵈ (N tokens, d-dimensional embeddings), three linear projections produce:
- Q = X**Wᵠ** ∈ ℝᴺˣᵈᵏ (queries — "what am I looking for?")
- K = X**Wᴷ** ∈ ℝᴺˣᵈᵏ (keys — "what do I advertise?")
- V = X**Wᵛ** ∈ ℝᴺˣᵈᵛ (values — "what do I actually contain?")
The score matrix S = Q**Kᵀ ∈ ℝᴺˣᴺ is a full pairwise dot product: entry S**[i, j] answers "how much should token i attend to token j?" This is N² dot products of length dₖ, computed in one batched matrix multiplication.
High S[i, j] means the query vector for position i and the key vector for position j point in similar directions — token i finds token j relevant. After a softmax normalisation, these scores become attention weights that are used to compute a weighted sum of the value vectors, producing the final attended representation.
The learnable weight matrices Wᵠ, Wᴷ, Wᵛ are trained so that the resulting dot products capture meaningful linguistic relationships: "the" attending to its noun head, a pronoun attending to its referent, a verb attending to its subject. The dot product is the computational primitive; the learned projections determine what kinds of similarity it scores.
Mathematical Model: Why Dividing by √dₖ Prevents Softmax Saturation
The full attention formula is:
Attention(Q, K, V) = softmax(Q**Kᵀ / √dₖ) V**
The √dₖ scaling factor is not cosmetic. When dₖ is large, the dot products in Q**Kᵀ** grow in expected magnitude — roughly proportional to √dₖ — because they sum dₖ independent random products. Without scaling, these large dot products push the softmax into a near-one-hot regime: one score dominates and all others collapse toward zero. The softmax gradient becomes negligibly small, and the model cannot learn diverse attention patterns.
Dividing by √dₖ standardises the scores back to approximately unit variance, keeping the softmax in its sensitive, high-gradient region across all values of dₖ. For dₖ = 64 (typical), the divisor is 8; for dₖ = 256, it is 16. Without it, GPT-2's 12 attention heads would all collapse to attending to a single token per position.
The intuition: the dot product of two d-dimensional unit vectors has expected value 0 and variance d. Dividing by √d normalises the variance to 1, making attention scores well-behaved regardless of head dimensionality.
Performance Analysis: The O(n²·dₖ) Cost of Attending to Everything
Computing Q**Kᵀ** costs O(n²·dₖ) in time and O(n²) in memory for the attention matrix. For a sequence of 512 tokens with dₖ = 64, that is 512² × 64 ≈ 16 million multiply-accumulate operations per attention head, per layer, per sample in the batch. A standard 12-layer transformer with 12 heads runs this ~2.3 billion times per forward pass.
The n² factor is the scaling wall that limits vanilla attention on long sequences. At n = 4,096 (GPT-4's context), the attention matrix is 4,096² × 4 bytes ≈ 64 MB per head — no longer fits in L2 cache. This is why flash attention (Dao et al., 2022) restructures the computation into tiled blocks that stay in SRAM, computing and immediately discarding intermediate results rather than materialising the full n × n matrix, reducing memory from O(n²) to O(n).
The dot product itself is O(d) per pair; the bottleneck is the n² factor in generating every pair. All efficient attention variants — linear attention, sparse attention, sliding-window attention — are fundamentally tricks for approximating the full Q·Kᵀ dot product matrix without computing all n² entries.
📊 Visualising Dot Product Flow Through the Full Attention Mechanism
The scaled dot-product attention pipeline moves from raw token embeddings through three parallel projections to a weighted context vector in four steps. The diagram below makes each step explicit:
flowchart TD
X["Input Embeddings X"] --> QP["Linear Projection to Q"]
X --> KP["Linear Projection to K"]
X --> VP["Linear Projection to V"]
QP --> QK["Matrix Multiply Q times K-transpose"]
KP --> QK
QK --> SC["Scale by 1 over sqrt of d_k"]
SC --> SM["Softmax over each row"]
SM --> WV["Weighted Sum with V"]
VP --> WV
WV --> OUT["Attention Output Z"]
Reading this diagram top to bottom: a single input matrix X feeds three separate linear projections, producing queries Q, keys K, and values V. The matrix multiply Q**Kᵀ is the core dot product step — it produces an N×N score grid where every cell is a dot product between one query and one key. Scaling by 1/√dₖ normalises the magnitudes, softmax converts raw scores into probability weights that sum to 1 across each row, and the final weighted sum with V produces the attended output Z. Each row of Z** is a context-aware blend of all value vectors, proportionally weighted by how relevant each key was to that row's query.
The critical insight from the diagram: the dot product appears exactly once (Q·Kᵀ), but it is the gating mechanism for the entire flow. Everything else — scaling, softmax, value mixing — is applied on top of dot-product scores.
🌍 Real-World Applications: Where Dot Products Do the Heavy Lifting
Semantic Search and Retrieval-Augmented Generation
Google's Universal Sentence Encoder and OpenAI's embedding APIs both return vectors designed so that semantic similarity equals geometric proximity. When you call text-embedding-ada-002 and compare two documents, you are computing dot products. Production retrieval-augmented generation (RAG) systems store millions of chunk embeddings in a vector database; at query time, the retrieval step is a maximum inner product search (MIPS) — find the k vectors with the highest dot product against the query embedding.
Airbnb's listing search (embedding 160M listings into dense vectors) and Spotify's music recommendation (similar artist/track embeddings) both rely on this: the dot product is the only similarity function fast enough to rank millions of candidates in real time.
Recommendation Systems: YouTube's Two-Tower Model
YouTube's Deep Neural Network for Recommendations (Covington et al., 2016) uses a two-tower architecture: one tower encodes the user's watch history into a user embedding u, another tower encodes each video into a video embedding v. The predicted relevance score is simply u · v. During training, the model pushes user and watched-video vectors to align (high dot product); during serving, approximate nearest-neighbour search finds the top-k videos with the highest dot product to the current user vector in milliseconds.
The dot product here has a direct business interpretation: its magnitude tells the system how much the user "points toward" this video in latent preference space.
Large Language Model Inference
In every GPT-style model during inference:
- The embedding lookup for each input token is a dot product between the one-hot token index and the embedding matrix — selecting the matching row.
- Each transformer layer computes Q·Kᵀ attention scores.
- Each MLP layer computes a weight matrix dot product with the hidden state.
- The final language model head scores all vocabulary tokens via a dot product between the hidden state and the vocabulary embedding matrix.
A single forward pass of GPT-3 (175B parameters, 96 layers) executes tens of trillions of dot product operations. The model's ability to predict the next token fluently is the aggregate result of every one of those dot products encoding which patterns in the input are relevant to the output.
⚖️ Dot Product vs. Cosine Similarity: When Magnitude Matters (and When It Doesn't)
The cosine similarity between two vectors a and b is:
cos(a, b) = a · b / (|a| × |b|)
It is the dot product normalised by the product of the magnitudes — stripping magnitude out entirely so the score depends only on direction. The choice between dot product and cosine similarity has concrete implications:
| Criterion | Dot Product | Cosine Similarity |
| Magnitude sensitivity | Yes — longer vectors score higher | No — magnitude cancelled out |
| Computational cost | O(d) | O(d) + 2 norm computations |
| When to use | Embeddings already L2-normalised | Raw embeddings with variable magnitude |
| Failure mode | Biased toward high-frequency or long documents | Treats short and long docs as equally relevant |
| Common usage | Production vector DBs with pre-normalised embeddings | TF-IDF document retrieval, un-normalised outputs |
The production rule: if you control the embedding model, normalise all output vectors to unit length at generation time (L2 normalisation: divide by magnitude). Then dot product and cosine similarity become mathematically identical (cos(θ) = â · b̂ when both are unit vectors), and you can use the faster dot product everywhere without worrying about magnitude bias.
Most modern embedding APIs (OpenAI, Cohere, Sentence-Transformers with normalize_embeddings=True) already return normalised vectors precisely for this reason. If your embeddings come from a custom model that does not normalise, use cosine similarity or add a normalisation step before indexing.
When magnitude should influence the score: If you are comparing product descriptions and want long, detailed listings to score higher than stub descriptions (more information = more relevant), a raw dot product naturally boosts longer vectors. This is occasionally useful in recommendation settings where "richer" profiles should dominate.
🧭 Decision Guide: Choosing Your Similarity Function
| Situation | Recommendation |
| Use dot product when | Embeddings are unit-normalised (OpenAI, Cohere, sentence-transformers with normalize=True); you need maximum inference speed; you are running FAISS IndexFlatIP or IndexIVFFlat with inner product |
| Use cosine similarity when | Embeddings are raw outputs without normalisation; you are comparing documents of highly variable length; you are prototyping and do not want to commit to a normalisation scheme |
| Avoid dot product when | Embeddings have large magnitude variance and you have not normalised — a long document will dominate rankings regardless of topic relevance |
| Edge case — negative dot products | Dot products can be negative (vectors pointing in opposite directions). Cosine similarity ranges [−1, 1] and makes this explicit; dot product magnitude is unbounded. If your downstream system expects non-negative scores, use cosine or apply a max(0, ·) clip |
The practical starting point for any new vector search project: normalise your embeddings at generation time and use inner product search everywhere. You lose nothing and gain simplicity.
🧪 Practical Examples: NumPy and PyTorch in Action
These examples demonstrate the dot product at three increasing levels of abstraction: basic geometry in NumPy, embedding similarity search, and the manual implementation of scaled dot-product attention in PyTorch. Each builds on the last to show how the same arithmetic operation powers both the simplest similarity check and the full transformer attention mechanism.
Example 1 — Geometric Intuition and Embedding Similarity with NumPy
This first example makes the geometry tangible. We create two 3-dimensional vectors, compute their dot product and cosine similarity, and verify that the angle between them matches what we expect from the values. This is the exact calculation a vector database runs at scale.
import numpy as np
# Two mock sentence embeddings (normally 768-d or 1536-d; kept 3-d for clarity)
apple = np.array([0.9, 0.3, 0.1]) # "apple fruit" embedding
banana = np.array([0.8, 0.4, 0.15]) # "banana fruit" embedding
computer = np.array([-0.1, 0.2, 0.9]) # "computer hardware" embedding
# Raw dot product
print("apple · banana :", np.dot(apple, banana)) # ~0.8 — high similarity
print("apple · computer:", np.dot(apple, computer)) # ~-0.01 — low similarity
# Cosine similarity (dot product of L2-normalised vectors)
def cosine_sim(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print("cos(apple, banana) :", cosine_sim(apple, banana)) # ~0.99
print("cos(apple, computer):", cosine_sim(apple, computer)) # ~-0.01
# Batch similarity — find the most similar vector to a query (MIPS)
corpus = np.stack([apple, banana, computer]) # shape: (3, 3)
query = np.array([0.85, 0.35, 0.12]) # "tropical fruit" query
scores = corpus @ query # shape: (3,) — one dot product per corpus vector
best_match = np.argmax(scores)
print("Most similar to query:", ["apple", "banana", "computer"][best_match]) # "apple"
Notice that corpus @ query is a matrix–vector product — it computes the dot product between the query and every row of the corpus matrix in a single vectorised call. This is exactly how nearest-neighbour retrieval works before approximate indexing is introduced.
Example 2 — Scaled Dot-Product Attention in PyTorch
This example manually implements scaled dot-product attention, then validates it against PyTorch's own F.scaled_dot_product_attention function. Walking through the manual version makes every component visible — the Q·Kᵀ dot product, the √dₖ scaling, the softmax, and the value weighting.
import torch
import torch.nn.functional as F
import math
torch.manual_seed(42)
batch, seq_len, d_k, d_v = 2, 6, 64, 64 # 2 samples, 6 tokens, 64-dim keys and values
# Simulate Q, K, V projections (normally produced by learned linear layers)
Q = torch.randn(batch, seq_len, d_k)
K = torch.randn(batch, seq_len, d_k)
V = torch.randn(batch, seq_len, d_v)
# --- Manual scaled dot-product attention ---
# Step 1: raw dot product scores → shape: (batch, seq_len, seq_len)
scores = torch.bmm(Q, K.transpose(1, 2)) # Q·Kᵀ
print("Max score before scaling:", scores.abs().max().item()) # large values
# Step 2: scale by 1/√d_k to prevent softmax saturation
scores = scores / math.sqrt(d_k)
print("Max score after scaling:", scores.abs().max().item()) # ~N(0,1) range
# Step 3: softmax converts scores to attention weights (rows sum to 1)
attn_weights = F.softmax(scores, dim=-1) # shape: (batch, seq_len, seq_len)
# Step 4: weighted sum of value vectors
output_manual = torch.bmm(attn_weights, V) # shape: (batch, seq_len, d_v)
# --- PyTorch built-in (same result, fused CUDA kernel) ---
output_builtin = F.scaled_dot_product_attention(Q, K, V)
# Verify: both outputs are numerically identical
max_diff = (output_manual - output_builtin).abs().max().item()
print(f"Max difference between manual and built-in: {max_diff:.2e}") # ~1e-6
# Inspect the attention weight matrix for one sample
sample_attn = attn_weights[0] # shape: (6, 6)
print("Row sums (should all be 1.0):", sample_attn.sum(dim=-1).tolist())
print("Attention from token 0 to each token:", sample_attn[0].tolist())
The key observation: torch.bmm(Q, K.transpose(1, 2)) computes a batch of matrix products — for each sample in the batch, it computes the full N×N dot product matrix between all query–key pairs. This is the O(n²·dₖ) operation that defines standard attention complexity. The F.scaled_dot_product_attention call uses FlashAttention under the hood (on CUDA GPUs) to compute the same result without materialising the full attention matrix.
🛠️ PyTorch and FAISS: The OSS Stack That Runs on Dot Products
PyTorch — torch.matmul, F.scaled_dot_product_attention, and nn.MultiheadAttention
PyTorch exposes the dot product at three levels of abstraction:
torch.dot(a, b)— scalar dot product of two 1-D tensorstorch.matmul(A, B)/A @ B— general matrix multiplication (batch-aware, CUDA-accelerated); this is the primitive that runs inside everynn.LinearlayerF.scaled_dot_product_attention(Q, K, V)— fused attention kernel introduced in PyTorch 2.0; uses FlashAttention on CUDA, math attention on CPU; automatically handles batching, masking, and dropout
nn.MultiheadAttention wraps the scaled dot-product attention with multi-head projection, making it the standard drop-in for transformer encoder/decoder blocks. Every model you load from Hugging Face — BERT, GPT-2, LLaMA, T5 — uses this exact path internally.
import torch.nn as nn
# Multi-head attention: 8 heads, 512-d model
mha = nn.MultiheadAttention(embed_dim=512, num_heads=8, batch_first=True)
x = torch.randn(2, 10, 512) # batch=2, seq_len=10, embed=512
out, attn_weights = mha(x, x, x) # self-attention: Q=K=V=x
print(out.shape) # torch.Size([2, 10, 512])
For a full deep-dive on PyTorch's attention implementation and FlashAttention, see Attention Mechanism Explained.
FAISS — GPU-Accelerated Inner Product Search at Scale
Facebook AI Similarity Search (FAISS) is the production-grade library for running maximum inner product search (MIPS) over millions or billions of vectors. Its IndexFlatIP index computes the exact dot product between the query and every stored vector — optimal for small-to-medium corpora (< 1M vectors). For large corpora, IndexIVFFlat partitions the vector space into Voronoi cells, reducing the number of dot products needed at query time by 10–100×.
import faiss
import numpy as np
d = 128 # embedding dimension
n = 100_000 # corpus size
k = 5 # top-k results
# Build corpus of L2-normalised embeddings
corpus = np.random.randn(n, d).astype("float32")
faiss.normalize_L2(corpus) # normalise so inner product == cosine sim
# Build an exact inner product index
index = faiss.IndexFlatIP(d)
index.add(corpus) # O(n·d) — indexes all vectors
# Query
query = np.random.randn(1, d).astype("float32")
faiss.normalize_L2(query)
scores, indices = index.search(query, k) # O(n·d) dot products; <1ms for 100K vectors
print("Top-5 scores:", scores)
print("Top-5 indices:", indices)
FAISS offloads the inner product computation to optimised BLAS routines on CPU and cuBLAS on GPU. On a single A100, it can search 1 billion 128-dimensional vectors in under a second. For a full deep-dive on how vector databases wrap FAISS with persistence, filtering, and hybrid search, see A Beginner's Guide to Vector Database Principles.
📚 Lessons Learned from Production ML Systems
Normalise once, search fast forever. The most common vector similarity bug in production is mixing normalised and un-normalised embeddings in the same index. If the embedding model changes and the new model does not normalise its outputs, dot product scores become meaningless — high-magnitude vectors dominate all rankings. The fix: normalise at embedding generation time and add a unit-norm assertion in the ingestion pipeline.
The √dₖ scaling factor is not optional. Teams that implement attention from scratch occasionally omit the 1/√dₖ divisor "to simplify." The result is that softmax collapses to near-one-hot weights as soon as the sequence gets longer or dₖ increases. Training still converges, slowly, but the model learns much more uniform attention patterns than it could with proper scaling. Always include it.
Dot product is cheap; the n² attention matrix is not. When you hit memory limits on long sequences, the bottleneck is always the attention matrix, not the embedding size. Flash attention or chunked attention implementations solve this. Do not try to reduce d first — it is rarely the bottleneck.
Inner product search != cosine similarity unless you normalise. A production team at a large e-commerce company once reported that their "cosine similarity" vector search was producing worse results than expected after switching embedding models. The root cause: the old model normalised outputs; the new model did not. FAISS IndexFlatIP computes inner products, not cosine similarity. The fix was a single faiss.normalize_L2(embeddings) call before indexing.
Quantisation preserves dot product structure. INT8 quantisation of embedding vectors reduces storage 4× and speeds up inner product computation on modern CPUs/GPUs with negligible recall loss (< 1% degradation at top-10 retrieval). FAISS IndexFlatIP with ScalarQuantizer(d, ScalarQuantizer.QT_8bit) is a production-ready path.
📌 Summary and Key Takeaways
- The dot product multiplies corresponding elements of two vectors and sums them: a · b = Σ aᵢbᵢ. Geometrically, this equals |a| |b| cos(θ) — a projection-based similarity score.
- Vectors that point in the same direction have a high positive dot product; perpendicular vectors score zero; opposite vectors score negative. This geometric property is why dot products measure semantic similarity.
- Every neuron in a fully connected layer computes a dot product between its weight vector and the input. A full layer is a matrix multiplication — a batch of dot products in one GPU kernel.
- Transformer attention uses Q·Kᵀ to score every pair of tokens simultaneously. Dividing by √dₖ prevents softmax saturation when the key dimensionality is large.
- Cosine similarity is the dot product normalised by both vector magnitudes. If you pre-normalise all embeddings to unit length, cosine similarity and dot product become identical, so you can use the faster inner product everywhere.
- FAISS
IndexFlatIPruns exact dot-product search at production scale; approximate indexes (IndexIVFFlat,IndexHNSW) trade a small recall loss for 10–100× speedup on large corpora. - The practical takeaway: normalise your embeddings at generation time, use inner product search everywhere, and always include the √dₖ scaling factor when implementing attention.
🔗 Related Posts
- Attention Mechanism Explained: How Transformers Learn to Focus — The full story of multi-head attention, Q/K/V projections, and why transformers replaced RNNs
- Why Embeddings Matter: Solving Key Issues in Data Representation — How dense vector representations encode semantic meaning and enable the dot-product similarity this post relies on
- A Beginner's Guide to Vector Database Principles — How FAISS, Pinecone, and Weaviate scale dot-product search to billions of vectors with approximate indexing
- How Transformer Architecture Works: A Deep Dive — From positional encoding through multi-head attention and feed-forward layers — the full transformer stack
Test Your Knowledge
Ready to test what you just learned?
AI will generate 4 questions based on this article's content.

Written by
Abstract Algorithms
@abstractalgorithms
More Posts
Softmax Function Explained: From Raw Scores to Probabilities
TLDR: Softmax converts a vector of raw scores (logits) into a valid probability distribution by exponentiating each value and dividing by the total. Subtracting the max before exponentiating prevents floating-point overflow. Temperature scaling contr...
Fine-Tuning LLMs with LoRA and QLoRA: A Practical Deep-Dive
TLDR: LoRA freezes the base model and trains two tiny matrices per layer — 0.1 % of parameters, 70 % less GPU memory, near-identical quality. QLoRA adds 4-bit NF4 quantization of the frozen base, enabling 70B fine-tuning on 2× A100 80 GB instead of 8...
RAG vs Fine-Tuning: When to Use Each (and When to Combine Them)
TLDR: RAG gives LLMs access to current knowledge at inference time; fine-tuning changes how they reason and write. Use RAG when your data changes. Use fine-tuning when you need consistent style, tone, or domain reasoning. Use both for production assi...
Build vs Buy: Deploying Your Own LLM vs Using ChatGPT, Gemini, and Claude APIs
TLDR: Use the API until you hit $10K/month or a hard data privacy requirement. Then add a semantic cache. Then evaluate hybrid routing. Self-hosting full model serving is only cost-effective at > 50M tokens/day with a dedicated MLOps team. The build ...
