Skip to content

训练、优化与部署工程 ​

本页从正则化和优化器出发,整理初始化、混合精度、归一化融合、训练与推理差异、CUDA 资源约束和 INT8 量化。

一、正则化与交叉验证 ​

正则化通过限制有效模型复杂度或扰动训练过程来降低过拟合风险;交叉验证则主要用于估计泛化表现和选择超参数。二者经常一起使用,但交叉验证本身不是正则化项。

1.1 Dropout、L1 与 L2 ​

Dropout 在训练时随机生成掩码 mi,让部分隐藏单元暂时不参与当前次前向计算。以 inverted dropout 为例:

mi∼Bernoulli(1−p),h~i=mihi1−p

训练时的缩放使得推理阶段可以直接使用完整网络。Dropout 会改变每次迭代实际参与训练的子网络,减少模型对少数神经元或特征的过度依赖;它不等价于永久删除神经元。

L1 和 L2 则直接把参数惩罚加入目标函数:

Ltotal=Ldata+λ∑i|wi|(L1)Ltotal=Ldata+λ∑iwi2(L2)
方法作用机制常见结果
Dropout随机屏蔽部分激活,改变每次训练时的子网络降低共适应,缓解过拟合
L1 正则惩罚参数绝对值倾向产生精确零值,具有稀疏化和特征选择作用
L2 正则惩罚参数平方倾向整体缩小权重,通常不直接产生大量零值

λ 控制正则化强度。λ 过小可能导致对过拟合的抑制不足,过大则可能造成欠拟合;是否选择某个 λ 可以交给交叉验证或独立验证集判断。

1.2 交叉验证的职责 ​

以 K 折交叉验证为例,将训练数据分成 K 份,每次用其中一份验证、其余部分训练,最后汇总 K 次验证结果。它可以用于:

  • 估计模型在未见数据上的表现;
  • 比较模型结构;
  • 选择学习率、正则化系数、树深或其他超参数。

交叉验证没有像 L1、L2 那样直接改变损失函数,也没有自动限制模型参数。若使用交叉验证选择 λ,产生正则化作用的是最终训练时的 L1/L2 惩罚,交叉验证负责比较候选强度。

正则化与交叉验证速记

Dropout、L1、L2 是控制模型复杂度或训练依赖的正则化方法;交叉验证是评估和选超参数的方法,不是正则化本身。

二、优化器:AdaGrad、RMSProp 与 Adam ​

反向传播得到梯度后,优化器决定如何利用梯度更新参数。最基本的梯度下降为:

θt+1=θt−ηgt,gt=∇θLt

不同优化器的主要差异在于是否平滑历史梯度,以及是否为不同参数使用不同的步长。

2.1 AdaGrad:按参数累计平方梯度 ​

AdaGrad 为每个参数维护累计平方梯度:

Gt,i=Gt−1,i+gt,i2

更新形式为:

θt+1,i=θt,i−ηGt,i+εgt,i

经常出现梯度的参数会积累较大的 Gt,i,有效学习率下降得更快;稀疏或低频更新的参数积累较慢,仍能保持相对较大的步长。因此 AdaGrad 适合稀疏特征,但由于 Gt 只增不减,训练后期可能出现学习率过小、参数几乎不再更新的问题。

2.2 RMSProp:指数衰减旧梯度 ​

RMSProp 将 AdaGrad 的永久累计改成指数移动平均:

vt=βvt−1+(1−β)gt2θt+1=θt−ηvt+εgt

旧梯度的影响会逐渐衰减,因此有效学习率不再像 AdaGrad 那样只能单调下降。梯度长期较大的方向会得到更小的步长,梯度较小的方向相对得到更大的步长,这有助于减轻狭长损失谷底中的震荡。

2.3 Adam:一阶动量与二阶矩结合 ​

RMSProp 主要维护平方梯度的指数移动平均 vt,根据梯度大小调整每个参数的步长;Adam 在此基础上再维护梯度本身的一阶移动平均 mt,因此同时具有方向平滑和尺度自适应能力。

Adam 同时维护梯度的一阶指数移动平均和平方梯度的二阶指数移动平均:

mt=β1mt−1+(1−β1)gtvt=β2vt−1+(1−β2)gt2

由于 m0=v0=0,训练初期的估计会偏向 0,所以使用偏差修正:

m^t=mt1−β1t,v^t=vt1−β2t

