Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 人工智能 / Artificial Intelligence

AI 编译器 / AI Compilers

1. AI 编译器全景——为什么模型需要编译器 / The AI Compiler Landscape and Why Models Need Compilers

2. 编译原理速通——面向 ML 工程师的核心概念 / Compiler Fundamentals for Machine Learning Engineers

3. 中间表示基础——理解 IR 层级与 lowering 链路 / Intermediate Representation Levels and Lowering Pipelines

4. 计算图的构建与表示 / Building and Representing Computational Graphs

5. MLIR 架构、方言与渐进式降级 / MLIR Architecture, Dialects, and Progressive Lowering

6. 算子语义、广播、归约与形状推导 / Operator Semantics, Broadcasting, Reduction, and Shape Inference

7. 模型前端格式:ONNX、TFLite、HLO 与 SavedModel / Model Frontend Formats: ONNX, TFLite, HLO, and SavedModel

8. 图优化 Pass——经典优化在 ML 中的应用 / Graph Optimization Passes for Machine Learning

9. 算子融合——编译器最重要的性能优化 / Operator Fusion as a Core Compiler Optimization

10. 内存规划——Buffer 分配与生命周期管理 / Memory Planning, Buffer Allocation, and Lifetime Management

11. Layout 优化——数据排布转换与内存效率 / Layout Optimization for Data Movement and Memory Efficiency

12. 动态 Shape——符号分析与形状处理 / Dynamic Shapes, Symbolic Analysis, and Shape Processing

13. 硬件约束下的操作调度 / Operation Scheduling Under Hardware Constraints

14. 从模板、DSL 到 IR 降级的代码生成架构 / Code Generation Architectures from Templates and DSLs to IR Lowering

15. CPU 后端:SIMD、分块与多线程 / CPU Backends with SIMD, Tiling, and Multithreading

16. CUDA 后端:合并访存与 Tensor Core / CUDA Backends, Memory Coalescing, and Tensor Cores

17. NPU 后端:脉动阵列与端侧 AI 生态 / NPU Backends, Systolic Arrays, and Edge AI Ecosystems

18. Kernel 性能基础:Roofline 与 Occupancy / Kernel Performance Fundamentals with Roofline and Occupancy

19. CUTLASS 与分层 GEMM 模板 / CUTLASS and Hierarchical GEMM Templates

20. TVM Tensor Expression 与计算调度分离 / TVM Tensor Expressions and Compute-Schedule Separation

21. 使用 Triton 编写高性能 GPU Kernel / Triton for High-Performance GPU Kernels in Python

22. 基于成本模型与实测搜索的自动调度 / Automatic Scheduling with Cost Models and Measurement-Based Search

23. XLA 内部机制:HLO、融合与 SPMD / XLA Internals, HLO, Fusion, and SPMD

24. Torch-MLIR:从 PyTorch 算子到 MLIR 方言 / Torch-MLIR from PyTorch Operators to MLIR Dialects

25. torch.compile:Dynamo、AOTAutograd、Inductor 与 Triton / Torch Compile with Dynamo, AOTAutograd, Inductor, and Triton

26. 从 MLIR 经 LLVM 降级到机器码 / Lowering from MLIR Through LLVM to Machine Code

27. 量化——低精度推理的工程实践 / Engineering Low-Precision Inference with Quantization

28. 分布式编译与训练——多设备编排的编译器支持 / Compiler Support for Distributed Training and Multi-Device Orchestration

29. 生产调试——真实问题的编译器视角排查 / Production Debugging from the Compiler Perspective

30. 未来方向——AI 编译器的新挑战与机遇 / Future Challenges and Opportunities for AI Compilers

本页目录

量化——低精度推理的工程实践 / Engineering Low-Precision Inference with Quantization ​

📅 创建时间:2026-06-03 🏷️ 标签:#量化 #Quantization #PTQ #QAT #INT8 #FP8 #BF16 #混合精度 #SmoothQuant #GPTQ #AWQ 📚 前置知识:[[/04-ai/01-llm-engineering/07-llm-evolution]](LLM 发展脉络) 📚 相关知识:[[18-cutlass]](CUTLASS) [[08-operator-fusion]](算子融合)


