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

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

FP8 E4M3 E5M2 NVFP4 MXFP4 Hopper Blackwell

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

1.1 Cùng một chuỗi bit, hai ý nghĩa

Trong lượng tử hóa INT8 đều (uniform), số nguyên qq được đọc theo s(q−z)s(q-z); 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ánhLượng tử hóa số nguyên đềuLượng tử hóa số thực bit thấp
Ý nghĩa của mã hóaSố nguyên, kèm Scale và zero-pointDấu, số mũ, phần định trị, kèm Scale ngoài
Khoảng cách khi Scale cố địnhBằ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 chungSai số tương đối gần như đều trong miền số chuẩn
Tính toánCần Kernel nhận đầu vào số nguyênCầ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ạngDấu / số mũ / phần định trịGiá trị hữu hạn lớn nhấtGiá trị đặc biệtĐánh đổi
E4M31 / 4 / 3±448Chỉ có NaN, không InfĐộ chính xác cao hơn, phạm vi nhỏ hơn
E5M21 / 5 / 2±57344±Inf và NaNPhạ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 [1,2)[1,2) khoảng cách liền kề là 1/8=0.1251/8=0.125; tới [8,16)[8,16) 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 s=0.125s=0.125, vạch lục là E4M3 với Scale 1, nền vàng đánh dấu [1,2)[1,2) nơi cả hai cùng khoảng cách:

INT8 phân bố đều từ 0 đến 16, khoảng cách mọi chỗ là 0.125; E4M3 nhân đôi khoảng cách mỗi khi qua một lũy thừa của 2, từ 0.125 lên 1, dày hơn gần 0

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 s=0.125s=0.125 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 127×0.125=15.875127\times0.125=15.875. 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ề [−448,448][-448,448] 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:

q=cast⁡FP8(x/s),x^=sqq=\operatorname{cast}_{\text{FP8}}(x/s),\qquad \hat{x}=s q

Khi scale theo max, lấy s=max⁡∣x∣/Fmax⁡s=\max|x|/F_{\max}, với E4M3 thì Fmax⁡=448F_{\max}=448, và xử lý riêng tensor toàn 0. Ở đây ss 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 ánWeightActivation
fp8_per_tensorE4M3, mỗi tensor một FP32 ScaleMỗ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_channelE4M3, mỗi kênh đầu ra một ScalePer-token động
fp8_per_blockE4M3, mỗi khối 128×128 một ScaleMỗ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, 0.5, 1, 1.5, 2, 3, 4, 60,\ 0.5,\ 1,\ 1.5,\ 2,\ 3,\ 4,\ 6

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à 22×1.52^2\times1.5. 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 Wtorch∈R256×512W_{\text{torch}}\in\mathbb{R}^{256\times512} (mỗi hàng một kênh đầu ra):

📊 Cấu hìnhPhần tử dùng chung ScaleSố lượng Scale
Per-group, G=128G=128128 phần tử liên tiếp trong cùng hàng256×(512/128)=1024256\times(512/128)=1024
Block 2D, 128×128128\times128Vùng 128 hàng × 128 cột(256/128)×(512/128)=8(256/128)\times(512/128)=8

Block 1D 1×1281\times128 cùng kiểu nhóm với Per-group ở trên; Block 2D 128×128128\times128 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:

MXFP4: mỗi 32 phần tử E2M1 dùng chung một E8M0 Scale, trung bình 4.25 bit; NVFP4: mỗi 16 phần tử dùng chung một E4M3 Scale, rồi nhân thêm một FP32 Scale cấp tensor, trung bình 4.5 bit

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 [0.5,1,2,6][0.5,1,2,6] và [5,10,20,60][5,10,20,60], 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ì 10=1.25×2310=1.25\times2^3.

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ánhMXFP4NVFP4
NguồnĐặc tả OCP MX (nhiều hãng cùng xây)NVIDIA, ra cùng Blackwell
Phần tử dữ liệuE2M1E2M1
Kích thước khối32 phần tử16 phần tử
Scale khốiE8M0, chỉ lũy thừa của 2E4M3, có 3 bit phần định trị
Scale cấp tensorKhôngThêm một FP32 Scale
Bit width trung bình (không kể Scale cấp tensor)4+8/32=4.254+8/32=4.25 bit4+8/16=4.54+8/16=4.5 bit

Dequantization của NVFP4 là nhân hai tầng:

x^=stensor⋅sblock⋅q\hat{x}=s_{\text{tensor}}\cdot s_{\text{block}}\cdot q

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ạngGhi 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 ScaleW8A8 cần SM100 trở lên, GPU khác fallback về W8A16
Checkpoint ModelOpt (modelopt_fp4)NVFP4, hoặc weight NVFP4 kiểu W4A16Trê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:

  1. 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.
  2. 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.
  3. 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 khaiThực thi thực tếLợi ích chính
Mô hình FP8, Ada / Hopper / BlackwellFP8 W8A8 GEMMGiảm một nửa bộ nhớ, tăng throughput tính toán
Mô hình FP8, AmpereFP8 Marlin, W8A16Giảm một nửa bộ nhớ, Decode đọc weight nhanh hơn
Mô hình NVFP4, Kernel gốc BlackwellFP4 GEMMBộ nhớ nhỏ nhất, tăng throughput tính toán
Mô hình NVFP4, GPU không có FP4 gốcMarlin W4A16Chỉ 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_channel và 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 ss 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