最终更新为:

θt+1=θt−ηm^tv^t+ε

可以把 Adam 的直觉概括为“Momentum 平滑方向 + RMSProp 调整尺度 + Bias Correction 修正初期估计”。其中 η/(v^t+ε) 只是对有效学习率的直观近似,实际步长还包含 m^t;Adam 仍需要选择基础学习率,也不保证在非凸问题中找到全局最优。

如果某个参数连续多步满足 gt,i≈0,则其二阶矩会衰减:

vt,i≈β2vt−1,i→0

因此下一次非零梯度到来时,η/(v^t,i+ε) 可能变大。这里的“有效学习率变大”不表示参数在零梯度期间会自行更新;它只表示未来重新出现梯度时,相对步长可能更大。

2.4 优化器的状态与取舍 ​

优化器主要状态常见优点主要局限
SGD通常无额外一阶/二阶状态简单、内存开销低,部分任务泛化表现好对学习率和损失地形较敏感
SGD + Momentum一阶动量减少震荡,沿稳定方向积累速度仍需调学习率和动量
AdaGrad累计平方梯度适合稀疏特征学习率可能衰减过快
RMSProp平方梯度的指数移动平均缓解 AdaGrad 的后期停滞超参数和初始学习率仍需调整
Adam一阶矩、二阶矩自适应步长,通常易于起步每个参数约需两份额外状态,且不保证全局最优

在大模型训练中,优化器状态可能成为显存的重要组成部分;混合精度、分片、低精度状态或其他内存优化会改变实际占用,但不改变上述状态数量的基本关系。

2.5 AdamW、学习率调度与梯度累积 ​

AdamW 将权重衰减从 Adam 的自适应梯度项中解耦。抽象写法为:

θt+1=θt−ηm^tv^t+ε−ηλθt

最后一项直接收缩参数,而不是先把 L2 惩罚产生的梯度交给 Adam 的二阶矩缩放。因而 AdamW 的 weight decay 行为更容易独立控制;它仍然需要合理选择基础学习率、衰减系数和其他训练超参数。

大模型训练中常见的学习率调度是 Warm-up 加衰减。Warm-up 阶段从较小学习率逐步升至目标学习率,例如:

ηt=ηmaxtTwarmup,0≤t≤Twarmup

之后可以采用余弦退火:

ηt=ηmin+12(ηmax−ηmin)(1+cos⁡π(t−Twarmup)T−Twarmup)

Cosine Annealing with Restarts 则会在多个周期中重复类似的升降过程。Warm-up 主要降低训练初期发散风险,余弦衰减则帮助后期减小步长、进行更细致的收敛;它们不是优化器本身。

当单个 GPU 无法容纳目标 batch 时,可以使用梯度累积。设每个 micro-batch 大小为 Bmicro,累积 A 次后才调用一次参数更新,若有 G 个数据并行 GPU,则有效 batch size 近似为:

Beff=Bmicro×A×G

如果每次反向得到的是对应 micro-batch 的平均梯度,应在更新前进行平均:

g=1A∑a=1Aga

然后再执行一次参数更新:

θ←θ−ηg

梯度累积节省的是单次前向和反向所需的激活显存,不会让单个 micro-batch 的中间计算消失。它在梯度平均、没有 BatchNorm 等强依赖 mini-batch 统计的条件下,更接近一次大 batch 更新;Dropout 掩码、BatchNorm 统计、Adam 状态更新时机以及变长序列的 token 加权,都可能使两者并非严格等价。

扩大 batch 后常用的 linear scaling rule 是学习率随 batch 比例增加的经验起点:

Bnew=rBold⟹ηnew≈rηold

它不是普适定律。大 batch 训练还可能需要延长 warm-up、重新调整 weight decay、使用梯度裁剪,或根据具体任务调整其他正则项。常见的全局梯度范数裁剪可以写成:

g←g⋅min(1,τ‖g‖)

其中 τ 是允许的最大梯度范数。

大模型训练配套速记

AdamW 负责自适应更新与解耦权重衰减;Warm-up 和 Cosine 负责学习率调度;梯度累积用多个 micro-batch 模拟大 batch;batch 变大时,学习率、warm-up、weight decay 和梯度裁剪需要一起验证。

三、参数初始化与高斯分布 ​

若某层权重按:

W∼N(0,σ2)

