Chuyển đến nội dung chính
Tối ưu suy luận · Cập nhật 1 tháng 11, 2026

4.4 Lượng tử hóa KV Cache

Ước lợi ích lượng tử hóa KV Cache, hiểu ràng buộc ghi theo luồng, cách nhóm khác nhau của KIVI cho Key và Value, cùng hiệu chuẩn Scale, bỏ qua lớp và cách kiểm chứng FP8 KV Cache trong vLLM v0.32.0

KV Cache KIVI FP8 Ngữ cảnh dài vLLM

Weight nén xuống INT4 thì mô hình cuối cùng nhét được vào bộ nhớ GPU; kéo dài ngữ cảnh hoặc tăng concurrency một chút là lại hết bộ nhớ. Sổ sách ở Chương 1 đã giải thích lý do: weight là chi phí cố định một lần, còn KV Cache tăng theo từng token lịch sử của mỗi request. Phần này xem cách làm mỗi token lịch sử chiếm ít hơn.

KV Cache giống biên bản họp vừa viết vừa ghi: lượng tử hóa weight là in sẵn tài liệu tham khảo chữ nhỏ trước buổi họp, có thể thong thả sắp chữ; biên bản thì chỉ ghi được tại chỗ, và vừa ghi xong một câu đã phải tra được ngay. Vì vậy phương pháp lượng tử hóa KV phải rẻ và hỗ trợ nối thêm theo luồng.

⏰ Độ mới của thông tin: Phần KIVI theo bài báo; tham số, phương án Scale và đường hiệu chuẩn của vLLM dựa trên v0.32.0 (phát hành 2026-10-18). Tham số cũ calculate_kv_scales trong v0.32.0 đã không còn.

📑 Mục lục


1. Trước hết ước tiết kiệm được bao nhiêu cache

Với MHA/GQA thông thường, đặt số lớp LL, số request BB, độ dài cache mỗi request TT, số KV Head HKVH_{\text{KV}}, chiều Head dd, mỗi phần tử bb byte:

MKV=2LBTHKVdbM_{\text{KV}}=2LBTH_{\text{KV}}db

Số 2 đầu là cho K và V. Lấy L=32,HKV=8,d=128L=32,H_{\text{KV}}=8,d=128, một request cache 32768 token, ở 16-bit:

2×32×32768×8×128×2 B=4 GiB2\times32\times32768\times8\times128\times2\ \text{B}=4\ \text{GiB}

8-bit khoảng 2 GiB; 2-bit khoảng 0.5 GiB. Con số 2-bit chỉ là cận dưới của bản thân mã hóa, chưa tính Scale/zero-point theo nhóm, vùng dư độ chính xác cao, căn chỉnh và chi phí phân trang. MLA, sliding window và kiến trúc lai có cấu trúc cache khác, phải tính theo cấu trúc thật.

Hai việc có thể chồng: PagedAttention làm không gian sẵn có ít lãng phí hơn; lượng tử hóa làm mỗi phần tử nhỏ hơn. Lượng tử hóa weight không tự động đổi bb ở đây; độ chính xác KV phải cấu hình riêng.


2. Vì sao KV không thể chép nguyên lượng tử hóa weight

Ràng buộc thời gian khác. Weight có thể offline bỏ vài giờ chạy GPTQ, xử lý một lần dùng đi dùng lại. KV thì sinh ra trong lúc request chạy; mỗi token mới phải ghi một bản. Lượng tử hóa phải đủ rẻ, không thì bộ nhớ tiết kiệm được phải đổi bằng độ trễ.

Sai số đi vào vị trí khác. Viết P=softmax⁡(QKT/d)P=\operatorname{softmax}(QK^\mathsf T/\sqrt d):

  • Sai số của Key trước hết đổi QKTQK^\mathsf T, rồi qua Softmax, có thể khiến mô hình chú ý nhầm vị trí.
  • Sai số của Value đi vào PVPV, sau khi nhân trọng số attention thì ảnh hưởng đầu ra.

Vì vậy hiệu quả lượng tử hóa KV phải đưa về các tác vụ ngữ cảnh dài để kiểm. Vài đoạn hội thoại ngắn bình thường không chứng minh được truy hồi tài liệu dài, suy luận xuyên đoạn và đầu ra dài cũng bình thường.


