4.5 FP8 và NVFP4/MXFP4
Phân biệt lượng tử hóa số nguyên và số thực, hiểu phạm vi với độ chính xác của E4M3/E5M2, Scale của FP8, cùng micro-block scaling của NVFP4, MXFP4 và điều kiện phần cứng để chạy chúng trên vLLM v0.32.0
Các mục trước dùng mã hóa số nguyên, mục liền trước lại xuất hiện FP8 KV Cache. INT8 và FP8 đều chiếm 8 bit; khác nhau ở cách đọc: định dạng số nguyên như thước chia đều, định dạng số thực gần với thước log, dày gần 0 và thưa dần ra ngoài. Mục này bắt đầu từ những gì đổi khi chuyển từ INT sang FP, rồi tới FP8 phổ biến từ Hopper trở đi, và NVFP4 / MXFP4 trên Blackwell.
⏰ Độ cập nhật: Định nghĩa định dạng theo đặc tả OCP MX, bài FP8 Formats và tài liệu NVIDIA; hỗ trợ phần cứng cùng các phương án lượng tử hóa online của vLLM dựa trên tài liệu v0.32.0 (phát hành 2026-10-18).
📑 Mục lục
- 1. FP8: phạm vi và độ chính xác được chia thế nào
- 2. Vì sao FP8 vẫn cần Scale
- 3. FP4 và micro-block scaling
- 4. Khác biệt giữa NVFP4 và MXFP4
- 5. Hỗ trợ phần cứng trở thành lợi ích mô hình thế nào
- Tóm tắt
- Checklist tự kiểm
- Tài liệu tham khảo
1. FP8: phạm vi và độ chính xác được chia thế nào
1.1 Cùng một chuỗi bit, hai ý nghĩa
Trong lượng tử hóa INT8 đều (uniform), số nguyên được đọc theo ; khi Scale cố định, các giá trị khôi phục liền kề cách đều. FP8 chia 8 bit thành dấu, số mũ và phần định trị. Ví dụ 00111000:
- Đọc theo INT8 có dấu thì là 56;
- Đọc theo E4M3, dấu 0, số mũ
0111, phần định trị000, thì là 1.0.
Vậy nếu chỉ đổi nhãn kiểu của một mã hóa INT8 sang FP8, mọi giá trị đều đổi. Chuyển giữa các định dạng phải mã hóa lại từ giá trị gốc và tính lại Scale. Bảng mã E4M3 đầy đủ và các giá trị đặc biệt nằm trong ghi chú FP8 của ONNX.
| 📊 So sánh | Lượng tử hóa số nguyên đều | Lượng tử hóa số thực bit thấp |
|---|---|---|
| Ý nghĩa của mã hóa | Số nguyên, kèm Scale và zero-point | Dấu, số mũ, phần định trị, kèm Scale ngoài |
| Khoảng cách khi Scale cố định | Bằng nhau mọi chỗ | Đổi theo khoảng số mũ |
| Đặc điểm sai số | Một cận sai số tuyệt đối chung | Sai số tương đối gần như đều trong miền số chuẩn |
| Tính toán | Cần Kernel nhận đầu vào số nguyên | Cần Kernel cho đúng định dạng số thực |
Bit width không đổi; cái đổi là cách các giá trị biểu diễn được phân bố, phạm vi lớn thế nào, và đi đường tính toán nào. Sai số nào nhỏ hơn phải xem phân bố dữ liệu, Scale và độ chi tiết.
1.2 E4M3 và E5M2
Cách chia 8 bit cho số mũ và phần định trị chính là đánh đổi phạm vi với độ chính xác. Bài FP8 Formats đề xuất hai mã hóa mà NVIDIA và nhiều phần cứng sau này dùng rộng:
| 📊 Định dạng | Dấu / số mũ / phần định trị | Giá trị hữu hạn lớn nhất | Giá trị đặc biệt | Đánh đổi |
|---|---|---|---|---|
| E4M3 | 1 / 4 / 3 | ±448 | Chỉ có NaN, không Inf | Độ chính xác cao hơn, phạm vi nhỏ hơn |
| E5M2 | 1 / 5 / 2 | ±57344 | ±Inf và NaN | Phạm vi lớn hơn, độ chính xác thấp hơn |
E5M2 theo quy ước giá trị đặc biệt của IEEE 754; E4M3 không biểu diễn vô cực và chỉ giữ một mẫu phần định trị cho NaN, nhờ đó mở phạm vi tới 448. Weight và activation khi suy luận thường dùng E4M3, và FP8 W8A8 của vLLM cũng chủ yếu là E4M3; bài báo gợi ý chia E4M3 cho weight và activation, E5M2 cho gradient khi huấn luyện.
Lấy số chuẩn E4M3 làm ví dụ: trong khoảng cách liền kề là ; tới thành 1. Cái nó giữ là độ chính xác tương đối, khác với bước cố định của INT8. Gần 0 còn có quy tắc riêng cho số dưới chuẩn (subnormal).
Hình dưới vẽ các giá trị biểu diễn được từ 0 đến 16 theo tỉ lệ: vạch xanh là INT8 với , vạch lục là E4M3 với Scale 1, nền vàng đánh dấu nơi cả hai cùng khoảng cách:
Khi so sánh định dạng phải nêu Scale. Với Scale ngoài bằng 1, E4M3 biểu diễn chính xác 1.375, còn INT8 phải làm tròn về số nguyên gần nhất; nhưng nếu chọn cho INT8, nó cũng biểu diễn chính xác 1.375, đổi lại giá trị lớn nhất còn . Sai số của một số không đủ để kết luận định dạng nào tốt hơn tổng thể.
📌 Điểm then chốt: Chia cho Scale rồi round() sẽ rơi vào lưới số nguyên; dù có cắt về cũng chưa phải E4M3. Để mô phỏng sai số FP8 cần ép kiểu thật (ví dụ torch.float8_e4m3fn của PyTorch).
2. Vì sao FP8 vẫn cần Scale
Phạm vi số mũ của FP8 vẫn hữu hạn. Scale ngoài phù hợp giảm việc số rơi vào miền dưới chuẩn, dưới chảy thành 0 hoặc tràn giá trị lớn nhất. Trong miền số chuẩn, chỉ đổi Scale theo lũy thừa của 2 hầu như không nâng độ chính xác tương đối; vai trò chính của Scale là đưa phân bố vào phạm vi biểu diễn được.
Công thức thường gặp:
Khi scale theo max, lấy , với E4M3 thì , và xử lý riêng tensor toàn 0. Ở đây là Scale nhân khi dequantization; một số thư viện lưu nghịch đảo, nên khi đọc mã phải xác nhận chiều.
Giống số nguyên, Scale của FP8 cũng có thể đặt theo tensor, kênh, Token hoặc Block. Lượng tử hóa FP8 online của vLLM v0.32.0 chia theo độ chi tiết thành vài phương án (fp8_per_channel có trong tên phương án --quantization ở mã nguồn, nhưng bảng phương án trong tài liệu không liệt kê):
| 📊 Phương án | Weight | Activation |
|---|---|---|
fp8_per_tensor | E4M3, mỗi tensor một FP32 Scale | Mỗi tensor một Scale; trên Ada và Hopper, activation của lớp tuyến tính chuyển sang Per-token |
fp8_per_channel | E4M3, mỗi kênh đầu ra một Scale | Per-token động |
fp8_per_block | E4M3, mỗi khối 128×128 một Scale | Mỗi khối 1×128 một Scale |
Về phần cứng, tài liệu vLLM v0.32.0 ghi: tính toán FP8 cần compute capability 8.9 trở lên (Ada, Hopper, Blackwell); trên Turing và Ampere mô hình FP8 vẫn chạy được, nhưng đi đường W8A16 của FP8 Marlin, tức chỉ weight là FP8 còn phép nhân vẫn dùng 16-bit. Tài liệu nói FP8 có thể giảm một nửa bộ nhớ mô hình và tăng throughput tối đa 1.6 lần.
3. FP4 và micro-block scaling
3.1 E2M1 chỉ có 8 biên độ
Xuống 4-bit, E2M1 còn 1 bit dấu, 2 bit số mũ, 1 bit phần định trị, nên các biên độ không âm chỉ còn:
0.5 là số dưới chuẩn; giá trị lớn nhất là 6 chứ không phải 8 vì chỉ có 1 bit phần định trị, số chuẩn lớn nhất là . Bảng mã đầy đủ nằm trong ghi chú FP4 của ONNX.
Chỉ có 8 biên độ, nếu cả lớp vài triệu số dùng chung một Scale thì khác biệt cục bộ gần như mất hết. Từ đó có Micro-block Scaling (scale theo khối nhỏ): mỗi nhóm phần tử nhỏ dùng chung một Scale và tự khớp phạm vi của mình. Như một ảnh tập thể chỉ chỉnh phơi sáng một lần, tách thành nhiều ảnh nhỏ thì mỗi ảnh chỉnh sáng riêng.
3.2 Nói về độ chi tiết thì phải ghi rõ hình dạng
Micro-block scaling cùng ý tưởng với Per-group ở mục 4.1, nhưng cách gọi Per-group và Block-wise không thống nhất: Block có thể một chiều hoặc hai chiều. Lấy layout PyTorch (mỗi hàng một kênh đầu ra):
| 📊 Cấu hình | Phần tử dùng chung Scale | Số lượng Scale |
|---|---|---|
| Per-group, | 128 phần tử liên tiếp trong cùng hàng | |
| Block 2D, | Vùng 128 hàng × 128 cột |
Block 1D cùng kiểu nhóm với Per-group ở trên; Block 2D chia sẻ Scale xuyên kênh đầu ra, đúng như weight fp8_per_block ở mục trước. Khi so sánh độ chi tiết phải ghi layout tensor, trục nhóm và hình dạng nhóm. Khối cập nhật của GPTQ và Tile tính toán của GEMM thuộc quá trình thực thi, không quyết định phạm vi chia sẻ Scale.
3.3 Bản thân Scale cũng có định dạng
Với FP4, đặc tả định dạng quy định luôn kích thước khối và kiểu Scale. Hình dưới vẽ cùng 32 phần tử dưới hai định dạng: ô xanh là phần tử E2M1 4-bit, ô vàng là Scale khối 8-bit, ô lục là Scale cấp tensor FP32 thêm của NVFP4:
Cả hai định dạng đều dùng phần tử dữ liệu E2M1; khác ở số phần tử mỗi khối và định dạng lưu Scale.
Ví dụ: hai nhóm và , lần lượt Scale 1 và 10, đều biểu diễn chính xác bằng cùng một bộ giá trị E2M1. Nhưng bản thân Scale cũng phải mã hóa: E8M0 của MXFP4 chỉ lưu được lũy thừa của 2, không lưu được 10; E4M3 của NVFP4 thì được, vì .
Tài liệu NVFP4 của NVIDIA đưa một ví dụ: với cùng dữ liệu, MSE mã hóa Scale của E8M0 là 0.72, còn E4M3 chỉ 0.08. Scale càng sát phạm vi thật thì các vạch E2M1 trong khối càng được dùng đủ.
4. Khác biệt giữa NVFP4 và MXFP4
4.1 Scale hai tầng và Scale một tầng
| 📊 So sánh | MXFP4 | NVFP4 |
|---|---|---|
| Nguồn | Đặc tả OCP MX (nhiều hãng cùng xây) | NVIDIA, ra cùng Blackwell |
| Phần tử dữ liệu | E2M1 | E2M1 |
| Kích thước khối | 32 phần tử | 16 phần tử |
| Scale khối | E8M0, chỉ lũy thừa của 2 | E4M3, có 3 bit phần định trị |
| Scale cấp tensor | Không | Thêm một FP32 Scale |
| Bit width trung bình (không kể Scale cấp tensor) | bit | bit |
Dequantization của NVFP4 là nhân hai tầng:
Scale khối E4M3 tối đa chỉ 448, không phủ hết dải động của cả tensor, nên trước hết dùng FP32 Scale cấp tensor kéo tổng thể vào khoảng phù hợp, rồi Scale khối tinh chỉnh cục bộ. E8M0 của MXFP4 có 8 bit số mũ, phạm vi đủ lớn nên không cần tầng hai; đổi lại Scale chỉ lấy lũy thừa của 2, nên max trong khối thường không dùng hết các vạch E2M1.
NVFP4 dùng khối nhỏ hơn và Scale mịn hơn để đổi lấy độ chính xác, trả thêm 0.25 bit/phần tử metadata. Tài liệu NVIDIA báo cáo DeepSeek-R1-0528 từ FP8 sang NVFP4 thì độ sụt trên các eval then chốt trong 1%; bộ nhớ mô hình khoảng 1/3.5 so với FP16 và 1/1.8 so với FP8. Đó là kết quả của hãng trên mô hình cụ thể; đổi mô hình vẫn phải tự đánh giá.
4.2 Cách dùng trong vLLM v0.32.0
| 📊 Cổng vào | Định dạng | Ghi chú |
|---|---|---|
--quantization mxfp4 (online) | Weight E2M1 + mỗi 1×32 một E8M0 Scale | Độ chính xác activation của lớp tuyến tính phụ thuộc backend; một số backend vẫn dùng BF16 |
--quantization mxfp8 (online) | Weight FP8 + mỗi 1×32 một E8M0 Scale | W8A8 cần SM100 trở lên, GPU khác fallback về W8A16 |
Checkpoint ModelOpt (modelopt_fp4) | NVFP4, hoặc weight NVFP4 kiểu W4A16 | Trên GPU không có FP4 gốc thì fallback về Marlin W4A16 |
Lượng tử hóa online chuyển thẳng từ checkpoint BF16/FP16 lúc load, không cần dữ liệu calibration, hợp để thử nhanh; NVFP4 thường xuất offline bằng NVIDIA Model Optimizer trước rồi mới giao cho vLLM load. Kernel lớp tuyến tính có thể cố định bằng --linear-backend, các giá trị gồm cutlass, flashinfer_cutlass, flashinfer_trtllm, marlin và tương tự; nó thay biến môi trường cũ VLLM_NVFP4_GEMM_BACKEND.
⚠️ Lưu ý: Tên phương án FP8 online là fp8_per_tensor, fp8_per_channel hoặc fp8_per_block. Với checkpoint chưa lượng tử hóa, ghi --quantization fp8 sẽ báo lỗi ngay, khác hành vi bản cũ.
5. Hỗ trợ phần cứng trở thành lợi ích mô hình thế nào
“Card này hỗ trợ FP4” và “mô hình này chạy FP4 nhanh hơn” cách nhau vài bước. Để lấy được lợi ích tính toán cần đủ cả ba điều kiện:
- Tensor Core có lệnh tương ứng: trên GPU NVIDIA, FP8 bắt đầu từ Ada và Hopper; nhân ma trận gốc của NVFP4, MXFP4 cần Tensor Core thế hệ năm của Blackwell.
- Framework chọn đúng Kernel: vLLM chọn theo nền tảng; không có Kernel gốc thì fallback về đường Weight-only như Marlin, kèm cảnh báo trong log.
- Activation cũng được lượng tử hóa: khi chỉ weight là bit thấp (W4A16, W8A16), phép nhân vẫn chạy ở 16-bit, tiết kiệm băng thông chứ không phải sức tính.
Ba điều đều thỏa cũng mới chỉ làm lớp tuyến tính nhanh hơn. Với ngữ cảnh dài, Attention và đọc KV có thể chiếm phần lớn; phần đó dựa vào lượng tử hóa KV ở mục 4.4 và backend Attention ở chương 2.
| 📊 Tình huống triển khai | Thực thi thực tế | Lợi ích chính |
|---|---|---|
| Mô hình FP8, Ada / Hopper / Blackwell | FP8 W8A8 GEMM | Giảm một nửa bộ nhớ, tăng throughput tính toán |
| Mô hình FP8, Ampere | FP8 Marlin, W8A16 | Giảm một nửa bộ nhớ, Decode đọc weight nhanh hơn |
| Mô hình NVFP4, Kernel gốc Blackwell | FP4 GEMM | Bộ nhớ nhỏ nhất, tăng throughput tính toán |
| Mô hình NVFP4, GPU không có FP4 gốc | Marlin W4A16 | Chỉ còn lợi ích bộ nhớ và băng thông |
💡 Gợi ý: Để xác nhận đường thực tế, xem trong log khởi động các dòng kiểu Selected <kernel> for <module> hoặc cảnh báo fallback; đáng tin hơn tên mô hình.
📝 Tóm tắt
- INT và FP dùng cùng bit width; khác ở phân bố giá trị biểu diễn được: số nguyên cách đều, số thực giữ độ chính xác tương đối trong miền số chuẩn.
- E4M3 tối đa ±448, độ chính xác cao hơn, dùng cho weight và activation khi suy luận; E5M2 tối đa ±57344, phạm vi lớn hơn, bài báo gợi ý dùng cho gradient.
- FP8 vẫn cần Scale ngoài để dữ liệu rơi vào phạm vi biểu diễn được; các phương án FP8 online của vLLM v0.32.0 gồm
fp8_per_tensor,fp8_per_channelvàfp8_per_block. - E2M1 chỉ có 8 biên độ nên phải kèm micro-block scaling; khi so sánh độ chi tiết phải ghi rõ layout, trục nhóm và hình dạng nhóm.
- MXFP4 là 32 phần tử + E8M0 Scale; NVFP4 là 16 phần tử + E4M3 Scale + FP32 Scale cấp tensor, Scale mịn hơn.
- Lợi ích phần cứng cần đủ hỗ trợ lệnh, Kernel được chọn, và activation cũng được lượng tử hóa; nếu không sẽ fallback về đường Weight-only.
🎯 Checklist tự kiểm
- Giải thích được vì sao cùng 8 bit đọc theo INT8 và E4M3 ra số khác nhau
- Viết được phân bổ bit, giá trị lớn nhất và khác biệt giá trị đặc biệt của E4M3, E5M2
- Nói được vì sao FP8 vẫn cần Scale, và tính theo scale max
- Liệt kê được 8 biên độ không âm của E2M1, giải thích vì sao max là 6
- Tính được số lượng Scale của một tensor dưới độ chi tiết Per-group và Block 2D
- So sánh được MXFP4 và NVFP4 về kích thước khối, định dạng Scale và bit width trung bình
- Nói được ngưỡng phần cứng cho tính toán FP8 trong vLLM v0.32.0, và Ampere đi đường nào
- Phán được một mô hình NVFP4 trên GPU nào đó có thật sự dùng tính toán FP4 hay không
📚 Tài liệu tham khảo
- FP8 Formats for Deep Learning (Micikevicius et al., 2022)
- OCP Microscaling Formats (MX) Specification v1.0
- NVIDIA — Introducing NVFP4 for Efficient and Accurate Low-Precision Inference
- ONNX — Float stored in 8 bits
- ONNX — Float stored in 4 bits
- vLLM v0.32.0 — FP8 W8A8
- vLLM v0.32.0 — Online Quantization
- vLLM v0.32.0 — NVIDIA Model Optimizer