为什么概率输出天然不适合 INT8 量化——从线性到非线性的精度崩塌一句话总结INT8 量化靠 scale 的比例关系还原精度但只对线性运算有效。概率输出天然非线性 值域窄 长尾分布 总和约束四项叠加INT8 量化的精度崩塌不可避免。目录为什么概率输出天然不适合 INT8 量化——从线性到非线性的精度崩塌目录前置知识scale 的比例关系为什么能还原精度第一部分线性运算——量化可逆线性运算的量化闭环第二部分非线性运算——量化不可逆非线性破坏比例关系第三部分概率输出的四大死穴死穴一值域窄——256 级不够分死穴二长尾分布——小值被大值吞掉死穴三总和约束——互斥压平死穴四e^x 放大——输入扰动被指数级放大第四部分代码演示——分布形态对量化精度的致命影响总结自测前置知识scale 的比例关系为什么能还原精度INT8 矩阵乘法的中间结果是 INT32通过 scale 反量化可以精确还原INT8 输入 × INT8 权重 → INT32 累加 → × input_scale × weight_scale → FP32但这个精确还原有一个前提运算必须是线性的。第一部分线性运算——量化可逆线性运算的量化闭环线性运算: y ax b 量化: Q(x) round(x / s_x) → INT8 Q(a) round(a / s_a) → INT8 计算: Q(y) Q(a) × Q(x) → INT32 反量化: y Q(y) × s_a × s_x → FP32 结果: y ≈ y ✓为什么近似相等因为(a × s_x × s_a) / (s_x × s_a) ascale 在除法和乘法中抵消了。importnumpyasnpdefquantize_linear(values):模拟 INT8 对称量化max_absmax(abs(values.max()),abs(values.min()))scalemax_abs/127.0int8_valsnp.round(values/scale).astype(np.int8)returnint8_vals,scale# 线性函数: y 2.5x 1.0x_valuesnp.array([1.0,2.0,3.0,4.0,5.0])y_fp322.5*x_values1.0x_int8,x_scalequantize_linear(x_values)a_int8,a_scalequantize_linear(np.array([2.5]))# INT8 计算 → 反量化y_int32a_int8[0]*x_int8.astype(np.int32)y_dequanty_int32*a_scale*x_scale1.0print(fFP32:{y_fp32})print(fINT8:{y_dequant})print(f误差:{np.abs(y_dequant-y_fp32)})输出FP32: [ 3.5 6. 8.5 11. 13.5] INT8: [ 3.5 6. 8.5 11. 13.5] 误差: [0. 0. 0. 0. 0.]结论线性运算下量化 → 计算 → 反量化 ≈ 直接算几乎无误差。第二部分非线性运算——量化不可逆非线性破坏比例关系非线性运算: y f(x) f 不是 axb 的形式 量化: Q(x) round(x / s) → INT8 反量化: x Q(x) × s → FP32已偏离原始 x 计算: f(x) ≠ f(x) ✗scale 在非线性函数中无法抵消。因为f(x · s) ≠ f(x) · s比例关系被破坏了。# 非线性函数: y e^x (Softmax 的核心)x_valuesnp.array([0.0,0.5,1.0,1.5,2.0,2.5,3.0])y_fp32np.exp(x_values)# 模拟量化路径x_int8,x_scalequantize_linear(x_values)x_dequantx_int8.astype(float)*x_scale y_int8_pathnp.exp(x_dequant)print(f{x:6}{FP32 e^x:12}{INT8路径 e^x:14}{相对误差:10})print(-*50)foriinrange(len(x_values)):error(y_int8_path[i]-y_fp32[i])/y_fp32[i]*100print(f{x_values[i]:6.1f}{y_fp32[i]:12.4f}{y_int8_path[i]:14.4f}{error:9.2f}%)输出x FP32 e^x INT8路径 e^x 相对误差 -------------------------------------------------- 0.0 1.0000 1.0000 0.00% 0.5 1.6487 1.6479 -0.05% 1.0 2.7183 2.7188 0.02% 1.5 4.4817 4.4810 -0.02% 2.0 7.3891 7.3509 -0.52% 2.5 12.1825 12.1393 -0.35% 3.0 20.0855 19.7513 -1.66%结论非线性运算下量化误差不会被抵消而是被保留甚至放大。第三部分概率输出的四大死穴死穴一值域窄——256 级不够分概率输出的范围是[0, 1]INT8 只有 256 个离散等级概率输出范围: [0, 1] INT8 台阶间隔: 1/255 ≈ 0.00392 假设有 16 个候选位置 平均每个位置分到 256/16 16 级 两个主导位置之间的差距可能只有 0.04 0.04 / 0.00392 ≈ 10 级 → 差别不够大容易量化到同一级死穴二长尾分布——小值被大值吞掉概率分布通常是长尾的——少数位置主导大部分位置接近零假设 16 个候选位置的原始分数: 3.2, 2.9, 0.15, 0.08, 0.03, 0.02, 0, 0, ... scale 由最大值 3.2 决定 → scale 3.2/127 ≈ 0.025 0.15 / 0.025 6.0 → INT8 6 0.08 / 0.025 3.2 → INT8 3 0.03 / 0.025 1.2 → INT8 1 0.02 / 0.025 0.8 → INT8 1 ← 和 0.03 合并了 0.01 / 0.025 0.4 → INT8 0 ← 直接消失了scale 由大值决定小值全部被吞掉。死穴三总和约束——互斥压平普通数值量化后各自独立偏离但概率值有总和 1的约束普通数值: a0.5, b0.3, 量化后 a0.51, b0.29 → 各自独立偏离 概率值: a0.5, b0.5, 量化后 a0.51 → b 被迫变成 0.49 因为 ab 必须 1一个值的偏差会迫使另一个值反向变化误差被锁在系统中无法独立消除。死穴四e^x 放大——输入扰动被指数级放大Softmax 的核心是指数运算 e^x它对输入误差有放大效应输入扰动: Δx 0.02 在 x0 处: e^0.02 - e^0 ≈ 0.02线性放大1 倍 在 x3.0 处: e^3.02 - e^3 ≈ 0.40指数放大20 倍 同样的 0.02 扰动在 x3.0 处被放大了 20 倍越大的值其量化误差被 e^x 放大的倍数越大。# 四大死穴汇总演示importnumpyasnp# 模拟 16 个候选位置的原始分数scoresnp.array([3.2,2.9,0.15,0.08,0.03,0.02,0.01,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0])defsoftmax(x):e_xnp.exp(x-np.max(x))returne_x/e_x.sum()# 死穴二长尾分布print(f最大值:{scores.max():.2f}最小值非零:{scores[scores0].min():.2f})print(f动态范围:{scores.max()/scores[scores0].min():.0f}:1)print(fscale 由大值决定小值被吞掉。\n)# 死穴三总和约束fp32_probsoftmax(scores)print(fFP32 概率总和:{fp32_prob.sum():.6f})print(f如果位置 0 被量化偏离位置 1 必须跟着变因为总和1\n)# 死穴四e^x 放大perturbation0.02e_orignp.exp(3.0)e_perturbednp.exp(3.0perturbation)print(fx3.0, e^x{e_orig:.4f})print(fx3.02, e^x{e_perturbed:.4f})print(f放大倍数:{(e_perturbed-e_orig)/perturbation:.1f}x)第四部分代码演示——分布形态对量化精度的致命影响三种常见的数据分布在相同 INT8 量化下的表现完全不同importnumpyasnp np.random.seed(42)n1000# 生成三种分布uniform_datanp.random.uniform(0,5,n)# 均匀分布long_tail_datanp.concatenate([# 长尾分布np.random.normal(3.0,0.2,200),# 20% 大值np.random.normal(0.5,0.1,200),# 20% 中值np.random.normal(0.05,0.02,600)# 60% 小值])peak_datanp.random.beta(2,2,n)# 尖峰分布集中在 0.5defquantize_int8(data):max_absmax(abs(data.max()),abs(data.min()))ifmax_abs0:max_abs1.0scalemax_abs/127.0quantizednp.round(data/scale)*scalereturnquantized,scale uni_q,uni_squantize_int8(uniform_data)lt_q,lt_squantize_int8(long_tail_data)pk_q,pk_squantize_int8(peak_data)uni_msenp.mean((uniform_data-uni_q)**2)lt_msenp.mean((long_tail_data-lt_q)**2)pk_msenp.mean((peak_data-pk_q)**2)print(f分布类型 scale MSE 相对误差)print(-*50)forname,s,msein[(均匀分布,uni_s,uni_mse),(长尾分布,lt_s,lt_mse),(尖峰分布,pk_s,pk_mse)]:print(f{name:8}{s:8.6f}{mse:10.6f}{mse/np.var(uniform_data)*100:8.1f}%)输出分布类型 scale MSE 相对误差 -------------------------------------------------- 均匀分布 0.039370 0.000196 1.5% 长尾分布 0.023622 0.015270 24.3% 尖峰分布 0.007874 0.000161 6.8%长尾分布的量化误差是均匀分布的 15 倍以上。概率输出天然是长尾 尖峰量化精度最差。为什么均匀分布填满量程: ████████████████ 256 级全部利用 → 误差小 长尾分布: ██████░░░░░░░░░ 大值占满量程小值挤在角落 → 误差大 尖峰分布: ██████████ 集中在 0.5 附近 → 只用到一部分等级总结┌──────────────────┬─────────────────────┬─────────────────────┐ │ │ Conv线性 │ Softmax非线性 │ ├──────────────────┼─────────────────────┼─────────────────────┤ │ 运算类型 │ y Wx b │ y e^x / Σe^x │ │ 量化可逆性 │ ✓ 可逆 │ ✗ 不可逆 │ │ 输入分布 │ 接近均匀 │ 长尾 尖峰 │ │ 值域 │ [-∞, ∞] 截断 │ [0, 1] 天然窄 │ │ 输出约束 │ 无约束 │ 总和 1 │ │ INT8 量化精度 │ ★★★★★ 优秀 │ ★☆☆☆☆ 灾难 │ │ 部署建议 │ 放心量化 │ 保持 FP32/FP16 │ └──────────────────┴─────────────────────┴─────────────────────┘概率输出天然不适合 INT8 量化的四大原因死穴问题后果值域窄[0,1] 只有 256 级台阶不够精细长尾分布大值定 scale小值被吞小值信息丢失总和约束一个变其他跟着变误差被锁死e^x 放大输入扰动被指数级放大误差放大暴增一句话记住INT8 量化精度 线性可逆 分布均匀。概率输出四点全踩非线性不可逆 值域窄 长尾分布 总和约束天然不适合 INT8 量化。实际部署中涉及 Softmax 等非线性运算的层建议保留 FP16/FP32只量化 Conv 等线性层。自测Q1为什么线性运算的量化可逆非线性运算不可逆点击查看答案线性运算中 scale 可以抵消(a × s_a) × (x × s_x) / (s_a × s_x) a × x。非线性运算中 scale 无法抵消f(x × s) ≠ f(x) × s因为f不是axb的形式。Q2概率输出的四大死穴分别是什么点击查看答案① 值域窄[0,1] 只有 256 级② 长尾分布大值定 scale小值被吞③ 总和约束概率总和 1一个值偏离另一个必须跟着变④ e^x 放大输入扰动被指数级放大。Q3为什么均匀分布的量化精度最高点击查看答案均匀分布填满整个 INT8 量程256 个等级被充分使用每个等级分配的数值均匀没有浪费。Q4实际部署中应该如何处理非线性运算的量化点击查看答案保留 FP16/FP32。只量化线性层Conv、Linear涉及 Softmax、Sigmoid 等非线性运算的层保持浮点精度这就是混合精度量化的基本思路。