cd /news/ai-infrastructure/qdrant-1-19-turboquant-memory-tiers-… · home topics ai-infrastructure article
[ARTICLE · art-116297] src=blog.stackademic.com ↗ pub= topic=ai-infrastructure verified=true sentiment=↑ positive

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.

read10 min views1 publishedAug 31, 2026

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 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.

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 bitsTotal (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:

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.

Install the client:

pip install qdrant-client numpy

Full demo script:

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("Up 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 has detailed coverage of quantization options and the memory tier reference. If you’re running production workloads, Qdrant Cloud 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 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 was originally published in Stackademic on Medium, where people are continuing the conversation by highlighting and responding to this story.

── more in #ai-infrastructure 4 stories · sorted by recency
── more on @qdrant 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/qdrant-1-19-turboqua…] indexed:0 read:10min 2026-08-31 ·