初始化,则 σ 是标准差,σ2 是方差。它们决定权重围绕零点的离散程度:

E[W]=0,Var(W)=σ2

均值为零表示大量随机权重的平均值在理论上为零,不表示每个权重都等于零;实际样本仍会取正值和负值。

普通高斯分布的取值范围是整个实数轴:

W∈(−∞,+∞)

因此“约 99.7% 的值落在 ±3σ 内”不能理解为“权重被严格限制在 ±3σ 内”。只有使用截断正态或其他显式裁剪策略时,才存在硬边界。

正态分布的 68-95-99.7 规则

对 W∼N(μ,σ2):

P(|W−μ|≤σ)≈68.27%P(|W−μ|≤2σ)≈95.45%P(|W−μ|≤3σ)≈99.73%

当 μ=0 时,对应区间就是 [−σ,σ]、[−2σ,2σ] 和 [−3σ,3σ]。

初始化尺度需要与层的输入输出宽度和激活函数匹配。尺度过大可能使激活值、点积或梯度变大,尺度过小则可能使信号逐层衰减。经典的方差保持初始化会根据 fan-in 和 fan-out 选择方差,例如:

Var(W)≈2nin+nout(Xavier/Glorot 的典型尺度)

对于 ReLU 类激活,常见的 He 初始化会采用:

Var(W)≈2nin

这些是经典启发式,具体 Transformer 还会结合残差、归一化、投影结构和初始化缩放策略调整,不能只根据“正态分布均值为零”判断初始化一定合适。

参数初始化速记

N(0,σ2) 的期望是 0、标准差是 σ,但取值没有硬边界;1σ/2σ/3σ 约覆盖 68%/95%/99.7%,初始化尺度还要匹配网络宽度和激活函数。

四、混合精度训练与 Loss Scaling ​

混合精度训练通常让矩阵乘法等计算使用 FP16 或 BF16,同时在必要的位置保留 FP32 精度,以降低显存占用并提高吞吐量。低精度格式的动态范围或有效精度有限,反向传播中很小的梯度可能被舍入或下溢成 0。

Loss Scaling 的做法是先把损失放大 S 倍:

Lscaled=SL

根据链式法则,反向得到的梯度也会放大:

∇θLscaled=S∇θL

在反向传播结束后,再把梯度除以 S,恢复真正用于更新的梯度:

gtrue=gscaledS

典型流程是:

放大 Loss→反向传播→梯度除以 S→检查非有限值→梯度裁剪或参数更新

这样理想数学结果不变,改变的只是低精度反向计算时梯度所处的数值尺度。若缩放因子过大,梯度或激活可能溢出为 Inf/NaN,因此工程实现通常使用动态 Loss Scaling:检测到溢出时减小 S 并跳过本次更新,连续多次稳定后再逐步增大 S。

BF16 的指数范围接近 FP32,因此比 FP16 更不容易因为数值太小而下溢,很多 BF16 训练场景不需要显式 Loss Scaling;但 BF16 的尾数精度较低,仍需要结合算子实现、累加精度和训练稳定性进行验证。

需要区分两件事:混合精度本身可以减少显存并提高部分硬件上的计算吞吐,而 Loss Scaling 的直接目的只是避免低精度反向梯度下溢;它不会单独减少参数量或激活张量的数量。

Loss Scaling 速记

FP16 反向的小梯度可能下溢为 0;Loss Scaling 先放大 Loss 和梯度,再 unscale 后更新,数学目标不变。BF16 更不容易下溢,但不等于没有数值风险。

五、Conv-BN Folding:推理阶段的算子融合 ​

Conv-BN Folding 是在推理前把 BatchNorm 的固定统计量和仿射参数吸收到卷积权重、偏置中的等价变换。它要求 BatchNorm 已经处于推理模式,使用固定的 running mean 和 running variance;训练时 batch 统计量会变化,不能直接这样融合。

设某个卷积输出通道的结果为:

z=Wx+b

推理阶段的 BatchNorm 为:

y=γz−μrunσrun2+ε+β

令:

a=γσrun2+ε

则:

y=a(Wx+b−μrun)+β=(aW)x+[a(b−μrun)+β]

因此可以构造新的卷积参数:

W′=aWb′=a(b−μrun)+β

对实际卷积而言,a、μrun、σrun2、γ 和 β 都按输出通道逐通道应用;如果原卷积没有 bias,可以令 b=0。融合后:

Conv→BN⟹Conv′

在固定统计量和正常浮点舍入误差范围内,二者输出等价。运行时不再需要单独执行 BN kernel,也减少了中间特征的显存读写,因此通常可以降低推理延迟和带宽压力;它不是为了加速反向传播,也不意味着模型不能使用 Tensor Core。

Conv-BN Folding 速记

推理前把 BN 的 γ,β,μrun,σrun2 吸收到 Conv 的 W,b 中,运行时删除独立 BN;这是固定统计量下的数学等价融合。

六、训练与推理:前向行为与资源差异 ​

训练和推理使用同一套模型参数,但运行模式、保存的中间状态和计算目标不同。因此不能简单概括成“只换了输入数据,前向计算逻辑完全一样”。

6.1 训练模式与推理模式 ​

组件或行为训练阶段推理阶段
Dropout随机屏蔽部分激活并进行相应缩放关闭随机屏蔽,使用完整网络
BatchNorm使用当前 mini-batch 的统计量,并更新 running statistics使用训练期间累计的 running mean/variance
自动求导保存反向传播需要的中间激活通常关闭梯度计算,不保存训练用的反向缓存
参数状态计算梯度并由优化器更新参数参数固定,只执行前向计算

因此使用框架时,训练模式和推理模式的切换不仅影响性能,也可能改变输出:

训练:随机 Dropout + batch statistics推理:关闭 Dropout + fixed running statistics

对于没有 Dropout、BatchNorm 等状态差异的纯前向模块,训练和推理的数值路径可能更接近,但也不能据此推断所有网络都完全相同。

6.2 Transformer 的训练、Prefill 与 Decode ​

训练语言模型时,通常把一整段序列并行送入网络:

X=[x1,x2,…,xN]

在因果掩码约束下,一次计算整段序列的 Q,K,V 和注意力输出,并保留反向传播需要的 activation。这里会计算历史 K/V,但通常不会把它们作为跨多个解码步骤的持久 KV Cache。

自回归生成的 Decode 阶段每次只增加一个 token。当前隐藏状态产生:

qt=htWQ,kt=htWK,vt=htWV

当前 Query 读取历史缓存:

ot=Attention(qt,[K1,…,Kt],[V1,…,Vt])

然后把 kt,vt 追加到 KV Cache,供后续 Query 重复使用:

text
第1步:计算 q1、k1、v1 → 缓存 k1、v1
第2步:计算 q2、k2、v2 → 读取历史 KV,再缓存 k2、v2
第3步:计算 q3、k3、v3 → 读取更长的历史 KV

需要区分 Prefill 和 Decode:推理开始时,输入提示词仍可以整段并行计算并建立初始 KV Cache;之后逐 token 生成才是典型的增量 Decode。KV Cache 主要服务于自回归生成,不是所有推理任务都需要它。

6.3 Batch、吞吐量与延迟 ​

训练通常优先提高吞吐量,希望一个 batch 包含更多样本;显存不足时还可以用梯度累积获得更大的有效 batch。在线推理则需要在吞吐量和单请求延迟之间折中:

场景主要目标常见做法
训练单位时间处理更多样本,并稳定更新参数较大 batch、数据并行、梯度累积
在线推理降低用户等待时间,同时提高并发吞吐较小 batch、动态 batching 或 continuous batching
离线推理在可接受延迟下尽量提高总吞吐根据显存和任务规模使用较大 batch

因此推理 batch 往往小于训练 batch,但这不是硬性规则;离线批处理和高吞吐服务也可能使用很大的 batch。真正的约束是任务的延迟目标、并发量、序列长度和硬件资源。

训练与推理速记

Training:前向、保存 activation、反向、更新参数;Inference:只做前向,关闭 Dropout,BatchNorm 使用 running statistics;自回归 Decode 还会复用 KV Cache。

七、GPU Occupancy 与 CUDA 资源约束 ​

GPU Occupancy(占用率)描述一个 Streaming Multiprocessor(SM)上实际驻留的活跃 warp 数量,相对于该 SM 理论最大活跃 warp 数量的比例:

Occupancy=SM 上实际驻留的活跃 warp 数SM 理论最大活跃 warp 数