┌──────────────────────────────────────────────────────────────────────────────┐
│  📌 场景:量化精度崩塌                                                        │
├──────────────────────────────────────────────────────────────────────────────┤
│  你做了一次 INT8 量化:                                                        │
│  • 模型:Llama-3-8B,FP16 推理速度 100ms/token                                │
│  • 量化后速度:20ms/token(快 5 倍)                                          │
│  • 但 accuracy 测试:MMLU 从 68% 掉到 52%(掉了 16 个点!)                    │
│                                                                              │
│  你完全没想到精度下降这么多。你不知道这是量化方法的问题,                       │
│  还是有办法解决。                                                              │
└──────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11

第1节:量化基础理论 ​

1.1 什么是量化?为什么需要量化? ​

量化(Quantization)是将模型参数和计算从高精度浮点数(如 FP32、FP16)转换为低精度整数(如 INT8)或更小的浮点数(如 FP8、BF16)的过程。

为什么需要量化?

精度格式内存占用计算速度精度损失风险硬件支持
FP324 字节基准无所有 GPU
FP162 字节2x极低所有 Ampere+
BF162 字节2x极低所有 Ampere+
INT81 字节4x中等Tensor Core GPU
FP8 (E4M3)1 字节4x+中等Hopper+
FP8 (E5M2)1 字节4x+较高Hopper+
INT40.5 字节8x较高部分 GPU

量化的核心价值:

  1. 内存节省:8B 模型 FP16 需要 16GB,INT8 只需 8GB,INT4 只需 4GB
  2. 带宽提升:INT8 的内存带宽需求是 FP16 的一半
  3. 计算加速:Tensor Core 对 INT8 有专门的矩阵乘法加速

1.2 对称量化 vs 非对称量化 ​

对称量化(Symmetric Quantization):

  • 使用单个 scale 参数
  • 零点固定为 0
  • 公式:round(x / scale),其中 scale = max(|x|) / 127
  • 适用于 weights(分布相对对称)
python
# 对称量化实现
def symmetric_quantize(tensor, num_bits=8):
    """
    对称量化:将浮点 tensor 量化为 int8
    """
    # 计算 scale:确保原值范围 [-max_val, max_val] 映射到 [-127, 127]
    max_val = torch.max(torch.abs(tensor))
    scale = max_val / 127.0  # 量化范围是 -127 到 127(留出 -128 给特殊情况)
    
    # 量化:除以 scale 然后 round
    quantized = torch.round(tensor / scale)
    
    # 限制在 [-127, 127] 范围内
    quantized = torch.clamp(quantized, -127, 127)
    
    # 反量化(用于验证)
    dequantized = quantized * scale
    
    return quantized.to(torch.int8), scale, dequantized

# 示例:对一个权重矩阵进行对称量化
weight = torch.randn(1024, 1024) * 2.5  # 模拟实际权重
q_weight, scale, dq_weight = symmetric_quantize(weight)

