分布式训练面¶
💡 大模型训练与普通模型训练的本质区别是什么?为什么必须使用分布式训练?¶
大模型训练与普通模型训练的本质区别在于规模。当参数规模从几百万跨越到几十亿、甚至数千亿时,模型的参数量、计算量、所需的存储空间以及训练数据的体量都发生了指数级的增长。这会直接撞上单GPU的“物理天花板”:显存容量、计算能力和通信带宽。
🔍 本质区别——从“一人扛”到“万人抬”
-
显存瓶颈:一个千亿参数的模型,仅参数本身(以FP16精度存储)就需要约200GB显存,而目前最强的单张GPU(如NVIDIA H100)的显存只有80GB。这还没算上训练必需的梯度、优化器状态(如Adam的动量和方差,占用数倍于参数本身)和中间激活值。单卡根本无法容纳。
-
计算墙:假设单张GPU的计算能力足够(实际上远不够),用一张卡训练一个千亿模型需要数年甚至更久,这在工程上完全不可接受。必须把任务拆解到成百上千张GPU上并行执行。
-
通信墙:当模型被切分到数千张GPU上,这些GPU之间需要频繁、高速地交换数据(如梯度同步、中间激活值),如果网络带宽不足或拓扑设计不佳,通信就会成为瓶颈,让大量GPU空转等待。
🚀 为什么必须使用分布式训练?
分布式训练是突破上述三大瓶颈的唯一途径。它将模型、数据、甚至计算图切分到不同的计算设备上,利用集群的集体算力、显存和带宽来解决问题。常见策略包括:
-
模型并行(Tensor/Pipeline Parallelism):将模型本身切分,每张卡只存放一部分层或一部分权重,分工协作完成前向和反向传播。
-
数据并行(Data Parallelism):每张卡都持有完整的模型副本,但处理不同的数据批次,最后统一同步梯度更新参数。
-
ZeRO(零冗余优化器):由DeepSpeed提出,将优化器状态、梯度、参数分片到多张卡上,使得每张卡的显存占用大幅降低,可以实现用更少的卡训练更大的模型。
-
混合并行:现代大模型训练系统都是上述策略的组合,如3D并行(TP+PP+DP)结合ZeRO,以最大化集群利用率。
💡 一句话总结:大模型训练的本质是“如何用数千张GPU高效、稳定地协同,完成单卡无法承受的计算、存储和通信任务”。
🚧 训练一个千亿参数模型,主要面临哪些挑战?(计算、显存、通信)¶
训练千亿参数模型是一项系统工程,挑战来自三个方面:显存、计算和通信,三者相互制约,往往需要折中。
🔴 1. 显存挑战(Memory Wall)
这是最直接的限制。单个GPU的显存无法容纳训练过程中的所有数据:
-
模型参数:千亿参数,FP16精度约200GB。
-
梯度:同样约200GB(FP16)。
-
优化器状态(Adam):需要保存每个参数的一阶动量和二阶动量,通常用FP32存储以保证数值稳定性,这会占用约800GB。
-
激活值:前向传播产生的中间结果,在反向传播时需要用到,不能随意丢弃。其大小与批量大小(batch size)、序列长度和模型层数成正比,在长序列训练下可高达TB级。 这些加起来远超单卡80GB的容量,必须通过模型并行、ZeRO分片、激活重计算(Gradient Checkpointing)、CPU/NVMe Offload等技术来突破。
🟠 2. 计算挑战(Compute Wall)
千亿模型的一次前向传播需要巨大的浮点运算量(FLOPs)。例如,GPT-3(175B参数)的训练需要约3.14×10²³ FLOPs。即使在1万张A100 GPU上,也需要连续训练数周甚至数月。计算挑战包括:
-
如何高效利用GPU算力(如通过混合精度训练使用Tensor Core)。
-
如何设计并行策略使计算负载均衡,减少GPU空泡(idle time)。
-
如何通过算子融合、编译优化(如TorchScript/TensorRT)来提升单卡计算效率。
🟡 3. 通信挑战(Communication Wall)
当模型被切分到数百甚至数千张GPU上,它们之间必须频繁交换数据:
-
数据并行中,需要AllReduce操作来同步梯度,通信量等于参数总量。
-
模型并行(张量并行)中,每层需要多次AllReduce或AllGather,对卡间带宽要求极高,通常依赖NVLink等高速互联。
-
流水线并行需要传递中间激活值,通信量相对较小,但需要精细调度以避免“气泡”。 如果网络带宽不足,GPU就会花大量时间等待数据,整体训练效率急剧下降。
🛠️ 应对策略概览:
-
ZeRO-1/2/3:将优化器状态、梯度、参数分片,显著降低单卡显存。
-
3D并行:结合数据并行、张量并行和流水线并行的优势。
-
激活重计算:以时间换空间,反向传播时重新计算激活值而非全部存储。
-
混合精度训练:用FP16/BF16加速计算,FP32保持精度。
-
高性能网络:使用InfiniBand、RoCE等提供高带宽低延迟通信。
💡 核心矛盾:在千亿模型训练中,永远要在显存、计算和通信之间做权衡,没有一劳永逸的配置。
🔄 描述深度学习训练的一个标准迭代步骤:前向、反向、优化器更新。¶
训练神经网络的一个核心迭代循环,就是反复执行三个步骤:前向传播、反向传播、优化器更新。每一步都有其特定任务,互相依赖。
📌 步骤一:前向传播(Forward Pass)
-
做什么:将一小批数据(mini-batch)输入网络,从输入层到输出层逐层计算,每一层进行线性变换(矩阵乘法)和非线性激活(如ReLU/GELU)。
-
产出:最终的预测输出,以及损失值(Loss)——衡量预测与真实标签差距的标量。
-
额外工作:框架(如PyTorch)会悄悄地记录下这次前向传播的计算图(Computational Graph),并保留中间激活值(Activations)(如每层的输出、注意力得分等)。这些激活值将在反向传播中用于计算梯度。
📌 步骤二:反向传播(Backward Pass)
-
做什么:从损失值出发,沿着前向传播建立的计算图逆向行走,应用链式法则自动计算损失对于每一个可学习参数的梯度(Gradient)。
-
依赖:这个过程严重依赖前向传播保存的激活值。例如,计算一个线性层权重的梯度,需要该层的输入激活值。
-
结果:所有可学习参数(权重和偏置)都会得到对应的梯度,这些梯度指明了降低损失的方向。
📌 步骤三:优化器更新(Optimizer Step)
-
做什么:优化器(如AdamW)根据计算出的梯度,按照其特定的更新规则,去调整模型的参数值。
-
例如,简单的随机梯度下降(SGD)的更新规则是:
参数 = 参数 - 学习率 × 梯度。而AdamW会更复杂,会维护梯度的动量和方差来平滑更新。 -
收尾:更新完成后,清零所有参数的梯度,准备下一轮迭代。如果不清零,梯度会默认累加,这在某些场景下有用,但对标准训练通常需要清零。
💡 通俗比喻:这就像一个学生做练习题(Forward),然后对照答案批改打分(Loss)。接着分析每一道题的错误原因(Backward),最后根据错因和自己的学习策略调整知识掌握(Optimizer Step)。
💾 模型参数、梯度、优化器状态分别存储在什么地方?各占用多少显存(以 FP16 为例)?¶
这些数据在训练期间全部驻留在GPU的显存(HBM/VRAM)中,因为GPU是计算的核心,需要极快地读取和写入这些数据。它们的显存占用是大模型训练主要的内存消耗来源。
以下我们假设模型参数量为 P,并使用混合精度训练(前向和反向用FP16,但优化器状态和主参数副本用FP32来保证精度)进行估算。
🔹 模型参数(Model Weights)
-
存储内容:神经网络层的权重矩阵和偏置向量。
-
FP16格式:这是参与前向和反向计算的主力。每个参数占用2个字节。总占用 = 2P 字节。
-
FP32主参数副本:在混合精度训练中,通常还会在内存中保留一份FP32(4字节)的参数副本,用于累积微小的梯度更新,防止精度损失。总占用 = 4P 字节。有些框架(如ZeRO-1)通过分片共享该副本。
🔹 梯度(Gradients)
-
存储内容:反向传播计算出的、对应于每个参数的损失函数偏导数值。
-
存储格式:在前向和反向传播中,梯度的计算和存储通常与模型参数的FP16一致,以节省算力和显存,因此也以FP16(2字节)存储。总占用 = 2P 字节。
🔹 优化器状态(Optimizer States)
-
存储内容:对于Adam/AdamW优化器,需要为每个参数维护一阶动量(m)和二阶动量(v)。这些状态值需要保持高精度(FP32,4字节)以确保更新过程的数值稳定性。
-
总占用:
m占4P字节,v占4P字节,合计 = 8P 字节。
📊 显存占用比例(以7B参数模型为例)
-
参数(FP16):2 × 7B = 14 GB
-
参数主副本(FP32):4 × 7B = 28 GB (通常与优化器状态一起考虑在ZeRO分片中)
-
梯度(FP16):2 × 7B = 14 GB
-
优化器状态(FP32):8 × 7B = 56 GB
-
总计(不含激活值)至少为:14 + 28 + 14 + 56 = 112 GB。
💡 大模型为何用ZeRO:仅上述四项就远超单张A100(80GB)的容量,这还没算上体积可能更大的激活值。因此,ZeRO等分片策略成为必需,它们将优化器状态、梯度和参数拆分到多张GPU,从而极大降低单卡显存需求。例如,使用ZeRO-3后,单卡只需要存1/N的参数和状态(N为GPU数量)。
⚡ 什么是混合精度训练?为什么需要混合精度?¶
混合精度训练是一种在训练过程中同时使用低精度和高精度浮点数的技术。通常,前向和反向传播使用16位浮点(FP16或BF16)来大幅加速计算并节省显存,而参数的更新和主副本则保留32位浮点(FP32)以保持最终模型的精度和训练稳定性。
🚀 为什么需要混合精度?——速度与显存的双重收益
-
极致加速:现代GPU(如NVIDIA A100/H100、V100)都配备了专门的Tensor Core,它们在执行FP16/BF16格式的矩阵乘加运算时,吞吐量是FP32的数倍甚至十几倍。利用混合精度,能显著缩短训练时间。
-
显存减半:模型参数和激活值占用的显存减少一半,意味着可以训练更大的模型,或者使用更大的批量大小(Batch Size),这往往能提升训练稳定性和收敛效果。
🛡️ 然而,全FP16训练会崩溃——所以需要混合
-
数值范围太小:FP16能表示的最小正规数约为6.1×10⁻⁵,最大为65504。在训练中,很多梯度值(尤其是网络浅层或训练后期)会小到导致下溢(变为0),使参数无法更新;而有些值会大到溢出(变为Inf/NaN),导致模型崩溃。
-
精度损失:FP16只有10位尾数(有效数字约3-4位),而FP32有23位(有效数字约7-8位)。当梯度值差别很大时,小的梯度可能被大的梯度“吞没”而失去作用。
💡 混合精度的核心工作流:
-
维护FP32主参数副本:在内存中保留一份FP32的参数,作为训练过程中最可靠的“真实值”。
-
前向和反向用FP16:将主参数转换为FP16进行前向计算,得到损失;反向传播也使用FP16计算,得到FP16梯度。
-
Loss Scaling(仅在FP16下需要):将损失乘以一个较大的缩放因子,反向传播后梯度被等比例放大,从而避免小梯度下溢。在更新参数前,再将梯度缩小回原来比例。(如果使用BF16,则无需此步骤,因为BF16指数位多,动态范围与FP32一致)。
-
FP32更新:将缩放后的FP16梯度转换为FP32,应用于FP32的主参数副本上,完成一次参数更新。
💡 FP16与BF16的选择:现代大模型训练大多转向BF16,因为它与FP32有相同的8位指数位,动态范围极大,几乎不会出现溢出或下溢,因此不需要Loss Scaling,训练更加稳定,已成为主流。FP16在更老的GPU(V100)上仍然是首选。
⚖️ FP16 和 BF16 的区别是什么?训练中如何选择?¶
FP16(IEEE 754半精度浮点)和 BF16(Brain Floating Point 16)都是16位的浮点数格式,但它们在内部结构上的一个关键差异,直接决定了它们在大模型训练中的适用性。
📊 内部结构对比
| 格式 | 符号位 | 指数位 | 尾数位 | 动态范围 | 精度 |
|---|---|---|---|---|---|
| FP16 | 1 bit | 5 bits | 10 bits | ~±65,504 | 较高 |
| BF16 | 1 bit | 8 bits | 7 bits | ~±3.4e38 (与FP32相同) | 较低 |
🔍 核心差异:指数位决定了命运的走向
-
FP16 仅有5位指数,能表示的最小正规数约 6.1×10−56.1×10−5,最大值约65504。在深度学习中,训练早期的梯度可能非常大(溢出为Inf),而后期或浅层的梯度可能非常小(下溢为0)。这种狭窄的动态范围使得FP16训练变得脆弱,必须引入Loss Scaling来人工维持梯度在合理范围。
-
BF16 拥有与FP32相同的8位指数,动态范围极大,几乎不可能在正常训练中发生溢出或下溢。虽然尾数精度较低,但深度神经网络对权重的微小变化本身具有鲁棒性,而且梯度的更新方向主要由较大的梯度分量主导,BF16的精度损失在宏观上影响甚微。
🛠️ 训练中如何选择?
选择哪种格式主要取决于你的GPU硬件和训练稳定性需求:
-
如果使用 NVIDIA A100/H100 或 Google TPU:首选BF16。这些硬件原生支持BF16矩阵运算,且有Tensor Core加速。BF16的最大优势是无需Loss Scaling,训练曲线更平滑,超参数鲁棒性更强。它几乎不会出现梯度消失或爆炸的问题,显著降低了调试成本。
-
如果使用 NVIDIA V100 或更老的GPU:必须用FP16,因为这些硬件不支持BF16的Tensor Core。FP16训练需要谨慎使用Loss Scaling和动态调整策略来维持稳定。
-
混合场景:在超大规模训练中,即使硬件支持BF16,有时也会将优化器状态和主参数副本保持FP32,而前向和反向计算用BF16。这是标准混合精度训练的延伸。
💡 实践经验:现在几乎所有的大模型(LLaMA、GPT-4等)都采用BF16训练。如果条件允许,直接选BF16,省去Loss Scaling的调参烦恼,并能更稳定地收敛。
❓ 为什么 FP16 训练需要 Loss Scaling?动态 Loss Scaling 如何工作?¶
FP16 的5位指数位导致它能精确表示的数值范围极窄。在训练深层网络时,大量梯度的绝对值可能小于 10−510−5,直接用FP16表示就会变为0,导致这些参数无法得到更新,模型无法收敛。Loss Scaling 正是为了解决FP16梯度下溢问题而生的技术。
🔍 Loss Scaling 的核心原理
既然梯度太小会下溢,那就在计算梯度之前,人为地将Loss乘以一个较大的缩放因子(Scale Factor)。根据链式法则,Loss放大后,反向传播产生的所有梯度也会被等比例放大,使得原本微小的梯度值移入

