4. per-tensor与per-channel量化对比
任务目标
本案例以YOLOv5s backbone中首个3×3卷积层权重为对象,分别实施per-tensor(整个张量共享一个scale/zero_point)与per-channel(每个输出通道独立scale/zero_point)对称量化,对比两种粒度下反量化权重与原始权重的最大误差(Max Abs Error)及余弦相似度,从而量化分析量化粒度对权重精度的实际影响。
环境与依赖
需要 torch、torchvision(用于加载YOLOv5s预训练权重)。若网络受限,可离线下载权重文件后通过本地路径加载。
核心实现
import torch
import torchvision
# 1. 加载YOLOv5s backbone第一层卷积权重
model = torchvision.models.detection.yolov5s(weights=None) # 仅取结构
# 实际使用官方yolov5s权重(此处用随机初始化演示流程,真实评估请加载官方权重)
state = torch.load('yolov5s.pt', map_location='cpu')['model'].float().state_dict()
conv_weight = state['model.0.conv.weight'] # shape: [32, 3, 3, 3]
def symmetric_quantize(tensor, bits=8):
"""对称量化,返回量化值、scale"""
amax = tensor.abs().max()
scale = amax / (2**(bits-1) - 1)
q = torch.round(tensor / scale).clamp(-(2**(bits-1)), 2**(bits-1)-1)
return q, scale
def per_tensor_dequant(w, bits=8):
q, scale = symmetric_quantize(w, bits)
return q * scale
def per_channel_dequant(w, bits=8):
# w shape: [out_c, in_c, kh, kw]
out_c = w.shape[0]
w_reshaped = w.view(out_c, -1)
scales = w_reshaped.abs().max(dim=1).values / (2**(bits-1) - 1)
q_list = []
for i in range(out_c):
q_i = torch.round(w_reshaped[i] / scales[i]).clamp(-(2**(bits-1)), 2**(bits-1)-1)
q_list.append(q_i * scales[i])
return torch.stack(q_list).view_as(w)
# 2. 执行两种量化
w_pt = per_tensor_dequant(conv_weight)
w_pc = per_channel_dequant(conv_weight)
# 3. 误差与相似度评估
def max_abs_err(a, b):
return (a - b).abs().max().item()
def cos_sim(a, b):
return torch.nn.functional.cosine_similarity(a.flatten(), b.flatten(), dim=0).item()
print(f"per-tensor MaxAbsErr: {max_abs_err(conv_weight, w_pt):.6f}")
print(f"per-tensor CosSim: {cos_sim(conv_weight, w_pt):.6f}")
print(f"per-channel MaxAbsErr: {max_abs_err(conv_weight, w_pc):.6f}")
print(f"per-channel CosSim: {cos_sim(conv_weight, w_pc):.6f}")
# 4. 通道级误差分布(观察per-channel是否均匀)
channel_err_pt = (conv_weight - w_pt).view(conv_weight.shape[0], -1).abs().max(dim=1).values
channel_err_pc = (conv_weight - w_pc).view(conv_weight.shape[0], -1).abs().max(dim=1).values
print(f"per-tensor 通道误差范围: [{channel_err_pt.min():.6f}, {channel_err_pt.max():.6f}]")
print(f"per-channel 通道误差范围: [{channel_err_pc.min():.6f}, {channel_err_pc.max():.6f}]")
运行方式
- 将上述代码保存为
compare_quant.py。 - 确保当前目录存在官方
yolov5s.pt权重文件(从YOLOv5官方仓库release下载)。 - 执行
python compare_quant.py。
验证方法与判定标准
观察输出的四项指标:
- 通过条件:per-channel的MaxAbsErr应显著小于per-tensor(通常小一个数量级以上),且CosSim更接近1.0。
- 通道误差范围:per-tensor的通道误差范围跨度大(某些通道误差极大),而per-channel各通道误差均匀且整体偏小。这直接说明per-channel通过为每个通道独立分配scale,避免了动态范围差异大的通道被“拖累”。
若使用随机初始化权重(如上代码注释所示),由于各通道分布相似,两者差异可能不明显;因此务必加载真实预训练权重进行对比,才能观察到per-tensor因通道间范围差异导致的精度损失。
关键说明
- 本案例聚焦权重静态量化的粒度对比,不涉及激活量化与反向传播,因此误差分析仅反映权重本身的量化损失。
- 实际部署中,per-channel量化通常需要硬件支持(如NVIDIA TensorRT、Intel QDQ格式),若目标平台不支持,则需退化为per-tensor并接受相应精度损失。
- 若加载官方权重后运行发现per-tensor误差远大于per-channel,即验证了通道间权重范围差异是per-tensor精度下降的主因;若两者接近,则说明该层通道分布均匀,此时可结合后续层(如检测头)的敏感性决定是否值得采用per-channel。