Appearance
训练、优化与部署工程
本页从正则化和优化器出发,整理初始化、混合精度、归一化融合、训练与推理差异、CUDA 资源约束和 INT8 量化。
一、正则化与交叉验证
正则化通过限制有效模型复杂度或扰动训练过程来降低过拟合风险;交叉验证则主要用于估计泛化表现和选择超参数。二者经常一起使用,但交叉验证本身不是正则化项。
1.1 Dropout、L1 与 L2
Dropout 在训练时随机生成掩码
训练时的缩放使得推理阶段可以直接使用完整网络。Dropout 会改变每次迭代实际参与训练的子网络,减少模型对少数神经元或特征的过度依赖;它不等价于永久删除神经元。
L1 和 L2 则直接把参数惩罚加入目标函数:
| 方法 | 作用机制 | 常见结果 |
|---|---|---|
| Dropout | 随机屏蔽部分激活,改变每次训练时的子网络 | 降低共适应,缓解过拟合 |
| L1 正则 | 惩罚参数绝对值 | 倾向产生精确零值,具有稀疏化和特征选择作用 |
| L2 正则 | 惩罚参数平方 | 倾向整体缩小权重,通常不直接产生大量零值 |
1.2 交叉验证的职责
以
- 估计模型在未见数据上的表现;
- 比较模型结构;
- 选择学习率、正则化系数、树深或其他超参数。
交叉验证没有像 L1、L2 那样直接改变损失函数,也没有自动限制模型参数。若使用交叉验证选择
正则化与交叉验证速记
Dropout、L1、L2 是控制模型复杂度或训练依赖的正则化方法;交叉验证是评估和选超参数的方法,不是正则化本身。
二、优化器:AdaGrad、RMSProp 与 Adam
反向传播得到梯度后,优化器决定如何利用梯度更新参数。最基本的梯度下降为:
不同优化器的主要差异在于是否平滑历史梯度,以及是否为不同参数使用不同的步长。
2.1 AdaGrad:按参数累计平方梯度
AdaGrad 为每个参数维护累计平方梯度:
更新形式为:
经常出现梯度的参数会积累较大的
2.2 RMSProp:指数衰减旧梯度
RMSProp 将 AdaGrad 的永久累计改成指数移动平均:
旧梯度的影响会逐渐衰减,因此有效学习率不再像 AdaGrad 那样只能单调下降。梯度长期较大的方向会得到更小的步长,梯度较小的方向相对得到更大的步长,这有助于减轻狭长损失谷底中的震荡。
2.3 Adam:一阶动量与二阶矩结合
RMSProp 主要维护平方梯度的指数移动平均
Adam 同时维护梯度的一阶指数移动平均和平方梯度的二阶指数移动平均:
由于
最终更新为:
可以把 Adam 的直觉概括为“Momentum 平滑方向 + RMSProp 调整尺度 + Bias Correction 修正初期估计”。其中
如果某个参数连续多步满足
因此下一次非零梯度到来时,
2.4 优化器的状态与取舍
| 优化器 | 主要状态 | 常见优点 | 主要局限 |
|---|---|---|---|
| SGD | 通常无额外一阶/二阶状态 | 简单、内存开销低,部分任务泛化表现好 | 对学习率和损失地形较敏感 |
| SGD + Momentum | 一阶动量 | 减少震荡,沿稳定方向积累速度 | 仍需调学习率和动量 |
| AdaGrad | 累计平方梯度 | 适合稀疏特征 | 学习率可能衰减过快 |
| RMSProp | 平方梯度的指数移动平均 | 缓解 AdaGrad 的后期停滞 | 超参数和初始学习率仍需调整 |
| Adam | 一阶矩、二阶矩 | 自适应步长,通常易于起步 | 每个参数约需两份额外状态,且不保证全局最优 |
在大模型训练中,优化器状态可能成为显存的重要组成部分;混合精度、分片、低精度状态或其他内存优化会改变实际占用,但不改变上述状态数量的基本关系。
2.5 AdamW、学习率调度与梯度累积
AdamW 将权重衰减从 Adam 的自适应梯度项中解耦。抽象写法为:
最后一项直接收缩参数,而不是先把 L2 惩罚产生的梯度交给 Adam 的二阶矩缩放。因而 AdamW 的 weight decay 行为更容易独立控制;它仍然需要合理选择基础学习率、衰减系数和其他训练超参数。
大模型训练中常见的学习率调度是 Warm-up 加衰减。Warm-up 阶段从较小学习率逐步升至目标学习率,例如:
之后可以采用余弦退火:
Cosine Annealing with Restarts 则会在多个周期中重复类似的升降过程。Warm-up 主要降低训练初期发散风险,余弦衰减则帮助后期减小步长、进行更细致的收敛;它们不是优化器本身。
当单个 GPU 无法容纳目标 batch 时,可以使用梯度累积。设每个 micro-batch 大小为
如果每次反向得到的是对应 micro-batch 的平均梯度,应在更新前进行平均:
然后再执行一次参数更新:
梯度累积节省的是单次前向和反向所需的激活显存,不会让单个 micro-batch 的中间计算消失。它在梯度平均、没有 BatchNorm 等强依赖 mini-batch 统计的条件下,更接近一次大 batch 更新;Dropout 掩码、BatchNorm 统计、Adam 状态更新时机以及变长序列的 token 加权,都可能使两者并非严格等价。
扩大 batch 后常用的 linear scaling rule 是学习率随 batch 比例增加的经验起点:
它不是普适定律。大 batch 训练还可能需要延长 warm-up、重新调整 weight decay、使用梯度裁剪,或根据具体任务调整其他正则项。常见的全局梯度范数裁剪可以写成:
其中
大模型训练配套速记
AdamW 负责自适应更新与解耦权重衰减;Warm-up 和 Cosine 负责学习率调度;梯度累积用多个 micro-batch 模拟大 batch;batch 变大时,学习率、warm-up、weight decay 和梯度裁剪需要一起验证。
三、参数初始化与高斯分布
若某层权重按:
初始化,则
均值为零表示大量随机权重的平均值在理论上为零,不表示每个权重都等于零;实际样本仍会取正值和负值。
普通高斯分布的取值范围是整个实数轴:
因此“约
正态分布的 68-95-99.7 规则
对
当
初始化尺度需要与层的输入输出宽度和激活函数匹配。尺度过大可能使激活值、点积或梯度变大,尺度过小则可能使信号逐层衰减。经典的方差保持初始化会根据 fan-in 和 fan-out 选择方差,例如:
对于 ReLU 类激活,常见的 He 初始化会采用:
这些是经典启发式,具体 Transformer 还会结合残差、归一化、投影结构和初始化缩放策略调整,不能只根据“正态分布均值为零”判断初始化一定合适。
参数初始化速记
四、混合精度训练与 Loss Scaling
混合精度训练通常让矩阵乘法等计算使用 FP16 或 BF16,同时在必要的位置保留 FP32 精度,以降低显存占用并提高吞吐量。低精度格式的动态范围或有效精度有限,反向传播中很小的梯度可能被舍入或下溢成
Loss Scaling 的做法是先把损失放大
根据链式法则,反向得到的梯度也会放大:
在反向传播结束后,再把梯度除以
典型流程是:
这样理想数学结果不变,改变的只是低精度反向计算时梯度所处的数值尺度。若缩放因子过大,梯度或激活可能溢出为 Inf/NaN,因此工程实现通常使用动态 Loss Scaling:检测到溢出时减小
BF16 的指数范围接近 FP32,因此比 FP16 更不容易因为数值太小而下溢,很多 BF16 训练场景不需要显式 Loss Scaling;但 BF16 的尾数精度较低,仍需要结合算子实现、累加精度和训练稳定性进行验证。
需要区分两件事:混合精度本身可以减少显存并提高部分硬件上的计算吞吐,而 Loss Scaling 的直接目的只是避免低精度反向梯度下溢;它不会单独减少参数量或激活张量的数量。
Loss Scaling 速记
FP16 反向的小梯度可能下溢为
五、Conv-BN Folding:推理阶段的算子融合
Conv-BN Folding 是在推理前把 BatchNorm 的固定统计量和仿射参数吸收到卷积权重、偏置中的等价变换。它要求 BatchNorm 已经处于推理模式,使用固定的 running mean 和 running variance;训练时 batch 统计量会变化,不能直接这样融合。
设某个卷积输出通道的结果为:
推理阶段的 BatchNorm 为:
令:
则:
因此可以构造新的卷积参数:
对实际卷积而言,
在固定统计量和正常浮点舍入误差范围内,二者输出等价。运行时不再需要单独执行 BN kernel,也减少了中间特征的显存读写,因此通常可以降低推理延迟和带宽压力;它不是为了加速反向传播,也不意味着模型不能使用 Tensor Core。
Conv-BN Folding 速记
推理前把 BN 的
六、训练与推理:前向行为与资源差异
训练和推理使用同一套模型参数,但运行模式、保存的中间状态和计算目标不同。因此不能简单概括成“只换了输入数据,前向计算逻辑完全一样”。
6.1 训练模式与推理模式
| 组件或行为 | 训练阶段 | 推理阶段 |
|---|---|---|
| Dropout | 随机屏蔽部分激活并进行相应缩放 | 关闭随机屏蔽,使用完整网络 |
| BatchNorm | 使用当前 mini-batch 的统计量,并更新 running statistics | 使用训练期间累计的 running mean/variance |
| 自动求导 | 保存反向传播需要的中间激活 | 通常关闭梯度计算,不保存训练用的反向缓存 |
| 参数状态 | 计算梯度并由优化器更新参数 | 参数固定,只执行前向计算 |
因此使用框架时,训练模式和推理模式的切换不仅影响性能,也可能改变输出:
对于没有 Dropout、BatchNorm 等状态差异的纯前向模块,训练和推理的数值路径可能更接近,但也不能据此推断所有网络都完全相同。
6.2 Transformer 的训练、Prefill 与 Decode
训练语言模型时,通常把一整段序列并行送入网络:
在因果掩码约束下,一次计算整段序列的
自回归生成的 Decode 阶段每次只增加一个 token。当前隐藏状态产生:
当前 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 数量的比例:
一个 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 数等 | 即使其他资源有剩余,也不能超过硬件上限 |
用
实际结果还会受到寄存器和共享内存的分配粒度、每个架构的线程块限制,以及 kernel 其他资源使用情况影响。因此,这个式子适合用来理解瓶颈,不应当替代针对具体 GPU 的 Occupancy Calculator 或 profiler 结果。
共享内存与寄存器的简化估算
假设一个 SM 有
- 每个 block 使用
时,仅从共享内存看最多可同时驻留 个 block; - 每个 block 使用
时,仅从共享内存看最多可同时驻留 个 block。
再假设一个 SM 有
- 每线程使用
个寄存器时,单个 block 约需 个寄存器; - 每线程使用
个寄存器时,单个 block 约需 个寄存器。
第二种情况下,寄存器更可能先成为限制驻留 block 数量的瓶颈。以上计算忽略了线程数、共享内存和硬件上限等其他约束,实际 Occupancy 取所有约束共同作用后的结果。
7.2 Block 大小、warp 数与几何形状
CUDA 通常以
个 warp。因而 block 的总线程数会同时影响:
- 一个 block 占用多少 warp;
- 一个 SM 能驻留多少个 block;
- 最后一个 warp 是否存在未填满的线程。
二维或三维 block 的几何形状本身不是 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、寄存器压力、共享内存用量和访存效率之间做取舍,而不是盲目追求
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 Memory | GPU 上容量较大的设备内存,访问延迟通常高于寄存器 | Local Memory 通常由它提供物理后备 |
| L1/L2 Cache | 对部分内存访问进行缓存的硬件层次 | 可能缓存 Local Memory 的数据,但不是 spill 的目标地址空间 |
因此,“溢出到 Local Memory”不等于“溢出到 L1 Cache”。如果 Local Memory 访问命中缓存,实际代价可能降低;未命中时仍可能产生设备内存访问,延迟和带宽压力通常远高于寄存器访问。
寄存器压力会同时影响 Occupancy 和访存开销:
如果继续增加寄存器需求,还可能出现:
寄存器压力的取舍
降低每线程寄存器上限有时可以提高 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 为例,整数编码范围通常为:
一种常见的仿射量化与反量化形式是:
其中:
:原始浮点值; :量化后的整数; :反量化后对原值的近似; :scale,决定一个整数刻度对应多大的浮点范围; :zero-point,用于非对称地对齐浮点零点; :当数值超出 INT8 可表示范围时进行截断。
对称量化常令
8.1 校准集为什么重要?
PTQ(Post-Training Quantization,训练后量化)通常使用一批校准数据运行模型,统计权重或激活的数值分布,再据此选择 scale、zero-point 和必要的截断范围。校准集不需要覆盖所有训练数据,但应尽量接近真实部署时的输入分布,包括输入类型、长度、领域和典型激活模式。
如果部署数据的激活通常在
校准集原则
校准集不以样本数量为唯一目标,应尽量代表真实部署时的输入分布。
8.2 激活范围与离群值
INT8 只有有限的离散编码。如果激活范围稳定且与部署分布匹配,scale 可以较稳定地利用这些编码;如果不同 batch 的范围变化很大,固定的 scale 就难以同时兼顾常见值和极端值。
离群值尤其容易破坏 min-max 量化。假设
此时相邻整数刻度对应的浮点间隔约为
| 分布情况 | scale 的典型结果 | 主要风险 |
|---|---|---|
| 校准集与部署分布匹配,范围较稳定 | scale 能覆盖常见激活 | 量化误差较容易控制 |
| 校准集范围过窄 | scale 偏小 | 部署时更容易截断或饱和 |
| 少量离群值撑大范围 | scale 偏大 | 常见值只使用少量整数刻度 |
| batch 间动态范围波动很大 | 固定 scale 难以兼顾 | 不同输入的量化误差不稳定 |
处理动态范围问题时,常见思路包括:
- 使用百分位数或其他统计准则进行 clipping,在截断少量极端值和提高常见值分辨率之间折中;
- 使用 per-channel 或 per-group scale,避免所有通道共享一个被少数通道离群值支配的范围;
- 通过 SmoothQuant、AWQ 等方法重新分配权重与激活的量化难度;
- 针对特定层或特定通道采用 outlier-aware quantization。
这些方法的共同目标是减少少量极端值对整体 scale 的支配,同时控制被截断样本带来的误差。
一个简单的动态范围示例
采用对称 INT8 量化时,若正常激活范围为
实际量化通常还会考虑分位数、KL 散度、逐通道范围和硬件实现限制,因此这个例子用于说明离群值的影响,不代表所有框架都直接使用全局 min-max 规则。
8.3 PTQ 与 QAT
PTQ 在模型训练完成后进行量化,通常不需要完整地重新训练模型,成本较低,但对校准数据、量化粒度和离群值处理比较敏感。
QAT(Quantization-Aware Training,量化感知训练)则在训练或微调过程中插入 fake quantization,前向计算中模拟量化和反量化误差,使模型参数有机会适应 INT8 的离散表示。训练时通常仍保留浮点主权重和可反向传播的近似梯度,部署时再导出真正的低精度算子或权重。
因此,模型是否使用浮点数训练并不能单独决定 INT8 部署精度。更关键的是:
| 量化方式或因素 | 主要影响 |
|---|---|
| PTQ 校准集 | 决定校准统计量是否能代表部署分布 |
| 激活和权重范围 | 决定 scale、截断风险和量化分辨率 |
| 离群值处理 | 决定少量极端值是否牺牲大量常见值的精度 |
| 量化粒度 | 逐张量、逐通道或逐组 scale 会带来不同精度和开销 |
| QAT | 让模型在训练阶段适应量化误差,通常能改善敏感模型或层的量化效果 |
INT8 量化速记
量化精度主要取决于校准数据是否代表部署分布、激活范围是否稳定,以及离群值是否把 scale 撑得过大;PTQ 依赖校准,QAT 则在训练中提前适应量化误差。