Back to Blog
embeddingsAIreduce_costs

Embeddings, concretely

What a text embedding actually is as a point in R^n, why cosine similarity works, how the models are trained, and the vector-footprint math that decides your storage bill.

JohannaJanuary 27, 20266 min read

An embedding is a list of numbers. That sounds glib, but it's the whole thing, and most of the confusion around vector search evaporates once you take it literally. When a model embeds the sentence "the invoice is overdue," it produces something like [0.021, -0.34, 0.11, ...] with, say, 1,536 entries. That list is a coordinate — a single point in a 1,536-dimensional space. Every piece of text you embed becomes a point in the same space, and the entire game is arranging those points so that texts meaning similar things land near each other. Everything else — cosine similarity, ANN indexes, the storage bill — follows from that one fact.

A learned point in R^n

Picture a 2D plane. You could hand-place points so "dog" and "puppy" sit close together, "dog" and "wolf" a bit further, "dog" and "invoice" on the far side of the map. Two dimensions can't hold enough distinctions to be useful for language, so we use hundreds or thousands instead. The dimensionality is just how many numbers are in each list, and it sets a ceiling on how many independent distinctions the space can represent.

The word learned is doing the work. Nobody assigns these coordinates by hand. A neural network is trained to produce them, and the coordinates have no individual meaning — dimension 843 is not "formality" or "tense." Meaning lives in the geometry of the whole vector, in the directions and relative distances between points, not in any single axis. This is why you can't read an embedding, only compare it to other embeddings.

Why cosine similarity works

To ask "how similar are these two texts," you ask "how close are their two points." The near-universal choice is cosine similarity: the cosine of the angle between the two vectors, ignoring their lengths. Two vectors pointing the same direction score 1.0; perpendicular, 0.0; opposite, -1.0.

Why the angle and not the straight-line distance? Because embedding models are trained to put meaning in direction. A longer document and a short query about the same topic might have very different vector magnitudes but point the same way; cosine ignores the magnitude and reads only the orientation. In practice most modern models output vectors already normalized to unit length, at which point cosine similarity and Euclidean distance rank results identically — the choice becomes a formality. The reason "similar text lands nearby" isn't magic: the training objective made it true, by construction, as we'll see next.

How the models are trained: contrastive learning

Embedding models learn by example, from pairs of texts that should be close and pairs that should be far. The training signal is contrastive: pull positive pairs together, push negative pairs apart. Show the model a question and its correct answer (a positive pair) alongside a batch of unrelated passages (negatives), and adjust the weights so the positive pair's vectors move closer and the negatives move away. The clever, cheap trick that makes this scale is in-batch negatives — for any positive pair in a training batch, every other item in the batch is treated as a negative, so a batch of 1,024 examples yields on the order of a million contrasts for free.

Do that over hundreds of millions of pairs — search queries and clicked results, questions and answers, translated sentence pairs, adjacent passages — and the space self-organizes. "Nearby means similar" is not an emergent surprise; it is the literal loss function, minimized. That framing also explains the failure modes. Rare exact tokens (a part number, an error code) get pulled toward whatever semantic neighborhood the model saw them in during training, which is why embeddings are bad at exact-match retrieval and why you pair them with keyword search.

The curse of dimensionality (and why it mostly doesn't bite)

There's a real reason high-dimensional geometry is unintuitive: as dimensions grow, distances concentrate. Pick random points in a 1,000-dimensional cube and the nearest and farthest are almost the same distance apart, which sounds fatal for nearest-neighbor search. The saving grace is that embeddings don't fill the space uniformly — trained embeddings live on a much lower-dimensional manifold inside the high-dimensional space, a crumpled sheet rather than a filled volume. The distances that matter are along that sheet, where meaningful structure survives. The curse is why you can't just crank dimensions arbitrarily and expect free quality; it's not why vector search fails, because real embeddings are far from random.

Choosing a model: dimensions vs. quality vs. cost

The market gives you a spread. OpenAI's text-embedding-3-small outputs 1,536 dimensions and -3-large outputs 3,072; Cohere's embed v3 and AWS Titan Text Embeddings V2 sit around 1,024; the small open-source all-MiniLM-L6-v2 produces a compact 384. Bigger models and bigger vectors generally retrieve better, up to a point of sharply diminishing returns.

Here's the part people skip: dimensionality is not a free quality dial, because it directly sets your storage and memory cost, which is the rest of this article. A 3,072-dimensional model isn't twice as good as a 1,536-dimensional one, but it does cost twice as much to store and — in a RAM-resident index like HNSW — twice as much to serve. Pick the smallest model that clears your recall bar on your data, not the top of a leaderboard built on someone else's.

The vector footprint math nobody quotes

A vector's storage cost is boringly computable. Default embeddings are 32-bit floats: 4 bytes per dimension. So a single 1,536-dimensional vector is 1,536 x 4 = 6,144 bytes, call it 6 KB. For a corpus:

bytes = num_vectors x dimensions x bytes_per_dimension

10,000,000 vectors x 1,536 dims x 4 bytes = ~61 GB (raw, fp32)

Sixty-one gigabytes of raw vectors before you add the index overhead, which for HNSW roughly doubles it (the graph stores neighbor links per node). That's the number that turns a "just use a bigger embedding model" decision into a real line item: on an in-memory index, that 61 GB has to sit in RAM, and RAM is the expensive part of the bill.

Quantization and truncation: cutting the bill

Three levers shrink vectors, and they compose:

  • int8 quantization stores each dimension as one byte instead of four — a flat 4x reduction, with recall loss usually in the low single-digit percentages after calibration. The 61 GB above drops to ~15 GB.
  • Binary quantization goes further: one bit per dimension, a 32x reduction. Similarity becomes Hamming distance, which hardware computes blazingly fast. Recall drops more, so the standard pattern is to retrieve a wide candidate set with binary vectors and rescore the top few hundred with the full-precision vectors — most of the savings, most of the accuracy.
  • Matryoshka truncation is the elegant one. Models trained with Matryoshka representation learning pack the most important information into the earliest dimensions, so you can simply truncate a 1,536-dimension vector to its first 512 or 256 and keep most of the retrieval quality. OpenAI's v3 models and Titan V2 support this directly — you request a shorter vector and store a third of the bytes.

Stack them and the arithmetic gets pleasant. Take the 3,072-dimension model you wanted for quality, Matryoshka-truncate to 1,024, int8-quantize, and you're at 1 byte x 1,024 dims = 1 KB per vector instead of 12 KB — a 12x cut — with a recall hit you can measure and decide to accept. That single decision, made deliberately instead of by default, is often the difference between a vector store that fits on one machine and one that needs a sharded cluster. Embeddings are just lists of numbers; the size of the list is a cost you get to choose.