Budget every stage between a document and an answer
Embedding weights, vector payload, index links and reranking compute are separate bills in a retrieval pipeline.
AI-assisted learning visualization · schematic article explanation
The embedder’s weights are stored once, while every document chunk contributes a vector. Wider vectors multiply corpus payload; HNSW links, metadata and original vectors retained for rescoring add separate bytes.
Every "we'll just add RAG" decision is a series of arithmetic problems in a trench coat: how much VRAM the embedding model needs, how many bytes the vector index eats, how many distance comparisons a query costs, and whether the whole pipeline lands inside your latency budget. This guide does the math with numbers from primary sources — Hugging Face model cards, NVIDIA datasheets, Qdrant's official documentation, and live API pricing — so you can size the infrastructure before you believe the demo.
The embedding model is the cheap part — until it isn't
Three open embedding models spanning small, mid and large, parameter counts read straight from the Hugging Face repositories (fp16 = 2 bytes per parameter, the same convention as our quantization guide):
| Model | Params (HF) | fp16 weights | Embedding dim | Output tokens? |
|---|---|---|---|---|
| BAAI/bge-base-en-v1.5 | 109,482,7521 | 0.22 GB | 768 | — |
| BAAI/bge-m3 | 567,755,7772 | 1.14 GB | 1024 (+ sparse) | — |
| intfloat/e5-mistral-7b-instruct | 7,110,660,0963 | 14.22 GB | 4096 | — |
The bge-m3 model file is 2,271,145,830 bytes4. Dividing its size by four gives roughly 567.8M fp32 values, but serialized file size includes metadata and is not an exact parameter count. The e5-mistral checkpoint ships fp16 safetensors totaling about 14.2 GB3.
The GPU-class consequences at fp16:
- bge-base (0.22 GB) runs on anything — a laptop GPU, a CPU, a Jetson. Memory is not the constraint; throughput is.
- bge-m3 (1.14 GB) fits on any data-center GPU with room to spare; even 8 GB cards carry the model plus large batches.
- e5-mistral-7b (14.22 GB) needs memory for weights, activations and batches. A 24 GB RTX 4090 or A30, a 48 GB L40S, or an A100 40/80 GB provides more room than a 16 GB card; check the actual sequence length and batch.
The expensive tier is not about quality-per-dollar at serving time — it is about the vector width it produces. A 4096-dim model quadruples every downstream storage bill versus 1024-dim bge-m3, at bench scores that improve by single-digit points5.
Bytes per vector: where the real storage bill lives
For model-specific context on pooling and vector dimensions, see the EmbeddingGemma 2 architecture guide. Nemotron 3.5 Embed shows how a frozen text tower can preserve an existing text index while a trained visual path enters the same space; compare both single-vector designs with the pplx-embed-v2 guide to asymmetric retrieval and MaxSim. The OlmoEarth v1.3 guide shows a different production case: a trained 128/64-dimensional prefix whose Zarr chunk geometry determines whether truncation also saves network I/O.
Raw embedding storage is embarrassingly simple arithmetic: bytes per vector = dimensionality × bytes per component. As of 2026, Qdrant stores vectors internally as 32-bit floats by default6.
| Dim (model class) | fp32 (4 B) | fp16 (2 B) | int8 (1 B) |
|---|---|---|---|
| 768 (bge-base, MiniLM class) | 3.0 KiB | 1.5 KiB | 0.75 KiB |
| 1024 (bge-m3, e5-large class) | 4.0 KiB | 2.0 KiB | 1.0 KiB |
| 1536 (OpenAI text-embedding-3-small) | 6.0 KiB | 3.0 KiB | 1.5 KiB |
| 3072 (text-embedding-3-large) | 12.0 KiB | 6.0 KiB | 3.0 KiB |
| 3584 (gte-Qwen2-7B) | 14.0 KiB | 7.0 KiB | 3.5 KiB |
| 4096 (e5-mistral-7b) | 16.0 KiB | 8.0 KiB | 4.0 KiB |
Corpus sizing — assume 512-token chunks, so 500M tokens of source text ≈ 1M chunks, 50B tokens ≈ 98M chunks. The index-memory table for bge-class 1024-dim vectors:
| Chunks | fp32 raw | int8 (4×) | PQ, 0.5 B/comp (8×) |
|---|---|---|---|
| 1M | 4.10 GB | 1.02 GB | 0.51 GB |
| 10M | 40.96 GB | 10.24 GB | 5.12 GB |
| 100M | 409.60 GB | 102.40 GB | 51.20 GB |
And for 4096-dim (7B-class embedder) the same table reaches 1.64 TB raw at 100M chunks — 4× the vector payload. The HNSW graph adds memory: with 4-byte neighbor IDs and up to 2 × m links on layer 0, M=16 gives a rough 128 B per vector for those links alone, or 12.8 GB at 100M vectors. Upper levels, allocator overhead and payload storage add more; this is a sizing example, not an exact Qdrant footprint7.
For vector payloads, float32 → uint8 scalar quantization is 4× compression; binary encoding can reach 32× and product quantization depends on its configuration6. These ratios exclude index overhead and any original vectors retained for rescoring. Weaviate documents PQ for reducing vector memory8, and Milvus offers multiple quantized index types9. Recall and latency effects depend on the dataset, query distribution, search settings and rescoring; benchmark them on your corpus.
Latency decomposition: flat brute force vs HNSW
Flat search compares a query with every vector, so its comparison count is N. HNSW is approximate and aims to visit a much smaller portion of the graph, but ef × log₂(N) is not a reliable formula for the number of distance calculations or latency. Its cost depends on graph structure, dimensionality, search parameters and required recall; worst-case instances can take linear work10.
For 1M 1024-dimensional float32 vectors, an exact scan performs roughly 1M × 2 × 1024 ≈ 2 GFLOP and reads about 4.1 GB of vector payload. Even at an assumed 100 GB/s sustained memory bandwidth, reading that payload alone takes at least ~41 ms; CPU SIMD does not erase memory traffic. Qdrant's full_scan_threshold_kb selects brute force for small candidate sets7. Whether exact search remains viable at 1M or 10M vectors depends on hardware, filters, parallelism, QPS and latency targets.
For absolute QPS claims, the only numbers we cite are vendor-run and labeled as such: Qdrant publishes a dedicated HNSW search benchmark repository for exactly this purpose11, and their large-scale tutorial — 400M CLIP vectors, binary quantization, M=6 — reports a specific, reproducible resource breakdown (23.8 GB quantized vectors + 17.9 GB HNSW graph in RAM for the whole 400M)12. Treat any single-digit-milliseconds-per-query figure from an unconstrained benchmark with skepticism unless the hardware, ef and recall are stated.
Embedding is prefill, not decode — and that changes the math
The KV-cache intuition from our KV cache guide misleads people here, so state it precisely: an embedding forward pass over a 512-token chunk is pure prefill. One shot, all positions at once, no decode steps, no KV cache stored, no memory-bound token-by-token loop. Embedding throughput is compute-bound in a way generation never is.
The FLOPs per chunk are the standard forward-pass estimate, 2 × params × tokens:
- bge-m3 (568M params), 512-token chunk:
2 × 567,755,777 × 512 ≈ 581 GFLOP - e5-mistral-7b (7.11B params), 512-token chunk:
2 × 7,110,660,096 × 512 ≈ 7,281 GFLOP
Ceilings at spec TFLOPS from NVIDIA datasheets — dense fp16, assuming an honest 50% of peak (model-kernel MFU on well-batched encoder input):
| GPU | fp16 dense (datasheet) | bge-m3 tok/s | e5-mistral-7b tok/s |
|---|---|---|---|
| L40S | 362 TFLOPS13 | ~159,400 | ~12,700 |
| A100 80 GB | 312 TFLOPS14 | ~137,400 | ~11,000 |
The L40S number deserves its skepticism: 362 TFLOPS is the dense fp16 figure; 1,466 TFLOPS is the sparse FP8 figure13. Under the assumed 50% utilization and the linear-layer approximation, a 1M-chunk (500M-token) corpus takes a ~52-minute compute floor (500 × 10⁶ / 159,400 ≈ 52 min). Attention, tokenization, I/O and inefficient batching increase actual time.
Rerankers: the second stage that quietly owns your budget
Hybrid sparse+dense retrieval is the 2026 default because it works — dense vectors miss exact keywords; BM25-style sparse misses paraphrase; bge-m3 returns both from one forward pass2. But the accuracy lever people actually feel is the cross-encoder reranker, and it is the most mis-budgeted component in RAG.
A cross-encoder scores each pair separately: full forward pass over concatenated query + candidate. Per candidate pair with a 280M-class reranker (bge-reranker-base, 278,044,931 params15) over 32 query tokens + 512 doc tokens:
2 × 278M × 544 ≈ 302 GFLOP per pair
Scoring the top-100 candidates costs 100 × 302 GFLOP ≈ 30.3 TFLOP under this linear-layer approximation. On one L40S at an assumed 50% of 362 TFLOPS, that is a ~167 ms compute lower bound, before overhead and attention. Actual GPU and CPU times require measurement; choose the candidate count to meet your measured latency and quality targets.
The two-stage latency budget, per query, at realistic scale:
- Query embedding: measure batching, tokenizer and model latency
- Vector retrieval: measure index, filter selectivity, search width and recall together
- Rerank 100 candidates @ 278M parameters: ~167 ms compute floor under the stated L40S utilization assumption
- Generation: measure prefill and decode separately at the intended context length
In this worked example reranking can dominate retrieval latency; a different index, candidate count or model can change that ordering. Measure every stage of the actual pipeline.
API vs self-host: the break-even is not where you think
Live pricing, per 1M input tokens: OpenAI text-embedding-3-small at $0.02, text-embedding-3-large at $0.1316. Batch API halves both16. Embeddings bill input only — no output tokens, because there are none.
For an illustrative rented L40S at an assumed $1.20/hour, the earlier idealized bge-m3 rate of ~159,400 tok/s (50% of stated compute throughput) corresponds to 574M tokens per hour before tokenization, I/O, padding or attention overhead. The resulting compute-only cost per 1M tokens would be:
$1.20 / 574 ≈ $0.0021
That hypothetical compute-only figure is below either API price, but it is not a quote or a measured cost. Hourly billing, startup, CPU/host charges, utilization and model-quality differences can reverse the result.
The table applies the assumptions above with fractional GPU-hour billing; a one-hour minimum would make the 10M-token GPU entry $1.20:
| Embedding volume | GPU-hours/month (bge-m3) | GPU cost @$1.20/h | API 3-small | API 3-large |
|---|---|---|---|---|
| 10M tok/mo | 0.02 h | $0.02 | $0.20 | $1.30 |
| 1,000M tok/mo | 1.74 h | $2.09 | $20.00 | $130.00 |
| 10,000M tok/mo | 17.4 h | $20.91 | $200.00 | $1,300.00 |
Reading the table without wishful thinking:
- A rented GPU may be cheaper when work can be batched into paid GPU time, the measured model throughput approaches the assumed rate, and extra hosting costs are modest. Data-boundary requirements can also decide the deployment independently of price.
- The API can be simpler and cheaper for sporadic work. At current listed rates, 500M tokens on text-embedding-3-small cost $10 standard or $5 through Batch. Compare that with an actual GPU quote and minimum billing increment.
- At 10M tokens/month, the idealized compute demand in the table is about one minute, but an always-on rental would be billed for a month. The available billing granularity matters more than the arithmetic minimum.
And the quality caveat: quality parity is model-dependent. If you self-host bge-base because it's free, and your baseline is 3-large, the retrieval quality drop eats any infra savings. Match models before comparing prices.
Verdict, with pitfalls stated plainly
There is no universal token-volume crossover. The table itself gives a lower hypothetical rented-GPU cost than the API even at 10M tokens when short rentals are available; a monthly standing rental can instead cost much more. Compare actual prices, billing increments, measured throughput and retrieval quality for the same dataset. Data-boundary requirements may rule out an API regardless of cost.
The honest pitfalls:
- "RAG fixes hallucinations" is a category error. Retrieval grounds generation in what's indexed; if the correct chunk isn't retrieved, the model hallucinates confidently anyway. RAG moves the failure mode from "model doesn't know" to "retrieval missed it" — bounded by recall on your corpus, not benchmark MTEB scores.
- Recall loss is paid twice: approximate HNSW (ef too low) drops recall, quantization drops it further, and each loss compounds silently because the failure surface looks identical to "the model made it up."
- Chunking and model choice interact. Test chunk size, overlap, lexical and dense retrieval on a labeled query set before deciding which component matters most.
- The 7B embedder's 4096-dim vectors cost 4× storage and 4× flat-search time forever. Quality is rented; the vector width is bought.
The math here is the easy part. Every number above is a floor, not a forecast — real pipelines lose to network hops, serialization, and idle utilization. Compute the floors first, then measure the reality against them.
Footnotes
Footnotes
-
BAAI/bge-base-en-v1.5 — Hugging Face model repository, safetensors total 109,482,752 parameters (fp32 checkpoint, 437,955,512 bytes): https://huggingface.co/BAAI/bge-base-en-v1.5 ↩
-
BAAI/bge-m3 — Hugging Face model card: 1024 dims, 8192-token context, dense + sparse + multi-vector outputs from one forward pass: https://huggingface.co/BAAI/bge-m3 ↩ ↩2
-
intfloat/e5-mistral-7b-instruct — Hugging Face model repository, fp16 safetensors total 7,110,660,096 parameters across two shards: https://huggingface.co/intfloat/e5-mistral-7b-instruct ↩ ↩2
-
bge-m3 model file listing; serialized file size is only an approximate proxy for the number of fp32 values: https://huggingface.co/BAAI/bge-m3/tree/main ↩
-
Alibaba-NLP/gte-Qwen2-7B-instruct — Hugging Face model card: 3584 dims, 32k max input, fp32 weights 26.45 GB listed, MTEB(56) 70.24 vs bge-base-en-1.5 64.23: https://huggingface.co/Alibaba-NLP/gte-Qwen2-7B-instruct ↩
-
Qdrant documentation, "Quantization" — storage formats, rescoring and dataset-dependent accuracy: https://qdrant.tech/documentation/manage-data/quantization/ ↩ ↩2
-
Qdrant documentation, "Indexing" — HNSW connection formula (m edges upper levels, 2×m on level 0, 4-byte links),
full_scan_threshold_kb, and default m=16, ef_construct=100: https://qdrant.tech/documentation/concepts/indexing/ ↩ ↩2 -
Weaviate documentation, product quantization for reducing memory required to store vectors: https://weaviate.io/developers/weaviate/concepts/vector-quantization ↩
-
Milvus documentation, index types incl. scalar (IVF_SQ8), product (IVF_PQ) and binary/RaBitQ (IVF_RABITQ) quantization: https://milvus.io/docs/index.md ↩
-
Indyk and Xu, "Worst-case Performance of Popular Approximate Nearest Neighbor Search Implementations" (2023), including HNSW worst-case examples: https://arxiv.org/abs/2310.19126 ↩
-
Qdrant official HNSW benchmark repository (search_benchmark, memory_benchmark, filter_speed_benchmark): https://github.com/qdrant/benchmark ↩
-
Qdrant tutorial, "Large-Scale Search" — 400M 512-dim vectors: 23.84 GB quantized vectors + ~17.9 GB HNSW graph (M=6) in RAM, with full memory breakdown: https://qdrant.tech/documentation/tutorials-operations/large-scale-search ↩
-
NVIDIA L40S product page and datasheet — FP16 Tensor Core 362.05 TFLOPS dense (listed directly), 733 TFLOPS with 2:4 sparsity; FP8 733 dense | 1,466 sparse; 91.6 TFLOPS FP32: https://www.nvidia.com/en-us/data-center/l40s/ ↩ ↩2
-
NVIDIA A100 datasheet — 312 TFLOPS fp16 dense tensor core, 80 GB HBM2e at >2 TB/s: https://www.nvidia.com/en-us/data-center/a100/ ↩
-
BAAI/bge-reranker-base — Hugging Face model repository, 278,044,931 parameters: https://huggingface.co/BAAI/bge-reranker-base ↩
-
OpenAI platform pricing — text-embedding-3-small $0.02/1M input tokens, text-embedding-3-large $0.13/1M, Batch API 50% (retrieved live September 2026): https://platform.openai.com/docs/pricing ↩ ↩2