3. KIVI: Key theo kênh, Value theo token

3.1 Hai tensor, hai cách nhóm

KIVI là thuật toán lượng tử hóa KV Cache 2-bit không cần tinh chỉnh. Điểm xuất phát là một quan sát: trong các mô hình họ nghiên cứu, Key có outlier theo kênh rõ rệt, Value thì không. Vì vậy bài báo chủ trương lượng tử hóa Key theo kênh, Value theo token.

Cố định một lớp, một request và một KV Head, xem cả K và V là ma trận T×dT\times d, giữ quy ước mục 4.1 “hàng = token, cột = kênh”:

  • Key Per-channel: cố định một kênh, nhóm vài token liên tiếp để lấy tham số lượng tử hóa (cắt dọc).
  • Value Per-token: cố định một token, nhóm theo kênh để lấy tham số lượng tử hóa (cắt ngang).

Hình dưới dùng kích thước nhóm G = 4, độ dài dư R = 4 để minh họa hai cách nhóm. Xanh là dữ liệu 2-bit đã lượng tử hóa; cột đỏ là kênh outlier của Key; vàng là vùng dư toàn độ chính xác trong khung nét đứt; mỗi khung đen dùng chung một bộ tham số lượng tử hóa:

Hướng nhóm của KIVI: mỗi kênh Key tạo nhóm dọc 4 token; mỗi token Value tạo nhóm ngang 4 kênh; các token mới nhất nằm trong vùng dư toàn độ chính xác

Key cắt dọc thì kênh biên độ lớn bị tách riêng, không còn kéo các kênh khác; Value cắt ngang thì mỗi token dùng phạm vi của mình. Khung đen trong hình là nhóm dùng chung tham số lượng tử hóa, không phải KV Block vật lý của PagedAttention.

Điều này đặc biệt quan trọng ở 2-bit: mỗi nhóm chỉ có 4 mã. Số chênh lệch lớn chen vào một nhóm thì giá trị nhỏ gần như đều rơi vào cùng một mã (mục 4.1).

Asymmetric trong tiêu đề bài báo nghĩa là K và V dùng chiến lược khác nhau; trong nhóm vẫn là ánh xạ không đối xứng theo min/max. “Nhóm thế nào” và “mã hóa trong nhóm thế nào” là hai câu hỏi riêng.

3.2 Token mới chưa đủ một nhóm thì sao

Nhóm của Key trải nhiều token, vài token vừa sinh có thể chưa đủ một nhóm. KIVI vì thế giữ một Residual Cache (cache dư) độ chính xác cao, cùng lịch sử đã lượng tử hóa tham gia Attention; token mới tích đủ rồi mới lượng tử hóa theo lô và chuyển vào vùng lịch sử. Value lượng tử hóa theo token, không cần ghép nhóm. Vùng dư của nó chỉ giữ vài token gần nhất: mỗi token mới vào thì lượng tử hóa token cũ nhất rồi đẩy ra, giống sliding window.

Vùng dư làm ghi theo luồng khả thi và giữ token gần nhất ở độ chính xác cao, nhưng cũng chiếm thêm chỗ. Ngữ cảnh càng ngắn, tỷ trọng vùng dư và metadata càng cao, tỷ lệ nén thực tế càng xa “từ 16-bit xuống 2-bit là 8 lần”.

Bài báo thử trên Llama, Falcon và Mistral, báo cáo bộ nhớ đỉnh (gồm weight mô hình) giảm 2.6 lần, batch phóng to tới 4 lần, và dưới tải suy luận thực tế thông lượng tăng 2.35–3.47 lần.

📌 Điểm then chốt: KIVI là phương pháp trong bài báo, có triển khai riêng, không phải đường KV mặc định của vLLM. 2-bit là cấu hình bài báo kiểm trên mô hình và tác vụ của họ; đổi mô hình thì phải đánh giá lại.


4. FP8 KV Cache của vLLM

Đường kỹ thuật phổ biến hơn là lưu KV dạng FP8: dữ liệu giảm nửa, áp lực độ chính xác nhẹ hơn 2-bit nhiều, cũng không cần cấu trúc cache đặc biệt như KIVI. Định dạng số FP8 mở ở mục 4.5; ở đây chỉ xem phần dùng.

