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

4.1 Cơ bản về lượng tử hóa

Hiểu lượng tử hóa và giải lượng tử hóa qua thước có ít vạch chia: Scale, zero-point, độ rộng bit, độ hạt, hiệu chuẩn, cùng khác biệt giữa PTQ và QAT

Lượng tử hóa INT8 INT4 Scale PTQ QAT

Lượng tử hóa (quantization) giống như đổi sang thước có ít vạch hơn: trước đọc được tới milimet, giờ chỉ còn centimet. Dữ liệu nhỏ đi, và số đọc ra cũng thô hơn. Lượng tử hóa trả lời ba câu hỏi: cách đặt vạch chia (Scale và zero-point), bao nhiêu giá trị dùng chung một thước (độ hạt / granularity), và làm sao để mô hình ít bị ảnh hưởng bởi sai số đọc (hiệu chuẩn cùng PTQ/QAT).

Phần này chỉ nói các khái niệm không phụ thuộc framework. SmoothQuant, GPTQ/AWQ, lượng tử hóa KV Cache và số thực dấu phẩy thấp bit ở các phần sau đều xây trên những ý này.

⏰ Độ mới của thông tin: Phần này là nguyên lý chung, không phụ thuộc phiên bản framework cụ thể. Quy tắc làm tròn lấy định nghĩa toán tử QuantizeLinear của ONNX làm ví dụ.

📑 Mục lục


1. Trước hết hãy tách đối tượng lượng tử hóa

Khi suy luận, bộ nhớ GPU chủ yếu chứa ba loại dữ liệu. Cả ba đều có thể lượng tử hóa, nhưng tính chất khác nhau:

📊 Đối tượngĐặc điểm dữ liệuLợi ích chính sau lượng tử hóa
Weight (trọng số)Cố định sau khi huấn luyện; có thể xử lý offline thong thảMô hình nhỏ hơn; mỗi bước đọc ít weight hơn
Activation (kích hoạt)Đổi theo từng input; chỉ xử lý được onlineCho phép nhân ma trận đi kernel độ chính xác thấp
KV CacheTăng theo request và ngữ cảnhMỗi token lịch sử chiếm ít cache hơn

Ký hiệu thường gặp W4A16 nghĩa là weight 4-bit, activation 16-bit; W8A8 nghĩa là cả hai đều 8-bit. Ký hiệu này chỉ mô tả hai đầu vào của lớp tuyến tính. Độ chính xác của KV Cache phải nói riêng.

Ước nhanh: 7B tham số toàn 16-bit khoảng 14 GB dữ liệu thô; toàn 4-bit khoảng 3.5 GB (GB thập phân, chưa tính tham số lượng tử hóa). Lượng dữ liệu còn một phần tư; tốc độ nhanh thêm bao nhiêu thì phụ thuộc điểm nghẽn nằm ở đâu, như Chương 1 đã nói.


2. Ánh xạ số, Scale và Zero-point

2.1 Lượng tử hóa và giải lượng tử hóa

FP16 bản thân cũng chỉ biểu diễn hữu hạn giá trị. Lượng tử hóa ánh xạ chúng sang ít mã hơn nữa. Với mã hóa số nguyên đều, lượng tử hóa affine thường viết:

q=clip⁡(round⁡(x/s)+z, qmin⁡,qmax⁡),x^=s(q−z)q=\operatorname{clip}\left(\operatorname{round}(x/s)+z,\ q_{\min},q_{\max}\right), \qquad \hat{x}=s(q-z)
  • s>0s>0 là Scale, khoảng số giữa hai vạch liền kề trên thước;
  • zz là Zero-point, vạch số nguyên mà số thực 0 rơi vào;
  • Lượng tử hóa cho mã qq; giải lượng tử hóa (dequantization) dùng ss, zz để đưa mã về giá trị xấp xỉ x^\hat x.

