static quantization能否让节点直接接收前层量化输出?实践困惑
我在学习int8量化技术时,希望找到无需对节点输出执行反量化再量化操作,即可将其传递给下一层的方法,因此了解了static quantization与dynamic quantization的定义。根据ONNX Runtime文档,二者的区别在于(反)量化参数的计算时机,而非实际的(反)量化流程本身。但部分文章及论坛内容指出,static quantization可在节点间直接传递量化值,无需转换为float再转回int。
于是我使用onnxruntime.quantization.quantize_static手动量化模型,代码如下:
import torch import torchvision as tv import onnxruntime from onnxruntime import quantization MODEL_PATH = "best480x640.onnx" MODEL_OPTIMIZED_PATH = "best480x640_optimized.onnx" QUANTIZED_MODEL_PATH = "best480x640_quantized.onnx" class QuntizationDataReader(quantization.CalibrationDataReader): def __init__(self, torch_ds, batch_size, input_name): self.torch_dl = torch.utils.data.DataLoader( torch_ds, batch_size=batch_size, shuffle=False) self.input_name = input_name self.datasize = len(self.torch_dl) self.enum_data = iter(self.torch_dl) def to_numpy(self, pt_tensor): return (pt_tensor.detach().cpu().numpy() if pt_tensor.requires_grad else pt_tensor.cpu().numpy()) def get_next(self): batch = next(self.enum_data, None) if batch is not None: return {self.input_name: self.to_numpy(batch[0])} else: return None def rewind(self): self.enum_data = iter(self.torch_dl) preprocess = tv.transforms.Compose([ tv.transforms.Resize((480, 640)), tv.transforms.ToTensor(), tv.transforms.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) ds = tv.datasets.ImageFolder(root="./calib/", transform=preprocess) # optimisations quantization.shape_inference.quant_pre_process( MODEL_PATH, MODEL_OPTIMIZED_PATH, skip_symbolic_shape=False) quant_ops = {"ActivationSymmetric": False, "WeightSymmetric": True} ort_sess = onnxruntime.InferenceSession( MODEL_PATH, providers=["CPUExecutionProvider"]) qdr = QuntizationDataReader( ds, batch_size=1, input_name=ort_sess.get_inputs()[0].name) quantized_model = quantization.quantize_static( model_input=MODEL_OPTIMIZED_PATH, model_output=QUANTIZED_MODEL_PATH, calibration_data_reader=qdr, extra_options=quant_ops )
但通过Netron查看量化后模型的图结构时,发现添加的QuantizeLinear/DequantizeLinear节点是量化后立即执行反量化,Conv、Mul等节点的输入仍为float32张量。我想确认:
- static quantization是否允许节点直接接收前层的量化输出?
- 我的量化流程存在什么问题?
1. 静态量化是否支持节点间直接传递量化值?
是的,ONNX Runtime的静态量化确实支持节点间直接传递int8量化值,也就是常说的「量化节点融合」或「int8通路优化」。这种优化会把相邻的QuantizeLinear -> DequantizeLinear对合并,或者让Conv、MatMul等算子直接接收前层的int8输入,避免反复在int8和float32之间转换。
需要注意的是:这种优化不会体现在导出的ONNX模型结构里——Netron看到的是原始的量化节点布局,而实际推理时,ONNX Runtime的执行器会自动做图优化,把可融合的节点合并,实现int8数据的直接传递。
2. 当前量化流程的问题
你的代码里有几个可能导致「量化后立即反量化」的点:
- 缺少子图优化配置:默认情况下,ONNX Runtime的静态量化可能只完成基础的权重量化,没有触发子图级的量化通路优化。需要在
extra_options里明确开启相关选项,让工具识别并融合可形成int8通路的节点。 - 量化规则限制:如果模型中存在不支持量化的自定义算子、或者校准数据分布不具代表性,量化工具会自动插入DequantizeLinear节点, fallback 到float32执行以保证精度。
- 激活量化配置不足:仅设置
ActivationSymmetric不够,还需要明确开启激活值的量化传递逻辑。
修正后的量化配置示例
修改extra_options,添加子图优化和量化强制选项:
quant_ops = { "ActivationSymmetric": False, "WeightSymmetric": True, "EnableSubgraph": True, # 开启子图级量化融合优化 "ForceQuantizeNoInputCheck": True, # 强制量化符合条件的节点 "QuantizeBias": True # 量化偏置项,完善int8通路 }
量化完成后,可以通过ONNX Runtime的性能分析工具(如onnxruntime.PerfTrace)验证是否真的在执行int8算子,而非float32 fallback。
内容的提问来源于stack exchange,提问作者Andrea Tedeschi