🐕 动态 Loss Scaling (Dynamic Loss Scaling) 的工作流程
静态缩放因子很难选择:太大可能导致梯度上溢(变为Inf/NaN),太小则下溢问题依然存在。动态调整算法(在PyTorch的torch.cuda.amp.GradScaler中实现)解决了这个问题:

-
前向与反向传播:在每个iteration,将Loss乘以当前缩放因子,然后反向传播得到放大后的梯度。
-
梯度检查:在优化器更新之前,检查所有梯度中是否出现
Inf或NaN。 - 如果无溢出:说明当前缩放因子是安全的,甚至可能偏小。优化器使用还原后的梯度正常更新参数,并将缩放因子乘以一个增长因子(如1.05),准备在下一步进一步放大梯度,以更好地利用FP16的表示范围。
-
如果有溢出:表示放大过度,这一步的梯度无效。优化器跳过本次参数更新(因为梯度不可用),并将缩放因子除以一个缩小因子(如2.0),降低后续的放大强度,确保下一步梯度安全。
-
循环:通过这种动态调整,缩放因子会自动稳定在一个既不引起上溢、又能最大化抑制下溢的理想区间。
💡 现代视角:由于BF16的动态范围与FP32相同,使用BF16训练的模型完全不需要Loss Scaling,这是它相对于FP16的巨大优势。
💾 训练时,激活值 (Activations) 为什么要保存?它占用显存的量级大概是多少?¶
激活值是指神经网络在前向传播过程中,每层计算产生的中间输出张量。例如,线性层的输出(在激活函数之前)、ReLU的输出、注意力得分矩阵等。
🔍 为什么要保存?
反向传播需要它们来计算梯度。
反向传播的目的是计算损失对于每个可学习参数的梯度。参数的梯度公式通常包含了前向传播时的输入或输出。例如:

如果没有保存这些中间激活值,反向传播时就必须重新计算它们,这就是激活重计算技术的核心。但在标准训练中,为了节省时间,通常会直接保留这些激活值在显存中,直到反向传播完成后再释放。
📊 显存量级
激活值显存占用与批量大小(batch size)、序列长度(seq_len)、模型隐藏维度(hidden_dim)、层数成正比,是训练时最主要的显存消耗者之一,尤其是对于长序列任务。
-
对于Transformer,最大的激活值通常是注意力得分矩阵,形状为
[batch, heads, S, S]。如果 S=4096S=4096,heads=32,batch=1,一个单层的FP16得分矩阵就占用 1×32×4096×4096×2≈1 GB。如果层数多,或者采用标准实现(未用FlashAttention),激活值占用可轻易超过模型参数本身。 -
通用估算:在大模型训练中,激活值显存通常占模型参数显存的 0.5~3倍,具体取决于序列长度和并行策略。FlashAttention等优化通过分块计算避免了存储完整的平方级矩阵,能极大缩减激活值。
💡 应对策略:
-
激活重计算 (Gradient Checkpointing):选择性地不保存部分激活,反向时临时重算,以时间换空间,可节省50%以上的激活显存。
-
FlashAttention-2:融合算子,避免保存完整的注意力矩阵,显著降低激活显存。
📈 解释“计算图”在反向传播中的作用,以及显存如何被中间结果占用。¶
计算图是深度学习框架自动求导功能的基石。它是一个有向无环图(DAG),其中:
-
节点 代表操作(如矩阵乘法、加法、激活函数)。
-
边 代表数据(张量)的流向。
🔍 在反向传播中的作用
-
记录操作序列:在前向传播时,框架(如PyTorch的autograd)会实时构建这张计算图,记录了从输入张量到最终损失值的每一步操作,以及每个操作的输入输出张量。
-
自动梯度计算:反向传播时,框架从损失节点开始,逆序遍历计算图。对于每个操作节点,框架调用其对应的反向传播函数,利用该节点保存的输入输出张量(即中间结果)和上游传来的梯度,通过链式法则计算出对各个输入的梯度,并继续向前传播。
-
动态销毁:在PyTorch的动态图中,通常一个批次的反向传播完成后,该计算图会被立即释放,释放掉中间结果占用的显存(
retain_graph=False的情况)。
🧠 显存如何被中间结果占用?
计算图中的“中间结果”正是我们在前一个问题中讨论的激活值。这些张量作为图的边而存在。例如,一个操作 Mul(A, W) -> Y 会产生输出张量 Y。框架在构建图时,会自动将 A 和 W 等输入和 Y 这个输出保存下来。Y 就是中间结果,它将在反向传播计算 A 或 W 的梯度时被用到。
这些中间张量会实实在在地占用显存,直到反向传播完成。因此,计算图越深,操作越多,需要保存的中间结果就越多,显存占用也就越大。
💡 减少中间结果显存的方法:
-
算子融合:将多个连续操作合并为一个kernel,减少中间张量的保存(例如Conv+BN+ReLU融合)。
-
激活重计算:故意不保存某些中间结果,在反向时重新计算它们。
-
inplace操作:原地修改张量,避免分配新显存。
🛠️ 大模型训练为什么通常使用 AdamW 优化器?它的更新公式是怎样的?¶

AdamW 是 Adam 优化器的一个变种,它巧妙地将权重衰减(Weight Decay) 从自适应学习率中解耦出来,解决了标准 Adam 在泛化能力上的缺陷。
📌 AdamW 更新公式 假设参数为 θ,损失函数对 θ 的梯度为 gt。

🔍 为什么大模型用 AdamW?¶
-
收敛速度快:AdamW继承了Adam的自适应学习率特性,能处理稀疏梯度和非平稳目标函数,这对Transformer类模型至关重要。
-
泛化能力更强:标准Adam中的L2正则化项会被自适应学习率缩放,导致权重衰减效果不均匀,实际作用类似一个较弱的学习率调整。而AdamW直接将权重衰减独立出来,保证每个参数都以相同的速率向零衰减,这更符合理论上对“简单模型”的偏好,从而提升了模型在未知数据上的表现(泛化)。
-
广泛验证:从GPT-3到LLaMA,几乎所有大语言模型都采用AdamW,经过了大规模实验检验,社区支持极好。
💡 一句话:AdamW = Adam + 解耦的权重衰减,是当前大模型训练的默认优化器。
⚖️ AdamW 中的权重衰减与 L2 正则化有何不同?为什么解耦?¶
这个问题触及了优化器设计的核心。在标准SGD中,L2正则化和权重衰减是等价的,但在Adam这类自适应学习率算法中,它们完全不是一回事,而AdamW正是通过解耦纠正了这个错误。
🔍 L2正则化 vs 权重衰减