Quy tắc round do từng triển khai quyết định. Ví dụ QuantizeLinear kiểu số nguyên của ONNX dùng “làm tròn nửa về số chẵn” (Round Half to Even).

Lấy s=0.1,z=0s=0.1,z=0: x=0.26x=0.26 thành q=3q=3, đọc lại được x^=0.3\hat x=0.3. Phần 0.04 bị bỏ đi không lấy lại được. Đổi sang s=1/16s=1/16, cùng một 0.26 thành 4 và đọc lại 0.25. Vậy “giữ một chữ số thập phân” chỉ là trùng hợp của s=0.1s=0.1. Thứ thực sự quyết định biểu diễn được những giá trị nào là độ rộng bit và Scale. Mọi số trong một khoảng đều rơi vào cùng một ô; với s=0.1s=0.1, 0.251, 0.26 và 0.349 đều ánh xạ thành 3.

Hình dưới vẽ hai khoảng 4-bit trên cùng một trục số thực (đối xứng lấy s=0.25s=0.25; không đối xứng dùng lại [−1,3][-1,3] ở mục 2.3). Nền xanh là phạm vi lượng trình bao phủ: điểm xanh lá bị kéo về vạch gần nhất, tạo sai số làm tròn; điểm đỏ vượt ngoài lượng trình bị ép về đầu mút, tạo sai số cắt cụt. Sai số lượng tử hóa về cơ bản là hai loại này.

Lượng tử hóa 4-bit đối xứng và không đối xứng trên trục số: giá trị trong lượng trình làm tròn về vạch gần nhất; giá trị ngoài lượng trình bị cắt về đầu mút

2.2 Lượng tử hóa đối xứng

Dữ liệu dương âm khá cân bằng thì có thể đặt z=0z=0. Quy ước INT8 phổ biến chỉ dùng [−127,127][-127,127]:

s=max⁡∣x∣127,q=clip⁡(round⁡(x/s),−127,127)s=\frac{\max |x|}{127},\qquad q=\operatorname{clip}(\operatorname{round}(x/s),-127,127)

Kiểu INT8 bản thân biểu diễn được tới −128-128. Ở đây ta chủ động bỏ nó để hai đầu đối xứng. 4-bit cũng chỉ dùng [−7,7][-7,7]; trục đối xứng trong hình trên được lấy như vậy. Lợi ích là giải lượng tử hóa chỉ cần nhân một Scale, và nhân ma trận không phải sửa zero-point.

2.3 Lượng tử hóa không đối xứng

Nếu dữ liệu toàn nằm trong [0,6][0,6], lượng tử hóa đối xứng sẽ lãng phí nửa số mã cho số âm không dùng tới. Lượng tử hóa không đối xứng để lượng trình bám theo dữ liệu: chọn khoảng chứa 0 là [a,b][a,b], rồi tính

s=b−aqmax⁡−qmin⁡,z=clip⁡(round⁡(qmin⁡−as),qmin⁡,qmax⁡)s=\frac{b-a}{q_{\max}-q_{\min}},\qquad z=\operatorname{clip}\left(\operatorname{round}\left(q_{\min}-\frac{a}{s}\right),q_{\min},q_{\max}\right)

Ví dụ [a,b]=[−1,3][a,b]=[-1,3], khoảng mã [0,15][0,15], được s=4/15,z=4s=4/15,z=4. Với x=1x=1, q=round⁡(3.75)+4=8q=\operatorname{round}(3.75)+4=8, giải lượng tử hóa ra 4×4/15≈1.0674\times4/15\approx1.067. Zero-point phải làm tròn thành số nguyên, nên hai đầu lượng trình thực tế lệch một chút.

Lượng tử hóa không đối xứng khớp phân phối lệch tốt hơn. Cái giá là phải lưu thêm zero-point và thêm một bước sửa khi tính. Tensor toàn không cần xử lý riêng để tránh ra s=0s=0.

2.4 Độ rộng bit quyết định vạch dày đến đâu