4.1 Chọn định dạng

Các giá trị thường dùng của kv_cache_dtype:

📊 Giá trịÝ nghĩaGhi chú hỗ trợ (tài liệu v0.32.0)
autoKhớp kiểu dữ liệu của mô hìnhMặc định
fp8 / fp8_e4m3E4M3, độ chính xác cao hơnCUDA 11.8+ và ROCm
fp8_e5m2E5M2, phạm vi lớn hơn, độ chính xác thấp hơnCUDA 11.8+

CacheDType trong mã nguồn còn liệt kê thêm int8_per_token_head, fp8_per_token_head, nvfp4, v.v. Chúng phụ thuộc backend Attention cụ thể; trước khi dùng phải xem hỗ trợ của backend tương ứng. Ngoài ra một số ít mô hình như DeepSeek-V3.2 mặc định đã dùng FP8 KV.

4.2 FP8 cũng cần Scale

Phạm vi số mũ của FP8 có hạn, giá trị thực vẫn phải nhân một Scale để vào khoảng phù hợp. v0.32.0 hỗ trợ hai độ hạt Scale:

  • Per-tensor: Q, K, V mỗi cái một Scale.
  • Per-attention-head: mỗi Head một; q_scale có num_heads cái, k/v_scale có num_kv_heads cái. Hiện chỉ backend Flash Attention hỗ trợ, và phải hiệu chuẩn bằng LLM Compressor.

Nguồn của Scale có hai đường:

  1. Không hiệu chuẩn: chỉ đặt kv_cache_dtype="fp8", mọi Scale lấy 1.0. Checkpoint không có Scale thì log khởi động cảnh báo “Using KV cache scaling factor 1.0 for fp8_e4m3”.
  2. Hiệu chuẩn trên tập dữ liệu (tài liệu khuyến nghị): dùng QuantizationModifier của LLM Compressor với kv_cache_scheme, chạy một lần oneshot trên tập hiệu chuẩn, ghi Scale vào checkpoint. Ví dụ trong tài liệu dùng 512 mẫu, độ dài tối đa 2048, strategy chọn "tensor" hoặc "attn_head".

⚠️ Lưu ý: Tham số cũ calculate_kv_scales ước Scale từ token ngẫu nhiên trong v0.32.0 đã không còn. Chỉ còn “Scale = 1.0” và “đọc Scale hiệu chuẩn trong checkpoint”. Chạy được không có nghĩa là đã hiệu chuẩn.

4.3 Chạy thông trước, rồi mới hiệu chuẩn

Ví dụ offline dưới đây chỉ kiểm đường FP8 KV có nạp và sinh được không:

from vllm import LLM, SamplingParams

llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",
    dtype="bfloat16",  # độ chính xác phát hành của checkpoint
    max_model_len=4096,
    kv_cache_dtype="fp8",  # chưa hiệu chuẩn: mọi Scale đều 1.0
)
outputs = llm.chat(
    [[{"role": "user", "content": "Dùng hai câu nói rõ lượng tử hóa KV Cache tiết kiệm được gì."}]],
    SamplingParams(temperature=0, max_tokens=128),
)
print(outputs[0].outputs[0].text)

So sánh chính thức nên dùng checkpoint có Scale đã hiệu chuẩn, và trong log xác nhận không còn cảnh báo Scale bằng 1.0.

4.4 Bỏ qua các lớp nhạy

Một số lớp nhạy hơn với lượng tử hóa KV, ví dụ lớp Attention sliding window. --kv-cache-dtype-skip-layers giữ các lớp chỉ định ở độ chính xác gốc, các lớp còn lại lượng tử hóa bình thường:

vllm serve <model> \
  --kv-cache-dtype fp8 \
  --kv-cache-dtype-skip-layers sliding_window  # cũng có thể viết số lớp, như 0 1 23

Giao diện Python tương ứng là kv_cache_dtype_skip_layers=["sliding_window"].

4.5 Độ chính xác lưu trữ không phải độ chính xác tính toán

