def all_levels(onnx_model):"""{"原圖": 匯出的圖, 等級: 改寫後的圖}。"""return {"原圖": onnx_model} | {lv: optimized_model(onnx_model, lv) for lv in LEVELS}def counts_table(graphs): counts = {name: onnx_op_counts(g) for name, g in graphs.items()} ops =sorted(set().union(*counts.values()), key=lambda o: (-counts["原圖"].get(o, 0), o))return table([[op] + [counts[name].get(op, "") for name in graphs] for op in ops], ["op"] + [n.replace("ORT_", "") for n in graphs])def same_ops(a, b):return [n.op_type for n in a.graph.node] == [n.op_type for n in b.graph.node]
先用 M09-1 的 torchao 測試版 XNNPACKQuantizer 標一次,看哪些 op 沒有標籤:
gm = export_module(model, x)get_xnnpack_quantizer(is_per_channel=True)[0].annotate(gm)skipped =sorted({short(n.target) for n in unannotated_ops(gm) if n.op =="call_function"})print("torchao 測試版 XNNPACKQuantizer 沒有標的 op:", skipped)
用它做 PT2E,這些 op 前後不會有 Q/DQ,看不出 ORT 對它們做什麼。所以這裡改用 ORT 自己的量化工具 onnxruntime.quantization.quantize_static(ort_quantize,細節在 M09-4),它在 fp32 ONNX 上替幾乎每個 op 插 Q/DQ:
GROUP = {op: "換成 int8 op"for op in ["Conv", "MatMul", "Gemm", "Add", "Mul", "Concat", "AveragePool","GlobalAveragePool", "Sigmoid", "LeakyRelu", "Softmax", "Where"]}GROUP |= {op: "拿掉 Q/DQ"for op in ["MaxPool", "ReduceMax", "ReduceMin", "Reshape", "Transpose", "Flatten", "Squeeze","Unsqueeze", "Slice", "Gather", "Expand", "Tile", "Resize", "DepthToSpace", "Split"]}ext = qdq["ORT_ENABLE_EXTENDED"]ext_ops = onnx_op_counts(ext)dq_out = {n.output[0] for n in ext.graph.node if n.op_type =="DequantizeLinear"}rows, measured = [], {}for op indict.fromkeys(n.op_type for n in fp32_onnx.graph.node): # 依出現順序、不重複if op in ext_ops: fp32_left =any(set(n.input) & dq_out for n in ext.graph.node if n.op_type == op) measured[op] ="不在清單上"if fp32_left else"拿掉 Q/DQ" result ="留在 fp32(前後仍有 DQ/Q)"if fp32_left else"原 op,直接在 int8 上做"else: repl = [o for o in ext_ops if o.split(".")[-1] in (f"QLinear{op}", f"Q{op}")] measured[op] ="換成 int8 op"if repl else"併進別的 op" result =f"換成 {', '.join(repl)}"if repl else"不見了(併進別的 op)" rows.append([op, GROUP.get(op, "不在清單上"), result])table(rows, ["fp32 ONNX 的 op", "原始碼的分組", "實測(EXTENDED)"])
fp32 ONNX 的 op
原始碼的分組
實測(EXTENDED)
Conv
換成 int8 op
換成 QLinearConv
Sigmoid
換成 int8 op
換成 com.microsoft.QLinearSigmoid
LeakyRelu
換成 int8 op
換成 com.microsoft.QLinearLeakyRelu
Concat
換成 int8 op
換成 com.microsoft.QLinearConcat
Mul
換成 int8 op
換成 com.microsoft.QLinearMul
MaxPool
拿掉 Q/DQ
原 op,直接在 int8 上做
AveragePool
換成 int8 op
換成 com.microsoft.QLinearAveragePool
ReduceMean
不在清單上
留在 fp32(前後仍有 DQ/Q)
MatMul
換成 int8 op
換成 QLinearMatMul
Gemm
換成 int8 op
換成 com.microsoft.QGemm
Softmax
換成 int8 op
換成 com.microsoft.QLinearSoftmax
mismatch = [op for op in measured if GROUP.get(op, "不在清單上") != measured[op]]print("實測和原始碼分組不一致的 op:", mismatch)assertnot mismatch