Khi lượng trình cố định, mỗi lần bớt 1 bit thì số ô giảm nửa, khoảng cách ô nhân đôi. Lượng tử hóa đều [0,1][0,1]:

📊 Độ rộng bitSố mãKhoảng cách vạch liền kềSai số làm tròn tối đa
8-bit2561/255≈0.003921/255\approx0.00392≈0.00196\approx0.00196
4-bit161/15≈0.066671/15\approx0.06667≈0.03333\approx0.03333
2-bit41/3≈0.333331/3\approx0.33333≈0.16667\approx0.16667

Từ 8-bit xuống 4-bit, khoảng cách ô thành khoảng 17 lần. Vì vậy các phương án thấp bit gần như luôn cần độ hạt mịn hơn hoặc bù sai số thông minh hơn. GPTQ, AWQ, KIVI phía sau đều đang giải quyết việc này.


3. Trục của tensor và độ hạt lượng tử hóa

3.1 Nhìn rõ các trục của ma trận

Chương này thống nhất dùng cách viết toán Y=XWY=XW:

X∈RT×Cin,W∈RCin×Cout,Ytk=∑jXtjWjkX\in\mathbb R^{T\times C_{\text{in}}},\qquad W\in\mathbb R^{C_{\text{in}}\times C_{\text{out}}},\qquad Y_{tk}=\sum_j X_{tj}W_{jk}

Hàng của XX là token; cột là kênh đầu vào. nn.Linear.weight của PyTorch có shape Cout×CinC_{\text{out}}\times C_{\text{in}}, đúng bằng chuyển vị của WW ở đây. Đọc code thì đừng đảo “hàng” với “cột”.

Xem một ma trận activation nhỏ (3 token, 4 kênh):

X=[0.20.58.0−0.10.30.49.0−0.20.10.67.0−0.1]X=\begin{bmatrix} 0.2&0.5&8.0&-0.1\\ 0.3&0.4&9.0&-0.2\\ 0.1&0.6&7.0&-0.1 \end{bmatrix}

Cột 3 lớn trên mọi token. Hiện tượng “vài kênh liên tục lớn hơn hẳn” rất thường gặp ở activation của LLM, và là điểm xuất phát của SmoothQuant ở mục 4.2.

3.2 Bao nhiêu dữ liệu dùng chung một thước

Độ hạt là “nhóm dữ liệu dùng chung Scale lớn đến đâu”. Nhóm càng nhỏ, thước càng khớp dữ liệu cục bộ, nhưng cũng phải lưu và đọc nhiều Scale hơn:

📊 Độ hạtPhạm vi chia sẻDùng điển hìnhCái giá
Per-tensorCả tensor một ScaleActivation tĩnh, weight FP8Một outlier kéo cả nhóm
Per-channelMỗi kênh đầu ra mộtWeight INT8Số Scale bằng số kênh đầu ra
Per-groupMỗi gg phần tử liên tiếp một (vd. 128)Weight INT4Nhiều Scale hơn; kernel phải giải lượng tử hóa theo nhóm
Per-tokenMỗi token mộtActivation độngRuntime phải tìm max từng hàng trước

Hình dưới vẽ bốn độ hạt trên layout Y=XWY=XW. Ô cùng màu cùng nhãn dùng chung một Scale. XX góc dưới phải lấy từ ma trận trên; khung nét đứt đỏ là kênh đầu vào thứ 3 luôn lớn:

Bốn độ hạt lượng tử hóa: Per-tensor cả W một Scale; Per-channel mỗi kênh đầu ra một; Per-group nhóm theo kênh đầu vào trong một cột; Per-token mỗi token một hàng và cắt ngang cột outlier