-
恢复了权重衰减的本来意义——作为一个均匀的正则化器,驱动模型向简单解(参数更小)靠拢。
-
显著提升了泛化性能,使得AdamW既能享有Adam的快速收敛,又拥有接近SGD的泛化能力。
💡 一句话:L2正则化在Adam中被自适应学习率扭曲了,而AdamW的权重衰减是“直给”的,不被扭曲,因此效果更好。
🎯 什么是梯度累积?它如何模拟大 batch size?会影响 BatchNorm 吗?¶
梯度累积 是一种在显存受限时,通过时间换空间来模拟更大批量大小(batch size)的技术。
🔍 工作原理
假设我们的GPU显存只允许一次处理 micro_batch_size=16 的数据,但我们希望用 effective_batch_size=128 来训练。
-
不更新参数:我们依次处理 8 个 micro-batch。每个 micro-batch 都进行前向和反向传播,计算出梯度。
-
累加梯度:将这些梯度相加(累积) 到参数上,而不是在每个 micro-batch 后都执行优化器更新。
-
一次性更新:当处理完第 8 个 micro-batch 后,参数的梯度已经等于用
batch_size=128计算出的梯度。此时,才调用优化器用这个累积的大梯度去更新参数。 -
清零:更新后,将所有参数的累积梯度清零,开始下一轮累积。
这相当于用 8 次小批量的计算成本,模拟了一次大批量的更新。
⚠️ 对 BatchNorm 的影响
答案是:有严重影响,默认不可用。
BatchNorm 会在每个 mini-batch 内部统计该批数据的均值和方差。在梯度累积中,每个 micro-batch 都是独立统计的,这意味着它计算的是小批量(比如16)的统计量,而不是期望的大批量(128)的统计量。小批量的统计量方差很大,极不稳定,会严重损害模型性能和收敛。
因此,在使用梯度累积时,通常:
-
使用其他归一化层:如 LayerNorm 或 GroupNorm,它们不依赖 batch 统计量,天然兼容梯度累积。
-
SyncBN(同步批归一化):如果在多卡训练中,可以使用跨卡的SyncBN,让所有卡上的 micro-batch 共享统计量。但这依然不能解决累积步之间统计独立的问题。一般更推荐直接换成 LayerNorm。
💡 与数据并行的区别:数据并行是“空间上”的并行,多张卡同时计算不同数据,最后把梯度平均;梯度累积是“时间上”的串行,一张卡分多次计算,最后把梯度相加。梯度累积模拟的 batch size 大小是 micro_batch_size × 累积步数。
🆚 梯度累积与数据并行有什么区别?何时使用?¶
两者都能实现更大的有效批量大小,但机制和适用场景截然不同。
| 特性 | 梯度累积 | 数据并行 |
|---|---|---|
| 执行模式 | 串行,单卡分步执行多个小批量 | 并行,多张卡同时处理不同的小批量 |
| 梯度计算 | 梯度累加(求和) | 各卡独立计算梯度,然后平均 |
| 通信开销 | 无 | 每步需要AllReduce同步梯度,通信量大 |
| 硬件需求 | 单卡即可 | 需要多卡 |
| 批量大小 | 有效 = micro_batch × 累积步数 | 有效 = per_gpu_batch × GPU数量 |
| 速度 | 慢(串行) | 快(并行) |
| BN兼容性 | 不兼容,需用LayerNorm | 兼容SyncBN,但普通BN在多卡也需同步 |
| 显存压力 | 较低,因为单卡只存一个micro-batch的激活 | 每卡存自己的micro-batch激活 |
🔍 何时使用?
- 使用梯度累积:
- 单卡训练,显存不足但希望模拟更大的 batch size。
- 已有LayerNorm等不依赖batch的归一化层,或者模型不包含BN。
-
实验性调参,快速验证大 batch size 的效果。
-
使用数据并行:
- 多卡环境,需要加速训练。
- 需要真正的大 batch size,且希望各卡并行处理以减少时间。
- 训练包含BN的网络,且期望保持其统计特性(需配合SyncBN)。
💡 组合使用:现代大模型训练常将两者结合。例如,在2个节点的4张卡上进行数据并行,同时每张卡内部再做梯度累积,最终有效batch size = per_gpu_batch × 4 × 累积步数。这能最大化硬件利用率。
🌡️ 学习率预热 (Warmup) 的作用是什么?不做预热可能发生什么?¶
学习率预热 是指在训练开始阶段,将学习率从一个非常小的值(或0)逐步线性增加到预设的初始学习率,这个过程通常持续几千个迭代步。
🔍 为什么要预热?——度过危险的不稳定期
训练初期,模型参数是随机初始化的,距离一个良好的收敛区域还很远。此时,梯度的估计非常不准确且方差极大。如果直接使用较大的学习率,模型可能会:
-
参数更新过度:大步长瞬间将参数推向极值区域,导致后续训练中激活值或梯度爆炸,产生Inf/NaN,训练彻底崩溃。
-
陷入糟糕的局部极小:模型可能在初期就被“踢”到一个非常狭窄、泛化性极差的极小值点,之后再难跳出。
-
对优化器状态的破坏:对于Adam这类维护动量的优化器,在训练初期,动量和方差的估计几乎为零。最初的几次大更新会主导动量的方向,如果这些更新是噪音,就会污染优化器状态,使模型在错误的方向上走得更远。
预热就像一个缓冲期,让模型用小步长“试探”地形,逐渐建立起对梯度方向和尺度的可靠估计(优化器状态得以稳定),之后再放开学习率,在相对平滑的区域内进行高效优化。
💡 对Transformer尤其重要:Transformer的层归一化和残差连接使得网络对初始化极其敏感。没有预热,训练往往在几百步内就崩掉。因此,几乎所有大语言模型的训练都标配学习率预热。
📉 常用的学习率调度策略有哪些?Cosine Decay 和 Linear Decay 的异同。¶
学习率调度策略(LR Scheduler)决定了学习率在整个训练过程中如何变化。常见的有:
🔹 Cosine Decay (余弦退火)