# 计算量化误差
error = torch.mean((weight - dq_weight) ** 2)
print(f"量化误差 (MSE): {error:.6f}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

非对称量化(Asymmetric Quantization):

  • 使用 scale 和 zero_point 两个参数
  • 可以处理不对称分布
  • 公式:round((x - zero_point) / scale)
  • 适用于 activations(分布通常不对称)
python
# 非对称量化实现
def asymmetric_quantize(tensor, num_bits=8):
    """
    非对称量化:用于处理不对称分布(如 ReLU 后的 activations)
    """
    # 计算 min 和 max
    min_val = torch.min(tensor)
    max_val = torch.max(tensor)
    
    # 计算 scale 和 zero_point
    # 量化范围: [0, 255] for uint8
    quant_min = 0
    quant_max = 255
    
    scale = (max_val - min_val) / (quant_max - quant_min)
    zero_point = quant_min - min_val / scale
    
    # 量化
    quantized = torch.round(tensor / scale + zero_point)
    quantized = torch.clamp(quantized, quant_min, quant_max)
    
    # 反量化
    dequantized = (quantized - zero_point) * scale
    
    return quantized.to(torch.uint8), scale, zero_point, dequantized
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

1.3 Per-tensor vs Per-channel 量化 ​

Per-tensor 量化:整个 tensor 共享一个 scale

  • 优点:实现简单,开销小
  • 缺点:可能精度损失大(不同 channel 分布差异大时)
python
# Per-tensor 量化示意
# scale 对整个 tensor 是唯一的
# tensor: [C, H, W] -> 只有一个 global_scale
# 
# 问题示例:某个 channel 有 outlier,会影响其他所有 channel 的精度
# Channel 0: 值范围 [-0.5, 0.5] -> 量化精度高
# Channel 1: 值范围 [-50, 50] -> 需要更大的 scale,Channel 0 的精度被牺牲
1
2
3
4
5
6
7

Per-channel 量化:每个 channel 有独立的 scale

  • 优点:更好地捕捉每个 channel 的分布,精度更高
  • 缺点:需要存储更多 scale 参数(但相对权重本身很小)
python
# Per-channel 量化实现
def per_channel_quantize(tensor, num_bits=8, dim=0):
    """
    Per-channel 量化:每个 channel 独立计算 scale
    dim: 沿着哪个维度计算 channel(通常 dim=0 for linear weights)
    """
    # tensor shape: [out_channels, in_channels]
    # 每个 out_channel 有一个独立的 scale
    
    # 计算每个 channel 的最大绝对值
    max_vals = torch.max(torch.abs(tensor), dim=dim, keepdim=True)[0]
    
    # 每个 channel 的 scale
    scales = max_vals / 127.0
    
    # 量化
    quantized = torch.round(tensor / scales)
    quantized = torch.clamp(quantized, -127, 127)
    
    # 反量化
    dequantized = quantized * scales
    
    return quantized.to(torch.int8), scales, dequantized

# 示例:对 Linear 层的权重进行 per-channel 量化
# weight shape: [out_features, in_features]
# 每个 out_feature 是一个 channel,有独立的 scale
linear_weight = torch.randn(4096, 4096)
q_weight, scales, dq_weight = per_channel_quantize(linear_weight, dim=0)

# scales shape: [4096, 1] - 每个输出通道一个 scale
print(f"原始权重 shape: {linear_weight.shape}")
print(f"Scale 数量: {scales.numel()} (等于 out_features)")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

第2节:量化方法——PTQ vs QAT ​

2.1 PTQ(Post-Training Quantization) ​

PTQ 是在模型训练完成后进行量化的方法,不需要重新训练。

优势:

  • 实现简单,不需要训练资源
  • 快速部署,适合已训练好的模型
  • 支持任意预训练模型

劣势:

  • 精度损失可能较大(尤其是 INT8)
  • 无法处理复杂的量化误差分布
python
# PyTorch 动态量化(最简单的方式)
import torch.quantization

# 动态量化:权重 INT8, activations FP32(推理时动态量化)
model_dynamic = torch.quantization.quantize_dynamic(
    model,                                    # 原始模型
    {torch.nn.Linear, torch.nn.LSTM},          # 要量化的层类型
    dtype=torch.qint8                         # 量化精度
)

# 动态量化特点:
# - 只有权重被静态量化存储为 INT8
# - Activations 在计算时动态量化和反量化
# - 精度损失较小,但推理速度提升有限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
python
# PyTorch 静态量化(需要校准)
import torch.quantization

# 1. 模型准备:插入 QuantStub 和 DeQuantStub
model = Model()
model.eval()
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')  # x86 优化
# 对于 ARM: torch.quantization.get_default_qconfig('qnnpack')

# 2. 融合操作(conv + bn + relu -> fused)
torch.quantization.fuse_model(model)

# 3. 准备量化模型
model_prepared = torch.quantization.prepare(model)

# 4. 校准(使用代表性数据集)
calibration_data = load_calibration_data()
with torch.no_grad():
    for batch in calibration_data:
        model_prepared(batch)

# 5. 转换
model_int8 = torch.quantization.convert(model_prepared)

# 6. 推理
result = model_int8(input_data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

校准(Calibration)的重要性:

python
# 校准过程详解
class CalibrationRunner:
    """
    校准器:收集 activations 的统计信息,确定最佳量化参数
    """
    def __init__(self, model):
        self.model = model
        self histograms = {}  # 存储每个量化节点的直方图
        self.collector = HistogramObserver()
    
    def calibrate(self, calibration_loader, num_batches=100):
        """
        运行校准:收集 activations 的分布信息
        """
        self.model.eval()
        
        with torch.no_grad():
            for i, (data, _) in enumerate(calibration_loader):
                if i >= num_batches:
                    break
                self.model(data)
        
        # 计算最佳 scale 和 zero_point
        for name, observer in self.observer_store.items():
            # MinMaxObserver: 使用 min/max
            # HistogramObserver: 使用直方图 + KL 散度
            # MovingAverageObserver: 使用移动平均
            observer.calculate_qparams()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

2.2 QAT(Quantization-Aware Training) ​

QAT 在训练过程中模拟量化效果,让模型学会在低精度下保持精度。

优势:

  • 精度损失更小(通常可以接近 FP16 水平)
  • 模型可以适应量化带来的误差

劣势:

  • 需要重新训练,计算成本高
  • 需要训练数据和训练基础设施
python
# PyTorch QAT 实现
import torch.quantization

# 1. 模型准备(插入 fake quantization 节点)
model = Model()
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
model_prepared = torch.quantization.prepare_qat(model)

# 2. 使用小学习率微调
optimizer = torch.optim.Adam(model_prepared.parameters(), lr=1e-5)

# 3. 训练几个 epoch
model_prepared.train()
for epoch in range(3):  # 通常 1-3 个 epoch 就够了
    for batch in train_loader:
        optimizer.zero_grad()
        output = model_prepared(batch)
        loss = loss_fn(output, target)
        loss.backward()
        optimizer.step()

# 4. 转换为真正的 INT8
model_prepared.eval()
model_int8 = torch.quantization.convert(model_prepared)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Fake Quantization 的工作原理:

python
# Fake quantization 等效实现
class FakeQuantize:
    """
    模拟量化:在 forward 时执行量化-反量化过程
    让梯度能够绕过 quantize 节点(Straight-Through Estimator)
    """
    def __init__(self, scale, zero_point, quant_min, quant_max):
        self.scale = scale
        self.zero_point = zero_point
        self.quant_min = quant_min
        self.quant_max = quant_max
    
    def forward(self, x):
        # 量化
        quantized = torch.round(x / self.scale + self.zero_point)
        quantized = torch.clamp(quantized, self.quant_min, self.quant_max)
        
        # 反量化
        dequantized = (quantized - self.zero_point) * self.scale
        
        # STE: 直通估计 - 梯度直接传回去,不经过量化
        if self.training:
            return dequantized + (x - x.detach())
        return dequantized
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

2.3 PTQ vs QAT 对比 ​

维度PTQQAT
实现复杂度低高
训练时间无1-3 epoch 微调
精度损失中等(INT8 可能掉点严重)低(接近 FP16)
适用场景快速部署、演示生产环境、严格精度要求
模型适配无有
资源需求校准数据集完整训练数据 + 计算资源

第3节:数值安全——Outlier 与解决方案 ​

3.1 Outlier 问题 ​

量化精度损失的主要原因是 outlier——某些 channel 的值远大于其他 channel。

python
# 演示 outlier 问题
import torch

# 模拟一个有 outlier 的 activation tensor
# 假设有 100 个 channel,其中一个 channel 有极端值
activations = torch.randn(100, 512) * 0.5  # 正常 channel
activations[0] = activations[0] * 20  # outlier channel:值大 20 倍

# Per-tensor 量化
scale = torch.max(torch.abs(activations)) / 127
q_act = torch.round(activations / scale)
q_act = torch.clamp(q_act, -127, 127)
dq_act = q_act * scale

# 问题:为了容纳 outlier channel,其他 99 个 channel 的精度被牺牲了
# 正常 channel 的信号范围被压缩到 ±6 左右

# 计算量化误差分布
errors = (activations - dq_act).abs()
print(f"Outlier channel 误差: {errors[0].mean():.4f}")
print(f"正常 channel 误差: {errors[1:].mean():.4f}")
print(f"误差比值: {errors[0].mean() / errors[1:].mean():.1f}x")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

3.2 SmoothQuant ​

SmoothQuant 的核心思想:将 activation 的 outlier 平滑到 weights 上。

python
# SmoothQuant 原理
"""
原始公式: Y = X @ W
量化: Y_q = round(X / s_x) @ round(W / s_w) * (s_x * s_w)

问题: 如果 X 有 outlier,会导致 s_x 很大,其他 channel 精度差

SmoothQuant 解法: 引入平滑因子 S
Y = X @ W = (X @ diag(S)) @ (diag(S)^-1 @ W)
= X_smooth @ W_smooth

通过选择合适的 S,使得 X_smooth 和 W_smooth 都有合理的值域
"""

def smooth_quant(x, w, alpha=0.5):
    """
    SmoothQuant 实现
    
    alpha: 平滑强度,0 = 不平滑,1 = 完全平滑
    """
    # 计算每个 channel 的最大绝对值
    # x shape: [batch, seq_len, hidden_dim]
    # w shape: [hidden_dim, hidden_dim]
    
    # 沿着最后一个维度计算每个 token 的最大绝对值
    x_max = torch.max(torch.abs(x), dim=-1, keepdim=True)[0]
    
    # 沿着 weight 的行计算最大绝对值
    w_max = torch.max(torch.abs(w), dim=1, keepdim=True)[0]
    
    # 计算平滑因子
    # 让 x 和 w 的"难度"按 alpha 比例分配
    s = (x_max ** alpha) / (w_max ** (1 - alpha))
    
    # 确保 S 的值不会太大或太小
    s = torch.clamp(s, min=1e-5, max=1e5)
    
    # 平滑 X
    x_smooth = x / s
    
    # 平滑 W
    w_smooth = w * s
    
    return x_smooth, w_smooth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

3.3 GPTQ 与 AWQ ​

GPTQ (Generative Post-Training Quantization):

  • Weight-only 量化:只量化 weights,activations 保持 FP16
  • 使用二阶信息(hessian)来选择量化参数
  • 4-bit 量化效果好,但需要校准数据
python
# GPTQ 伪代码
def gptq_quantize(w, bits=4, per_channel=True):
    """
    GPTQ 量化流程
    """
    n, m = w.shape  # 权重矩阵 shape
    q_w = np.zeros((n, m), dtype=np.int32)
    q_scale = np.zeros(n)
    
    # 对每一行(channel)独立量化
    for i in range(n):
        w_row = w[i]
        
        # 计算 hessian 的逆(用于加权误差)
        hess_inv = compute_hessian_inverse(w_row, bits)
        
        # 量化这一行
        q_w[i], q_scale[i] = quantize_row_gptq(w_row, hess_inv, bits)
    
    return q_w, q_scale

# 实际使用:使用 autoawq 或 gptq 库
# pip install autoawq
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model = AutoAWQForCausalLM.from_pretrained("meta-llama/Llama-3-8B")
quant_config = {
    "zero_point": True,      # 非对称量化
    "q_group_size": 128,     # 量化组大小
    "w_bit": 4,              # 4-bit 量化
    "version": "GEMM"        # GEMM 风格量化
}

awq_model = AutoAWQForCausalLM.quantize(model, quant_config)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

AWQ (Activation-Aware Weight Quantization):

  • 考虑 activations 的分布来决定 weight 的量化参数
  • 比 GPTQ 更快,不需要 hessian 计算
  • 精度通常更好
python
# AWQ 核心思想
"""
AWQ 发现:weight 的重要性和其对应 activation 的幅度成正比
因此,对于幅度大的 activation,对应的 weight 应该用更高的精度

公式:
- 找到对最终输出影响大的 weight 通道
- 对这些通道使用更小的量化误差
- 通过搜索找到最优的 per-channel scale
"""

def awq_calibrate(model, calibration_data):
    """
    AWQ 校准过程
    """
    # 收集激活值的统计信息
    act_scales = {}
    
    for batch in calibration_data:
        with torch.no_grad():
            hooks = register_hooks(model, act_scales)
            model(batch)
            remove_hooks(hooks)
    
    # 计算 AWQ scale
    # 对于每个 weight channel,计算对应的 activation scale
    for name, act_scale in act_scales.items():
        weight_name = name.replace(".input", "").replace(".output", "")
        weight = get_weight(model, weight_name)
        
        # AWQ scale = activation scale 的某个函数
        awq_scale = find_awq_scale(act_scale, weight)
        
        # 应用平滑
        smooth_weight(weight, awq_scale)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

第4节:量化策略与精度恢复 ​

4.1 混合精度策略 ​

核心原则:不是所有层对量化都同样敏感。

python
# 混合精度量化策略
def mixed_precision_quantization(model, calibration_data):
    """
    根据各层对量化的敏感性,使用不同的精度
    """
    # 第一步:评估每个层对量化的敏感性
    sensitivity_scores = {}
    
    for name, module in model.named_modules():
        if isinstance(module, torch.nn.Linear):
            # 保存原始权重
            original_weight = module.weight.data.clone()
            
            # 量化这一层
            module.weight.data = quantize_to_int8(original_weight)
            
            # 评估精度损失
            loss_before = evaluate_on_calibration(model, calibration_data)
            
            # 恢复并评估
            module.weight.data = original_weight
            loss_after = evaluate_on_calibration(model, calibration_data)
            
            sensitivity = loss_after - loss_before
            sensitivity_scores[name] = sensitivity
    
    # 第二步:根据敏感性分配精度
    # 敏感层保持 FP16,不敏感层使用 INT8
    for name, score in sensitivity_scores.items():
        if score < threshold:  # 不敏感
            quantize_layer(model, name, dtype=torch.qint8)
        else:  # 敏感
            quantize_layer(model, name, dtype=torch.float16)
    
    return model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

敏感性分析结果(典型 LLM):

层类型量化敏感性推荐精度原因
Input Embedding高FP16错误会累积传播
First Attention Layer高FP16错误会累积传播
Last Output Layer高FP16直接影响最终结果
Middle Layers中INT8有一定容错空间
FFN Layers低INT8相对独立
LayerNorm中FP16 或 BF16归一化敏感

4.2 量化精度损失排查流程 ​

python
# 量化问题排查清单
class QuantizationDebugger:
    """
    量化精度问题的系统化排查
    """
    
    def check_outliers(self, model, calibration_data):
        """1. 检查是否存在 outlier"""
        print("=" * 50)
        print("1. 检查 Outlier")
        print("=" * 50)
        
        act_stats = self.collect_activation_stats(model, calibration_data)
        
        for name, stats in act_stats.items():
            # 计算变异系数(CV = std / mean)
            cv = stats['std'] / (stats['mean'].abs() + 1e-8)
            
            if cv > 10:  # 变异系数太大,说明有 outlier
                print(f"⚠️  {name}: CV={cv:.1f}, 存在严重 outlier")
            else:
                print(f"✅ {name}: CV={cv:.1f}, 分布正常")
    
    def compare_quantization_modes(self, model, calibration_data):
        """2. 对比 per-tensor vs per-channel"""
        print("\n" + "=" * 50)
        print("2. Per-Tensor vs Per-Channel")
        print("=" * 50)
        
        # Per-tensor 精度
        model_pt = copy.deepcopy(model)
        quantize_per_tensor(model_pt)
        acc_pt = evaluate(model_pt, calibration_data)
        
        # Per-channel 精度
        model_pc = copy.deepcopy(model)
        quantize_per_channel(model_pc)
        acc_pc = evaluate(model_pc, calibration_data)
        
        print(f"Per-Tensor INT8: {acc_pt:.2f}%")
        print(f"Per-Channel INT8: {acc_pc:.2f}%")
        print(f"Per-Channel 提升: +{acc_pc - acc_pt:.2f}%")
    
    def analyze_error_distribution(self, model, calibration_data):
        """3. 分析量化误差分布"""
        print("\n" + "=" * 50)
        print("3. 误差分布分析")
        print("=" * 50)
        
        for name, module in model.named_modules():
            if isinstance(module, torch.nn.Linear):
                # 计算量化误差
                original = module.weight.data
                quantized = quantize_int8(original)
                dequantized = dequantize(quantized)
                error = (original - dequantized).abs()
                
                # 找到误差最大的位置
                max_error_idx = error.argmax()
                max_error = error.max()
                max_error_ratio = max_error / original.abs().max()
                
                if max_error_ratio > 0.1:  # 误差超过 10%
                    print(f"⚠️  {name}: 最大误差比例 {max_error_ratio:.1%}")
    
    def try_smoothing(self, model, calibration_data):
        """4. 尝试 SmoothQuant"""
        print("\n" + "=" * 50)
        print("4. SmoothQuant 效果")
        print("=" * 50)
        
        # 原始 INT8 精度
        acc_before = evaluate(model, calibration_data)
        
        # 应用 SmoothQuant
        model_smooth = smoothquant_apply(model, alpha=0.5)
        acc_after = evaluate(model_smooth, calibration_data)
        
        print(f"SmoothQuant 前: {acc_before:.2f}%")
        print(f"SmoothQuant 后: {acc_after:.2f}%")
        print(f"精度恢复: +{acc_after - acc_before:.2f}%")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

第5节:硬件支持与编译器集成 ​

5.1 各精度格式的硬件支持 ​

精度NVIDIA GPUIntel GPUAMD GPUCPU
FP32✅ 所有✅ 所有✅ 所有✅ 所有
FP16✅ 所有 Ampere+✅ Gen9+✅ CDNA+⚠️ AVX512
BF16✅ Ampere+ (A100)✅ Gen9+✅ CDNA2+✅ AVX512-BF16
INT8✅ Tensor Core✅ IMKA✅ Matrix Core✅ VNNI
INT4⚠️ Turing+ (Tensor Core)⚠️ 部分⚠️ 部分⚠️ AMX
FP8 E4M3✅ Hopper (H100)❌❌❌
FP8 E5M2✅ Hopper (H100)❌❌❌

BF16 vs FP16 选择:

  • BF16 的动态范围更大(8-bit exponent vs 5-bit exponent)
  • LLM 训练推荐 BF16(更稳定)
  • 推理两者差别不大,但 BF16 更通用

5.2 PyTorch 量化 API ​

python
import torch
import torch.quantization

# ============================================================
# 方法 1:动态量化(最简单)
# ============================================================
# 特点:权重 INT8,activations FP32,计算时动态量化
model_dynamic = torch.quantization.quantize_dynamic(
    model=original_model,
    qconfig_spec={
        torch.nn.Linear: torch.qint8,    # Linear 层量化
        torch.nn.LSTM: torch.qint8,       # LSTM 层量化
        # torch.nn.Embedding: torch.quint8  # Embedding 量化
    },
    dtype=torch.qint8
)

# ============================================================
# 方法 2:静态量化(需要校准)
# ============================================================
# 准备量化
model_static = original_model.clone()
model_static.eval()
model_static.qconfig = torch.quantization.get_default_qconfig('fbgemm')
# fbgemm: x86 CPU 优化
# qnnpack: ARM CPU 优化
# onednn: Intel CPU 优化

torch.quantization.fuse_model(model_static)  # 融合 conv+bn
model_prepared = torch.quantization.prepare(model_static)

# 校准
with torch.no_grad():
    for batch, _ in calibration_loader:
        model_prepared(batch)

# 转换
model_int8 = torch.quantization.convert(model_prepared)

# ============================================================
# 方法 3:torch.compile + 量化
# ============================================================
# PyTorch 2.0+ 支持
model_compiled = torch.compile(model_int8, mode="reduce-overhead")

# 在编译时指定量化配置
from torch._export import capture_pre_autograd_graph

# 对于 XLA 后端
import torch_xla.core.xla_model as xm

xla_quant_config = {
    "quantization_method": "symmetric",
    "quantization_bytesize": 1,
    "round_mode": "ROUND_NEAREST_EVEN"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

5.3 Inductor 量化 ​

python
import torch

# ============================================================
# torch.compile 量化配置
# ============================================================

# 方式 1:使用默认量化后端
model_quantized = torch.compile(
    model,
    backend="inductor",
    options={
        "mode": "reduce-overhead",
        # inductor 会自动尝试量化
    }
)

# 方式 2:显式指定量化
torch._inductor.config.force_quantize = True

# 方式 3:自定义量化配置
inductor_config = {
    # 算子融合配置
    "max_autotune": True,
    "autotune_fallback_to_aten": False,
    
    # 量化相关
    "force_quantize": True,
    "quantization_config": {
        "weight_quantization": "int8",
        "activation_quantization": "int8",
        "per_channel_quant": True,  # 推荐 per-channel
    }
}

model_compiled = torch.compile(
    model,
    backend="inductor",
    options=inductor_config
)

# ============================================================
# 验证量化效果
# ============================================================
# 检查编译后的模型是否使用了量化
print(model_compiled)
# 应该在输出中看到 QuantizeLinear / DequantizeLinear 节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

第6节:量化策略决策流程 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│                         量化策略决策流程                                      │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
                         ┌─────────────────┐
                         │   确定目标场景    │
                         └────────┬────────┘
                                  │
              ┌───────────────────┼───────────────────┐
              ▼                   ▼                   ▼
       ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
       │   推理部署    │    │   模型训练    │    │   研究实验    │
       └───────┬──────┘    └───────┬──────┘    └───────┬──────┘
               │                   │                   │
               ▼                   ▼                   ▼
       ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
       │  PTQ 快速部署 │    │  BF16 训练   │    │  FP32 基线   │
       └───────┬──────┘    └──────────────┘    └──────────────┘
               │
               ▼
       ┌─────────────────────────────────────────────────┐
       │           精度要求测试                           │
       └─────────────────────┬───────────────────────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
       ┌────────────┐ ┌────────────┐ ┌────────────┐
       │ 精度 OK    │ │ 精度下降   │ │ 精度严重   │
       └─────┬──────┘ └─────┬──────┘ └─────┬──────┘
             │              │              │
             ▼              ▼              ▼
       ┌────────────┐ ┌────────────┐ ┌────────────┐
       │   部署     │ │ Per-channel│ │  混合精度   │
       └────────────┘ └─────┬──────┘ └─────┬──────┘
                             │              │
                             ▼              ▼
                       ┌────────────┐ ┌────────────┐
                       │  重新测试   │ │  SmoothQuant│
                       └─────┬──────┘ └─────┬──────┘
                             │              │
                             ▼              ▼
                       ┌─────────────────────────┐
                       │      精度满足要求?       │
                       └────────────┬────────────┘
                                    │
                           ┌────────┴────────┐
                           ▼                 ▼
                      ┌─────────┐     ┌─────────┐
                      │   部署   │     │ QAT 微调 │
                      └─────────┘     └─────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

升华 ​

┌──────────────────────────────────────────────────────────────────────────────┐
│                           量化工程实践原则                                    │
├──────────────────────────────────────────────────────────────────────────────┤
│  1. 先测后用:量化前先评估敏感性,不是所有层都需要同精度                         │
│  2. Per-channel 优先:相比 per-tensor 通常能获得更好的精度                    │
│  3. 校准要代表性:校准数据应该覆盖真实推理场景                                 │
│  4. 混合精度是常态:关键层保持高精度,次要层 INT8                             │
│  5. Outlier 处理:先检查 outliers,用 SmoothQuant 处理                       │
└──────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9

"AI 可查 vs 必须理解"清单 ​

必须理解(不理解就等于不会):

  • 🔴 对称量化 vs 非对称量化的公式和适用场景
  • 🔴 Per-tensor vs per-channel 量化的精度差异原因
  • 🔴 Outlier 问题是量化精度损失的核心原因
  • 🔴 SmoothQuant 的核心思想:把 outlier 平滑到 weights
  • 🔴 PTQ 和 QAT 的trade-off:速度 vs 精度

AI 可查(知道去哪查就行):

  • ✅ PyTorch 量化 API 的具体参数和语法
  • ✅ GPTQ/AWQ 的具体实现细节
  • ✅ 各硬件平台对不同精度的支持情况
  • ✅ torch.compile 量化配置的具体选项
  • ✅ 特定模型的量化敏感性数据

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇26. 从 MLIR 经 LLVM 降级到机器码 / Lowering from MLIR Through LLVM to Machine Code
下一篇28. 分布式编译与训练——多设备编排的编译器支持 / Compiler Support for Distributed Training and Multi-Device Orchestration

持续记录,持续成长

Copyright © Tidenflow