Với XX trên, Per-token cho mỗi hàng một lượng trình riêng, nhưng 8.0 và 0.2 trong cùng hàng vẫn dùng chung một thước, nên giá trị nhỏ vẫn bị thô. Độ hạt chỉ cắt được theo một trục nào đó. Hướng cắt không khớp hướng outlier thì có mịn hơn cũng chẳng giúp. Outlier của activation nằm theo kênh, trong khi nhân ma trận đòi hỏi Scale của activation rút được ra khỏi tổng (chỉ chia được theo token). Đó là gốc rễ khiến W8A8 khó làm.


4. Hiệu chuẩn: chọn khoảng đo cho thước

4.1 Lượng trình rộng thì mờ, hẹp thì đứt

Scale do lượng trình quyết định. Lấy lượng trình tới max thì outlier vừa, nhưng phần lớn dữ liệu chen vào vài ô và sai số làm tròn lớn; thu hẹp lượng trình thì phần lớn dữ liệu đọc mịn hơn, nhưng phần vượt ra bị cắt. Hiệu chuẩn (calibration) là cân bằng giữa sai số làm tròn và sai số cắt cụt. Cách thường gặp gồm lấy max, lấy phân vị, hoặc tìm điểm cắt theo mục tiêu MSE hay sai số đầu ra.

Weight cố định nên thống kê trực tiếp từ weight. Activation cần mẫu thật mới thấy phân phối; lô mẫu đó gọi là tập hiệu chuẩn (calibration set). Nó phải phủ ngôn ngữ, Chat Template và độ dài input thực tế trên production, và tách khỏi tập đánh giá, không thì điểm số sẽ lạc quan quá.

4.2 Tĩnh và động

  • Lượng tử hóa tĩnh (static): dùng tập hiệu chuẩn tính Scale offline, suy luận dùng luôn. Chi phí runtime thấp nhất, nhưng phân phối input đổi thì có thể sai.
  • Lượng tử hóa động (dynamic): lúc suy luận tính Scale theo dữ liệu hiện tại. Khớp input hơn; cái giá là mỗi lần phải tìm max trước.

Bản thân “động” không nói gì về độ hạt. Thấy một phương án tự xưng lượng tử hóa động thì còn phải xác nhận ba việc: Scale tính lúc nào, theo trục nào, và áp lên những toán tử nào.


5. PTQ và QAT

PTQ (Post-Training Quantization) lượng tử hóa ngay sau khi huấn luyện xong, nhiều nhất là chỉnh Scale hoặc weight bằng ít dữ liệu hiệu chuẩn. QAT (Quantization-Aware Training) thì trong lúc huấn luyện hoặc fine-tune đã để mô hình forward “đeo” sai số lượng tử hóa, để tham số tự thích nghi.

Forward của QAT dùng Fake Quant: lượng tử hóa rồi giải lượng tử hóa ngay. Dữ liệu vẫn là số thực, nhưng đã mang sai số làm tròn. Đạo hàm của round hầu như mọi nơi bằng 0, nên lan truyền ngược dùng xấp xỉ STE (Straight-Through Estimator): trong lượng trình truyền gradient nguyên xi, ngoài lượng trình thường đặt 0.

📊 ChiềuPTQQAT
Cần gìMô hình đã huấn luyện, thêm ít dữ liệu hiệu chuẩnQuy trình huấn luyện, dữ liệu và sức tính
Kiểm soát sai số thế nàoChọn Scale, điểm cắt, hoặc bù theo lớp (GPTQ/AWQ)Để tham số thích nghi lượng tử hóa trong lúc huấn luyện
Hợp lúc nàoWeight 8-bit, 4-bit và các cảnh sai số còn kiểm soát đượcBit thấp hơn, hoặc khi độ chính xác PTQ chưa đạt
Vẫn phải kiểmĐộ chính xác trên tác vụ mục tiêuĐộ chính xác trên tác vụ mục tiêu, và kernel suy luận có hỗ trợ định dạng đó không

Triển khai suy luận LLM chủ yếu dùng PTQ. Các phương pháp phía sau chương này về cơ bản đều thuộc PTQ.