-
特点:在训练初期和中期,学习率下降非常平缓,保持较大的值以充分探索;在末期下降加速,精细收敛。这被证明在许多视觉和语言任务中能带来更好的泛化性能。
-
变体:Cosine Annealing with Warm Restarts,周期性重置学习率。
🔸 Linear Decay (线性衰减)
学习率从初始值线性下降至最小学习率,中间没有曲率变化。
- 特点:实现简单,可预测性强。在一些大型语言模型预训练中(如LLaMA),线性衰减也被证明有效,且易于和warmup阶段无缝衔接。
🔸 Step Decay (阶梯衰减)
每过一定epoch数,学习率衰减一个固定的倍数(如0.1)。
- 特点:非平滑,容易在衰减点产生损失抖动,但实现简单,历史深远。
🔸 其他:Cosine Annealing with Warmup、Polynomial Decay等。
📊 Cosine与Linear的异同:
-
相同点:都从一个高点下降到一个低点,都能配合warmup。
-
不同点:下降速率不同。Cosine在中期下降更慢,给予模型更多时间在高学习率区域探索,这被认为是其泛化能力稍好的原因之一;Linear匀速下降,没有偏好性。实际效果上,两者差异不大,选择往往取决于团队经验或特定基线的设置。当前大模型训练(如GPT-3/LLaMA)常采用Cosine with Warmup或Linear with Warmup。
💡 经验:如果资源允许,可以尝试Cosine,它更具“探索精神”;Linear则更简单直接,不易出错。
📚 训练大模型时,Batch Size 如何影响训练稳定性和泛化能力?¶
Batch Size (BS) 是大模型训练中最关键的超参数之一,它直接影响梯度的质量、训练的稳定性和模型的最终能力。
📊 对训练稳定性的影响
-
小BS:梯度估计的方差大,噪声强。虽然这有助于模型跳出尖锐极小值,但方差过大可能导致训练震荡、损失曲线难以收敛,甚至不收敛。需要配合较小的学习率。
-
大BS:梯度估计更准确、方差小,训练曲线平滑,损失下降稳定。这使得我们可以使用更大的学习率(通常按BS比例线性缩放),加速训练。但是,过于准确的梯度会削弱梯度噪声带来的隐式正则化效果,导致模型更容易收敛到尖锐极小值,泛化能力下降。
🎯 对泛化能力的影响——“大batch陷阱”
经验规律是:在相同训练精度下,大BS训练的模型泛化能力往往更差。这被称为“大batch训练泛化差”现象。主要理论是:
-
大BS的低噪声使得优化器倾向于收敛到损失曲面上的尖锐极小值,这些点对参数微小的扰动敏感,测试时容易性能骤降。
-
小BS的高噪声帮助模型逃离这些尖锐点,最终停留在平坦极小值,这里损失变化平缓,泛化能力更强。 因此,在追求极限性能时,需要在训练速度和泛化能力之间做权衡。
🔧 实践策略:
-
线性缩放规则:当BS增加kk倍,学习率也增加kk倍,这在BN时代被证明近似有效,但需配合warmup。
-
精细化调参:超大批量(如32K)训练需要专门的算法(如LARS/LAMB)来稳定训练。
-
适度Batch:大多数大语言模型选择在1M~4M tokens的全局BS下训练,这被认为是稳定性和泛化的较好折中。
📉 解释“大 batch 训练泛化差”的一种理论(如平坦/尖锐极小值)。¶
大batch训练为什么会导致模型在新数据上表现变差?“平坦/尖锐极小值假说” 是最有影响力的理论之一。
🔍 什么是平坦/尖锐极小值?
损失函数在高维参数空间中形成的曲面,就像崎岖的山脉。极小值就是“山谷底”,代表着损失最低的点。这些山谷有两种典型形态:
-
平坦极小值:像一个宽阔平缓的盆地。即使参数在此基础上有一些扰动,损失值也不会大幅上升。
-
尖锐极小值:像一个深窄的洞穴。参数一旦偏离这个点,哪怕只是微小扰动(例如来自测试数据的不同分布),损失就会急剧飙升。
📊 小batch vs 大batch的导航差异
-
小batch:由于每次梯度更新带有很大的随机噪声,优化器的行走路径就像“醉汉散步”,充满了随机扰动。这种扰动使得模型很难落入尖锐的深洞,因为一靠近洞口,噪声就会把它“推”出来。相反,它更容易掉进宽阔的盆地(平坦极小值),即使扰动也只是在盆地里晃悠。
-
大batch:梯度估计非常准确,噪声极低。优化器就像一个“激光制导”,精准地沿着最陡峭的方向下降到离初始点最近的某个极小值。这个极小值往往是尖锐的,因为干净的梯度不会提供足够的推力让其越过小障碍去寻找更平坦的区域。
💡 泛化能力的体现
训练数据只是真实世界数据的一个样本,因此训练集和测试集的损失曲面并不完全重合,它们之间存在微小的偏移。一个在训练集上找到的平坦极小值,即使面对测试集的微小偏移,其损失依然能保持在低位(因为盆地够宽)。而尖锐极小值,稍有偏移就可能从洞底跳到洞壁,导致损失剧增,泛化性能骤降。
🛠️ 对抗大batch泛化差的策略:
-
增大学习率:在一定程度上能注入“噪声”,迫使优化器跳过尖锐解。
-
使用SGD而非Adam:SGD本身噪声就比Adam大,更有利于寻找平坦解(但收敛慢)。
-
Large Batch训练专用方法:如LARS/LAMB,它们对学习率进行层级自适应缩放,稳定大批量更新。
-
刻意加入噪声:梯度噪声、权重噪声等正则化手段。
💡 总结:平坦/尖锐极小值假说直观地解释了为什么“更准确”的优化(大batch)反而导致了“更脆弱”的模型。它是现代深度学习训练中指导超参数调整的核心理念之一。
. 什么是梯度裁剪?按值裁剪和按范数裁剪的区别。¶
梯度裁剪(Gradient Clipping)是训练深层网络,尤其是Transformer和大模型时,防止梯度爆炸的核心技术。它的逻辑很简单:在反向传播计算出所有参数的梯度后,如果某个梯度的大小超过了一个预设的阈值,就人为地把它“拉回”到阈值范围内,从而避免单步参数更新过于剧烈,导致损失飙升或溢出。
按值裁剪 (Clip by Value)
-
做法:遍历梯度张量中的每一个独立元素,如果它的值大于一个上界,就设为上界;小于一个下界,就设为下界。比如设定阈值为[-1.0, 1.0],所有大于1.0的梯度值强制改成1.0,所有小于-1.0的改成-1.0。
-
缺点:这会改变梯度的原始方向。假设一个参数的梯度向量是[0.8, 1.5],按值裁剪后变成[0.8, 1.0],向量的方向偏了。在深层网络中,方向信息往往比步长更关键,因此这种方法现在用得较少。
按范数裁剪 (Clip by Norm)
-
做法:先计算整个模型所有参数梯度的全局L2范数(即所有梯度值的平方和,再开根号)。如果这个全局范数超过了阈值,就按比例把每一个梯度值都缩小,使缩小后的全局范数恰好等于阈值。如果没超过,则什么都不做。
-
优势:它等比例地缩放整个梯度向量,完整保留了梯度的方向,只是限制了“步长”。这就像开车时发现超速,司机同时等比例地收油门和调整方向盘,行驶路线依然平滑。正因如此,按范数裁剪是当前训练Transformer和大模型的标准做法。
实践细节:通常阈值设为1.0,但需要根据模型大小、批次大小调整。训练时要监控梯度范数:正常时在0.1到10之间波动,如果突然跳到几百甚至上千,就说明出现了梯度爆炸,可能是学习率太大、数据有问题或者初始化不当。按范数裁剪就像一个“安全阀”,能防止这些异常情况直接摧毁训练。
训练过程中出现 NaN,可能的原因有哪些?如何排查?¶
训练中出现NaN,意味着模型参数或中间结果变成了非法数值,训练瞬间崩溃。排查需要系统性的方法,常见原因按概率从高到低排列如下。
可能的原因¶
-
学习率过大:这是最常见的元凶。学习率太高,单步更新量过大,权重值急剧膨胀,前向传播时激活值超出浮点数表示范围(FP16最大65504),反向时梯度变成Inf,Inf参与运算就产生NaN。
-
混合精度训练问题:FP16的范围很窄,很容易溢出。例如注意力机制中Q和K的点积值可能很大,softmax前没有正确缩放就会溢出。此外,FP16最小正规数约6e-8,更小的梯度会“下溢”变成0,虽然不直接产生NaN,但累积后可能造成优化器状态异常。
-
数据中有脏数据:文本中出现极端长度的token序列、图像损坏、音频爆音等。例如某条文本经过分词后产生了数万个token,远超模型最大长度,截断或填充不当可能引入异常值。
-
初始化不当:权重初始化方差太大或太小。深层网络中,若残差连接未配合恰当的归一化,信号可能逐层指数级放大,最终溢出。
-
损失函数计算错误:比如对log(0)操作,或自定义损失函数中存在除以零的风险。
-
除零操作或sqrt负数:在一些归一化层或自定义算子中可能出现。
如何排查¶
-
开启梯度监控:在训练循环中记录每一步的梯度范数。当NaN出现时,查看是哪个层的梯度最先变成NaN,这能直接锁定问题源。
-
前向传播插桩:在关键节点(注意力输出、FFN输出、loss计算前)插入数值检查
torch.isnan().any(),一旦发现NaN立刻打印当前步数和层名。 -
二分法排查数据:如果怀疑是数据问题,把当前批次的数据dump出来,用一个小模型单独过一遍,看是否复现NaN。也可以从检查点恢复,跳过当前批次继续训练。
-
降低学习率测试:将学习率减小10倍,观察是否还会出现NaN。如果消失,说明原学习率过大。
-
切换精度测试:如果使用FP16,尝试切换到BF16(动态范围与FP32相同,不易溢出)或FP32,看问题是否消失。这能判断是否是精度导致。
-
检查模型结构:确保所有层都有正确的初始化,残差连接、LayerNorm等位置无误。特别是自定义的模块。
大模型训练中,如何保存和恢复 Checkpoint?通常包含哪些状态?¶
大模型训练可能持续数周甚至数月,期间任何中断(硬件故障、网络抖动、任务抢占)都可能导致前功尽弃。因此,Checkpoint不仅是模型的存档,更是一个完整训练状态的快照,以确保能从断点精确恢复。
Checkpoint 应包含的完整状态¶
-
模型参数:最核心的部分,包括所有权重和偏置。如果使用了ZeRO等分布式策略,每个GPU通常只保存自己分片的那部分参数,恢复时再重新组装。
-
优化器状态:对AdamW而言,包括每个参数的一阶动量
m和二阶动量v(通常以FP32存储)。这部分体积是模型参数的两倍,但必须保存,因为优化器的内部状态决定了训练的动态,丢失则无法从断点继续优化。 -
学习率调度器状态:当前训练步数、当前学习率值、预热是否完成等。大模型通常使用复杂的余弦衰减等调度,若不恢复,学习率突然跳变可能导致训练不稳定。
-
数据迭代器状态:记录每个数据分片已经消费到的位置,以及全局随机种子。这样恢复后不会重复训练数据,也不会遗漏。
-
训练全局步数:已经完成的训练步数、消耗的token总数等元信息。
保存与恢复的最佳实践¶
-
定期自动保存:按步数间隔(如每1000步)或时间间隔(如每4小时)自动保存。同时保留最近3-5个checkpoint,防止某个checkpoint损坏。
-
分布式写入:每个GPU只写自己负责的那部分状态,利用并行写入加速,避免阻塞训练。可使用异步IO。
-
恢复验证:加载checkpoint后,用一个小批次数据跑一次前向和反向,对比保存前的loss和梯度范数,确保完全一致。
-
容灾备份:将checkpoint同时写入本地高速存储和远程对象存储,防止存储节点故障。
为什么大模型训练需要容错机制?常见的做法有哪些?¶
大模型训练通常需要成百上千张GPU连续运行数周,在如此长的时间窗口内,硬件故障是必然事件。GPU可能因为显存错误、过热、电源问题挂掉;InfiniBand网络可能链路中断;存储节点也可能出问题。如果没有容错机制,任何一个微小的硬件故障都会导致整个训练任务崩溃,浪费大量已投入的算力和时间。
常见的容错做法¶
-
基于Checkpoint的自动恢复:最基础的方案。训练框架定期保存checkpoint,监控进程发现节点故障后,自动终止当前任务,然后利用集群调度器(如Kubernetes或Slurm)重新申请健康节点,从最近的checkpoint恢复训练。这要求训练脚本支持断点续训。
-
弹性训练 (Elastic Training):允许训练过程中动态增减GPU数量。当某个节点故障时,框架自动剔除故障节点,利用剩余的GPU继续训练,而不是重启整个任务。如果有备用节点加入,还能自动扩展。DeepSpeed和PyTorch Elastic都支持这种模式。
-
慢节点检测与自动剔除:在大规模集群中,常有部分节点因硬件老化而计算速度明显偏慢,拖累整个集群。框架监控每张卡的迭代耗时,若某卡持续慢于平均水平,可将其上的工作负载迁移至其他卡,或主动将其从训练组中剔除。
-
网络冗余与通信容错:多机训练通常使用InfiniBand网络,配置多路径路由。当某条链路中断时,自动切换到备用路径。NCCL通信库内置了超时和重试机制,能在一定时间内恢复连接。
-
电源与散热冗余:数据中心层面的容错,如双路供电、N+1风扇冗余等,从物理环境上减少故障概率。
说明“先小模型验证,再大模型训练”的实验流程及其重要性。¶
大模型单次训练成本可达数百万美元,不允许任何未经验证的配置直接上线。因此,“小模型验证→大模型训练”是工业界铁律。这套流程通过在小规模模型上快速、低成本地迭代,筛选出可靠的超参数和架构设计,再迁移到全尺寸模型上,极大降低了试错风险和研发周期。
实验流程¶
- 第一阶段:微型模型快速验证
- 模型规模:参数量在几百K到几M。
- 数据:使用全量数据的极小随机子集(如1%)。
-
目标:跑通Pipeline。验证数据加载、前向传播、损失计算、反向传播、优化器更新等环节无bug。这个阶段应在几分钟内完成,一天能迭代几十次。
-
第二阶段:小模型消融实验
- 模型规模:目标模型的1/10到1/5。
- 数据:使用全量数据,但训练步数可能限制(如只训练几万步)。
-
目标:搜索核心超参数。找出最佳学习率、批次大小、模型深度/宽度、warmup步数等。同时验证新架构修改是否有效。这个阶段可在几小时内完成一次实验。
-
第三阶段:中等规模迁移验证
- 模型规模:介于小模型和全尺寸模型之间。
-
目标:验证从小模型上得到的最优超参数能否平滑迁移。通常会按照扩展律预测大模型性能,并在中等模型上实测确认。如果迁移性良好,则进入下一阶段;否则调整配置。
-
第四阶段:全量大规模训练
- 经过前三阶段充分验证,超参数已极度可靠。此时投入全量资源进行最终训练。即便如此,早期(如前1%步数)仍需密切监控loss、梯度范数等指标,一旦异常立即终止,避免后期巨大损失。
重要性¶
-
成本控制:全量训练可能花费百万美元,而微型模型验证仅需几十美元。
-
迭代速度:在大模型上做一次实验需要几周,在小模型上可能只要几小时,反馈周期缩短数十倍,研发效率天差地别。
-
风险规避:避免因一个简单的配置错误(如学习率设错)导致长达一个月的训练报废。
分布式训练中,全局批次大小 (Global Batch Size) 如何计算?¶
全局批次大小(GBS)指模型一次参数更新所消费的样本总数。在分布式训练中,数据被切分到多张GPU上,还可能配合梯度累积来模拟更大的批次。
计算公式¶
Global Batch Size=micro_batch_size×data_parallel_size×gradient_accumulation_steps
-
micro_batch_size:每张GPU单次前向能处理的样本数,受限于该卡的显存。
-
data_parallel_size:参与数据并行的GPU数量(通常为总GPU数,除非使用了流水线或张量并行拆分)。
-
gradient_accumulation_steps:梯度累积步数。即进行多少次前向计算(每次用不同的micro batch)后,才合并梯度更新参数。
举例¶
假设64张GPU,每张GPU一次只能处理2个样本(micro_batch_size=2),目标GBS为1024。
-
64张卡每步共处理 2 × 64 = 128 个样本。
-
需要累积 1024 ÷ 128 = 8 步,再执行一次优化器更新。
以Token数量计算¶
在LLM训练中,样本长度差异大,更常用Token数量来衡量GBS。公式为:

权衡¶
-
太大:吞吐提升,但可能损害泛化性能(“大批次退化”),需要相应调整学习率。
-
太小:梯度噪声大,训练不稳定,收敛慢。
什么是 Tokenizer,它在大模型训练中的地位和作用?¶
Tokenizer是将原始文本转换成模型可处理的数字序列(token IDs)的组件,也是模型生成文本后将数字还原为人读文字的解码器。它位于整个训练Pipeline的最前端,定义了模型的词汇表。
核心作用¶
-
定义词汇边界:将无限的自然语言文本映射到一个固定大小的词汇表(如32k、64k)。词汇表大小直接影响模型的嵌入层参数和计算效率。
-
平衡粒度与效率:好的Tokenizer能用尽可能短的token序列表示文本,从而减少自注意力层的计算量(复杂度O(n²))。同时,它需保留有意义的语义单元(如词干、词缀),避免过度碎片化。
-
支持多语言:对于中文、日文等无空格语言,Tokenizer需要能够自动发现词汇边界。现代Tokenizer(如BPE, SentencePiece)直接在字节/字符级操作,天然语言无关。
-
处理未登录词:通过子词分解,任何罕见词都能被拆分为已知子词片段,彻底消灭OOV问题。
训练过程(以BPE为例)¶
-
准备大规模、多样化的语料。
-
将所有文本拆成字符序列,并初始化词汇表为所有基础字符。
-
迭代统计相邻符号对的频率,合并最高频对,将其加入词汇表。
-
重复合并直到词汇表达到预设大小。
-
记录合并顺序,推理时用这些规则将新文本切分为子词。
重要地位¶
Tokenizer是模型的“器官”,一旦训练完成就固定下来,更换Tokenizer通常需要重新训练模型。一个低质量的Tokenizer会导致序列过长、信息碎片化、多语言支持差,严重制约模型性能。因此,Tokenizer的设计与训练需要与模型架构同等重视。
预训练任务(如自回归语言模型)的损失函数是什么?如何计算?¶
自回归语言模型(如GPT系列)的预训练任务是:给定前面的token序列,预测下一个token。
损失函数¶