一个 SM 可以同时驻留多个线程块(block)。驻留的 block 越多,通常就有越多可调度的 warp,越有机会在某些 warp 等待内存访问时切换到其他 warp,从而隐藏一部分延迟。但 Occupancy 衡量的是并发驻留能力,不等同于 SM 利用率、内存带宽利用率或 kernel 的最终执行速度。

7.1 影响驻留 block 数量的资源 ​

一个 block 能否驻留在 SM 上,取决于它所需资源是否还能在该 SM 的资源上限内分配。常见约束包括:

资源主要由什么决定对 Occupancy 的影响
线程数每个 block 的总线程数,以及 SM 的线程上限block 越大,单个 block 占用的线程和 warp 越多,能同时驻留的 block 可能越少
寄存器每个线程使用的寄存器数,以及 block 的线程数寄存器压力越大,单个 block 所需寄存器越多,可能限制驻留 block 数
共享内存每个 block 的静态和动态 shared memory 用量每个 block 使用越多,SM 能同时容纳的 block 越少
架构上限SM 支持的最大 resident block、线程数和 warp 数等即使其他资源有剩余,也不能超过硬件上限

用 Tblock 表示每个 block 的线程数,Rthread 表示每线程寄存器数,Sblock 表示每个 block 的共享内存需求,则在忽略硬件分配粒度取整等细节时,驻留 block 数量可以粗略看作受以下上界约束:

Nresident blocks≤min(⌊TSMTblock⌋,⌊RSMTblockRthread⌋,⌊SSMSblock⌋,⌊WSMmax⌈Tblock/32⌉⌋,BSMmax)

实际结果还会受到寄存器和共享内存的分配粒度、每个架构的线程块限制,以及 kernel 其他资源使用情况影响。因此,这个式子适合用来理解瓶颈,不应当替代针对具体 GPU 的 Occupancy Calculator 或 profiler 结果。

共享内存与寄存器的简化估算

假设一个 SM 有 64 KB shared memory:

  • 每个 block 使用 32 KB 时,仅从共享内存看最多可同时驻留 2 个 block;
  • 每个 block 使用 8 KB 时,仅从共享内存看最多可同时驻留 8 个 block。

再假设一个 SM 有 65,536 个可分配寄存器,一个 block 有 256 个线程:

  • 每线程使用 32 个寄存器时,单个 block 约需 256×32=8,192 个寄存器;
  • 每线程使用 128 个寄存器时,单个 block 约需 256×128=32,768 个寄存器。

第二种情况下,寄存器更可能先成为限制驻留 block 数量的瓶颈。以上计算忽略了线程数、共享内存和硬件上限等其他约束,实际 Occupancy 取所有约束共同作用后的结果。

7.2 Block 大小、warp 数与几何形状 ​

CUDA 通常以 32 个线程组成一个 warp。若一个 block 有 Tblock 个线程,则它至少需要:

Wblock=⌈Tblock32⌉

个 warp。因而 block 的总线程数会同时影响:

  • 一个 block 占用多少 warp;
  • 一个 SM 能驻留多少个 block;
  • 最后一个 warp 是否存在未填满的线程。

二维或三维 block 的几何形状本身不是 Occupancy 的独立决定因素;在总线程数相同且其他资源使用相近时,32×8 和 16×16 都包含 256 个线程,单从线程数量看通常会产生相近的 Occupancy。但是,几何形状仍可能影响全局内存访问合并、共享内存访问模式、边界处理和线程间协作,因此两者的实际性能不一定相同。

通常会让 block 的线程数尽量接近 warp 大小的整数倍,以减少未填满 warp 带来的浪费;这只是经验原则,还需要结合 kernel 的访存模式和资源压力判断。

7.3 时钟频率与 Occupancy 的区别 ​

GPU 核心时钟频率主要影响单位时间内可以执行多少个时钟周期,因此会影响 kernel 的执行速度。但它不会直接增加某个 SM 的:

  • 寄存器容量;
  • shared memory 容量;
  • 最大 resident block 数;
  • 最大 resident warp 数。

因此,时钟频率不是 Occupancy 公式中的直接资源约束。GPU 型号、计算能力和 kernel 启动配置会影响 Occupancy;时钟频率则更多影响在既定并发能力下的实际执行速率。功耗或温度导致的降频可能间接影响性能,但不能把它与 Occupancy 混为一谈。

7.4 Occupancy 高是否一定更快? ​

