{"slug": "qdrant-1-19-turboquant-memory-tiers-store-more-spend-less", "title": "Qdrant 1.19 | TurboQuant & Memory Tiers: Store More, Spend Less", "summary": "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.", "body_md": "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.\n\n[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.\n\nLets’ Gid Deeper Guys!!\n\nHere’s the thing about vector search at scale: the math is brutal.\n\nSay 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.\n\nBefore 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.\n\nAnd 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).\n\nQdrant 1.19 fixes both of these.\n\nTurboQuant 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.\n\nIn 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.\n\n**In 1.19, Turbo4 is a datatype, not just a quantization layer.**\n\n``` python\nfrom 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    ),)\n```\n\nOr in the REST API:\n\n```\nPUT /collections/my_collection{    \"vectors\": {        \"size\": 1536,        \"distance\": \"Cosine\",        \"datatype\": \"turbo4\"    }}\n```\n\nWhen 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.\n\nWhy does this matter for storage numbers? Let’s look at what the 1.18 TurboQuant approach actually stored:\n\nWhatBits per dimfloat32 original (disk)32 bits4-bit TurboQuant copy (RAM)4 bits**Total (v1.18 approach)36 bitsTurbo4 datatype (v1.19)4 bits**\n\n36 / 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?\n\nFor 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.\n\n**Okay but what’s the catch?**\n\nRescoring 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.\n\nIn 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.\n\nOne 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.\n\n**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:\n\n```\nclient.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,        ),    ),)\n```\n\nOkay so the old memory config in Qdrant was honestly kind of a mess. You had:\n\nThree 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.\n\nQdrant 1.19 replaces all of this with a single memory parameter that works on every major component:\n\nSame 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:\n\n```\nclient.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,    ),)\n```\n\nThis “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.\n\n**On migrating old configs** — the old flags still work (deprecated but not removed), and the translation is straightforward:\n\n```\n# 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)\n```\n\n**You can also update tiers without recreating a collection:**\n\n```\nclient.update_collection(    collection_name=\"my_collection\",    vectors_config={        \"my_vector\": models.VectorParamsDiff(memory=models.Memory.COLD)    },)\n```\n\nYup, hot change. No migration needed.\n\n**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.\n\nHmm, so how does this stack up against the other major vector databases?\n\n**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.\n\nFor 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.\n\n**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.\n\n**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.\n\n**Chroma** — no quantization at all. Great for getting started, but not a real option for storage-constrained production deployments.\n\nThe quick summary:\n\nFeatureQdrantWeaviateMilvusPineconeSub-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\n\nAlright, 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.\n\n**Start Qdrant locally:**\n\n```\ndocker run -p 6333:6333 -p 6334:6334 \\    -v $(pwd)/qdrant_storage:/qdrant/storage:z \\    qdrant/qdrant\n```\n\nQdrant is now running at http://localhost:6333. The web UI is at [http://localhost:6333/dashboard.](http://localhost:6333/dashboard.)\n\n**Install the client:**\n\n```\npip install qdrant-client numpy\n```\n\n**Full demo script:**\n\n``` python\nimport 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}\")\n```\n\nRun 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.\n\n**Expected storage comparison for 10,000 x 1536-dim vectors:**\n\nFormatApprox disk usagefloat32~58 MBfloat32 + int8 quant (hybrid)~73 MBTurbo4 only~7.5 MB\n\nAt 10M vectors, scale those numbers by 1000. The Turbo4 savings become very real very fast.\n\n**Quick decision guide before you go:**\n\nSo 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.\n\nThe 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.\n\n[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.", "url": "https://wpnews.pro/news/qdrant-1-19-turboquant-memory-tiers-store-more-spend-less", "canonical_source": "https://blog.stackademic.com/qdrant-1-19-turboquant-memory-tiers-store-more-spend-less-50b64a2fe0ef?source=rss----d1baaa8417a4---4", "published_at": "2026-08-31 06:51:18+00:00", "updated_at": "2026-08-31 07:21:50.538722+00:00", "lang": "en", "topics": ["ai-infrastructure", "ai-tools"], "entities": ["Qdrant", "OpenAI", "Google Research"], "alternates": {"html": "https://wpnews.pro/news/qdrant-1-19-turboquant-memory-tiers-store-more-spend-less", "markdown": "https://wpnews.pro/news/qdrant-1-19-turboquant-memory-tiers-store-more-spend-less.md", "text": "https://wpnews.pro/news/qdrant-1-19-turboquant-memory-tiers-store-more-spend-less.txt", "jsonld": "https://wpnews.pro/news/qdrant-1-19-turboquant-memory-tiers-store-more-spend-less.jsonld"}}