Skip to content

Bộ 02 · Bộ nhớ và năng lực ​

Những gì vừa trên một GPU: trọng số, KV cache, và còn chỗ cho bao nhiêu người dùng. Bài 03–05, 07, 16–19, 25, 42, 50. Trả lời thành tiếng mỗi câu trong khoảng một phút, rồi mới xem thẻ.

Q1 · Suy ra số byte KV mỗi token, và GQA mua được gì vững ​

Bài: 05, 07, 17

Ghi chú

Llama 3 8B có 32 layer, 8 KV head, head dimension 128, ở BF16. Một token chiếm bao nhiêu byte KV cache? Llama 2 7B tương tự nhưng có 32 KV head. Điều đó ảnh hưởng thế nào tới số người dùng mỗi GPU?

Trả lời trong 60 giây

  • Công thức: mỗi token lưu một vector K và một vector V ở mọi layer, cho mọi KV head, nên KV mỗi token = 2 (K và V) × số layer × số KV head × head dim × số byte.
  • Llama 3 8B: đó là 2 × 32 layer × 8 KV head × 128 × 2 byte (BF16) = 131.072 byte, tức 128 KiB mỗi token.
  • Llama 2 7B: nó có 32 KV head thay vì 8 vì không dùng GQA, nên mỗi token chiếm gấp 4 lần, 512 KiB. Cùng pool đó khi ấy chỉ chứa một phần tư số token, nghĩa là ít người dùng hơn khoảng 4 lần mỗi GPU.
  • GQA mua được gì: nó chia mỗi K/V head cho nhiều query head, nên có ít KV head phải lưu hơn. Điều đó cắt giảm bộ nhớ KV và cả số byte KV phải đọc ở mỗi bước decode, mà chất lượng gần như không giảm.

Các con số chính: 8B: 128 KiB mỗi token; 70B (80 layer, 8 KV head): 320 KiB mỗi token.

Câu hỏi tiếp theo hay gặp: "Một cuộc hội thoại 4.096 token nặng bao nhiêu?" Mỗi token chiếm 128 KiB, nên 4.096 token chiếm 4.096 × 128 KiB = 512 MiB, khoảng nửa gigabyte cho một cuộc chat.

Mẹo

Nói to từng thừa số (K và V, layer, KV head, head dim, byte). Có lần tôi đưa ra luỹ thừa mà không nói chúng là gì.

Q2 · Số tham số và "host MoE như một model 13B" ưu tiên ​

Bài: 05, 07

Ghi chú

Tham số của Llama 3 8B nằm ở đâu (h = 4.096, kích thước MLP 14.336, độ rộng KV 1.024)? Và một đồng nghiệp nói Mixtral 8x7B mỗi token chỉ dùng khoảng 13 tỷ tham số, nên cứ host nó như một model 13B trên một H100 80 GB ở BF16. Đúng không?

Trả lời trong 60 giây

  • Mỗi block: Q và O mỗi cái h × h, K và V mỗi cái h × 1.024, và MLP có ba ma trận h × 14.336. Đó là 2 × 4.096² ≈ 34 triệu, cộng 2 × 4.096 × 1.024 ≈ 8 triệu, cộng 3 × 4.096 × 14.336 ≈ 176 triệu, tổng ≈ 218 triệu mỗi block. Vì 176 ÷ 218 ≈ 0,81, MLP chiếm ≈ 81% mỗi block.
  • Toàn model: 32 block × 218 triệu ≈ 7 tỷ. Embedding và LM head thêm ≈ 1 tỷ nữa, ra tổng ≈ 8 tỷ.
  • Quy tắc MoE: bộ nhớ đi theo tổng số tham số, tính toán đi theo số tham số đang hoạt động. Mọi expert đều phải nằm trong bộ nhớ GPU, dù mỗi token chỉ chạy qua vài expert.
  • Kết luận về Mixtral: nó lưu 46,7 tỷ tham số, mỗi cái 2 byte là ≈ 93 GB ở BF16, không vừa 80 GB. Vậy đồng nghiệp sai: mỗi token nó tính như một model ≈ 13B, nên rẻ về FLOP, nhưng không rẻ về bộ nhớ.

Các con số chính: block ≈ 2h² + 2h × độ rộng KV + 3h × kích thước MLP.

Câu hỏi tiếp theo hay gặp: "Decode MoE có đọc cả 93 GB mỗi bước không?" Ở batch lớn, gần như expert nào cũng được ít nhất một token trong batch chọn, nên có, mỗi bước đọc gần như toàn bộ. Ở batch 1, chỉ các expert đang hoạt động được đọc.

Mẹo

Kết thúc bằng kết luận. Tôi đã nêu khái niệm (bộ nhớ = tổng) nhưng chưa bao giờ nói "93 GB không vừa 80 GB".

Q3 · Llama 3 70B có vừa một H100 không? ưu tiên ​

Bài: 03, 04, 16, 18, 50

Ghi chú

