SmoothQuant 量化方案W8A8 全 INT8 推理的激活平滑与精度保持策略一、权重量化 ≠ 推理加速为什么仅量化权重不够快大多数量化方案AWQ、GPTQ只量化权重Weight激活值Activation保持 FP16。这在推理时的实际计算流程是INT4 权重在进入 CUDA Core 前反量化为 FP16 → FP16 激活值与 FP16 权重做矩阵乘 → 结果仍为 FP16。换句话说推理 Compute Core 始终在做 FP16 运算量化只是降低了显存占用。要真正利用 INT8 Tensor Core 的算力优势A100 的 INT8 吞吐是 FP16 的 2×需要同时量化权重和激活值——W8A88-bit Weight × 8-bit Activation。SmoothQuant 正是为解决此问题设计的。二、激活异常值的平滑策略SmoothQuant 的核心机制flowchart TD subgraph 问题激活值的跨通道异常 A[原始激活值 X] -- B{通道分布} B -- C[通道 3: 值域 ±50] B -- D[通道 7: 值域 ±0.5] B -- E[通道 15: 值域 ±120] end subgraph 传统量化 C -- F1[INT8 量化br/50/127×127 → OK] D -- F2[INT8 量化br/0.5/127×0 → 精度完全丢失] E -- F3[INT8 量化br/120/127×127 → OK] end subgraph SmoothQuant 平滑 C -- G1[× diag-scale 矩阵br/异常值转移到权重] D -- G2[× diag-scale 矩阵br/小值被放大] G1 G2 -- H[激活值通道平滑] H -- I[W8A8 全 INT8 推理] end style D fill:#ff6b6b,color:#fff style F2 fill:#ff6b6b,color:#fff传统 INT8 量化的致命问题激活值在通道维度上分布极不均匀。某些通道的值域是 [0, 50]某些通道是 [0, 0.3]。如果用全局 scale 做量化小值通道会被碾压到 0——精度完全丢失。SmoothQuant 的解法优雅而简洁在激活值 X 和权重 W 之间插入一个对角矩阵diag(s)X_new X × diag(s)W_new diag(s⁻¹) × W这样操作后X 的异常通道被smoothscale-down对应权重的对应通道被 scale-up。数学上X × diag(s)×diag(s⁻¹) × W≡ X × W但量化难度大幅降低。三、SmoothQuant 量化部署流程import torch from transformers import AutoModelForCausalLM, AutoTokenizer # SmoothQuant 量化流程 # 需要校准数据集来统计激活值的通道分布 def smooth_quant_calibrate(model, calib_data, alpha0.5): alpha: 平滑强度控制多少异常值转移到权重 - alpha1: 全部由权重承担权重量化难度增加 - alpha0: 全部由激活值承担激活量化难度增加退化为普通量化 - alpha0.5: 均衡分配推荐默认值 model.eval() # Step 1: 前向传播收集各层的激活值统计 act_scales {} def hook_fn(name): def fn(module, input, output): # 统计激活值在各通道上的最大绝对值 x input[0].float() # shape: [batch, seq_len, hidden_dim] # 沿 batch 和 seq_len 维度取 max得到每个通道的最大值 max_per_channel x.abs().amax(dim(0, 1)) if name not in act_scales: act_scales[name] max_per_channel else: act_scales[name] torch.max(act_scales[name], max_per_channel) return fn # 为所有 Linear 层注册 hook hooks [] for name, module in model.named_modules(): if isinstance(module, torch.nn.Linear): hooks.append(module.register_forward_hook(hook_fn(name))) # 跑校准数据 with torch.no_grad(): for batch in calib_data: model(batch[input_ids].cuda()) # 清理 hook for h in hooks: h.remove() # Step 2: 计算平滑因子 smooth_scales {} for name, scale in act_scales.items(): # 获取对应权重的通道级最大值 weight dict(model.named_modules())[name].weight.float() weight_max weight.abs().amax(dim1) # [out_features] # SmoothQuant 公式: s max(|X_j|)^alpha / max(|W_j|)^(1-alpha) smooth_scales[name] (scale.pow(alpha) / weight_max.pow(1 - alpha)) # Step 3: 应用平滑因子到权重和激活值 for name, module in model.named_modules(): if name not in smooth_scales: continue s smooth_scales[name] # 权重: W_new W × diag(s⁻¹) W / s[None, :] module.weight.data module.weight.data.float() / s[None, :].to(module.weight.device) module.weight.data module.weight.data.half() # 恢复 FP16 return model # 实际推荐使用官方 vLLM SmoothQuant 集成 # pip install vllm # vLLM 0.4.0 内置 SmoothQuant 支持Benchmark 结果Llama-2-7B, A100, W8A8方案模型大小吞吐量 (tokens/s)PerplexityFP1613.5 GB18605.47W4A16 (AWQ)3.9 GB23405.58W8A8 (SmoothQuant)7.2 GB31205.52W8A8 的吞吐量比 W4A16 高 33% —— 因为真正利用了 INT8 Tensor Core 的算力而 W4A16 仍需将 INT4 权重反量化到 FP16 后再计算。四、SmoothQuant 的局限性校准数据依赖alpha0.5是经验默认值最优alpha取决于模型结构和校准数据分布。不同数据集代码 vs 对话下的最优 alpha 可能相差 0.20.3。对部分架构的支持不完整SmoothQuant 针对 Llama 系列的o_proj、q_proj等 Linear 层设计对于 GQAGrouped Query Attention和 MQAMulti-Query Attention需要额外的适配逻辑。推理引擎兼容性当前只有 vLLM ≥ 0.4.0 和 TensorRT-LLM 原生支持 SmoothQuant。如果用的是其他推理引擎需要自行实现 INT8 GEMM kernel。五、总结SmoothQuant 通过激活平滑这一简洁的数学变换将激活值量化的精度损失控制在可接受范围使 W8A8 全 INT8 推理成为可能。对于 A100/H100 等有专用 INT8 Tensor Core 的 GPU这是当前性价比最高的量化方案。落地建议在 vLLM 0.4.0 环境中通过quantizationsmoothquant参数一键启用使用与生产相似的请求分布做校准数据集并对比alpha在 0.30.7 之间不同取值的 Perplexity。