
128k context trên desktop là ảo tưởng: KV cache ăn hết RAM
Model 8B chạy vừa 16 GiB bỗng thành bài toán 32 GiB khi bạn bật 128k context. Công thức tính KV cache và cách chọn num_ctx đúng cho máy bạn.
Bạn set num_ctx lên 128k cho con model 8B đang chạy ngon lành, rồi máy bắt đầu swap. Weights vẫn 16 GiB như cũ — thứ vừa ăn hết RAM là KV cache.
Phép tính công khai và rất ngắn
KV cache giữ hai tensor, keys và values, cho mỗi layer, mỗi KV head, mỗi token. Theo bài phân tích trên Dev.to dẫn tài liệu Transformer Inference Arithmetic, công thức là:
cache_bytes = 2 × layers × context × kv_heads × head_dim × bytes_per_element
Lấy Llama 3.1 8B làm ví dụ: 32 layers, 8 KV heads, head_dim 128, đọc thẳng từ config.json trên Hugging Face. Ở 131.072 token với fp16 (2 byte):
2 × 32 × 131072 × 8 × 128 × 2 = 17.179.869.184 byte ≈ 16 GiB
Weights của chính model đó ở fp16 cũng khoảng 16 GiB. Nói cách khác, ở context tối đa, cache lớn ngang chính model. Con "8B chạy vừa 16 GiB" lặng lẽ thành bài toán 32 GiB ngay khi bạn set num_ctx lên 128k.
Vì sao cache phình theo context
Attention phải so mỗi token với mọi token đứng trước, và inference là autoregressive — không có gì được tính lại, mọi thứ đều được nhớ. Đó chính là mẹo làm generation nhanh: keys và values của mỗi token tính một lần rồi lưu.
Dung lượng mỗi token là hằng số, nên tổng dung lượng tuyến tính theo context. 128k không phải là "một con số to hơn" — nó là 128 lần cache của cửa sổ 1k, và được cấp phát suốt vòng đời session.
GQA là khoản giảm giá phía model
Grouped-query attention (GQA) cắt số KV head, nên cache mỏng đi. Qwen2.5 7B dùng 28 layers với chỉ 4 KV heads, nên cùng cửa sổ 128k chỉ tốn khoảng 7 GiB ở fp16.
| Model (fp16) | Layers × KV heads | KV cache @ 128k | Weights | Cache/weights |
|---|---|---|---|---|
| Llama 3.1 8B | 32 × 8 | ~16 GiB | ~16 GiB | ~100% |
| Qwen2.5 7B | 28 × 4 | ~7 GiB | ~15 GiB | ~47% |
Tự chạy lại con số cho model của bạn — đây là toàn bộ phần kiểm tra (ví dụ minh hoạ):
def kv_cache_gib(layers, kv_heads, head_dim, ctx, bytes_per=2):
return 2 * layers * ctx * kv_heads * head_dim * bytes_per / 2**30
print(kv_cache_gib(32, 8, 128, 131_072)) # Llama 3.1 8B -> 16.0
print(kv_cache_gib(28, 4, 128, 131_072)) # Qwen2.5 7B -> 7.0
Bốn chỗ con số marketing bỏ qua
- Prefill là hoá đơn thứ hai. Trước token trả lời đầu tiên, toàn bộ prompt bị xử lý một lượt. Prompt 100k token nghĩa là nhai 100k token trước khi bạn thấy một ký tự output. Trên 3060, đó là vài phút chứ không phải mili giây.
- Model sliding-window phá công thức — theo hướng có lợi. Kiến trúc kiểu Mistral chặn attention ở một cửa sổ cố định mỗi layer, nên cache ngừng phình. Công thức đơn giản ở trên tính dư cho nhóm này; nó đúng với transformer full-attention, tức phần lớn thứ người ta chạy.
- KV quantization có thật nhưng chỉ giải quyết một nửa. llama.cpp cache được KV ở q8_0, giảm khoảng một nửa cache với chút rủi ro chất lượng. Một nửa của 16 GiB vẫn là 8 GiB.
- Spec hiếm khi ghi số KV head. Bạn phải lục
config.jsonmới thấy, và điều đó nói lên cách con số 128k được bày ra để đọc.
Việc nên làm
Cách sửa khá chán: set context đúng bằng mức workload của bạn thật sự dùng. Cửa sổ 32k tốn một phần tư cache của 128k, và phủ gần hết prompt mà một dev đơn lẻ thực sự gửi đi.
Trước khi kéo num_ctx lên kịch, mở config.json của model, lấy layers và kv_heads, chạy đúng hàm ở trên. Nếu con số vượt RAM trống, bạn đã có câu trả lời mà không cần khởi động lại server lần nào.
Điều này không làm long context vô dụng. RAG tồn tại chính vì nhồi đầy cửa sổ là cách đắt đỏ để nói "đọc mục 4".
Không spam, hủy đăng ký bất kỳ lúc nào.