Llama 3 70B chạy BF16 trên hai H100 (mỗi cái 80 GB, 3,35 TB/s). PM muốn đưa nó lên một GPU. Trọng số ở BF16, FP8 và 4-bit là bao nhiêu, cái nào thực sự phục vụ được người dùng, và thời gian decode mỗi token là bao nhiêu?

Trả lời trong 60 giây

  • Trọng số: byte trọng số = số tham số × byte mỗi tham số. Ở 2 byte là ≈ 141 GB với BF16, ở 1 byte là ≈ 70 GB với FP8, và ở nửa byte là ≈ 36 GB với 4-bit, đã tính hệ số scale.
  • FP8 nạp được nhưng không phục vụ được: 70 GB trọng số trên GPU 80 GB chỉ còn khoảng 10 GB, và phần đó phải gánh activation, CUDA graph và KV cache. Vậy model nạp được, nhưng gần như không có chỗ cho người dùng.
  • 4-bit phục vụ được: nó chừa ≈ 40 GB cho KV cache, và với 320 KiB mỗi token, đó là chỗ cho ≈ 120 nghìn token.
  • Mức sàn decode: mỗi bước decode đọc toàn bộ trọng số, nên thời gian mỗi token = byte trọng số ÷ băng thông. BF16 là 141 GB ÷ 3,35 TB/s ≈ 42 ms trên một GPU, hoặc ≈ 21 ms khi chia cho hai. FP8 là 70 ÷ 3,35 ≈ 21 ms, và 4-bit là 36 ÷ 3,35 ≈ 10,7 ms.
  • Kết luận: FP8 trên một GPU đã ngang BF16 trên hai ở ≈ 21 ms, nhưng không có chỗ cho KV. 4-bit trên một GPU chứa được người dùng và nhanh gần gấp đôi BF16 trên hai (≈ 10,7 ms so với ≈ 21 ms).

Các con số chính: trọng số = số tham số × số bit ÷ 8; mức sàn decode = byte trọng số ÷ băng thông.

Câu hỏi tiếp theo hay gặp: "Ta có A100. FP8 còn giúp không?" Một phần. Trọng số FP8 vẫn tiết kiệm bộ nhớ và thời gian decode, vì thời gian decode đi theo số byte đọc. Nhưng A100 không có tensor core FP8, nên trọng số phải nở ra 16 bit để tính, nghĩa là phép tính chạy ở tốc độ 16-bit và prefill không nhanh lên.

Mẹo

Lúc đầu tôi nói thời gian decode không đổi khi giảm số bit. Nó đi theo số byte, nên giảm một nửa mỗi khi số bit giảm một nửa. Và đó là byte ÷ (byte/s), không phải "byte mỗi giây ÷ băng thông".

Q4 · "Mỗi GPU chỉ phục vụ được 3 người dùng?" ưu tiên ​

Bài: 19, 25, 42

Ghi chú

Llama 3.1 8B trên một H100, mặc định của vLLM. Log khởi động ghi "Maximum concurrency for 131,072 tokens per request: 3.43x" và PM hoảng. Thực sự chứa được bao nhiêu cuộc chat 1.800 token, và max_num_seqs nên là bao nhiêu nếu request ở p95 dài 2.400 token?

Trả lời trong 60 giây

  • Log có nghĩa là gì: con số 3.43x là trường hợp xấu nhất ở max_model_len, giả định mọi request lớn tới 131.072 token. Pool 54,8 GiB với 128 KiB mỗi token chứa ≈ 449 nghìn token, và 449 nghìn ÷ 131.072 = 3,43, đúng con số trong log.
  • Chat thật: với 1.800 token mỗi cuộc, cùng pool đó chứa 449 nghìn ÷ 1.800 ≈ 249 cuộc cùng lúc, không phải 3.
  • Đặt max_num_seqs: định cỡ theo percentile cao, không phải trung bình, vì chính các request dài là thứ làm tràn pool. Ở p95 2.400 token, 449 nghìn ÷ 2.400 ≈ 187, rồi làm tròn xuống kèm chút biên, khoảng 160–180.
  • Vì sao làm tròn xuống: nếu nhận nhiều request hơn mức vừa ở độ dài cuối cùng, engine phải đẩy một số ra giữa chừng, gây ra preemption. Chúng hiện ra thành các đỉnh nhọn âm thầm ở phần đuôi mà không có lỗi nào.

Các con số chính: số token của pool = số byte pool ÷ KV mỗi token; số request vừa = số token của pool ÷ số token cuối cùng mỗi request.

Câu hỏi tiếp theo hay gặp: "Một sản phẩm agent (prompt 1.000 token, đầu ra 6.000 token) có P99 TPOT tăng vọt nhưng không lỗi, với max_num_seqs 1.024 và pool 533 nghìn token." Mỗi request lớn tới 1.000 + 6.000 = 7.000 token, nên chỉ 533 nghìn ÷ 7.000 ≈ 76 vừa ở độ dài cuối, thấp xa so với 1.024 được nhận. Đó là lý do bạn bị preemption thay vì lỗi, và counter num_preemptions_total xác nhận điều đó. Đặt max_num_seqs ở ≈ 70, ngay dưới con số 76 vừa.