高 Occupancy 通常有利于隐藏访存和流水线延迟,尤其当 kernel 经常等待数据时,更多驻留 warp 可以提供更多可调度工作。但 Occupancy 不是越高越好,也不是性能的充分条件:

  • kernel 可能已经达到足够的延迟隐藏能力,再提高 Occupancy 也没有明显收益;
  • 为了提高 Occupancy 而强行减少寄存器使用,可能导致 register spilling,把数据溢出到较慢的 local memory;
  • 某些计算密集型 kernel 更需要寄存器保存中间结果、提高指令级并行度,较低 Occupancy 反而可能换来更好的单个 warp 执行效率;
  • 最终瓶颈还可能来自全局内存带宽、指令吞吐、分支分化或同步开销。

因此优化时应先确认 kernel 的瓶颈,再在 Occupancy、寄存器压力、共享内存用量和访存效率之间做取舍,而不是盲目追求 100% Occupancy。

7.5 Register Spilling 与 Local Memory ​

线程中的局部变量通常会优先由编译器放入寄存器。寄存器是线程访问速度很快的存储资源,但每个 SM 的寄存器总量有限;当 kernel 的寄存器需求过高,编译器无法把所有需要长期保存的值都放入寄存器时,就可能发生 register spilling(寄存器溢出)。

发生 spilling 后,一部分原本希望保存在寄存器中的值会被放到 Local Memory。这里的“Local”表示该地址空间对线程私有,并不表示它位于片上 SRAM 中:

text
寄存器足够:
局部变量 → Registers

寄存器压力过高:
局部变量 → Local Memory(线程私有的逻辑地址空间)
                         ↓
              Device Global Memory(通常的物理后备)
                         ↕
                    L1/L2 Cache(可能命中)

Local Memory 与 L1/L2 Cache 不是同一层概念:

概念含义与 register spilling 的关系
Registers线程直接使用的片上寄存器,容量有限且访问快局部变量的首选位置
Local Memory每个线程私有的逻辑地址空间寄存器放不下时,可能成为 spill 的逻辑落点
Device Global MemoryGPU 上容量较大的设备内存,访问延迟通常高于寄存器Local Memory 通常由它提供物理后备
L1/L2 Cache对部分内存访问进行缓存的硬件层次可能缓存 Local Memory 的数据,但不是 spill 的目标地址空间

因此,“溢出到 Local Memory”不等于“溢出到 L1 Cache”。如果 Local Memory 访问命中缓存,实际代价可能降低;未命中时仍可能产生设备内存访问,延迟和带宽压力通常远高于寄存器访问。

寄存器压力会同时影响 Occupancy 和访存开销:

Registers/Thread↑⇒每个 block 的寄存器需求↑⇒可驻留 block/warp 数可能下降⇒Occupancy↓

如果继续增加寄存器需求,还可能出现:

Register Spill→Local Memory Traffic→更高的访存延迟和带宽压力

寄存器压力的取舍

降低每线程寄存器上限有时可以提高 Occupancy,但限制过严可能增加 spilling;保留更多寄存器虽然可能降低 Occupancy,却可能减少 Local Memory 访问并提高指令级并行度。应结合寄存器使用量、spill load/store 和 kernel 实际瓶颈综合判断。

GPU Occupancy 速记

Occupancy 是活跃 warp 数与 SM 理论最大 warp 数之比;线程数、寄存器、shared memory 和 SM 架构上限共同决定驻留能力。高 Occupancy 有助于隐藏延迟,但不等于 kernel 一定更快。

八、INT8 量化与校准 ​

INT8 量化的目标是用有限的整数表示近似原来的 FP32、FP16 或 BF16 数值,从而减少模型存储、内存带宽和部分推理计算开销。以有符号 INT8 为例,整数编码范围通常为:

qmin=−128,qmax=127

一种常见的仿射量化与反量化形式是:

q=clip(round(xs)+z,qmin,qmax)x^=s(q−z)

其中:

  • x:原始浮点值;
  • q:量化后的整数;
  • x^:反量化后对原值的近似;
  • s>0:scale,决定一个整数刻度对应多大的浮点范围;
  • z:zero-point,用于非对称地对齐浮点零点;
  • clip:当数值超出 INT8 可表示范围时进行截断。

对称量化常令 z=0,实现简单;非对称量化可以更灵活地覆盖不以零为中心的分布。无论采用哪种方式,scale 和表示范围的选择都会直接影响量化误差。

