Qdrant 1.19 | TurboQuant & Memory Tiers: Store More, Spend Less Qdrant 1.19 introduces the Turbo4 datatype, a 4-bit-only storage format that eliminates the float32 copy, and a unified memory tier system, reducing storage footprint by 9x compared to the prior TurboQuant approach. For 10 million 1536-dimension vectors, storage drops from ~64 GB to ~7 GB, addressing high RAM costs in vector databases. If you’ve been running a vector database at any real scale, you’ve probably hit the moment where you open your cloud bill and just… stare at it. RAM is expensive. Disk is cheap. And somehow you ended up with a setup where 80% of your AWS bill is memory for a collection that gets queried maybe 50 times a day. Qdrant http://qdrant.tech/?utm medium=referral&utm source=stars&utm campaign=devrel&utm content=niranjan-akella 1.19 has two features that directly attack this problem: the Turbo4 datatype a 4-bit-only storage format with no full-precision copy and a unified memory tier system that replaces a confusing pile of scattered boolean flags with one clean parameter. Together they give you real control over where your data lives and how much RAM you’re actually paying for. Lets’ Gid Deeper Guys Here’s the thing about vector search at scale: the math is brutal. Say you’re storing OpenAI text-embedding-3-small embeddings. Those are 1536 dimensions at float32. One vector = 6,144 bytes. 10 million vectors = ~57 GB . And that's just the raw vectors - you still need RAM for the HNSW graph which is proportional to vector count , quantized copies, payload indexes, etc. Before 1.19, Qdrant’s “standard” setup for large collections looked like this: store float32 vectors on disk, keep a quantized say, int8 copy pinned in RAM for graph traversal, rescore against the float32 originals for final ranking. Smart pattern But your disk footprint was float32 size + quantized size. For a 4-bit TurboQuant copy that's 32 bits + 4 bits = 36 bits per dimension total. And the memory config API was… not great, honestly. You had on disk: true on VectorParams for the original vectors, always ram: true on quantization configs for the compressed copy, and on disk payload: true at the collection level for payloads. Three different boolean flags, none of them named consistently, none of them applying to HNSW links at all those were always heap-loaded with no control . Qdrant 1.19 fixes both of these. TurboQuant itself shipped in Qdrant 1.18 and it’s a quantization method based on a technique from Google Research that applies a random rotation to your vectors before compressing them. The rotation redistributes information evenly across all dimensions, so when you quantize down to 4 bits 16 discrete levels per dimension , you’re losing a tiny bit from every dimension rather than potentially destroying a few high-signal ones. That’s the key insight, and it makes 4-bit quantization dramatically more accurate than naive 4-bit approaches. In 1.18, TurboQuant was a quantization method sitting on top of float32 storage. Your float32 vectors lived on disk, and the 4-bit compressed copy lived in RAM for fast graph traversal. You could rescore against the originals. Nice. In 1.19, Turbo4 is a datatype, not just a quantization layer. python from qdrant client import QdrantClient, modelclient = QdrantClient "http://localhost:6333"client.create collection collection name="my collection", vectors config=models.VectorParams size=1536, distance=models.Distance.COSINE, datatype=models.Datatype.TURBO4, <-- this is it , Or in the REST API: PUT /collections/my collection{ "vectors": { "size": 1536, "distance": "Cosine", "datatype": "turbo4" }} When you use datatype: "turbo4", Qdrant stores only the 4-bit representation. There is no float32 copy anywhere - not on disk, not in RAM, not in your object storage. The 4-bit encoding IS the storage format. Full stop. Why does this matter for storage numbers? Let’s look at what the 1.18 TurboQuant approach actually stored: WhatBits per dimfloat32 original disk 32 bits4-bit TurboQuant copy RAM 4 bits Total v1.18 approach 36 bitsTurbo4 datatype v1.19 4 bits 36 / 4 = 9x reduction vs the prior approach. For 10 million 1536-dim vectors: the old way costs ~64 GB total, Turbo4 costs ~7 GB. Pretty wild right? For ColBERT-style multi-vector collections where you store dozens of token-level embeddings per document , this compounds hard. Every single vector gets that 9x reduction. Okay but what’s the catch? Rescoring is gone. When you query a Turbo4 collection, the HNSW graph traversal AND the final ranking both use 4-bit distances. There’s no float32 backup to re-rank against. The 4-bit approximation is your final answer. In practice, 4-bit quantization without rescoring typically lands in the 0.90–0.95 recall range for most dense embedding tasks vs ~0.97–0.99 with rescoring . For semantic search, recommendations, first-stage retrieval before a cross-encoder reranker and that’s totally fine. For legal document search or compliance lookups where missing a true match has real consequences — stick with TurboQuant quantization on top of float32 storage. One more thing: Turbo4 is dense-vectors-only. Sparse vectors don’t support it. Also, Manhattan L1 distance technically works but requires full vector reconstruction — use Cosine, Dot, or Euclidean for good performance. Want the best of both worlds? You can stack quantization on top of Turbo4. Use 1-bit TurboQuant for HNSW traversal, then rescore against the 4-bit Turbo4 vectors instead of float32 . You don’t get the recall of rescoring against float32, but you get way better performance than no rescoring at all, with the disk footprint of 4-bit storage: client.create collection collection name="my collection", vectors config=models.VectorParams size=1536, distance=models.Distance.COSINE, datatype=models.Datatype.TURBO4, , quantization config=models.TurboQuantization turbo=models.TurboQuantQuantizationConfig memory=models.Memory.PINNED, bits=models.TurboQuantBitSize.BITS1, , , Okay so the old memory config in Qdrant was honestly kind of a mess. You had: Three different flag names, different locations in the config, and you couldn’t control HNSW memory placement at all. Not great for operators trying to tune memory usage carefully. Qdrant 1.19 replaces all of this with a single memory parameter that works on every major component: Same parameter, same values, everywhere. Vectors, HNSW links, quantized vectors, sparse indexes, payloads, payload field indexes — all of them. Here’s a full hybrid config in Python: client.create collection collection name="warm hybrid", vectors config=models.VectorParams size=768, distance=models.Distance.COSINE, memory=models.Memory.COLD, original vectors on disk , hnsw config=models.HnswConfigDiff memory=models.Memory.CACHED, HNSW warm in OS cache , quantization config=models.ScalarQuantization scalar=models.ScalarQuantizationConfig type=models.ScalarType.INT8, memory=models.Memory.PINNED, quantized vectors locked in RAM , , payload storage config=models.PayloadStorageConfig memory=models.Memory.COLD, , This “warm hybrid” pattern is the recommended large-scale setup: quantized vectors pinned in RAM for fast HNSW traversal, original vectors cold on disk only accessed for the final rescore of a tiny candidate set. Disk I/O happens for maybe 200 vectors per query the rescore candidates , not for the thousands of comparisons during graph traversal. On migrating old configs — the old flags still work deprecated but not removed , and the translation is straightforward: Old waymodels.VectorParams size=768, distance=models.Distance.COSINE, on disk=True New waymodels.VectorParams size=768, distance=models.Distance.COSINE, memory=models.Memory.COLD Old waymodels.ScalarQuantizationConfig type=models.ScalarType.INT8, always ram=True New waymodels.ScalarQuantizationConfig type=models.ScalarType.INT8, memory=models.Memory.PINNED You can also update tiers without recreating a collection: client.update collection collection name="my collection", vectors config={ "my vector": models.VectorParamsDiff memory=models.Memory.COLD }, Yup, hot change. No migration needed. The cost math is real. RAM runs roughly $3–8/GB/month in cloud VMs. NVMe SSD is around $0.10–0.30/GB/month. That’s a 20–30x price difference. For a billion 768-dim float32 vectors ~2.9 TB , going from all-pinned to cold originals + pinned int8 quantized drops your RAM requirement from ~3.7 TB to ~768 GB. At $5/GB/month that’s the difference between $18,500/month and $3,840/month in RAM costs alone. Hmm, so how does this stack up against the other major vector databases? Weaviate has the most comparable quantization story. They support PQ, BQ, SQ, and their own Rotational Quantization RQ which is architecturally similar to TurboQuant rotation before compression . Their HFresh index does the compressed HNSW in memory + posting lists on disk split. Solid. But — Weaviate always keeps uncompressed vectors for rescoring. There’s no equivalent to Qdrant’s Turbo4 “store only the compressed version, discard the original” mode. If you need maximum disk efficiency, Weaviate can’t match Qdrant’s ~9x reduction at 4-bit. For memory tiering, Weaviate’s HNSW index is entirely in RAM by default 2–12 GB for 1M vectors . HFresh moves it to disk but that’s a specific index type choice, not a flexible tier parameter. You can’t independently set your HNSW to cached while keeping vectors cold - you have to pick a different index architecture entirely. Milvus supports 8-bit scalar quantization IVF SQ8 index type and product quantization IVF PQ , but nothing sub-8-bit. No 4-bit, no 2-bit, no TurboQuant-style rotation. For memory, they have mmap settings you can configure per-component type, plus DiskANN for on-disk graph search. It works, but it’s scattered across multiple config knobs in different places, nothing as unified as Qdrant’s single memoryparameter on every component. Pinecone has essentially zero user-configurable quantization. They make these decisions inside their infrastructure. No PQ, no SQ, no BQ, no compression knob of any kind. If you’re on serverless which is the only option for new customers now , you also have no control over memory placement, Pinecone manages tiering opaquely. Fine for simple use cases, but you’re flying blind on cost optimization. Chroma — no quantization at all. Great for getting started, but not a real option for storage-constrained production deployments. The quick summary: FeatureQdrantWeaviateMilvusPineconeSub-8-bit quantizationYes 4-bit, 2-bit, 1-bit Yes 1-bit RQ NoNoRescore-free compressed-only storageYes Turbo4 NoNoN/AUnified per-component memory tier APIYesNoNoNo Alright, let’s actually run this. Here’s a complete demo,local Qdrant via Docker, create a Turbo4 collection, compare storage numbers across formats, and see the memory tier patterns in action. Start Qdrant locally: docker run -p 6333:6333 -p 6334:6334 \ -v $ pwd /qdrant storage:/qdrant/storage:z \ qdrant/qdrant Qdrant is now running at http://localhost:6333. The web UI is at http://localhost:6333/dashboard. http://localhost:6333/dashboard. Install the client: pip install qdrant-client numpy Full demo script: python import numpy as npimport timefrom qdrant client import QdrantClient, modelsclient = QdrantClient "http://localhost:6333" DIM = 1536N VECTORS = 10 000 Collection 1: float32 baselineclient.recreate collection collection name="float32 collection", vectors config=models.VectorParams size=DIM, distance=models.Distance.COSINE, , Collection 2: Turbo4 datatype - 4-bit only, no float32 copyclient.recreate collection collection name="turbo4 collection", vectors config=models.VectorParams size=DIM, distance=models.Distance.COSINE, datatype=models.Datatype.TURBO4, , Collection 3: Hybrid - cold originals + pinned int8 for RAM-efficient high recallclient.recreate collection collection name="hybrid collection", vectors config=models.VectorParams size=DIM, distance=models.Distance.COSINE, memory=models.Memory.COLD, , hnsw config=models.HnswConfigDiff memory=models.Memory.CACHED , quantization config=models.ScalarQuantization scalar=models.ScalarQuantizationConfig type=models.ScalarType.INT8, quantile=0.99, memory=models.Memory.PINNED, , , payload storage config=models.PayloadStorageConfig memory=models.Memory.COLD , Generate random vectorsvectors = np.random.rand N VECTORS, DIM .astype np.float32 ids = list range N VECTORS def upload vectors collection name, vectors, ids, batch size=500 : for i in range 0, len ids , batch size : batch ids = ids i:i+batch size batch vecs = vectors i:i+batch size .tolist client.upsert collection name=collection name, points= models.PointStruct id=pid, vector=vec for pid, vec in zip batch ids, batch vecs , print f" Uploaded {len ids } vectors to {collection name}" print "Uploading vectors..." upload vectors "float32 collection", vectors, ids upload vectors "turbo4 collection", vectors, ids upload vectors "hybrid collection", vectors, ids Query latency benchmarkquery = np.random.rand DIM .astype np.float32 .tolist def bench query collection name, n queries=100 : start = time.perf counter for in range n queries : client.search collection name=collection name, query vector=query, limit=10, elapsed = time.perf counter - start / n queries 1000 return elapsedprint "\nQuery latency avg over 100 queries :" for name in "float32 collection", "turbo4 collection", "hybrid collection" : ms = bench query name print f" {name}: {ms:.2f} ms" Recall comparison vs float32 brute forceprint "\nRecall@10 comparison vs float32 :" exact results = client.search collection name="float32 collection", query vector=query, limit=10, exact ids = set r.id for r in exact results for name in "turbo4 collection", "hybrid collection" : results = client.search collection name=name, query vector=query, limit=10, returned ids = set r.id for r in results recall = len exact ids & returned ids / len exact ids print f" {name}: {recall:.2f}" Run this and you’ll see latency and recall numbers across all three setups. The hybrid collection cold originals + pinned int8 typically gets close to float32 recall while using much less RAM. Turbo4 will show lower recall on this small dataset — the gains really show at scale where storage costs dominate. Expected storage comparison for 10,000 x 1536-dim vectors: FormatApprox disk usagefloat32~58 MBfloat32 + int8 quant hybrid ~73 MBTurbo4 only~7.5 MB At 10M vectors, scale those numbers by 1000. The Turbo4 savings become very real very fast. Quick decision guide before you go: So yeah, 1.19 is a solid release for anyone running Qdrant at scale. The unified memory tier API alone is worth the upgrade just for operational clarity. Turbo4 is the cherry on top for storage-heavy workloads. The full Qdrant documentation https://qdrant.tech/documentation/?utm medium=referral&utm source=stars&utm campaign=devrel&utm content=niranjan-akella has detailed coverage of quantization options and the memory tier reference. If you’re running production workloads, Qdrant Cloud https://cloud.qdrant.io/signup?utm medium=referral&utm source=stars&utm campaign=devrel&utm content=niranjan-akella handles the operational side so you can focus on tuning rather than managing nodes. And for major releases and new quantization research as it drops, the Qdrant newsletter https://qdrant.tech/subscribe/?utm medium=referral&utm source=stars&utm campaign=devrel&utm content=niranjan-akella is worth subscribing to — Turbo4 specifically came out of a Google Research paper and the team moves fast on this stuff. Qdrant 1.19 | TurboQuant & Memory Tiers: Store More, Spend Less https://blog.stackademic.com/qdrant-1-19-turboquant-memory-tiers-store-more-spend-less-50b64a2fe0ef was originally published in Stackademic https://blog.stackademic.com on Medium, where people are continuing the conversation by highlighting and responding to this story.