Mẹo

Tôi đã nhầm số token mỗi request với số request ("bất cứ gì trên 2.400"), và lưỡng lự khi làm tròn. Luôn làm tròn xuống: nhận nhiều hơn mức vừa là thứ gây ra preemption.

Q5 · Ba lần crash lúc khởi động, và mức trần utilization ưu tiên ​

Bài: 18, 42

Ghi chú

vLLM crash ở ba ngày khác nhau: lúc nạp model, lúc định cỡ KV cache sau khi đặt max_model_len = 1.000.000, và trong lúc capture CUDA graph. Mỗi lần cái gì bị cạn? Sau đó có người muốn nâng gpu_memory_utilization từ 0.92 lên 0.99. Điều gì có thể hỏng?

Trả lời trong 60 giây

  • Lúc nạp: bản thân trọng số không vừa bộ nhớ GPU, nên cách sửa là độ chính xác thấp hơn hoặc thêm GPU.
  • Lúc định cỡ cache: với max_model_len = 1.000.000, KV cho một request độ dài tối đa lớn hơn cả pool. Bạn không thể nới cache, vì nó đã nhận hết phần còn lại, nên cách sửa là hạ max_model_len.
  • Lúc capture graph: CUDA graph cần bộ nhớ từ khoảng dự phòng nằm ngoài ngân sách, và khoảng đó quá nhỏ. Cách sửa là hạ gpu_memory_utilization hoặc capture ít kích thước graph hơn.
  • Nâng nhẹ: từ 0.92 lên 0.95 trên 79,1 GiB thêm 0,03 × 79,1 ≈ 2,4 GiB vào pool. Với 128 KiB mỗi token, đó là thêm ≈ 19 nghìn token, khoảng 10 cuộc chat 1.800 token nữa.
  • Lên 0.99: gần như không còn khoảng dự phòng, nên có rủi ro crash out-of-memory lúc đang chạy khi tải đỉnh, kể cả sau một lần khởi động sạch sẽ.

Các con số chính: số token KV thêm = Δutilization × bộ nhớ GPU ÷ KV mỗi token.

Câu hỏi tiếp theo hay gặp: "Vì sao lỗi lúc chạy tệ hơn lỗi lúc khởi động?" Lỗi lúc khởi động hiện ra ngay. Lỗi lúc chạy qua được mọi bài test ở tải thấp, nên nó được ship, rồi crash trên production lúc cao điểm.

Mẹo

Với crash 2 tôi đã nói "giảm KV cache". Thứ quá lớn là độ dài tối đa. Với 0.99 tôi đã nêu khoảng dự phòng nhưng không nói khi nào nó hỏng (lúc đang chạy).

Q6 · KV cache fp8 hay trọng số 8-bit? ưu tiên ​

Bài: 42, 50

Ghi chú

Cùng server 8B: pool 54,8 GiB, 128 KiB mỗi token, 15 GiB trọng số. Bạn được bật một cờ: KV cache fp8, hoặc trọng số 8-bit (15 → 7,5 GiB). Cái nào thêm nhiều dung lượng KV hơn, và câu trả lời có đổi với 70B trên hai H100 không?

Trả lời trong 60 giây

  • KV cache fp8: nó giảm một nửa số byte mỗi token, từ 128 KiB xuống 64 KiB, nên cùng pool chứa gấp đôi số token. Điều đó nhân đôi dung lượng, từ 449 nghìn lên ≈ 900 nghìn token.
  • Trọng số 8-bit: chúng thêm một lượng cố định, bằng phần bộ nhớ chúng giải phóng. Từ 15 xuống 7,5 GiB giải phóng 7,5 GiB, và 7,5 GiB ÷ 128 KiB ≈ thêm 61 nghìn token.
  • Với 8B, KV fp8 thắng dễ dàng: pool lớn và trọng số nhỏ, nên nhân đôi pool thêm ≈ 449 nghìn token trong khi thu nhỏ trọng số chỉ thêm ≈ 61 nghìn.
  • Với 70B trên hai H100, câu trả lời đảo ngược: trọng số (141 GB) chiếm ưu thế và pool nhỏ, nên nhân đôi nó không thêm được bao nhiêu. Trọng số 8-bit giải phóng ≈ 70 GB, nhiều hơn hẳn so với việc nhân đôi một pool nhỏ, nên trọng số 8-bit thắng.

Các con số chính: KV fp8: dung lượng × 2; lượng tử hoá trọng số: + số byte giải phóng ÷ KV mỗi token.

Câu hỏi tiếp theo hay gặp: "Làm cả hai được không?" Được, vì chúng tiết kiệm bộ nhớ ở những chỗ khác nhau, và với 70B đó là cấu hình thường gặp: trọng số FP8 và KV cache FP8.

Mẹo

Tôi đã nói cả hai cờ đều nhân đôi dung lượng. Chỉ KV fp8 nhân đôi; trọng số nhỏ hơn thêm một lượng cố định.