FP8 KV trước hết giảm lưu trữ; sau khi đọc ra, backend có thể chuyển lại độ chính xác cao rồi mới tính, hoặc tính Attention thẳng trong miền FP8. Tài liệu vLLM ghi rõ, backend Flash Attention 3 kết hợp FP8 KV thì Attention tính trong miền FP8, và Query cũng bị lượng tử hóa thành FP8. Vì vậy chỉ nhìn kv_cache_dtype không kết luận được độ chính xác cả đoạn Attention; phải kết hợp GPU, backend và log khởi động (Chương 2 đã nói chọn backend).


5. Kiểm chứng lợi ích ngữ cảnh dài thế nào

Cố định định dạng weight, chỉ đổi cấu hình KV, mới thấy thay đổi đến từ đâu:

📊 Lợi ích cần kiểmĐiều kiện so sánhQuan sát gì
Dung lượng cacheCùng ngân sách bộ nhớ KVSố token cache được; ngữ cảnh và concurrency chịu được
Độ trễ một requestCùng độ dài input/output và concurrencyTTFT, TPOT; lượng tử hóa và chuyển đổi có thêm chi phí không
Thông lượng dịch vụCùng yêu cầu chất lượng và mục tiêu độ trễDung lượng thêm có thành concurrency hữu ích hơn không
Chất lượng ngữ cảnh dàiTập tác vụ văn bản dài độc lậpĐộ chính xác truy hồi, vị trí trích dẫn, suy luận xuyên đoạn và đầu ra dài

vLLM chia hết bộ nhớ còn lại cho pool KV, nên hai lần chạy trong nvidia-smi có thể trông gần như nhau. Thứ thực sự đổi là cùng ngân sách chứa được bao nhiêu KV token. Con số đó nằm trong log khởi động, nói nhiều hơn một ảnh chụp tổng bộ nhớ.

Khi ghép lượng tử hóa weight với lượng tử hóa KV, hãy kiểm riêng từng cái rồi mới kiểm tổ hợp. Hai nguồn sai số chồng trong cùng một mạng; mỗi cái qua đánh giá riêng không có nghĩa tổ hợp cũng ổn.


📝 Tóm tắt

  • Lượng tử hóa KV giảm lưu trữ mỗi token lịch sử; lợi ích tăng theo độ dài ngữ cảnh và concurrency.
  • KV sinh online, nên phương pháp phải rẻ và hỗ trợ nối thêm theo luồng; sai số của K ảnh hưởng phân bố attention, sai số của V ảnh hưởng đầu ra có trọng số.
  • KIVI nhóm Key theo kênh, Value theo token, và dùng cache dư độ chính xác cao cho token mới chưa đủ một nhóm.
  • FP8 KV trong vLLM v0.32.0 hỗ trợ Scale Per-tensor và Per-attention-head; Scale hoặc lấy 1.0, hoặc lấy từ checkpoint hiệu chuẩn bằng LLM Compressor.
  • Lớp nhạy có thể giữ độ chính xác gốc bằng --kv-cache-dtype-skip-layers; dưới backend FA3 thì bản thân Attention cũng tính trong miền FP8.
  • Đánh giá lợi ích hãy nhìn dung lượng KV token trong log, và làm riêng đánh giá chất lượng ngữ cảnh dài.

🎯 Danh sách tự kiểm

  • Dùng MKV=2LBTHKVdbM_{\text{KV}}=2LBTH_{\text{KV}}db ước được chiếm dụng KV của một mô hình ở ngữ cảnh cho trước
  • Nói được khác biệt lượng tử hóa KV với lượng tử hóa weight về ràng buộc thời gian và lan truyền sai số
  • Vẽ được hướng nhóm Key, Value trong KIVI và giải thích vì sao nhóm vậy
  • Giải thích được vì sao Residual Cache kéo thấp tỷ lệ nén thực tế
  • Nói được hai độ hạt và hai nguồn của Scale FP8 KV trong vLLM v0.32.0
  • Từ log khởi động phán đoán được FP8 KV có thực sự dùng Scale đã hiệu chuẩn không
  • Giải thích được khi chiếm dụng bộ nhớ trông giống nhau thì làm sao biết lượng tử hóa KV có tăng dung lượng không

📚 Tài liệu tham khảo