8.1 校准集为什么重要? ​

PTQ(Post-Training Quantization,训练后量化)通常使用一批校准数据运行模型,统计权重或激活的数值分布,再据此选择 scale、zero-point 和必要的截断范围。校准集不需要覆盖所有训练数据,但应尽量接近真实部署时的输入分布,包括输入类型、长度、领域和典型激活模式。

如果部署数据的激活通常在 [−2,2],而校准数据主要落在 [−0.2,0.2],根据校准结果得到的 scale 可能过小。上线后遇到 x=1.8 时,数值可能被截断到 INT8 边界,或者出现较大的饱和误差。反过来,如果校准集包含大量部署时几乎不会出现的极端值,scale 又可能被估得过大,使常见的小数值只占用很少的整数刻度。

校准集原则

校准集不以样本数量为唯一目标,应尽量代表真实部署时的输入分布。

8.2 激活范围与离群值 ​

INT8 只有有限的离散编码。如果激活范围稳定且与部署分布匹配,scale 可以较稳定地利用这些编码;如果不同 batch 的范围变化很大,固定的 scale 就难以同时兼顾常见值和极端值。

离群值尤其容易破坏 min-max 量化。假设 99.9% 的激活位于 [−1,1],但偶尔出现一个值 100。若直接按照最大绝对值确定对称量化的 scale,则近似有:

s≈100127

此时相邻整数刻度对应的浮点间隔约为 100/127;如果没有这个离群值,间隔本来可以接近 1/127。大量正常的 [−1,1] 激活会被压缩到很少的 INT8 档位,反量化误差明显增大。

分布情况scale 的典型结果主要风险
校准集与部署分布匹配,范围较稳定scale 能覆盖常见激活量化误差较容易控制
校准集范围过窄scale 偏小部署时更容易截断或饱和
少量离群值撑大范围scale 偏大常见值只使用少量整数刻度
batch 间动态范围波动很大固定 scale 难以兼顾不同输入的量化误差不稳定

处理动态范围问题时,常见思路包括:

  • 使用百分位数或其他统计准则进行 clipping,在截断少量极端值和提高常见值分辨率之间折中;
  • 使用 per-channel 或 per-group scale,避免所有通道共享一个被少数通道离群值支配的范围;
  • 通过 SmoothQuant、AWQ 等方法重新分配权重与激活的量化难度;
  • 针对特定层或特定通道采用 outlier-aware quantization。

这些方法的共同目标是减少少量极端值对整体 scale 的支配,同时控制被截断样本带来的误差。

一个简单的动态范围示例

采用对称 INT8 量化时,若正常激活范围为 [−1,1],scale 大致可以按 1/127 量级选择;若一个离群值把范围扩大到 [−100,100],scale 则变为约 100/127。后者的浮点刻度约放大 100 倍,正常激活的表示精度会明显下降。

实际量化通常还会考虑分位数、KL 散度、逐通道范围和硬件实现限制,因此这个例子用于说明离群值的影响,不代表所有框架都直接使用全局 min-max 规则。

8.3 PTQ 与 QAT ​

PTQ 在模型训练完成后进行量化,通常不需要完整地重新训练模型,成本较低,但对校准数据、量化粒度和离群值处理比较敏感。

QAT(Quantization-Aware Training,量化感知训练)则在训练或微调过程中插入 fake quantization,前向计算中模拟量化和反量化误差,使模型参数有机会适应 INT8 的离散表示。训练时通常仍保留浮点主权重和可反向传播的近似梯度,部署时再导出真正的低精度算子或权重。

因此,模型是否使用浮点数训练并不能单独决定 INT8 部署精度。更关键的是:

量化方式或因素主要影响
PTQ 校准集决定校准统计量是否能代表部署分布
激活和权重范围决定 scale、截断风险和量化分辨率
离群值处理决定少量极端值是否牺牲大量常见值的精度
量化粒度逐张量、逐通道或逐组 scale 会带来不同精度和开销
QAT让模型在训练阶段适应量化误差,通常能改善敏感模型或层的量化效果

INT8 量化速记

量化精度主要取决于校准数据是否代表部署分布、激活范围是否稳定,以及离群值是否把 scale 撑得过大;PTQ 依赖校准,QAT 则在训练中提前适应量化误差。

使用 Markdown 与 VitePress 构建