💡 Gợi ý: PTQ không có nghĩa là không cần dữ liệu; hầu hết phương pháp vẫn cần tập hiệu chuẩn. QAT cũng không phải huấn luyện trên số nguyên; phần tính chính vẫn là số thực, sai số lượng tử hóa được mô phỏng bằng Fake Quant.


6. Thực hành: quan sát ảnh hưởng của outlier

Đoạn Python thuần dưới đây làm lượng tử hóa INT8 đối xứng trên 4 số, so sánh “bao phủ hết” và “cắt về 1”:

def quantize(values, amax):
    scale = amax / 127 if amax > 0 else 1.0
    codes = [max(-127, min(127, round(x / scale))) for x in values]
    return codes, scale


def dequantize(codes, scale):
    return [q * scale for q in codes]


x = [0.2, 0.5, 0.8, 20.0]
for label, bound in [("bao phủ hết", 20.0), ("cắt về 1", 1.0)]:
    codes, scale = quantize(x, bound)
    restored = dequantize(codes, scale)
    errors = [abs(a - b) for a, b in zip(x, restored)]
    print(label, "scale =", round(scale, 6))
    print("mã:", codes)
    print("khôi phục:", [round(v, 6) for v in restored])
    print("sai số tuyệt đối:", [round(v, 6) for v in errors])

Khi lượng trình bao phủ 20, Scale khoảng 0.15748. Ba giá trị nhỏ chỉ khôi phục được khoảng 0.157, 0.472, 0.787, và hai cái đầu đã có sai số tương đối trên 5%. Cắt về 1 thì giá trị nhỏ gần như không sai, còn 20 bị ép thành 1 với sai số tới 19.

Cả hai lựa chọn đều không tốt: một outlier đủ khiến cả nhóm tiến thoái lưỡng nan. Chỉ chỉnh lượng trình không giải quyết được; hoặc chuyển outlier đi chỗ khác (SmoothQuant ở 4.2), hoặc thu nhỏ nhóm dùng chung Scale và bù sai số (GPTQ/AWQ ở 4.3).


📝 Tóm tắt

  • Lượng tử hóa ánh xạ số sang ít mã hơn; sai số đến từ làm tròn và cắt cụt.
  • Scale là khoảng cách vạch; Zero-point là ô chứa 0. Lượng tử hóa đối xứng bỏ bước sửa zero-point; không đối xứng khớp phân phối lệch tốt hơn.
  • Mỗi lần bớt 1 bit thì khoảng cách vạch nhân đôi; khoảng cách 4-bit khoảng 17 lần so với 8-bit.
  • Độ hạt quyết định bao nhiêu dữ liệu dùng chung một Scale; hướng cắt phải khớp hướng outlier mới có hiệu quả.
  • Hiệu chuẩn chọn lượng trình giữa làm tròn và cắt cụt; “động” phải nói rõ thời điểm, trục và toán tử áp dụng.
  • PTQ là dòng chính của suy luận LLM; QAT dùng Fake Quant và STE để tham số thích nghi sai số lượng tử hóa.

🎯 Danh sách tự kiểm

  • Viết được công thức lượng tử hóa affine và giải lượng tử hóa, giải thích ý nghĩa của Scale và Zero-point
  • Tính tay được Scale, zero-point và kết quả khôi phục của lượng tử hóa đối xứng lẫn không đối xứng
  • Ước được dung lượng lưu weight của một mô hình ở 16-bit và 4-bit
  • Nói được Per-tensor, Per-channel, Per-group, Per-token mỗi loại chia sẻ Scale theo trục nào
  • Giải thích được vì sao outlier theo kênh của activation không thể chỉ dựa Per-token để xử lý
  • Phân biệt được lượng tử hóa tĩnh và động, nêu ba việc cần xác nhận khi gặp một phương án “động”
  • Nói được khác biệt PTQ và QAT, cùng vai trò của Fake Quant và STE

📚 Tài liệu tham khảo