6.1 — Quantization cho vector: scalar, product, binary, TurboQuant
Áp vào Qdrant. Mục này nói về cơ chế quantization. Phần cấu hình thật trong Qdrant — khác biệt giữa
datatypevàquantization_config, ba tầng bộ nhớpinned/cached/cold,rescorevàoversampling, inline storage, và vì sao bật nén mà RAM lại tăng — nằm ở 7.10 — Mô hình bộ nhớ.
3.3 nén postings của inverted index. Mục này nén thứ còn lại: vector embedding. Cùng một động lực — dung lượng — nhưng cơ chế khác hẳn, vì nén postings là không mất mát còn nén vector thì luôn mất mát, và điều thú vị là mất mát đó thường không làm giảm chất lượng tìm kiếm bao nhiêu.
Mục này giải thích cơ chế. Cấu hình thực tế (tham số Qdrant, chọn mức nén nào) ở Tầng 5 — ANN & hạ tầng vector.
1. Mục đích: vì sao phải nén
Phần tiêu đề “1. Mục đích: vì sao phải nén”Một chunk, embedding 1536 chiều, float32:
1536 chiều × 4 byte = 6 144 byte = 6 KB cho MỘT chunk1 triệu chunk = 6,1 GB6,1 GB nghe không to. Vấn đề là nó phải nằm trong RAM, vì mỗi truy vấn ANN phải đọc vector của mọi node nó ghé qua. Và đây là chỗ hầu hết người mới hiểu sai:
Với vector nhiều chiều, phép tính cosine không bị giới hạn bởi CPU, mà bởi băng thông đọc RAM. Đọc 6 KB rồi làm 1536 phép nhân-cộng — CPU rảnh, nó đang chờ RAM.
Suy ra hai mục đích của quantization, và mục đích thứ hai mới là mục đích hay:
- Giảm RAM — hiển nhiên. 6,1 GB → 1,5 GB → 0,19 GB.
- Tăng tốc — vì đọc 384 byte nhanh hơn đọc 6 144 byte. Cộng thêm: so sánh int8 và so sánh bit đều có lệnh SIMD riêng, nhanh hơn float32.
Và một câu cần thuộc, vì nó phân biệt quantization với ANN:
ANN (HNSW, IVF) giảm số phép so sánh. Quantization giảm giá của mỗi phép so sánh. Hai trục độc lập. Dùng chung được, và thường nên dùng chung.
2. Ý tưởng chung: thay vector bằng một “mã”
Phần tiêu đề “2. Ý tưởng chung: thay vector bằng một “mã””Một quantizer là hàm q biến vector thật thành một phần tử của tập hữu hạn:
gọi là codebook.
Lưu x không còn là lưu D số thực, mà là lưu chỉ số của centroid gần nhất. Với
k centroid, chỉ số tốn log2(k) bit. Giải nén (reconstruct) là tra codebook:
x̂ = c_i.
Cái mất đi gọi là distortion:
Bài toán duy nhất của cả lĩnh vực này: với một ngân sách bit cố định, làm sao cho distortion nhỏ nhất. Ba câu trả lời khác nhau về chia không gian thế nào sinh ra ba họ dưới đây.
Có một điểm quan trọng cần thấy trước: mục tiêu thật không phải khôi phục x
chính xác. Mục tiêu là giữ đúng thứ tự khi so d(q, x₁) với d(q, x₂). Vector
có thể lệch khá nhiều mà thứ hạng vẫn đúng — đó là lý do nén 32× vẫn dùng được.
3. Scalar quantization — mỗi chiều một mình
Phần tiêu đề “3. Scalar quantization — mỗi chiều một mình”Đơn giản nhất: quên tương quan giữa các chiều đi, xử lý từng chiều độc lập. Mỗi
float32 → 1 uint8, tức 256 mức.
min/max được hiệu chuẩn (calibrate) trên một tập mẫu của corpus, không phải
trên từng vector — nếu mỗi vector một thang đo thì không so sánh được với nhau.
Tính bằng tay
Phần tiêu đề “Tính bằng tay”Giả sử hiệu chuẩn xong được min = −1,30, max = 2,10 → step = 3,40 / 255 = 0,01333.
x | (x − min)/step | code | x̂ | sai số |
|---|---|---|---|---|
| 0,42 | 129,0 | 129 | 0,4200 | 0,0000 |
| −1,30 | 0,0 | 0 | −1,3000 | 0,0000 |
| 0,05 | 101,25 | 101 | 0,0467 | 0,0033 |
| 2,10 | 255,0 | 255 | 2,1000 | 0,0000 |
Sai số tối đa luôn là step / 2 = 0,0067. Với vector đã chuẩn hoá, giá trị mỗi chiều
thường trong khoảng ±0,1 → sai số 0,0067 là nhiễu nhỏ so với tín hiệu. Đây là lý
do scalar quantization gần như miễn phí: tài liệu Qdrant ghi sai số “thường dưới 1%”,
và báo cáo của Hugging Face đo
int8 giữ ~99,3% chất lượng retrieval, nhanh trung bình ~3,7×.
⚠️ Vì sao có tham số quantile
Phần tiêu đề “⚠️ Vì sao có tham số quantile”Thêm một chiều outlier x = 20,0 vào tập hiệu chuẩn:
trước: min=−1,30 max= 2,10 → step = 0,0133 → sai số tối đa 0,0067sau: min=−1,30 max=20,00 → step = 0,0835 → sai số tối đa 0,0418 (×6,3)Một chiều lệch làm sai số của 1 535 chiều còn lại tăng hơn 6 lần. Đó chính xác là
việc mà quantile: 0.99 của Qdrant làm: bỏ 1% giá trị cực trị trước khi tính
[min, max], chấp nhận cắt ngọn vài giá trị để không phá thang đo của tất cả.
4. Product quantization — quantization cả cụm chiều
Phần tiêu đề “4. Product quantization — quantization cả cụm chiều”Scalar bỏ qua tương quan giữa các chiều. PQ (Jégou, Douze & Schmid, 2011) khai thác nó: quantization cả một cụm chiều cùng lúc, bằng k-means.
1. Chia D chiều thành m đoạn liên tiếp, mỗi đoạn D/m chiều.2. Với MỖI đoạn: chạy k-means trên corpus → k* centroid (thường k* = 256).3. Mã của x = (chỉ số centroid gần nhất ở đoạn 1, …, ở đoạn m) → m byte, vì log2(256) = 8 bit.Bước 2 là gì. k-means ở đây không phải “phân cụm dữ liệu” theo nghĩa ML thông thường — nó chính là bài toán quantization của Lloyd (1982), và mục tiêu nó tối thiểu hoá đúng là sai số nén mà mục này quan tâm. Cơ chế đầy đủ (thuật toán Lloyd, chi phí
O(N·K·d), khởi tạo,sphericalcho cosine, và vì sao codebook lệch dần): 6.2 §1.1 — k-means, cái máy sinh ra centroid.
Với D = 1536, m = 96: mỗi đoạn 16 chiều, mã dài 96 byte thay vì 6 144 byte —
nén 64×.
Điểm đẹp của PQ: 256^m centroid mà chỉ học 256×m
Phần tiêu đề “Điểm đẹp của PQ: 256^m centroid mà chỉ học 256×m”Ghép tự do các centroid của m đoạn, ta biểu diễn được:
k = (k*)^m = 256^96 ≈ 10^231 centroid "ảo"nhưng chỉ phải học và lưu: m × k* = 96 × 256 = 24 576 centroidMột quantizer thường muốn 10^231 centroid là bất khả thi (k-means không chạy nổi,
codebook không lưu nổi). PQ đạt được nó bằng cách phân rã không gian thành tích
Descartes của các không gian con — đây là toàn bộ ý tưởng của chữ “product”.
Tính bằng tay: D = 4, m = 2, k* = 4
Phần tiêu đề “Tính bằng tay: D = 4, m = 2, k* = 4”Codebook đoạn 1 (chiều 1–2): c0=(0,0) c1=(1,0) c2=(0,1) c3=(1,1)Codebook đoạn 2 (chiều 3–4): d0=(0,0) d1=(2,0) d2=(0,2) d3=(2,2)Nén tài liệu x = (0,9 ; 0,1 ; 0,2 ; 1,8):
- đoạn 1 =
(0,9 ; 0,1)→ gầnc1=(1,0)nhất (0,01 + 0,01 = 0,02) → code 1 - đoạn 2 =
(0,2 ; 1,8)→ gầnd2=(0,2)nhất (0,04 + 0,04 = 0,08) → code 2
→ mã của x là (1, 2) — 2 byte thay vì 16 byte. Giải nén: x̂ = (1, 0, 0, 2).
ADC: vì sao không cần nén query
Phần tiêu đề “ADC: vì sao không cần nén query”Có hai cách tính khoảng cách, và sự khác biệt quan trọng:
| Cách làm | Nhận xét | |
|---|---|---|
| SDC (symmetric) | nén cả query, so hai mã | Cả hai bên đều méo → sai số cộng dồn |
| ADC (asymmetric) | query giữ nguyên float32, chỉ tài liệu bị nén | Chỉ một bên méo → chính xác hơn. Đây là cách mặc định |
ADC chạy thế nào — query q = (0,8 ; 0,0 ; 0,1 ; 2,0). Tính trước bảng khoảng cách
m × k* ô, mỗi ô là khoảng cách từ đoạn query tới một centroid:
bảng đoạn 1, q1=(0,8 ; 0,0): c0: 0,64 c1: 0,04 c2: 1,64 c3: 1,04bảng đoạn 2, q2=(0,1 ; 2,0): d0: 4,01 d1: 7,61 d2: 0,01 d3: 3,61Giờ khoảng cách tới bất kỳ tài liệu nào chỉ là tra bảng rồi cộng. Với x có mã (1,2):
d(q,x)² ≈ bảng1[1] + bảng2[2] = 0,04 + 0,01 = 0,05thực tế: ‖q − x‖² = 0,01 + 0,01 + 0,01 + 0,04 = 0,07Ước lượng 0,05 lệch so với 0,07 — nhưng nó lệch theo cùng một hướng cho mọi tài
liệu, nên thứ hạng phần lớn được giữ. Còn chi phí:
tính bảng: m × k* = 96 × 256 = 24 576 phép — MỘT LẦN cho cả truy vấnmỗi tài liệu: m = 96 lần tra bảng + cộng (thay vì 1 536 phép nhân-cộng)→ với 1 triệu tài liệu: giảm 16× số phép tính, và đọc 96 byte thay vì 6 144 byteGiá của PQ
Phần tiêu đề “Giá của PQ”- Không SIMD-friendly. Tra bảng là truy cập bộ nhớ ngẫu nhiên (gather), không phải phép toán vector. Đây là lý do PQ nén sâu nhất nhưng chậm hơn scalar.
- Cần huấn luyện. k-means phải chạy trên dữ liệu thật, và codebook phụ thuộc phân bố dữ liệu. Corpus đổi chủ đề (data drift) → codebook lệch → recall tụt âm thầm. Scalar và binary không có vấn đề này. (Cùng một lớp lỗi với centroid của IVF — 6.2 §1.1.)
5. Binary quantization — chỉ giữ dấu
Phần tiêu đề “5. Binary quantization — chỉ giữ dấu”Đẩy tới cực hạn: 1 bit mỗi chiều, giữ đúng cái dấu.
Nén 32× (32 bit → 1 bit). Khoảng cách không còn là cosine mà là Hamming:
popcount là một lệnh CPU xử lý 64 chiều một lượt. Đây là lý do binary nhanh
đến mức khó tin — Hugging Face đo tới 45× (trung bình ~25×).
Tính bằng tay
Phần tiêu đề “Tính bằng tay”x = ( 0,42 ; −1,30 ; 0,05 ; 2,10) → 1 0 1 1y = (−0,30 ; 0,90 ; 0,20 ; −1,10) → 0 1 1 0 ↑ ↑ ↑d_H(x, y) = 3Vì sao nó lại hoạt động — và vì sao nó sập ở chiều thấp
Phần tiêu đề “Vì sao nó lại hoạt động — và vì sao nó sập ở chiều thấp”Giữ dấu thôi mà vẫn tìm được? Có, và có lý thuyết đàng hoàng đứng sau: đây chính là sign random projection / SimHash (Charikar, 2002). Kết quả cốt lõi: với hướng chiếu ngẫu nhiên, xác suất hai vector khác bit ở một chiều bằng chính góc giữa chúng chia π:
Nghĩa là Hamming distance là một ước lượng của góc. Nhưng nó là ước lượng thống kê
từ D mẫu nhị phân, nên độ chính xác của nó co lại theo O(1/√D). Đó là toàn bộ
câu trả lời cho câu hỏi “vì sao binary cần vector nhiều chiều”:
| D | Hamming nhận bao nhiêu giá trị | Sai số góc cỡ 1/√D |
|---|---|---|
| 384 | 385 | ~5,1% |
| 768 | 769 | ~3,6% |
| 1 536 | 1 537 | ~2,6% |
| 4 096 | 4 097 | ~1,6% |
Ở 384 chiều, toàn bộ thang điểm chỉ có 385 mức cho cả triệu tài liệu → hàng loạt tài liệu đồng điểm, và thứ tự trong nhóm đồng điểm là ngẫu nhiên. Ở 1 536 hay 4 096 chiều thì vẫn còn thô, nhưng đủ để lọc.
Số Qdrant công bố khớp với hình này: text-embedding-ada-002 (1 536 chiều) đạt
recall@100 = 0,98 với oversampling 4×; Cohere embed-english-v2.0 (4 096 chiều) đạt
recall@50 = 0,98 với oversampling 2×.
Điều kiện thứ hai, hay bị quên: phân bố mỗi chiều phải cân quanh 0. Nếu một chiều luôn dương thì bit của nó luôn bằng 1 — chiều đó không mang thông tin nào, bạn vừa mất nó miễn phí.
Cách mua lại phần đã mất
Phần tiêu đề “Cách mua lại phần đã mất”rescore+oversampling— mục 7 dưới đây. HF đo: binary + rescore ×4 giữ 96,45%, không rescore chỉ 92,53%.- 2-bit / 1.5-bit — thay vì chỉ dấu, chia thành 4 (hoặc 3) mức. Nén 16× / 24×.
- Bất đối xứng — nén tài liệu về binary nhưng giữ query ở độ chính xác cao (float32 hoặc scalar 8-bit). Cùng logic ADC ở mục 4: chỉ một bên méo thì sai số nhỏ hơn hẳn, mà dung lượng index không đổi.
6. Vì sao “quay vector trước khi nén” lại giúp
Phần tiêu đề “6. Vì sao “quay vector trước khi nén” lại giúp”Cả ba họ trên đều giả định ngầm rằng thông tin dàn đều trên các chiều. Embedding thật thì không: vài chiều mang phương sai lớn, phần còn lại gần như phẳng (xem Tầng 1 — anisotropy). Cấp cho mọi chiều cùng một số bit là phân bổ sai ngân sách.
Cách sửa hoá ra rất gọn: nhân vector với một phép quay trực giao ngẫu nhiên trước khi nén (biến đổi Johnson–Lindenstrauss). Phép quay không đổi khoảng cách giữa các vector, nhưng dàn đều năng lượng ra mọi chiều — sau đó mỗi bit gánh một phần thông tin tương đương, và distortion giảm mà không tốn thêm bit nào.
Đây là ý tưởng chung của hai dòng gần nhất:
- RaBitQ (SIGMOD 2024, arXiv:2405.12497) —
chứng minh biên sai số
O(1/√D)và cho thấy đó là tối ưu tiệm cận. - TurboQuant (ICLR 2026, arXiv:2504.19874) — cùng dòng, mở rộng sang 1 / 1.5 / 2 / 4 bit. Đây là thứ Qdrant v1.18+ dùng.
Nói cách khác: O(1/√D) ở mục 5 không phải giới hạn của binary quantization, nó là
giới hạn của việc nén vector nói chung. Điều RaBitQ/TurboQuant làm là chạm tới nó,
thay vì nằm cách nó một quãng như binary thô.
TurboQuant làm gì: bốn bước, không bước nào cần nhìn dữ liệu
Phần tiêu đề “TurboQuant làm gì: bốn bước, không bước nào cần nhìn dữ liệu”Đây là phần đáng đọc kỹ, vì nó giải thích vì sao TurboQuant không có bước huấn luyện — khác hẳn PQ ở mục 4.
1. Tách độ dài: v → ‖v‖ (lưu riêng, 4 byte) và u = v/‖v‖ (điểm trên mặt cầu)2. Quay: u → Ru, R là phép quay trực giao ngẫu nhiên3. Nén: mỗi toạ độ của Ru → 1 trong 2^b mức, bằng MỘT bảng cố định4. Chấm điểm: ⟨q, v⟩ tính thẳng từ chỉ số codebook — không dựng lại vectorBước 4 chạy được vì phép quay là trực giao: ⟨Rq, Ru⟩ = ⟨q, u⟩. Quay cả hai bên
thì tích vô hướng và khoảng cách L2 giữ nguyên, nên nén xong vẫn so sánh được.
Điểm khác biệt nằm ở bước 3: bảng mã là cố định, viết sẵn trong code. Không k-means, không cần corpus mẫu, không có codebook nào để lệch khi dữ liệu đổi. Paper gọi tính chất này là data-oblivious, và đó là lý do Qdrant báo thời gian index “gần như bằng không” so với PQ.
Vì sao quay xong lại biết trước phân bố
Phần tiêu đề “Vì sao quay xong lại biết trước phân bố”Câu hỏi tự nhiên: bảng mã cố định thì làm sao tối ưu được cho mọi dữ liệu?
Trả lời: sau bước 1 và 2, dữ liệu của bạn không còn là dữ liệu của bạn nữa. u nằm
trên mặt cầu đơn vị; nhân với một phép quay ngẫu nhiên thì Ru là một điểm phân bố đều
trên mặt cầu — bất kể v ban đầu là embedding tiếng Việt, ảnh CLIP hay KV cache.
Mà phân bố của một toạ độ đơn lẻ của điểm đều trên mặt cầu S^(D-1) thì đã biết chính xác:
x_i² ~ Beta(1/2, (D-1)/2) ← đây là "concentrated Beta" trong abstract của paper√D · x_i → N(0,1) khi D lớn ← xấp xỉ Gaussian, cái Qdrant dùng để mô tảHai cách nói đó không mâu thuẫn — Beta là phân bố đúng, Gaussian là giới hạn của nó ở
chiều cao. Bài viết kỹ thuật của Qdrant mô tả bằng N(0,1) vì đó là thứ họ dựng bảng mã
Lloyd–Max lên, còn paper nói Beta vì đó mới là phát biểu chặt ở mọi D.
Biết trước hình dạng phân bố thì bài toán “chia trục thành 2^b mức sao cho MSE nhỏ nhất”
là bài toán Lloyd–Max cổ điển, giải một lần offline, ra một bảng dùng cho mọi dataset.
Còn một mảnh nữa: vì sao quantization từng chiều riêng lại đủ tốt? PQ phải gom chiều thành cụm chính vì các chiều có tương quan. Sau phép quay ngẫu nhiên ở chiều cao, các toạ độ gần như độc lập với nhau (paper gọi là near-independence property) — mà với các biến độc lập thì quantization riêng từng biến đã gần tối ưu. Đó là lý do TurboQuant bỏ được cả cụm máy móc k-means của PQ mà chất lượng không tệ hơn.
Bảng tổng kết một dòng: phép quay biến một bài toán phụ thuộc dữ liệu (nén embedding lệch tâm, dị hướng) thành một bài toán đã giải xong từ lâu (nén biến Gaussian độc lập).
Hai tầng: quantizer tối ưu MSE lại lệch khi tính inner product
Phần tiêu đề “Hai tầng: quantizer tối ưu MSE lại lệch khi tính inner product”Đây là chỗ TurboQuant khác RaBitQ, và cũng là chỗ dễ bỏ qua nhất.
Quantizer tối ưu MSE luôn trả về kỳ vọng có điều kiện của giá trị gốc. Kỳ vọng có điều kiện thì co về giữa — vector nén ngắn hơn vector gốc một cách hệ thống. Hệ quả:
⟨q, v̂⟩ < ⟨q, v⟩ đều đặn, với mọi vSai số này không phải nhiễu, nó là thiên lệch (bias). Nhiễu thì lấy trung bình sẽ triệt tiêu; bias thì không — và vì mức co phụ thuộc từng vector, nó làm đảo thứ tự giữa các ứng viên chứ không chỉ dịch đều điểm số.
Cách paper sửa: sau tầng MSE, lấy phần dư r = u − û rồi đẩy qua QJL (Quantized
Johnson–Lindenstrauss) — chiếu ngẫu nhiên rồi chỉ giữ dấu, đúng 1 bit mỗi chiều.
Phần dư được ước lượng lại từ các bit dấu đó, và kết quả tổng là một ước lượng inner product
không lệch. Giá: thêm 1 bit/chiều.
Cách Qdrant sửa cùng căn bệnh đó rẻ hơn: thay vì 1 bit mỗi chiều, lưu một số thực duy
nhất mỗi vector ghi lại “phép nén đã làm vector ngắn đi bao nhiêu”, rồi nhân bù lại lúc
chấm điểm. Ý tưởng mượn từ RaBitQ, và nó dùng lại đúng 4 byte ‖v‖ vốn đã phải lưu.
Chạm tới đáy: hằng số ≈ 2,7
Phần tiêu đề “Chạm tới đáy: hằng số ≈ 2,7”Phần lý thuyết mạnh nhất của paper không phải thuật toán, mà là cận dưới: họ chứng minh
một giới hạn information-theoretic cho distortion mà bất kỳ bộ quantization nào ở b
bit/chiều cũng không thể vượt qua, rồi cho thấy TurboQuant cách cận dưới đó chỉ một hệ số
≈ 2,7.
Điều này đáng để nội hoá khi bạn chọn công nghệ:
- Khoảng cách từ “state of the art” tới “bất khả thi” giờ chỉ còn một hệ số hằng nhỏ. Sẽ không có thuật toán nào nén tốt hơn TurboQuant một bậc nữa.
- Muốn tiến tiếp thì phải đổi bài toán, không phải đổi quantizer: giảm số chiều trước khi nén (Matryoshka embedding), học theo dữ liệu, hoặc mua lại recall bằng rescoring (mục 7).
Qdrant cài thêm gì so với paper
Phần tiêu đề “Qdrant cài thêm gì so với paper”Paper giả định dữ liệu đẹp; embedding thật thì không. Bốn thứ Qdrant thêm vào:
| Vấn đề thật | Cách Qdrant xử lý |
|---|---|
| Vector nén bị co ngắn → lệch điểm | Lưu thêm tỉ lệ co mỗi vector, nhân bù khi chấm điểm (mượn RaBitQ) |
Embedding dị hướng, quay xong vẫn chưa khớp N(0,1) | Hiệu chỉnh (shift, scale) từng toạ độ mỗi segment, ước lượng quantile kiểu streaming (P-Square) |
| Chỉ hỗ trợ cosine | Lưu thêm ‖v‖ rồi dựng lại L2 qua ‖q−v‖² = ‖q‖² + ‖v‖² − 2⟨q,v⟩ |
| Tra bảng codebook thường không SIMD được | 4-bit/2-bit: nhét cả 16 centroid vào một thanh ghi, tra bằng pshufb; trên CPU có VNNI gộp thành VPDPBUSD. 1-bit: bit-plane + popcount kiểu RaBitQ |
Hai hệ quả cần nhớ:
- L1 (Manhattan) không dùng được với TurboQuant. Phép quay bảo toàn L2 và dot, không bảo toàn L1. Dùng L1 thì quay lại scalar quantization.
- Phần hiệu chỉnh miễn phí lúc truy vấn: vì query giữ full precision (bất đối xứng), hệ
số
1/scalegộp thẳng vào query và⟨q, shift⟩chỉ là một số — tính một lần mỗi truy vấn.
Cấu hình trong Qdrant:
{ "quantization_config": { "turbo": { "bits": "bits4", "memory": "pinned" } }}bits nhận bits4 (8×), bits2 (16×), bits1_5 (~21–24×), bits1 (32×).
⚠️ Con số nén của mức 1.5-bit được ghi khác nhau giữa các nguồn — tài liệu Qdrant ghi 24× ở phần binary quantization, còn bài kỹ thuật về TurboQuant ghi ~21×. Chênh lệch đến từ phần metadata mỗi vector, không phải từ thuật toán. Cứ đo dung lượng thật thay vì tin con số quảng cáo.
Nửa còn lại của paper: nén KV cache của LLM
Phần tiêu đề “Nửa còn lại của paper: nén KV cache của LLM”Điều dễ bỏ sót khi đọc TurboQuant qua lăng kính vector search: một nửa paper không nói về search. Cùng bộ toán đó được dùng để nén KV cache của LLM — thứ chiếm phần lớn VRAM khi context dài.
Số trong paper: 3,5 bit/channel thì chất lượng trung tính tuyệt đối, 2,5 bit/channel thì suy giảm nhẹ. Google Research báo thêm mức tăng throughput đáng kể so với key chưa nén 32-bit trên H100.
Vì sao chuyện này liên quan tới RAG: nếu bạn tự host LLM cho bước sinh, KV cache là thứ giới hạn số truy vấn đồng thời và độ dài context bạn dám nhồi vào (Tầng 8 — context assembly). Nén index và nén KV cache hoá ra là cùng một bài toán, và giờ có cùng một lời giải.
7. Rescoring: vì sao nó cứu được recall
Phần tiêu đề “7. Rescoring: vì sao nó cứu được recall”Cấu trúc này lặp lại đúng logic two-stage của 5.4 — trần recall@k:
vector nén → dùng để LỌC → nhanh, thô, lấy nhiều (oversampling × k)vector gốc → dùng để XẾP HẠNG → chậm, chính xác, chỉ chạy trên số ít vừa lọcVector nén không cần đúng thứ tự, nó chỉ cần không đánh rơi tài liệu đúng ra khỏi danh sách ứng viên. Thứ tự cuối cùng do vector gốc quyết định. Đó là vì sao nén 32× mà recall vẫn 0,98.
Hai điều kiện, cả hai đều dễ vi phạm:
- Vector gốc phải còn truy cập được. Nếu nó nằm trên disk chậm, rescore biến thành nút cổ chai latency. Khi đó phải tắt rescore và bù bằng oversampling cao hơn.
oversamplingphải đủ lớn. Đây là núm đổi latency lấy chất lượng tại runtime, vai trò y hệtefSearchcủa HNSW — không cần build lại index.
8. Bảng so sánh bốn họ
Phần tiêu đề “8. Bảng so sánh bốn họ”| Scalar (int8) | Product (PQ) | Binary (1-bit) | TurboQuant | |
|---|---|---|---|---|
| Nén | 4× | tới 64× | 32× | 8× / 16× / ~21× / 32× |
| Cơ chế | mỗi chiều → 256 mức | k-means trên từng cụm chiều | giữ dấu | quay ngẫu nhiên → Lloyd–Max từng chiều |
| Khoảng cách | dot/cosine trên int8 | tra bảng ADC | Hamming (popcount) | dot từ chỉ số codebook |
| SIMD | ✅ rất tốt | ❌ gather, không SIMD | ✅ tốt nhất | ✅ pshufb / VNNI, 1-bit dùng bit-plane |
| Cần huấn luyện | ❌ chỉ cần min/max | ✅ k-means, phụ thuộc dữ liệu | ❌ | ❌ bảng mã cố định (data-oblivious) |
| Cần rescore | thường không | có | bắt buộc | ở 1 / 1.5 / 2-bit thì có |
| Chất lượng | ~99% | tuỳ m | ~96% (có rescore) | 4-bit ≈ scalar; hơn binary 9–24 pp ở cùng mức nén |
| Hỏng khi nào | có outlier mà không đặt quantile | corpus drift lệch codebook | D nhỏ, hoặc chiều lệch khỏi 0 | dùng khoảng cách L1 (phép quay không bảo toàn L1) |
Đọc bảng theo một câu: TurboQuant lấy ưu điểm “không cần huấn luyện” của scalar/binary,
ghép với mức nén của PQ, và trả giá bằng một phép quay O(D log D) lúc ghi và lúc truy vấn.
9. Bốn lỗi thường gặp
Phần tiêu đề “9. Bốn lỗi thường gặp”- Bật quantization mà vẫn giữ vector gốc trong RAM → RAM tăng, không giảm. Bản
nén là bản thêm vào. Muốn giảm RAM thật thì phải đẩy vector gốc sang disk (
cold) — và lúc đó xem lại điều kiện 1 ở mục 7. - Không đo recall trước/sau. Tắt nén bằng
ignore: truerồi chạy lại cùng bộ truy vấn. Không có hai con số này thì bạn không biết mình đã trả giá bao nhiêu. - Binary trên embedding 384 chiều. Xem bảng ở mục 5. Đây là lỗi hay gặp nhất, vì phần lớn model embedding tiếng Việt nhỏ đều 768 chiều hoặc thấp hơn.
- PQ với codebook train một lần rồi để đó. Corpus đổi → phân bố đổi → centroid lệch. Phải train lại theo chu kỳ, và phải đo để biết chu kỳ đó là bao lâu.
Các con số ở mục này là số dẫn từ nguồn ngoài (Qdrant, Hugging Face, paper gốc), không phải số đo trên
data/corpus.jsonl— 50 tài liệu quá nhỏ để quantization có ý nghĩa. Xem Phụ lục — Sai số của tài liệu.