为什么用交叉熵¶
它直接最大化训练数据中真实序列的似然,梯度形式简洁(预测概率减真实标签),且收敛稳定。
为什么 GPT 系列使用自回归方式,而 BERT 使用自编码方式?¶
这源于它们根本的预训练目标不同,从而决定了架构和应用场景的差异。
| 特性 | GPT(自回归) | BERT(自编码) |
|---|---|---|
| 预训练任务 | 下一个token预测(语言模型) | 掩码语言模型(MLM)+ 下一句预测(NSP) |
| 架构 | Decoder-only,单向(因果)注意力 | Encoder-only,双向注意力 |
| 上下文方向 | 仅看上文(左侧) | 同时看上下文(左右两侧) |
| 强项 | 文本生成、长序列续写、零样本任务 | 文本理解、分类、序列标注、提取式问答 |
| 代表模型 | GPT-2, GPT-3, GPT-4, LLaMA | BERT, RoBERTa, DeBERTa |
为什么这样选择¶
-
GPT选择自回归:因为它的设计初衷是成为一个强大的文本生成器。自回归方式(逐token生成)天然与人类写作过程一致,模型在训练时学到的“根据上文补全下文”的能力,可以直接用于推理生成。通过将不同任务统一成序列续写,一个模型可以处理翻译、问答、摘要等,展现出极强的通用性。
-
BERT选择自编码:它的设计初衷是成为一个强大的文本理解器。MLM任务通过“完形填空”迫使模型学习深层的双向上下文表示。双向注意力让每个token的表示融合了左右两侧的信息,这对需要精确理解句子含义的任务(如情感分析、命名实体识别)至关重要。
两者不能互换:GPT的双向受限使它不适合需要全局理解的任务(早期),BERT则无法直接生成文本。近年来的趋势是Decoder-only架构通过超大规模预训练和指令微调,在理解任务上也达到了极佳水平,且天然支持生成,因此逐渐成为主流。
什么是 Teacher Forcing?它在训练和推理时的差异会带来什么问题?¶
Teacher Forcing是训练自回归模型时的一种技巧:在每一步预测时,模型不是使用上一步自己的预测输出作为输入,而是直接使用真实的、来自训练数据的历史token(即“教师”)。这使训练过程变为:给定标准答案的前缀,让模型预测下一个词。
训练 vs 推理的差异¶
-
训练时:每个时间步的输入都是完美的ground truth。即使模型在某一步预测错了,它下一步仍然被“扶正”到正确路径上。因此误差不会累积,训练非常稳定。
-
推理时:模型完全依赖自己生成的token作为下一步的输入。一旦某个token预测不佳,这个错误就会作为输入“污染”后续步骤,导致误差不断累积,生成质量下降。
这种差异就是曝光偏差。模型在训练时从未见过自己“犯错”后的状态,因此在推理时面对自己的错误输入,容易“不知所措”,产生一连串的漂移、重复或逻辑混乱。
如何缓解¶
-
计划采样:在训练时,以一定的概率用模型自己生成的token替换ground truth,让模型逐渐适应推理时的状态。
-
强化学习:直接优化生成序列的评价指标(如BLEU、ROUGE),而不完全依赖交叉熵。
-
知识蒸馏:用大模型生成的多样化文本训练小模型,间接学习错误恢复能力。
尽管有曝光偏差,Teacher Forcing因其训练速度和稳定性,仍然是自回归模型训练的标准方法。
解释自回归模型训练时的“并行化”是如何实现的(通过因果掩码)。¶
自回归模型的核心限制是:预测第 tt 个token时,只能看到前 t−1 个token。理论上这要求串行计算,但训练时通过因果掩码(Causal Masking)实现了整个序列的并行计算。
因果掩码的机制¶
假设输入序列为“A B C D”,长度4。在自注意力的计算中:
-
计算所有位置的Query和Key。
-
构建一个上三角为负无穷的注意力掩码矩阵(形状为4×4):
- 在softmax之前,将这个掩码加到注意力分数矩阵上。被掩码的位置加上负无穷后,softmax输出趋近于0,相当于无法看到未来token。
训练时的并行化¶
-
整个句子“A B C D”一次性输入模型。
-
通过掩码,每个位置的注意力计算自动被限制在前文范围内:
- 位置1只能看“A”。
- 位置2能看“A B”。
- 位置3能看“A B C”。
-
位置4能看“A B C D”。
-
这样,一次前向传播就同时得到了所有位置的预测输出,可以与目标序列“B C D E”直接计算损失。
-
无需像RNN那样按时间步串行展开,训练效率得到极大提升。
为什么推理时不能并行¶
推理时,模型必须逐个生成token,因为生成第 t+1t+1 个token需要第 tt 个token的输出作为输入,而第 tt 个token尚未生成。因此推理是串行的,但KV缓存机制可以缓存历史键值对,避免重复计算。
训练数据中出现大量重复文本,对模型训练有什么影响?¶
训练数据中的重复文本是常见问题,主要来源包括:网页内容重复、模板化生成的文本、数据集清洗不彻底等。其影响显著且负面。
主要影响¶
-
过拟合与记忆化:模型会“记住”高频重复的段落,而不是学习泛化能力。在生成时,可能逐字逐句复现训练数据,出现“抄袭”现象,不仅损害创造性,还可能引发版权纠纷。
-
数据分布扭曲:重复样本在训练中被多次采样,频率被人为提高。模型会过度学习这些样本中的模式,低估其他模式的重要性,导致在非重复数据上的表现下降。
-
评估失真:如果验证/测试集中也存在与训练集重复的文本,评估结果会虚高,无法反映真实泛化能力。
-
训练效率降低:算力被浪费在学习已经学过的内容上,长尾概念难以被充分学习。
-
多轮对话与长文本性能下降:重复片段会破坏文本的连贯性和多样性,模型生成的对话可能单调乏味,缺乏信息量。
应对措施¶
-
去重:在数据预处理阶段,使用MinHash、SimHash等算法对文本进行近似去重。
-
采样策略:在训练时对数据集进行加权采样,降低高频样本的权重。
-
数据增强:对文本进行改写、同义词替换等,增加多样性。
-
监控:在训练过程中监控训练集和验证集上的损失差异,异常扩大可能提示过拟合。
什么是 Curriculum Learning?在大模型训练中有哪些应用?¶
Curriculum Learning(课程学习)是一种受人类学习启发的训练策略:先让模型学习简单的样本,再逐步增加难度,而不是一开始就将所有复杂数据随机打乱训练。
核心思想¶
模型在训练初期就像一个初学者,如果直接抛给它极其复杂的任务,它可能什么都学不会,或者学到错误的“捷径”。通过从易到难的课程安排,模型能先掌握基础模式,建立起稳固的内部表示,再去挑战更困难的数据,从而加速收敛并提升最终性能。
在大模型训练中的应用¶
-
预训练数据排序:将质量较高、结构简单的文本(如维基百科)放在训练初期,将质量参差不齐、噪声大的网页数据放在后期。一些研究表明,这种排序能提升下游任务性能。
-
序列长度课程:训练开始时使用较短的序列(如512),训练一段时间后再逐步扩展到2048、4096甚至更长。这有助于节省早期训练的计算量,并让模型先学会局部依赖,再处理长程依赖。
-
任务难度课程:在指令微调阶段,先使用简单的任务(如情感分类),再引入复杂的推理任务(如多跳问答、代码生成)。
-
多模态模型:先学习简单的图文匹配,再学习复杂的视觉推理和对话。
-
学习率课程:学习率预热(warmup)本身就是一种课程学习:从极小的学习率开始,逐渐增加到目标值,避免早期训练不稳定。
优势与挑战¶
课程学习可以提高训练效率,改善收敛性和泛化能力。但难点在于如何客观地定义“简单”和“困难”,以及如何设计课程的进阶速度。通常需要结合启发式规则和实验验证。
大模型训练前,如何估计所需的总训练步数和时间?¶
在启动一次大模型训练之前,精确估算总训练步数和时间对于资源规划、预算控制和实验周期管理至关重要。估算通常从“数据量”和“模型计算量”两个维度展开。
总训练步数的估算:
-
确定训练数据总量,通常以 token 数量为单位。例如,预训练数据共有 1T(一万亿)tokens。
-
确定全局批次大小(Global Batch Size, GBS),同样以 tokens 为单位。例如,GBS 设为 4M(四百万)tokens。
-
总训练步数 = 总 tokens / GBS。在上例中,1T / 4M = 250,000 步。如果用样本数计算,则需要考虑平均序列长度进行转换。
单步耗时的估算:
单步耗时主要由模型的计算量、硬件性能和并行策略共同决定。估算过程如下:
-
计算一次前向+反向传播所需的浮点运算次数 (FLOPs per step)。对于 Transformer,可近似为
6 × 参数量 × tokens_per_step(不含激活重计算等额外开销)。 -
评估集群的理论峰值算力。例如,一张 A100 (80GB) GPU 在半精度下的理论峰值算力为 312 TFLOPS (FP16/BF16 Tensor Core)。
-
考虑实际利用率 MFU (Model FLOPs Utilization)。大模型训练的 MFU 通常在 30%-60% 之间,取决于并行策略、通信效率和框架优化。
-
单步耗时 ≈ FLOPs_per_step / (单卡峰值算力 × GPU数量 × MFU)。
总训练时间的估算:
-
总训练时间 = 单步耗时 × 总训练步数。
-
这只是一个理想化的估算,实际训练中还需要加入以下时间开销:
- 初始化开销:模型加载、权重初始化、数据预处理。
- 通信开销:多机多卡下的梯度同步、参数更新(使用 ZeRO 或模型并行时更明显)。
- IO 开销:Checkpoint 保存与恢复、数据加载。
-
故障恢复:大规模集群的硬件故障不可避免,每次恢复都会带来时间损耗。
-
因此,实际训练时间通常比理论估算值高出 10%-30%。经验丰富的团队会在理论估算基础上乘以一个“工程系数”来制定计划。
为什么需要验证集?训练集损失下降但验证集上升说明什么?¶
验证集是从训练数据中剥离出来、模型在训练过程中“看不到”的数据子集。它的核心作用是对模型进行无偏评估,指导超参数选择和防止过拟合。
为什么需要验证集?
-
监控泛化能力:训练集上的损失只能反映模型对已见过数据的拟合程度,验证集上的损失反映的是模型对未知数据的预测能力,即泛化能力。
-
超参数调优:学习率、批次大小、模型深度等超参数的选择,不能仅依据训练集表现,需要看它们在验证集上的效果来做出决策。
-
早停机制:当验证集损失不再下降甚至开始上升时,继续训练只会导致过拟合,此时应该停止训练。
-
模型选择:在训练的不同阶段会保存多个 Checkpoint,最终选择验证集上表现最好的那个,而不是最后一个。
训练集损失下降但验证集上升说明什么?
这种现象是过拟合的典型标志。它意味着模型已经开始“死记硬背”训练数据中的噪声和细节,而不是学习其中的通用规律。模型的容量超过了数据所需的复杂度,或者训练时间过长。此时,模型在训练数据上表现得越来越好,但面对新数据时表现反而变差。
解决方法:
-
早停:最直接有效的方法,在验证集指标开始恶化时停止训练。
-
增加训练数据:获取更多、更多样的数据是解决过拟合的根本方法。
-
正则化:增大权重衰减系数 (Weight Decay),增加 Dropout 率,或使用标签平滑等技巧。
-
降低模型容量:减少模型参数量、层数或隐藏层维度。
如何监控训练过程?除了 Loss 还需要看哪些指标?¶
监控训练过程就像是给训练任务做“体检”,全面的指标体系可以提前发现潜在问题,避免训练崩溃或资源浪费。
必须监控的核心指标:
-
Loss 曲线:训练集和验证集的损失是基础。关注其下降趋势是否平滑,有无剧烈震荡、尖峰或长时间停滞。训练初期若 Loss 不降,通常是学习率过大或模型初始化、数据有问题。
-
梯度范数 (Gradient Norm):按范数计算所有参数梯度的 L2 范数。健康的训练中,梯度范数会稳定在一个合理范围。如果突然飙升,预示梯度爆炸;如果持续极小,可能是梯度消失或模型陷入平坦区域。
-
学习率 (Learning Rate):实时观察当前学习率,确保预热、衰减策略按预期执行。学习率曲线出现异常波动通常表示配置错误。
-
GPU 利用率和显存占用:高 GPU 利用率(>80%)说明计算资源得到有效利用。显存占用需要保持稳定,突然增加可能表示内存泄漏或计算图异常。
-
模型参数和更新的范数:监控权重本身的范数和每次更新的范数。如果更新范数远大于权重范数,说明学习率可能过高。
-
网络通信和 IO 吞吐:在分布式训练中,监控 InfiniBand/NVLink 带宽利用率、GPU 间通信耗时,以及数据加载的吞吐量,确保计算核心不被数据 I/O 阻塞。
多模态或特定任务特有指标:
-
在多模态训练中,需额外监控视觉/文本模态的梯度比率,防止某一模态主导训练。
-
对于扩散模型,需监控不同时间步的损失分布,以及 EMA 模型在验证集上的生成质量(如 FID)。
-
对于 VQA 或描述任务,可在训练中定期在固定子集上运行零样本评估,观察准确率或 CIDEr 等指标的变化趋势。
什么是“训练不稳定”的早期信号?(如梯度范数突变)¶
训练不稳定是深度学习中的常见问题,尤其在训练大模型时。如果能识别早期信号,就能在模型彻底崩溃(出现 NaN)前采取措施,节省数小时甚至数天的训练时间。
早期信号主要有:
-
Loss 尖峰:训练损失在某个 Step 突然剧烈升高,达到正常值的数倍甚至数十倍,随后又回落。这是梯度爆炸的直接体现,通常由异常数据或学习率与模型当前状态不匹配引起。
-
梯度范数突变:与 Loss 尖峰相伴,梯度范数会在某个 Step 远超历史平均水平。持续监控梯度范数,并设置一个动态阈值(如历史均值的 5-10 倍),可以在参数被破坏前发出警报。
-
激活值分布漂移:监控各层输出的均值和方差。如果发现深层的激活值均值逐渐偏离 0,或方差持续增大/减小,说明信号传播出现问题,可能是初始化或学习率不当。
-
权重范数突变:某一层的权重 L2 范数突然大幅增加,说明该层可能成为训练崩溃的“爆发点”。
-
优化器状态异常:对于 Adam,如果某个参数组的二阶动量 (v) 出现 NaN 或 Inf,预示着该组参数即将崩溃。
-
“峰值”后的连锁反应:一次 Loss 尖峰虽然可能自动恢复,但它往往会在优化器动量中留下“后遗症”,导致后续训练更加不稳定,可能在短时间内再次出现尖峰,最终引发 NaN。
应对措施:当检测到上述信号时,最佳实践是立即暂停训练,回滚到最近的健康 Checkpoint,诊断并排除故障(如剔除异常数据、降低学习率),然后从该点恢复训练。很多训练框架支持自动跳过导致 Loss 尖峰的 Batch。
为什么大模型训练前往往要做“小规模试探性训练”?¶
在大模型训练中,一次完整的训练周期可能长达数月,耗费数百万美元。任何微小的配置错误——比如一个错位的 LayerNorm、一个不合适的初始化,或数据预处理 Bug——都会让整个训练功亏一篑。因此,小规模试探性训练是必不可少的保险措施。
试探性训练的核心目的:
-
验证全流程正确性:在一个极小的模型和数据集上完整运行所有代码,确保数据加载、前向传播、损失计算、反向传播、优化器更新、Checkpoint 保存与恢复等所有步骤都没有错误。
-
检验训练稳定性:在小规模下观察 Loss 曲线、梯度范数等指标是否健康。小模型对学习率、初始化等超参数更加敏感,更容易暴露训练配置中的潜在不稳定性。如果小模型都无法稳定训练,大模型大概率会崩溃。
-
为超参数搜索提供初始范围:在小模型上进行学习率、批次大小等超参数的粗粒度搜索。由于Transformer模型在不同规模下的最优超参数之间存在经验性的迁移规律(如学习率通常与模型宽度成反比),小模型的较优配置可以为大模型提供一个极好的搜索起点,大幅减少大模型上的昂贵调参。
-
评估模型架构修改:在引入新的注意力机制、FFN结构或归一化方法时,先在小模型上验证其有效性,避免将未经证实的想法直接应用于大模型造成浪费。
解释“计算量 (FLOPs)”与“训练时间”的关系,如何估算训练所需 GPU 小时?¶
计算量(FLOPs)是衡量模型运算规模的静态指标,训练时间则是动态的耗时。二者通过硬件算力和利用率连接起来。
关系公式:
估算训练所需 GPU 小时的步骤:
-
计算单步计算量:对于 Transformer,一次前向+反向传播的 FLOPs 可近似为
6 × P × T,其中P是模型参数量,T是每步处理的 token 总数 (Global Batch Size in tokens)。这个近似公式在序列长度远大于模型维度时较为精确。 -
计算总计算量:
总计算量 = 单步FLOPs × 总训练步数。 -
估算硬件算力:以 NVIDIA A100 (80GB) 为例,其 FP16 Tensor Core 的峰值算力为 312 TFLOPS。多卡集群的总算力 = 312e12 FLOPS × GPU数量。
-
估算利用率 (MFU):大模型训练的 MFU 通常在 30%-60%。这受并行策略、通信带宽、数据加载等因素影响。
-
代入公式计算:
训练小时数 ≈ (总FLOPs) / (312e12 × GPU数量 × MFU) / 3600例如,一个65B模型,用1T tokens训练,总FLOPs约为6 × 65e9 × 1e12。在512张A100上,假设MFU=50%,则大致需要数十天。
训练一个 7B 模型,用 1T tokens,大概需要多少计算量?¶
计算量可以用经验公式 C ≈ 6 × N × D 估算,其中 N 是参数量,D 是训练 tokens 数。对于前向+反向传播,这是普遍接受的近似。
-
N= 7B = 7 × 10^9 -
D= 1T = 1 × 10^12 -
总计算量 C ≈ 6 × (7e9) × (1e12) = 4.2 × 10^22 FLOPs
因此,训练一个 7B 参数模型、使用 1T tokens 的数据,大致需要 4.2e22 次浮点运算。如果再估算时间,假设在 256 张 A100 上训练,理论峰值总算力为 256 × 312 TFLOPS ≈ 8e16 FLOPS。在 MFU=50% 的情况下,有效算力约 4e16 FLOPS,训练时间约为 (4.2e22) / (4e16) ≈ 1,000,000 秒 ≈ 278 小时 ≈ 11.6 天。
什么是 MFU (Model FLOPs Utilization)?如何提高 MFU?¶
MFU (Model FLOPs Utilization) 是衡量训练过程中 GPU 实际有效计算能力发挥程度的指标,定义为:
它反映了硬件计算资源被真正用于模型计算的比例,而非消耗在通信、等待、或低效计算上。MFU 越接近 100%,训练效率越高。
如何提高 MFU?
-
增大批次大小 (Batch Size):更大的批次可以提高 GPU 的并行度,使 Tensor Core 更充分地工作,减少单次计算启动的开销。但受限于显存,需要配合梯度累积。
-
优化并行策略:数据并行(DP)通信量较小但显存冗余,模型并行(MP)可突破单卡显存但通信量大。使用 3D 并行(DP+TP+PP)并结合 ZeRO 优化,可以找到计算与通信的最佳平衡点。
-
计算与通信重叠:利用 CUDA Stream 等技术,将梯度同步等通信操作与反向传播计算重叠,隐藏通信延迟。
-
选择高效的数据类型:使用 BF16/FP16 混合精度训练,充分利用 Tensor Core 的加速能力。与 FP32 相比,吞吐量可以翻倍。
-
使用优化的库和编译器:使用 FlashAttention 加速注意力计算,使用 PyTorch 2.0 的
torch.compile、xFormers、DeepSpeed 等框架,自动进行算子融合和图优化。 -
确保数据加载不成为瓶颈:使用高 I/O 带宽的存储,多线程预取数据,确保 GPU 总是有数据可算。
显存带宽和计算吞吐哪个更容易成为大模型训练的瓶颈?¶
对于大模型训练,显存带宽通常比计算吞吐更容易成为瓶颈,尤其是在计算注意力机制和进行大规模梯度通信时。
-
计算吞吐:现代 GPU (如 H100) 的计算能力非常强大,Tensor Core 提供了极高的 FLOPS。如果计算任务(如大矩阵乘法)能够充分利用 Tensor Core,计算本身的速度是很快的。
-
显存带宽:Attention 计算本质上是“内存密集型”操作。计算注意力矩阵时,需要对 Q、K、V 等大型张量进行反复的读取和写入,这些张量的大小随序列长度和批次大小急剧增加。每次运算都需要将数据从高带宽显存 (HBM) 搬入计算核心,如果数据搬运速度跟不上计算速度,计算单元就会“空转”等待数据。这就是所谓的“内存墙”。
因此,提高训练效率的关键往往在于减少显存访问次数和数据搬运量,这正是 FlashAttention 等技术的核心思想——通过算子融合和分块计算,将多次显存读写合并为一次,从而逼近计算吞吐的理论上限。
分布式训练中,如何确保模型参数在所有设备上初始化一致?¶
确保参数初始化一致是分布式训练正确收敛的前提。如果不同设备上的模型初始化不同,它们计算的梯度将基于不同的参数状态,梯度聚合就失去了意义,导致训练失败或性能严重下降。
实现方法:
-
相同的随机种子 (Random Seed):这是最简单也最关键的一步。在所有参与训练的 GPU 进程中,统一设置 Python、NumPy 和 PyTorch/CUDA 的随机种子。这样,每个进程在调用随机初始化函数时,会生成完全相同的随机数序列,从而得到完全相同的初始化权重。
-
广播主节点的参数 (Broadcasting):更保险的做法是,只在其中一个进程(通常是 Rank 0)上进行初始化,然后将该进程的模型参数通过
broadcast操作发送给其他所有进程。其他进程直接用接收到的参数覆盖自己的本地模型。主流分布式框架(如 PyTorch DDP)会在构建模型后自动处理同步。 -
从 Checkpoint 统一加载:如果是从预训练权重或之前的 Checkpoint 恢复训练,确保所有进程读取的 Checkpoint 文件是完全相同的,并且在加载后不进行任何随机的微调(如新增模块的随机初始化),需要将新增部分单独广播。
常用的参数初始化方法有哪些?Xavier 和 Kaiming 初始化原理。¶
Xavier 初始化 (Glorot 初始化)
-
原理:其目标是让每一层输出的方差和反向传播梯度的方差在整个网络中保持一致,防止信号在前向或反向传播中指数级衰减或增长。它假设激活函数是线性或 Tanh 这类关于原点对称的函数。
-
方法:权重从均值为 0、方差为
2 / (fan_in + fan_out)的分布(均匀或正态)中采样。fan_in是输入维度,fan_out是输出维度。这个方差综合考虑了前向和反向的尺度平衡。
Kaiming 初始化 (He 初始化)
-
原理:专门为 ReLU 及其变体设计。因为 ReLU 会将负半轴的输入置零,相当于每层“丢弃”了一半的神经元,导致输出方差减半。为了补偿这种方差缩减,Kaiming 初始化会相应增大权重方差。
-
方法:权重从均值为 0、方差为
2 / fan_in(前向模式)或2 / fan_out(反向模式)的分布中采样。通常使用前向模式 (2 / fan_in),实验证明它适用于多数情况。
在 Transformer 中的应用
-
注意力层的 QKV 投影矩阵:常用 Xavier 初始化或其变体。
-
FFN 层:第一层(升维)常用 Kaiming 初始化(配合 ReLU/GELU 激活),第二层(降维)常用 Xavier 初始化。
-
特殊初始化:一些大模型(如 GPT-3)会采用更小的初始化标准差(如 0.02),这是根据经验调整的结果,能提升深层 Transformer 训练的初期稳定性。
为什么 Transformer 的残差分支常采用小初始化或 0 初始化?¶
残差分支(即每个子层本身的输出通路)采用小初始化或 0 初始化,是为了在训练初期让模型表现为一个“恒等映射”,从而确保深层网络的信号和梯度能够稳定传播。
-
训练早期的稳定性:在训练刚开始时,注意力层和 FFN 层的权重是随机初始化的,它们的输出带有大量噪声。如果这些噪声不加约束地通过残差连接与主干信号相加,会迅速破坏主干中有用的嵌入信息,导致模型早期训练极不稳定甚至发散。
-
如何实现“恒等映射”:通过将残差分支的最后一层(如注意力输出投影矩阵、FFN 的第二层线性层)的权重初始化为 0,或者初始化为非常小的值,那么整个子层(例如一个完整的注意力块)的初始输出就几乎为 0。这样,
x + Sublayer(x) ≈ x,模型输出近似于输入,表现为恒等映射。 -
好处:随着训练的进行,这些被抑制的权重会逐渐学习到有意义的变换,模型平稳地从“复述”输入过渡到“改造”输入。这种技巧显著提高了深层 Transformer 的训练稳定性,降低了对学习率的敏感性,是现代大模型训练的标配。例如,LLaMA 在 FFN 的第二层和注意力输出投影上就采用了特殊的初始化缩放。
什么是 Meta-Device 初始化?在超大模型加载时有何用?¶
Meta-Device 初始化是一种在“纸上”或“元空间”中初始化超大模型的技术。它的核心思想是:在模型被真正加载到 GPU 显存之前,就在不消耗实际显存的情况下,完成模型结构的构建和参数张量的“占位”。
工作原理:
-
PyTorch 等框架允许在“meta”设备上创建张量。在 meta 设备上,张量只存储形状、数据类型等元信息,并不分配实际的显存或内存来存储数据。
-
整个超大模型可以先在 meta 设备上完成初始化,此时内存占用几乎为 0,速度极快。
-
随后,在真正需要使用模型时(如加载 Checkpoint 权重),框架再根据 meta 张量提供的信息,在目标设备(GPU)上分配真实显存,并将权重数据从磁盘填充进去。
在超大模型加载时的用处:
-
解决模型无法一次性加载的问题:一个几百 GB 的模型根本无法完整装入一张 GPU 卡甚至一个节点的内存。通过 Meta-Device 初始化,可以“画”出完整的模型蓝图,然后配合分布式加载策略(如 PyTorch 的
distributed.checkpoint或 DeepSpeed 的 ZeRO-3),将模型权重分片加载到多台机器的 GPU 上。 -
加速开发调试:在进行模型代码测试、结构分析或计算参数量时,无需等待漫长的模型加载和显存分配,在 meta 设备上瞬间即可完成模型的“实例化”,极大提高了开发迭代效率。
词表大小 (Vocabulary Size) 如何影响 Embedding 层的参数量和训练效果?¶
词表大小是语言模型最基础的超参数之一,它直接决定了输入嵌入层(Input Embedding)和输出投影层(Output Projection / LM Head)的规模。这两个矩阵的参数量均为 vocab_size × hidden_dim,因此词表大小对总参数量的贡献是线性的。
参数量计算示例¶
以 hidden_dim=4096 为例:
| 词表大小 | 嵌入参数量(单层) | 输入+输出总参数量 | 占65B模型的近似比例 |
|---|---|---|---|
| 32,000 | 131M | 262M | ≈ 0.4% |
| 50,000 | 205M | 410M | ≈ 0.6% |
| 100,000 | 410M | 820M | ≈ 1.3% |
| 256,000 | 1.05B | 2.1B | ≈ 3.2% |
虽然对于百亿甚至千亿参数的大模型,嵌入层参数占比看似不高,但在训练和推理时仍然会带来不可忽视的影响:
-
显存占用:嵌入矩阵通常以FP32或FP16存储,大词表会占用更多显存,尤其在分布式训练中,嵌入层可能需要特殊的并行策略(如词表并行)。
-
计算开销:输出层需要计算全词表的softmax,其复杂度为
O(batch_size × seq_len × vocab_size)。当词表很大时,softmax会成为计算瓶颈,通常需要采用Sampled Softmax或Hierarchical Softmax等近似方法。
对训练效果的影响¶
-
小词表(如8k-16k):每个token携带的信息量少,序列会被切分得更长,这增加了自注意力的计算量(复杂度与序列长度平方成正比)。同时,大量单词被拆分为子词甚至字符,语义被碎片化,模型可能难以学习到有意义的表示。
-
大词表(如64k-256k):能覆盖更多完整单词,序列长度缩短,计算效率更高;低频词也能获得独立的嵌入向量,有助于提升模型对罕见词汇的理解。但过大的词表会导致:
- 稀疏性:大量低频token的嵌入向量训练不充分,泛化能力差。
- 过拟合:模型可能“记住”某些罕见token的模式,而不是学习语言规律。
- 计算资源浪费:多数词表容量被极少使用的token占据,造成参数冗余。
最优词表大小的选择¶
实践中,32k~64k 是当前大模型(如LLaMA, GPT-3, PaLM)的常见选择。这一区间能在编码效率(序列长度)和计算成本之间取得良好平衡。对于多语言模型,往往需要更大的词表(如128k)以覆盖不同文字系统。
训练时如何设置 Epoch、Step、数据采样策略?¶
Epoch vs Step¶
-
Epoch:完整遍历一次训练数据。在数据量极大(TB级)的预训练中,通常不会完成一个Epoch,因此更多使用Step(优化器更新次数)来控制训练进度。
-
总训练步数:由目标总token数和全局批次大小决定。例如,计划训练1T tokens,全局批次大小为4M tokens,则总步数 = 1T / 4M = 250,000步。
数据采样策略¶
预训练数据通常来自多个异构数据源(网页、书籍、论文、代码等),质量与规模差异巨大。合理的采样策略能够显著提升模型性能:
(1) 加权采样 (Weighted Sampling)¶
-
根据经验或下游任务表现为每个数据源分配采样权重。
-
高质量数据源(如维基百科、书籍)权重更高,低质量但海量的数据源(如CommonCrawl)权重较低,避免模型被噪声淹没。
-
实际实现时,通过构造一个概率分布来从不同数据集中抽取样本,概率与权重成正比。
(2) 课程学习 (Curriculum Learning)¶
-
初始阶段:使用结构清晰、难度较低的数据(如维基百科、简短的新闻报道),帮助模型快速建立基础语言能力。
-
后续阶段:逐步引入更复杂、噪声更多的数据(如网页、论坛对话),甚至增加代码、数学等内容,扩展模型的能力边界。
-
这种平滑过渡能避免模型在初期陷入混乱,提升最终收敛质量。
(3) 动态调整 (Dynamic Resampling)¶
-
训练过程中,定期评估模型在不同数据源上的损失变化。
-
对损失下降缓慢的源增加采样频率,对损失已经极低的源适当降低频率,使训练资源聚焦于“难学”的数据。
(4) 确保每个Batch的多样性¶
-
即使在小批次内,也应混合多个数据源,而不是一个batch全是同类型数据。
-
这能稳定梯度更新方向,避免优化器被单一分布带偏。
说明 Dataloader 的 num_workers 和 pin_memory 对训练速度的影响。¶
num_workers¶
-
作用:
num_workers指定了用于数据预加载的子进程数量。当设为0时,主进程自己负责从磁盘读取和预处理数据,这会导致GPU在等待下一个batch时空闲。 -
多进程并行:多个worker并行执行I/O和解码、增强等CPU密集型操作,预先将处理好的数据放入队列,保证GPU始终有数据可算。
-
最佳实践:
- 并非越大越好。每个worker会复制主进程的内存空间,过多的worker会导致CPU内存占用激增,甚至引起上下文切换开销。
- 通常从
num_workers=4或8开始,逐步增加到CPU核心数/2左右,同时观察CPU利用率和数据加载延迟。一旦GPU不再等待数据,继续增加worker就无意义了。
pin_memory¶
-
作用:将数据从普通CPU内存复制到页锁定(Page-Locked)内存中。
-
原理:GPU通过DMA(直接内存访问)从CPU内存拷贝数据。如果内存是可分页的,DMA控制器需要先锁定页面,效率较低。而页锁定内存(pinned memory)避免了换页开销,GPU可以直接以最高带宽进行拷贝。
-
性能提升:通常可使数据传输速度提升 2~3 倍。
-
注意事项:
- 页锁定内存不可被操作系统换出,会占用物理内存。在数据量大或内存紧张的服务器上,需谨慎使用。
- 一般与
non_blocking=True配合使用,将数据拷贝到GPU的操作异步化,进一步隐藏延迟。
组合优化¶
dataloader = DataLoader(
dataset,
batch_size=32,
shuffle=True,
num_workers=8,
pin_memory=True,
prefetch_factor=4 # 每个worker预取的batch数
)
for batch in dataloader:
inputs, labels = batch
inputs = inputs.cuda(non_blocking=True)
labels = labels.cuda(non_blocking=True)
# 训练...
数据预处理(如 tokenization, masking)应该在线还是离线完成?¶
离线预处理(推荐于大规模预训练)¶
-
流程:在训练启动前,一次性将原始文本转换为token ID序列,并保存为二进制文件(如.npy, .arrow, .tfrecord)。
-
优势:
- 训练时仅需从磁盘读取数值序列,无需重复执行分词、拼接等CPU密集操作,极大释放训练过程中的计算资源。
-
支持随机访问,方便实现变长序列打包、跳过无效样本等高级特性。
-
劣势:
- 需要额外的存储空间(通常是原始数据的1/3~1/2)。
- 预处理逻辑一旦变更,需要重新生成数据,迭代不灵活。
在线预处理¶
-
流程:在每个训练Step中,实时对原始文本进行分词、截断等操作。
-
优势:
-
无需额外存储空间,适合快速实验,修改预处理逻辑后立即生效。
-
劣势:
- 严重依赖CPU资源,当GPU计算速度很快时,数据预处理极易成为瓶颈。
- 对于需要复杂预处理的任务(如BERT的动态掩码),CPU负载会很高,导致GPU利用率低下。
折中方案:半在线预处理¶
-
将最耗时的步骤(如HTML标签去除、文本清洗、分句)离线处理,保存为中间格式。
-
训练时仅执行轻量级的操作(如随机掩码、动态截断),兼顾灵活性和效率。
为什么训练大模型通常采用分布式文件系统存储数据?¶
核心原因¶
大模型训练需要数百甚至数千个GPU节点同时从存储系统读取训练数据。如果使用单机存储(如NFS),存储服务器很快就会成为I/O瓶颈,导致所有GPU等待数据,严重浪费算力。
分布式文件系统的优势¶
-
高聚合带宽:数据分布在多台存储服务器上,训练节点可以并行从多个存储节点读取数据。总带宽随着存储节点数线性扩展,轻松达到TB/s级别。
-
高并发、低延迟:支持数千个客户端同时发起请求,通过元数据服务优化文件定位,避免单点压力。
-
容错与冗余:数据通常有多副本或纠删码保护,单台存储服务器宕机不会导致数据丢失或训练中断。
-
POSIX兼容性:大多数分布式文件系统(如Lustre, GPFS, WekaFS)支持标准文件操作接口,应用程序无需修改代码即可像使用本地磁盘一样读写数据。
-
数据亲和性:可通过配置使训练节点优先从同一机架或网络交换域的存储节点读取数据,减少跨机架带宽占用,降低延迟。
常用系统¶
-
Lustre:高性能计算领域最流行的开源并行文件系统。
-
GPFS (IBM Spectrum Scale):企业级分布式文件系统,广泛应用于AI训练集群。
-
CephFS:可扩展的统一存储系统,支持对象、块和文件三种接口。
-
WekaFS:专为AI/ML优化的高性能并行文件系统,具有优秀的元数据处理能力。
数据格式建议¶
为充分发挥分布式文件系统性能,训练数据通常预先打包为大文件序列(如TFRecord, WebDataset tar包),避免海量小文件造成的元数据压力。每个训练节点读取自己的数据分片,实现完美的并行数据加载。
解释 PyTorch 中 DDP 的基本用法及 init_process_group 的作用。¶
DDP (DistributedDataParallel) 基本用法¶
DDP在每个GPU上启动一个独立的进程,每个进程持有模型的一个副本。通过集合通信(AllReduce)在反向传播后自动同步梯度,保证所有副本的参数一致。
典型代码结构:
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
# 1. 初始化进程组
dist.init_process_group(backend='nccl')
# 2. 获取当前进程的rank和local_rank
local_rank = int(os.environ['LOCAL_RANK'])
torch.cuda.set_device(local_rank)
# 3. 构造模型并移到对应GPU
model = MyModel().to(local_rank)
model = DDP(model, device_ids=[local_rank])
# 4. 使用DistributedSampler确保不同进程处理不同数据
dataset = MyDataset()
sampler = DistributedSampler(dataset)
dataloader = DataLoader(dataset, sampler=sampler, batch_size=...)
# 5. 训练循环
for epoch in range(num_epochs):
sampler.set_epoch(epoch) # 每epoch重新shuffle
for batch in dataloader:
outputs = model(batch)
loss = criterion(outputs, targets)
loss.backward()
# DDP会自动进行梯度AllReduce
optimizer.step()
optimizer.zero_grad()
init_process_group 的作用¶
init_process_group 是分布式训练的通信入口,它完成以下工作:
-
指定通信后端:
backend='nccl'表示使用NVIDIA NCCL库进行GPU间通信(最高效);backend='gloo'适用于CPU或GPU的通用通信。 -
建立进程间连接:根据环境变量
MASTER_ADDR,MASTER_PORT找到协调节点,所有进程向协调节点注册,形成通信组。 -
分配Ranks:每个进程获得唯一的全局rank(
WORLD_RANK)和本地rank(LOCAL_RANK),用于后续的通信和GPU绑定。 -
初始化拓扑感知:NCCL会自动探测GPU间的拓扑连接(NVLink, PCIe, InfiniBand),选择最优通信路径。
什么是“模型分桶”或“分阶段训练”?有什么好处?¶
分阶段训练是指在训练过程中,动态切换训练配置(如序列长度、学习率、数据分布等),将整个训练进程划分为若干个具有不同目标的阶段。
常见形式¶
(1) 序列长度分桶¶
-
阶段1:使用短序列(如256 tokens)训练基础语言模型。注意力计算量小,可以快速处理大量数据,建立局部语法和词法能力。
-
阶段2:提升至中等长度(如512或1024 tokens),扩大模型感受野。
-
阶段3:最终目标长度(如2048或4096 tokens),训练长程依赖能力。
好处:节省早期训练计算量;避免模型在初期被长序列的复杂依赖“难倒”。
(2) 学习率分阶段调度¶
-
预热期 (Warmup):学习率从极小值线性增长到峰值,防止初期梯度爆炸。
-
恒定期 (Constant):保持峰值学习率训练主要阶段。
-
衰减期 (Decay):逐步降低学习率,精细调整模型。
(3) 数据难度课程¶
-
早期:使用更干净、结构清晰的数据(如维基百科、书籍)。
-
后期:引入更多噪声、更广领域的数据(如全网爬虫数据、论坛对话),甚至加入代码、数学内容。
(4) 冻结-解冻策略¶
-
阶段1:冻结视觉编码器,仅训练跨模态连接器和投影层,实现快速对齐。
-
阶段2:解冻LLM主干,进行全模型微调,提升多模态推理能力。
优势总结¶
-
训练稳定:渐进式增加难度,避免模型崩溃。
-
资源高效:初期使用较低计算开销,快速迭代验证。
-
更好收敛:课程学习式的安排有助于模型找到更优的极小值。
为什么有些参数(如 bias、LayerNorm 的权重)不进行权重衰减?¶
权重衰减(Weight Decay)通过对损失函数添加L2正则项,引导参数在每次更新后向零方向收缩,其本质假设是参数应该尽可能小。这一假设对某些参数并不成立。
不适用的参数类型¶
-
偏置项 (bias):偏置的作用是平移激活函数的输出分布,使其更好地拟合数据。限制偏置为零会严重限制模型的表达能力。例如,对于ReLU神经元,若偏置为零且输入全为负,则该神经元将永久死亡。
-
LayerNorm 的缩放参数 (γ) 和偏移参数 (β):LN层的γ用于恢复归一化后的数据尺度,β用于恢复均值偏移。强制其衰减为零会破坏归一化的效果,导致每层输出被不恰当地缩放,阻碍梯度传播。
-
嵌入层 (Embedding):嵌入向量表示离散token的语义,其模长通常与词频等信息相关。对嵌入层施加权重衰减,特别是对低频词,会迫使其趋向于零,损害表示能力。
实践准则¶
所有主流框架(PyTorch的AdamW、HuggingFace的Trainer)和模型(BERT, GPT, LLaMA)的默认配置中,均将bias和LayerNorm参数设置为无权重衰减。这是经过大量实验验证的稳定实践,通常只需对权重矩阵(如全连接层、卷积层)应用衰减。
训练长序列时,如何平衡序列长度与 batch size?¶
长序列训练面临的核心矛盾是:序列长度增加 → 显存占用指数上升 → 每张GPU能容纳的样本数(micro batch size)下降 → 为达到目标全局批次大小,需要更多梯度累积步数 → 训练效率降低。
平衡策略¶
-
优先级排序:如果下游任务对长距离依赖有刚性需求(如长文档问答、代码理解),则序列长度是首要保证的指标。可以接受micro batch size很小,通过梯度累积和分布式扩展来弥补。
-
序列打包 (Sequence Packing):将多个短文本首尾相连打包成一个长序列,通过特殊的注意力掩码防止不同文本间互相干扰。这样既能填满序列长度,又能增加有效batch size,一举两得。
-
梯度累积与分布式扩展:当micro batch size因为长序列而无法增大时,通过增加梯度累积步数或使用更多GPU(数据并行)来维持目标全局批次大小。但累积步数过多会降低训练速度,需要在计算效率和稳定性间权衡。
-
显存优化技术:使用梯度检查点 (Gradient Checkpointing) 以时间换空间;使用ZeRO-3或模型并行进一步降低单卡显存占用,从而可以增大micro batch size或序列长度。
-
动态序列长度:训练前期使用较短序列,让模型快速学习基础模式;中后期再逐步增加序列长度,平滑地适应长程依赖。
解释“Gradient Noise Scale”及其在 batch size 选择中的应用。¶
定义¶
Gradient Noise Scale (GNS) 衡量的是随机梯度估计的噪声水平。它定义为参数梯度协方差矩阵的迹与真实梯度向量范数之比,可以近似为:
直观上,如果梯度噪声很大,需要更大的batch size来平滑噪声,获得更准确的下降方向。
在 batch size 选择中的应用¶
-
小批次 (< GNS):梯度噪声主导,每步更新方向随机性强。增大batch size能显著降低噪声,加速收敛,提高计算效率。
-
批次接近 GNS:继续增大batch size带来的梯度噪声降低效果边际递减,此时应转向提高学习率或改进优化器。
-
批次远大于 GNS (> GNS):进一步增大batch size几乎不再提升训练效率,纯粹浪费算力,且可能导致模型泛化能力下降。
实践应用¶
-
GNS会随训练阶段变化:早期噪声大,可适当用较大batch size;后期噪声减小,可适度降低batch size或提高学习率。
-
一些自适应批次调度策略通过在线估计GNS来动态调整batch size。
-
对于大模型预训练,由于算力昂贵,通常采用经验性的大batch(如2M~8M tokens),而不严格依赖GNS理论。但GNS提供了一种解释框架,帮助理解不同batch size下训练行为的差异。
什么是“Zero Redundancy Optimizer”的基本思想?¶
ZeRO (Zero Redundancy Optimizer) 是DeepSpeed框架提出的显存优化技术,旨在消除数据并行训练中的冗余状态存储。
数据并行的冗余问题¶
在标准数据并行中,每张GPU都保存一份完整的:
-
模型参数 (Parameters)
-
梯度 (Gradients)
-
优化器状态 (Optimizer States,如Adam的m和v)
这些数据在所有GPU上完全相同,造成了巨大的显存浪费。ZeRO的核心思想是将这些状态分片 (Partition) 到所有GPU上,每张卡只持有其中的一部分。
ZeRO 的三个优化阶段¶
| 阶段 | 分片对象 | 单卡显存削减 | 额外通信量 |
|---|---|---|---|
| ZeRO-1 | 优化器状态 | 约4× | 很小(与标准DP相当) |
| ZeRO-2 | 优化器状态 + 梯度 | 约8× | 稍增 |
| ZeRO-3 | 优化器状态 + 梯度 + 模型参数 | 线性均分 | 显著增加 |
-
ZeRO-1:将优化器状态切分。每张卡只维护1/N的优化器状态,负责更新对应分片的参数。更新后通过AllGather收集完整参数。
-
ZeRO-2:进一步切分梯度。反向传播时,每张卡只保留自己负责参数部分的梯度,减少显存和通信。
-
ZeRO-3:更进一步切分模型参数本身。每张卡在前向/反向需要某层参数时,通过AllGather从其他卡收集,用完后立即释放。这使得千亿参数模型的训练成为可能,因为单卡不再需要存储整个模型。
ZeRO-Offload¶
在ZeRO-3基础上,可将优化器状态甚至模型参数卸载到CPU内存,利用CPU进行计算,进一步打破GPU显存墙,同时以PCIe带宽换取极端规模下的可行性。
训练大模型时,如何保证可复现性?随机种子的设置有哪些注意事项?¶
确定性随机性的设置¶
要保证两次训练得到完全相同的结果,必须严格控制所有随机性来源:
import random
import numpy as np
import torch
SEED = 42
# 1. Python & Numpy
random.seed(SEED)
np.random.seed(SEED)
# 2. PyTorch CPU & GPU
torch.manual_seed(SEED)
torch.cuda.manual_seed(SEED)
torch.cuda.manual_seed_all(SEED) # 多GPU时
# 3. cuDNN 确定性
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False # 关闭自动算法搜索
# 4. DataLoader worker 种子
def worker_init_fn(worker_id):
worker_seed = torch.initial_seed() % 2**32
np.random.seed(worker_seed)
random.seed(worker_seed)
dataloader = DataLoader(..., num_workers=8, worker_init_fn=worker_init_fn)
# 5. 分布式 Sampler 的 shuffle
sampler = DistributedSampler(dataset, seed=SEED)
注意事项¶
-
浮点数非结合性:由于GPU并行计算的非确定性顺序(如归约操作),即使所有随机种子相同,不同硬件或不同CUDA版本也可能导致微小差异(通常 < 1e-7)。完全bit-wise的复现几乎不可能,尤其在大规模分布式环境下。
-
Dropout / 随机深度:这些层的随机性也受随机种子控制,种子固定后行为一致。
-
优化器状态初始化:Adam的m和v初始为0,不受种子影响;但若使用带噪的优化器(如NoisyAdam),则需种子。
-
数据增强:所有涉及随机性的增强(如图像随机裁剪、文本随机删除词)都需要由统一的种子控制。
实践建议¶
追求统计可复现性(多次训练指标在可接受范围内波动)而非绝对比特级复现。当需要调试或对比消融实验时,固定种子是必须的;在最终大模型训练时,可以不固定数据加载种子以获得更好的数据多样性。
为什么使用 BF16 后一般不需要 Loss Scaling?¶
FP16 的问题:动态范围狭窄¶
FP16 使用 1 bit 符号、5 bit 指数、10 bit 尾数,能表示的数值范围约为 [6.0e-8, 65504]。训练中很多梯度值远小于 6.0e-8,在FP16下直接被截断为0,这就是梯度下溢。Loss Scaling 通过将损失值放大(例如乘以1024),使得反向传播的梯度相应放大,从而将小梯度值“推入”FP16的可表示范围,更新前再除回去。
BF16 的优势:动态范围与FP32一致¶
BF16 (Brain Float 16) 采用 1 bit 符号、8 bit 指数、7 bit 尾数。其指数位数与FP32完全相同,因此:
-
最大表示值约
3.4e38,与FP32相同,永不溢出。 -
最小正规数约
1.2e-38,远小于训练中实际梯度的量级,永不发生梯度下溢。 -
动态范围的完全匹配使得BF16下无需对损失做任何缩放。
额外优势¶
-
BF16 与 FP32 之间的转换非常高效:只需截断或填充尾数,指数部分无需调整。
-
硬件支持广泛:NVIDIA A100/H100、AMD MI250、Google TPU等均提供BF16的硬件加速,性能与FP16相当甚至更优。
因此,使用BF16可以彻底摆脱Loss Scaling的复杂性和潜在精度损失,成为当前大模型训练的首选半精度格式。