训练
整理各种面试中常见的八股问题,涵盖 Python 基础、PyTorch 基础、算法与数据结构等内容
分布式训练
参数量计算
-
符号定义
- b:batch_size(批次大小)
- s:seq_length(文本序列长度)
- h:hidden_dim(隐藏层维度)
- a:多头注意力的头数
- ha:hidden_dim_per_head(每个注意力头的隐藏层维度),实际使用中满足 ha∗a=h
- vocab_size:词表大小
- L:Transformer 总层数
-
MHA 层参数量计算
计算项 权重维度 参数量 说明 WQ 查询向量投影矩阵 [h,h] h^2 输入投影到 Q 空间 WK 键向量投影矩阵 [h,h] h^2 输入投影到 K 空间 WV 值向量投影矩阵 [h,h] h^2 输入投影到 V 空间 Wo 输出投影矩阵 [h,h] h^2 注意力结果融合输出 LayerNorm 缩放参数 [h] h 层归一化缩放系数 LayerNorm 偏移参数 [h] h 层归一化偏移系数 -
FFN 层参数量计算
计算项 权重维度 参数量 说明 Wff1 第一层全连接权重 [h,4h] 4h^2 隐层升维投影 bff1 第一层偏置 [4h] 4h 升维层偏置 Wff2 第二层全连接权重 [4h,h] 4h^2 隐层降维投影 bff2 第二层偏置 [h] h 降维层偏置 LayerNorm 缩放参数 [h] h 层归一化缩放系数 LayerNorm 偏移参数 [h] h 层归一化偏移系数 -
单 Transformer 层总参数量 —— 将 MHA 和 FFN 层参数量相加:
-
若有 L 层 Transformer,则 L 层总参数量为:
-
模型中除 Transformer 层外,还包含 embedding 层和解码层,需单独计算:
- embedding 层:将 token ID 映射为隐层向量
- 权重维度:
- 参数量:
- 解码层(语言模型头):将隐层向量映射回词表概率
- 权重维度:
- 参数量:
- 注:解码层通常与 embedding 层权重共享,实际仅计算一次
- embedding 层:将 token ID 映射为隐层向量
-
整个模型总参数量 —— 结合 Transformer 层、embedding 层(权重共享):
-
当模型层数 L 较大、隐层维度 h 较大时, 占绝对主导,可简化估算为:
-
核心规律:
- Transformer 模型参数量与隐层维度 h 成平方关系,与层数 L 成线性关系
- 注意力层和 FFN 层是参数量主要来源,LayerNorm 等小参数可忽略
- 词表大小 vocab_size 对总参数量影响远小于隐层维度 h
激活值计算
-
符号定义
- b:batch_size(批次大小)
- s:seq_length(文本序列长度)
- h:hidden_dim(隐藏层维度)
- a:多头注意力的头数
- ha:hidden_dim_per_head(每个注意力头的隐藏层维度),实际使用中满足 ha∗a=h
-
MHA 层激活值及显存占用
计算项 维度 显存大小(字节) [b,a,s,ha]=[b,s,h] 2bsh [b,a,s,ha]=[b,s,h] 2bsh [b,a,s,ha]=[b,s,h] 2bsh [b,a,s,s] 2bas^2 [b,a,s,s] 2bas^2 [b,a,s,s] bas^2 [b, a, s, ha] = [b, s, h] 2bsh [b,s,h] 2bsh [b,s,h] bsh [b, s, h] 2bsh -
FFN 层激活值及显存占用
计算项 维度 显存大小(字节) [b,s,4h] 8bsh [b,s,4h] 8bsh [b,s,h] 2bsh [b,s,h] bsh [b,s,h] 2bsh -
单 Transformer 层总显存 —— 将 MHA 和 FFN 层显存相加:
-
若有 L 层 Transformer,则 L 层总中间激活值显存为:
-
模型中除 Transformer 层外,还包含 embedding 层和解码层,其中解码层无中间激活值,仅需分析 embedding 层:
- embedding 层功能:将输入的 token ID 转为向量
- 输出矩阵维度:
- 显存占用: 字节
-
整个模型的中间激活值总显存 —— 结合 Transformer 层和 Embedding 层,整个模型的中间激活值显存为:
-
当模型层数 L 较大时,可忽略 embedding 层的 ,直接用 估算
-
对于短序列()而言,,FFN / QKV activation 占主导
-
对于长序列()而言,,Attention softmax 成为绝对瓶颈,正是 Activation Recomputation、Long Context、FlashAttention、Linear Attention 等这些工作的动机来源
MFU 计算
-
基本配置
GPU: 4×4090 24GB 数据并行 (DP): 4 流水线并行 (PP): 1 张量并行 (TP): 1 优化器: Distributed Optimizer DDP 混合精度: bfloat16(AMP enabled) Layers: 24 Hidden size: 1024 Heads: 16 Intermediate size (FFN): 4096 Sequence length: 1024 Batch Size: 48 gradient_accumulation_steps: 2 DP: 4 -
全局批次 = 单卡批次 × DP 数 × 梯度累积步数 = 48 × 4 × 2 = 384
-
对于 m×k 和 k×n 的矩阵,其浮点运算数 FLOPs 为
- 每个结果矩阵的元素都要做 k 次乘法,k-1 次加法,即每个元素都需要做 2k-1 次 FLOPs
- 整个矩阵为 m×n×(2k-1),即约为 2×m×k×n
-
模型 FLOPS 计算
- 单层 Transformer 模型
- 多头注意力机制(b 为 batch size,s 为 sequence length,h 为 hidden size,a 为 attention heads,d 为 h/a)
- Q、K、V 三个线性层,共计
- 输出投影线性层,共计
- Q@K、Attn@V,共计
- 因此共计
- FFN 层(线性层 ->GELU-> 线性层):
- 单层 Transformer 的 FLOPS 为
- 多头注意力机制(b 为 batch size,s 为 sequence length,h 为 hidden size,a 为 attention heads,d 为 h/a)
- 前向计算的 FLOPS 共计 (当 s 远小于 h 时,有 )
- 反向传播的 FLOPS 一般为前向的两倍,也即总 FLOPS 为
- 模型的总 FLOPS 为 103.9 TFLOPS(1 TFLOPS = 10^12 FLOPS,1 GFLOPS = 10^9 FLOPS)
- 以上计算得到的是单卡单步总 FLOPS,然后再乘以梯度累积步数和 DP 并行度,总 FLOPS 为 831.23 TFLOPS
- 根据吞吐率为 0.27 steps/s,则每秒的 FLOPS 为 224.432 TFLOPS
- 单层 Transformer 模型
-
理论 FLOPS 计算
- 单卡 4090,bfloat16 Tensor Core 理论峰值:≈ 165 TFLOPs
- 4 张 GPU 总峰值:660 TFLOPs
-
MFU 为
DP
-
小模型为什么不能用更大的 batch size?
- 泛化性能
- 小 batch:梯度噪声强 → 易跳出局部最优 → 收敛到平坦极小值(泛化好)
- 大 batch:更新平滑 → 易陷入尖锐极小值(泛化差)
- 大模型参数多,需要大量样本平滑梯度
- 小模型容量小,batch 太大反而不收敛 / 泛化差 / 容易过拟合训练分布
- 梯度方差效应:
- batch↑ → 梯度噪声↓ → 方差↓ → 更新过稳
- 小模型容量小、表达力弱,依赖梯度噪声的隐式正则防过拟合
- 学习率原因
- 大 batch 需线性放大学习率(batch×k → lr×k)
- 小模型参数敏感,高 lr 易震荡 / 发散,无法支撑超大 batch
- 泛化性能
-
数据并行中如何逐步优化通信的?
- 梯度同步与反向传播重叠、梯度分桶、no_sync
- 梯度同步与反向传播重叠:为每个参数注册 backward hook 来实现,当某个参数的梯度刚刚计算完成,就立刻触发一次 all-reduce
- 梯度分桶:每个参数都触发 all-reduce 存在一个问题 —— 每次 all-reduce 都有固定的启动开销,而且频繁的小张量通信,kernel launch 多、NCCL 调度频繁、网络带宽利用率低,导致通信被碎片化;因此可以预先定义若干个 bucket,将参数映射到指定的桶中,当桶中的所有参数的梯度都计算完成后,立即触发一次 all-reduce,这样就变成了 bucket 粒度的 overlap,这样可以减少 kernel launch 次数,提高带宽利用率
- no_sync:实际上,只需要在梯度累积的最后一步才需要更新参数,因此,通过 no_sync,在前 K−1 次 backward 不做梯度同步,第 K 次 backward 正常 all-reduce
- 你的代码里如何实现 no_sync 的?
- 类似地,在类里实现一个 no_sync 的 ContextManger,当进入这个上下文时,last_gradient_accum_step 置为 false,此时 hook 函数中就不会减少 bucket 的 pending 计数
- 梯度分桶为什么能加速?
- 减少通信开销:通信次数大幅减少,固定启动开销被平摊,有效带宽利用率显著提升
- 最大化计算 - 通信重叠
- 适配 Ring-AllReuce:Ring-AllReduce 对大消息效率更高(带宽饱和),对小消息效率极低(延迟主导),而分桶让每次通信都接近带宽瓶颈,而非延迟瓶颈
- bucket 太大 / 太小有什么影响?
- 默认值 25MB:PyTorch 官方经验值,在 ResNet、BERT 等主流模型上表现均衡
- bucket 太小
- 通信次数爆炸:大量小桶、频繁 AllReduce,启动开销占比飙升,带宽利用率暴跌
- 重叠收益下降:桶太小,通信启动频繁但单次通信时间短,计算 - 通信重叠的时间窗口被压缩
- bucket 太大
- 通信启动延迟:必须等待更多梯度计算完成才能填满桶,通信启动被推迟,计算 - 通信重叠的时机变差
- 单桶通信时间过长:超大桶的 AllReduce 耗时过长,可能阻塞后续桶的调度,反而拉长总通信时间
- 如何调优
- 高带宽环境(如 InfiniBand、NVLink):可适当调大(50–100MB),提升带宽利用率
- 低带宽环境(如 Ethernet):保持默认或略小,避免单桶通信耗时过长
- 模型结构:大模型(如 LLM)、小参数多的模型,分桶收益更明显;大参数为主的模型,分桶影响相对较小
- 梯度分桶会截断参数吗?
- 在模型初始化时就完成了参数→桶的静态映射,而参数是最小分配单元
- 每个参数注册一个钩子,当梯度就绪时,只向它所属的桶汇报就绪
- 桶内梯度会被展平(flatten)成连续张量做通信,结束后再还原(unflatten)回原参数
- 跨桶拆分 = 破坏原子性:一个参数的梯度被拆到两个桶,会导致:
- 两个桶都无法 “满就绪”,AllReduce 永远不触发
- 或错误触发,导致梯度数据错乱、部分未同步
- 如果大量参数都大于桶大小,会导致每个大参数单独一个桶,通信次数变多,启动开销变高,速度变慢
- 梯度同步与反向传播重叠、梯度分桶、no_sync
-
All-Reduce 的底层实现
- Ring All-Reduce(NVLink、RoCE)
- 把所有卡串成一个环:卡 0 ↔ 卡 1 ↔ 卡 2 ↔ 卡 3 ↔ 卡 0
- Scatter-Reduce(分散规约):每块卡只发送一部分数据给下一张卡,一边传一边加,最终每块卡得到总和的一部分
- All-Gather(全局收集):每块卡把自己持有的那部分总和,再沿着环传给别人,最终所有卡拼出完整总和
- ReduceScatter
- 数据切成 N 片,沿着 Ring 传递、累加
- 最终 GPU0 拿到第 0 片的总和,GPU1 拿到第 1 片的总和,…
- AllGather
- 每块卡持有不同分片,沿着 Ring 依次发送分片,最终所有卡都得到完整张量
- Ring All-Reduce(NVLink、RoCE)
-
DDP 在大规模情况下的问题
- Ring all-reduce 延迟问题
- Ring AllReduce 完成一次全局归约需要 2 (N-1) 轮通信(Scatter-Reduce + All-Gather)
- 单轮通信时间:
t_hop = chunk_size / bandwidth + latency - 总通信时间:
T = 2*(N-1) * t_hop - N(GPU 数)越大,通信轮数线性增长,总延迟线性上升
- 例如,单机 8 卡:14 轮;1024 卡:2046 轮 → 延迟放大 146 倍
- 可以看出,在大规模场景下,存在跨机多跳延迟、小张量通信延迟占主导、最慢的节点会拖累全局
- 通信与计算重叠开始失效
- DDP 重叠的前提是反向计算流(backward_stream)与通信流(comm_stream)异步执行,且桶填满时后续仍有大量计算可做
- 重叠成立的充要条件:通信启动时间 ≤ 反向传播总时间 − 单桶通信时间
- 通信无法重叠的场景
- 模型的最后几层的通信过程无法重叠
- 桶太大,导致通信启动被推迟,错过最佳重叠窗口
- 模型参数分布极端不均(大参数穿插小参数)
- find_unused_parameters=True
- 大规模多机场景,为什么 DDP 通信叠加后长尾延迟爆炸?
- 强同步放大:AllReduce 必须等所有 rank 完成 → 一个慢节点拖慢全局
- 异构性累积:多机下网络、CPU、GPU、PCIe 差异被 N 倍放大
- Ring all-reduce 延迟问题
-
NVLink / PCIe / 网卡瓶颈分别是什么?
- NVLink
- H100 单卡 NVLink 总带宽~900GB/s,但受拓扑限制(非全互联)
- 跨 NVSwitch 开销:多机箱通过 NVSwitch 互联 → 增加跳数与延迟
- PCIe
- 带宽低:PCIe 5.0 x16 仅~64GB/s,是 NVLink 的 1/14
- 必须中转:GPU 间通信需经 CPU 内存 → 两次 DMA 拷贝,延迟 20–50μs
- 网卡 / 网络瓶颈(跨机)
- 带宽不足:100Gbps IB 理论 12.5GB/s,实际~10GB/s,远低于 NVLink
- 延迟高:RDMA 延迟 10–100μs,是 NVLink 的 10–50 倍
- NVLink
-
Ring AllReduce 的原理
- All-Reduce 由两个阶段组成
- Reduce-Scatter:各节点分片累加,每个 GPU 最终持有唯一一份全局累加后的分片
- All-Gather:把各自累加好的分片广播同步到所有节点,全员拿到完整全局张量
- Ring AllReduce 的步骤
- 假设 N 张 GPU 构成单向逻辑环:G0→G1→⋯→GN−1→G0
- 输入梯度均等切分为 N 个 chunk
- 阶段 1:Reduce-Scatter(共 N−1 轮),每一轮:
- 每个 GPU 向下一环发送自己的一个 chunk
- 从上一环接收一个 chunk,并本地累加
- 跑完 N−1 轮:每张 GPU 刚好拥有 1 个完全聚合完毕的 unique chunk
- 阶段 2:All-Gather(再 N−1 轮)
- 继续流水线转发已累加完成的分片,再传 N−1 轮
- 最终,所有 GPU 集齐全部 N 个 chunk,得到完整全局平均 / 求和梯度
- 总通信步数:2 (N−1) 轮点对点收发
- All-Reduce 由两个阶段组成
-
Ring AllReduce 相比 Tree AllReduce 优势和劣势?什么数据量下自动切换?
- Ring AllReduce 的每个节点收发负载完全对称,大规模带宽饱和效果好
- 在大规模场景下,带宽开销主导,Ring 流水线吞吐更高
- 小消息(一般 < 256KB ~ 1MB):切 Tree/Star AllReduce,规避 Ring 线性 hop 延迟,压低 latency
- 大消息( > 1MB,梯度大桶):切 Ring AllReduce,打满带宽、流水线掩盖传输开销
-
Ring AllReduce 的 latency & bandwidth 理论计算公式?
-
符号定义
- :单条点对点通信启动时延 (latency,s)
- :单位数据传输反比, 单链路带宽 (s/Byte)
- :总数据量 (Byte)
- :通信进程 / GPU 数量
-
Ring AllReduce 理论总时间
-
每轮搬运数据量:
-
总时延 = 启动时延总和 + 数据传输时延总和
-
化简近似(N 大):
-
时延项:随节点数线性涨
-
带宽项:和节点数无关,只和总数据成正比(核心优势)
-
-
Tree AllReduce 理论总时间
- 时延项:对数增长,小 N / 小包碾压 Ring
- 带宽项:和 Ring 一致,但树根瓶颈现实会更差
-
-
DDP 每张卡存完整参数副本,三类张量(参数 / 梯度 / 优化器)各自显存占比大概多少?
- 符号定义
- M:模型总参数量
- FP32:每个参数 4 字节
- FP16/BF16:每个参数 2 字节
- 模型参数:2M
- 梯度:2M(如果以 FP32 进行梯度累加,还需要再维护 4M)
- 优化器状态:4M 主参数副本,4M 动量,4M 方差,总共 12M
- 符号定义
ZeRO
-
DeepSpeed ZeRO 的核心思想 —— 减少冗余副本
- 每张 GPU 都留存完整副本 —— 完整参数、完整梯度、完整 Adam 优化器状态,多卡之间大量内存冗余,卡数越多冗余越爆炸
- ZeRO 的核心思想是按张量类型分级分片存储,消除全局冗余
- 把「优化器状态→梯度→模型参数」依次分散到所有 GPU,每张卡只存 1/N,仅计算、通信时按需汇聚,极大削减显存;同时分层控制通信开销,平衡显存 & 吞吐
-
符号定义
- M:模型总参数量
- N:全局训练 GPU 卡数
- 模型权重 / 梯度:FP16/BF16 → 2 Byte/element
- Adam 主权重、动量 m、方差 v:FP32 → 4 Byte/element
- 权重:2M
- 梯度:2M(如果以 FP32 进行梯度累加,还需要再维护 4M)
- Adam (m/v/master):4M,4M,4M
-
ZeRO-1
- 只切分 Adam 优化器状态(m、v、fp32 master weight),仍然保留完整的模型权重和完整梯度
- 执行的通信操作
- 更新阶段:各卡局部梯度 → AllReduce 全局梯度规约(和 DDP 一致)
- 优化器步进:在分片所属的 GPU 局部完成 FP32 参数更新
- All-Gather 同步 FP16 权重,所有 GPU 收集更新后的分片 → 合成完整 FP16 权重
- 通信量计算
- All-Reduce:梯度 2×2M = 4M
- All-Gather:FP16 参数 2M
- 显存计算
- 权重:2M
- 梯度:2M(如果以 FP32 进行梯度累加,还需要再维护 4M)
- Adam (m/v/master):12M/N
- 单卡总显存变为 4M+12M/N
-
为什么优化器分片更新时,只需要本地对应分片梯度?数学原理是什么?
-
根据 AdamW 基础更新公式(逐参数粒度),设某一个标量参数为 w
-
全局平均梯度:
-
grank:每张 GPU 本地产生的该参数梯度
-
Adam 一阶动量 / 二阶动量(逐元素):
-
参数步进更新:
-
每一个参数 w 的更新,只依赖自己对应的那一个全局平均梯度 ,不同参数之间解耦、互不干扰
-
把全局模型参数按区间切分为 N 份:
-
GPU k 仅托管分片 Wk 常驻该分片的
- FP32 master weight Wk,fp32
- Adam 动量 、二阶方差
-
由上面逐元素独立原理更新 ,不需要其他分片的参数、不需要全局完整梯度,只需要 对应的全局聚合梯度分片
-
-
ZeRO-2
- 在 ZeRO-1 的基础上,切分梯度,仍然保留完整的模型权重
- 执行的通信操作
- 反向传播:梯度不做全局 AllReduce,本地生成局部梯度分片
- 梯度归约:跨卡做 Reduce-Scatter,每个 GPU 只得到归属分片的全局梯度
- 优化器更新:本机分片独立更新 FP32 参数
- All-Gather 同步 FP16 权重,所有 GPU 收集更新后的分片 → 合成完整 FP16 权重
- 通信量计算
- Reduce-Scatter:梯度 2M
- All-Gather:FP16 参数 2M
- 显存计算
- 权重:2M
- 梯度:2M/N(如果以 FP32 进行梯度累加,还需要再维护 4M/N)
- Adam (m/v/master):12M/N
- 单卡总显存变为 2M+14M/N
- ZeRO-2 为什么必须先 reduce-scatter 再更新,不能直接全局求和更新?
- 把每张卡本地散乱的梯度,按参数区间分片路由 + 累加,最终每一块参数的全局求和梯度,精准落到该参数优化器所在的那张归属 GPU
- 如果直接全局求和更新,就与 ZeRO-1 的逻辑一致,先进行 All-Reduce,但实际上每个 GPU 只需要维护各自的分片
- ZeRO-2 是如何进行 reduce-scatter 和 all-gather 的通信掩盖的?
- Reduce-Scatter 可与反向传播末尾 / 参数更新前的计算重叠:反向传播最后几层计算、梯度整理、Adam 状态准备等,与 Reduce-Scatter 无数据依赖 —— 通过梯度分桶,实现流水线重叠
- All-Gather 可与下一轮前向传播的计算重叠:前向传播第一层 / 前几层计算,与 All-Gather 无数据依赖(权重未完全聚合前,可先算不依赖最新权重的部分)
- 通信与计算在不同硬件流 / 队列执行:GPU 有独立的计算流(Compute Stream)和通信流(Communication Stream/NCCL Stream),可并行执行
-
ZeRO-3
- 在 ZeRO-2 的基础上,切分权重
- 执行的通信操作
- 前向传播的每层计算前发起 AllGather,临时汇聚该层完整权重,计算完成后立即释放权重显存,只保留本地分片
- 反向传播同样 AllGather 拉取权重,梯度生成后执行 Reduce-Scatter 归集梯度分片
- 优化器更新:本地分片独立更新
- 权重是「按需临时汇聚、用完即销毁」,不常驻完整副本
- 通信量计算
- 前向 All-Gather:2M
- 反向 All-Gather:2M
- Reduce-Scatter 梯度:2M
- 显存计算
- 权重:2M/N
- 梯度:2M/N(如果以 FP32 进行梯度累加,还需要再维护 4M/N)
- Adam (m/v/master):12M/N
- 单卡总显存变为 16M/N
-
ZeRO-3 是怎么切分参数的?按层切分还是层内切分?
- ZeRO-3 强制做「层内细粒度分片(intra-layer sharding)」,不做「按层切分(inter-layer sharding)」
- 对每个参数张量独立分片,每个参数(如 Linear.weight、Embedding.weight、LayerNorm.weight)被均匀切成 N 份(N=DP 并行度)
- 每个 rank 持有所有层的所有参数的 1/N 分片,而不是持有完整的某几层
- 在进行分片时,默认沿最后一个维度(dim=-1)分片(PyTorch 连续存储友好)
- 对任意形状张量:
shape = [d0, d1, ..., dn-1]→ 沿dim = n-1切分 - 分片后每个 rank 持有:
shape = [d0, d1, ..., dn-1 / N] - 为什么选最后一维?
- 符合 PyTorch 行优先存储,分片后内存连续,All-Gather 效率最高
- 与 GEMM 算子的内存访问模式匹配,减少 stride 访问开销
-
ZeRO-3 参数 prefetch 预取怎么做?如何掩盖 all-gather 通信?
- Prefetch 机制
- 预取下一层参数:当前层计算时,异步 All-Gather 下一层的参数分片
- 分桶流水线:参数按 stage3_prefetch_bucket_size(默认 50M 元素)拆成小桶,边计算边通信
- 双缓冲:当前层计算缓冲区 + 下一层预取缓冲区,无等待切换
- 通信掩盖时序
- 当前层前向计算:使用已 All-Gather 完成的参数
- 同时启动 Prefetch:异步 All-Gather 下一层参数分桶(非阻塞)
- 当前层计算结束:下一层参数已 All-Gather 完成 / 接近完成
- 立即进入下一层计算:无通信等待,延迟完全被掩盖
- 反向传播 Prefetch
- 反向时按逆序预取上一层(计算顺序的下一层)参数
- 同样用 All-Gather 预取 + 计算重叠,掩盖反向通信延迟
- Prefetch 机制
-
ZeRO-3 内存释放时机是什么?exit module 立刻 free 有什么风险?
- 标准释放时机(DeepSpeed 实现)
- 前向传播:层计算完成 → 延迟释放(不立即 free),放入参数缓存
- 反向传播:梯度计算完成 → 释放参数,仅保留梯度分片
- 优化器更新:更新完成 → 释放完整参数,仅保留本地分片
- 全局控制:由 stage3_max_live_parameters 限制缓存大小,超过则 LRU 释放
- exit module 立刻 free 的风险
- Prefetch 失效:下一层参数还在 All-Gather,当前层立刻 free 会导致缓存击穿,下一层必须重新 All-Gather,通信延迟暴露
- 显存抖动:频繁 alloc/free 导致显存碎片,降低分配效率
- 反向传播错误:反向时需要重新 All-Gather 已释放的参数,额外通信开销翻倍
- 多流竞争:立即 free 与异步 All-Gather 流冲突,可能导致内存访问错误(UAF)
- 正确策略:延迟释放 + 缓存复用
- 不立即 free,而是放入缓存,供后续层复用
- 仅当缓存超过阈值时,才异步释放不活跃参数
- 平衡显存占用与通信开销,最大化 Prefetch 效果
- 标准释放时机(DeepSpeed 实现)
-
什么是 zero-3 hook?如何自动接管参数分片、拼装、释放?
- ZeRO-3 Hook 是是 PyTorch Module Forward/Backward Pre-Hook,由 DeepSpeed 自动注册
- 完全透明接管模型的前向 / 反向生命周期,用户无需修改模型代码
- Forward Pre-Hook(层前向入口)
- 触发时机:module.forward () 执行前
- All-Gather 当前层参数:从各 rank 拉取分片,拼装完整参数
- Prefetch 下一层参数:异步启动下一层 All-Gather
- 替换模块参数:将分片参数替换为 All-Gather 后的完整参数
- Forward Post-Hook(层前向出口)
- 触发时机:module.forward () 执行后
- 恢复分片参数:将完整参数换回本地分片,释放非本地分片内存
- 更新缓存状态:将当前层参数加入缓存,标记为可复用
- Backward Pre-Hook(层反向入口)
- 触发时机:module.backward () 执行前
- All-Gather 当前层参数:重新拼装完整参数用于梯度计算
- Prefetch 上一层参数:异步启动上一层(反向顺序)All-Gather
- Backward Post-Hook(层反向出口)
- 触发时机:module.backward () 执行后
- Reduce-Scatter 梯度:将完整梯度规约为分片,每个 rank 保留自己部分
- 释放参数:释放完整参数,仅保留梯度分片
- 自动接管流程
- 模型初始化:deepspeed.initialize () 自动为所有 nn.Module 注册 Hook
- 前向传播:每层自动 All-Gather → 计算 → 释放 → Prefetch
- 反向传播:每层自动 All-Gather → 计算梯度 → Reduce-Scatter → 释放
- 优化器更新:Hook 接管参数分片更新,仅更新本地分片
-
ZeRO 引入的额外通信量相比标准 DDP 是增还是减?什么场景反超?
范式 核心通信构成 总物理通信量 标准 DDP 梯度 AllReduce 4M ZeRO-1 梯度 AllReduce + 权重 AG 6M ZeRO-2 梯度 RS + 权重 AG 4M ZeRO-3 前向 AG + 反向 AG + 梯度 RS 6M -
ZeRO Offload 的原理
-
ZeRO Offload 主流指 Optimizer State Offload(优化器卸载),把巨量 Adam 状态扔到 CPU DRAM,省 GPU 显存
-
什么张量可以卸载到 CPU 上?
- 满足迭代间低频读写、不需要参与 GPU 正向 / 反向计算、更新串行
- Adam 全系优化器状态(大头,占显存 75%),包括主模型权重、一阶动量、二阶动量,这些张量每迭代仅 step 阶段读写 1 次
- 次要可卸载:闲置参数分片(非即时 gathered 的 ZeRO 分片)、部分不紧急的梯度累积 buffer
-
为什么 forward/backward 高频访存数据不能卸载,只能驻留 GPU
- 前向 / 反向是 GPU 硬件算子:GEMM、Norm、激活、梯度反向求导只能在 GPU 执行,参数 / 梯度必须显存可见,不能在 CPU 内存
- 访存频率极高、毫秒级反复读写:每层瞬时读写权重 + 梯度,每秒几十~几百轮,如果卸载到 CPU 上,每次计算都触发 H2D 拷贝,PCIe 带宽(~64GB/s)远小于显存带宽(~900GB/s NVLink)
- 无法做异步掩盖:这类张量强数据依赖、串行刚需,预取 / 双缓冲掩盖收益极低,拷贝延迟直接阻塞迭代
- 数值稳定性保障:频繁跨设备拷贝易引入同步误差、精度抖动,梯度累加要求低延迟原子访问
- 包括瞬时用到的 FP16/BF16 模型权重(临时 gathered)、即时生成的梯度 buffer、中间激活值、dropout 随机掩码、算子临时 Workspace
-
CPU 优化器更新完整流程
-
第一步:GPU 侧准备 & 梯度 D2H 搬运
- 反向结束,GPU 显存中:本地分片全局梯度
- 发起异步 D2H 拷贝:把该分片梯度从 VRAM → CPU DRAM
- 拷贝期间 GPU 可预调度下一轮 / 清理临时缓存,不等待
-
第二步:CPU 侧精度提升,即 FP16/BF16 梯度 → FP32
- CPU 等待梯度分片就位
- 执行类型强制转换:
- 匹配 CPU 上的 FP32 优化器状态精度(避免混合精度误差)
-
第三步:CPU 内存执行 AdamW 完整数学更新
-
CPU 侧常驻 ,逐元素独立更新:
-
动量平滑:
-
二阶平方平滑:
-
偏差校正 + 参数步进 + 权重衰减
-
全程在 CPU DRAM 运算,不占 GPU 算力、不占 GPU 显存
-
-
第四步:更新后主权重转回低精度 + H2D 写回 GPU
- CPU:FP32 master → 转回 FP16/BF16
- 异步 H2D 拷贝:更新后的参数分片写回 GPU 显存
- 后续由 ZeRO 完成全局 All-Gather,恢复完整权重进入下一轮前向
-
-
Offload 会引入 CPU-GPU 拷贝开销,DeepSpeed 怎么异步掩盖?
- 多 Stream 解耦:计算流 / 梯度拷贝流 / 优化器流 隔离
- GPU:独立 NCCL 通信流、D2H/H2D 拷贝流
- CPU:独立 Pthread 优化器计算线程池
- 三者并行,互不阻塞
- 梯度拷贝与后处理 / 清理重叠
- 反向传播收尾、显存垃圾回收、DDP/ZeRO 同步间隙
- 后台异步发起 D2H 梯度搬运,隐藏 PCIe 延迟
- 双缓冲(Double Buffer)流水线
- 缓冲 A:本轮迭代 CPU 更新使用
- 缓冲 B:后台预取下一轮梯度 / 参数
- 交替复用,消除分配 & 同步等待
- 延后主机更新、跨迭代掩盖
- 小 batch / 轻负载下:
- 把 CPU Adam 更新流水线延后到下一轮前向计算期间后台执行
- 用 GPU 正向计算时间完全吞掉 CPU 更新 + 内存拷贝耗时
- 梯度累积周期调度
- 多步梯度累积期间,不频繁触发 Offload 搬运,减少 PCIe 小包风暴
- 多 Stream 解耦:计算流 / 梯度拷贝流 / 优化器流 隔离
-
哪些张量支持卸载、哪些严禁卸载?混合卸载策略怎么配置最优?
- 支持安全卸载
- Adam 三大状态:master fp32 /m/v(核心推荐必卸)
- 梯度累积分片(低频)
- ZeRO 闲置参数分片
- LossScaling、clip_grad 统计量
- 绝对严禁卸载
- 模型激活值(最热数据)
- 正在 AllGather 的 FP16 权重
- 即时反向梯度(未完成规约)
- CUDA Kernel 临时 shared memory /workspace
- Embedding 高频查表权重
- 生产集群最优混合 Offload 配置
- 优先开启:Optimizer CPU Offload(收益最大,显存砍 60%~75%)
- 关闭:Gradient Offload 全量卸载(容易带宽瓶颈拖慢)
- 配合 ZeRO-2/3:分片 + 卸载叠加,显存线性下降
- 开启
pipeline_offload=True、async_module=True异步流水线 - 限制 CPU 内存水位,防止 Swap 磁盘(致命减速)
- 禁用不必要的 host 同步、禁用频繁 print/tensor 打印(减少 D2H)
- 支持安全卸载
-
-
ZeRO 与 PP/TP 的区别
-
ZeRO、TP、PP 三者分片对象、并行维度、通信类型正交对比?
全称 并行维度 核心定位 通信原语 ZeRO 零冗余数据并行优化 数据并行 DP 维度 切「存储副本」,省显存,每张卡只存 1/N 参数 / 梯度 / 优化器 AllGather / ReduceScatter TP 张量并行 / 模型内并行 单层张量内部 切「算子计算」,拆分 GEMM,每层分布式计算 AllReduce / AllGather / ReduceScatter(层内同步激活 / 梯度) PP 流水线并行 模型层堆叠空间 切「网络 Stage」,拆分整体模型,分段流水执行 Send / Recv(相邻 stage 传递中间激活) -
ZeRO 和 PP 的区别?
- ZeRO 聚合参数,不聚合分块的计算结果
- 参数分片存各处,计算前临时 AllGather 拼成完整权重
- 每张卡独立跑完整层计算,输出完整激活,不需要跨卡合并计算输出
- 逻辑:先拼参数 → 本地独享计算 → 不汇总计算结果
- PP 聚合计算结果,不聚合参数
- 每一个 Pipeline Stage 只持有自己几段 Layer 的完整参数
- 前一个 Stage 算完中间激活 → Send 给下一个 Stage 接力计算
- 全程不全局合并参数,只逐层接力合并 “计算中间结果”
- ZeRO 聚合参数,不聚合分块的计算结果
-
为什么 ZeRO 聚合参数不聚合计算结果,模型并行反过来?底层数学差异?
- ZeRO 必须先 AllGather 拼完整参数,才能单机独立算;算完不用合并输出
- TP 底层利用了矩阵乘法可以拆分为加法,从而需要计算后 AllReduce 累加激活
- PP 则是在流水线不同 stage 直接流转激活值,参数属地保存,只传递中间激活、聚合多级计算结果
-
ZeRO+TP+PP 混合并行时,分组通信域怎么划分?跨组会不会冲突?
- TP 通信域:同一 PP stage 内,组内做张量 AllReduce
- PP 通信域:同一 TP rank 内,跨 stage 点对点 Send/Recv
- ZeRO-DP 通信域:同一 TP + 同一 PP 位置内,跨 DP 组做 ReduceScatter/AllGather
-
同等卡数下:纯 ZeRO、纯 TP、纯 PP,各自吞吐 & 显存瓶颈谁最优?
- 显存压力从小到大(最优→最差)
- 纯 ZeRO 碾压最优:显存随卡数线性分摊,极致省内存
- 纯 TP:单层切分省激活 / 权重,但优化器不分摊
- 纯 PP 最差:仅分层隔离常驻参数,无全局分片,显存节约最弱
- 训练吞吐从高到低(通信效率)
- 纯 ZeRO:通信模式简单、allgather/rs 可深度重叠,单卡计算饱和,吞吐最高
- 纯 TP:层内频繁 allreduce,小消息多,latency 开销大,吞吐中等
- 纯 PP:流水线气泡 (bubble) 损耗,利用率天生低,吞吐最差
- 适用场景速判
- 超大模型显存瓶颈 → 优先 ZeRO
- 单卡计算饱和、单层超大矩阵 → 优先 TP
- 超深 Transformer、超长序列 → 优先 PP
- 显存压力从小到大(最优→最差)
-
-
DP 和 ZeRO 存在的问题
- 为什么激活值不能分片、不能跨 DP 组共享?数学 & 数据依赖底层原因?
- 激活张量严格绑定当前局部输入样本,每卡激活天然不相等、不可通用、不能合并、不能共享
- 故而激活值在数据并行中天然不是冗余的,因此在 DP 场景下并不是同一份数据的拷贝
- 激活重计算(rematerialization)和 ZeRO 互补关系是什么?各自解决什么?
- ZeRO:权重 + 梯度 + 优化器的副本冗余显存爆炸
- 激活重计算(Rematerialization):前向激活常驻显存爆炸,用少量重算算力换大量激活显存
- 微批次增大、梯度累积,为什么缓解不了激活显存爆炸?
- 梯度累积是多次小 micro-batch 前向 + 反向,累加梯度、不更新权重,增大全局 batch
- 但是每个 micro-step 激活峰值独立存在,激活显存正比于单卡单 micro batch 的序列长度 × hidden_dim,而梯度累积只增加迭代次数,不改变单微批输入尺寸
- 为什么激活值不能分片、不能跨 DP 组共享?数学 & 数据依赖底层原因?
-
ZeRO 训练问题
- ZeRO 训练会不会引入精度损失?哪些环节容易数值漂移?
- 理论无额外数学误差;工程实现不当会引入微小数值漂移,正常配置下不影响收敛
- ZeRO 只是分布式分片存储 + 集合通信归约,更新数学和单机 SGD/Adam 完全等价,本身不创造误差源
- 工程误差来源
- 跨节点梯度 Reduce-Scatter / AllReduce 浮点累加误差
- FP16/BF16 ↔ FP32 Master 往返拷贝
- ZeRO-3 频繁 AllGather + 临时拼接 / 释放显存
- Offload 卸载链路额外 D2H/H2D 传输
- ZeRO3 训练不稳定的常见原因
- 通信等待 & 同步不均衡:多机 NCCL 时序差、慢 rank 拖全局同步
- 参数分片边界不对齐:ZeRO3 层内切分必须张量均分,若维度不能被 DP 整除且 padding / 截断处理不当,可能会存在问题
- 显存释放 & Reuse 乱序
- ZeRO 和动态 loss scaling 如何协同工作?
- 前向 FP16 计算,loss 放大 scale 避免下溢
- 反向:梯度带 scale 回传
- 梯度规约(ZeRO RS 前)先 unscale,保证多卡求和是真实梯度,不放大通信误差
- 检测梯度 inf/NaN:
- 异常:跳过 optimizer step、下调 scale
- 正常:ZeRO 分片 Adam 正常更新
- Unscale 必须在分布式梯度同步之前执行,否则多卡带缩放值求和 → 权重爆炸、训练崩坏
- 断点续训时,ZeRO 分片参数如何全局合并加载?
- 保持 checkpoint:每张 rank 只保存自己管辖的分片参数、分片梯度、分片 Adam (m/v/master),不是完整权重,是局部分片 ckpt
- 如果拓扑发生变化,则需要
- 所有 rank 各自 load 本地分片
- 全局执行 AllGather 按张量拼接
- 按原始参数维度重组成完整 fp32/fp16 master 权重
- 统一导出标准 state_dict
- ZeRO 训练会不会引入精度损失?哪些环节容易数值漂移?
-
ZeRO 和 FSDP 的区别?
TP
-
介绍一下张量并行
- 张量并行是一种层内细粒度的模型并行技术,核心是将单一层内的大张量按维度切分,分配到多 GPU 并行计算,然后通过集合通信合并结果
- 通过这种方式,它能够将权重、梯度、优化器状态甚至是激活值进行分片,而且不需要在计算前将它们全部聚合
- 切分对象主要是层内的权重矩阵,如注意力层的 QKV 和投影矩阵,MLP 层的线性层;通过将参与矩阵相乘的两个矩阵分别按行或列切分,能够多 GPU 并行计算矩阵乘法,最后进行 all-reduce 通信聚合结果
- 显然,TP 在每层的前向 / 反向都需要进行 all-reduce 通信,通信量与激活 / 参数量正相关
-
TP 切分粒度是什么?是层内还是层间?
- TP 是纯层内并行,切分粒度是 “单个算子 / 层内的张量”
-
为什么 TP 一般绑定单节点 NVLink,不跨多机?
-
TP 的通信频率很高,前向计算时需要在 Attn 层和 MLP 层之后进行 all-reduce 通信,反向传播时,需要在 MLP 梯度计算和 Attn 梯度计算之和进行 all-reduce 通信
-
TP 的通信数据量很大,与激活张量的大小成正相关,一般是 [batch,seq,hidden] 大小的
-
对于这种情况,跨机网络带宽 / 延迟远远无法达到通信要求,会导致通信成为绝对瓶颈,吞吐率严重下降
-
同步要求:必须等通信完成才能进入下一层计算,强同步、无计算重叠空间
-
不同硬件的带宽差异
互联方式 带宽(典型) 延迟 适配场景 NVLink(单机内) 800–900 GB/s 纳秒级 TP(高频 All-Reduce) PCIe 32 GB/s 微秒级 次优,仅应急 以太网 / RoCE(跨机) ~100–200 GB/s 微秒–毫秒级 仅 DP/PP,不适合 TP -
综上,最佳实践是:TP 仅用单机内 NVLink;跨机并行用数据并行(DP)+ 流水线并行(PP),通信量小、可异步重叠,效率更高
-
-
如何切分
-
能够进行切分的数学意义?为什么需要进行 all-reduce 通信?
- 设 ,则
- 按照列切分:,结果需要拼接
- 按照行切分:,结果需要求和
- 本质是线性变换可按维度解耦拆分计算,非线性操作不可随意拆分
- 由此可知,对于列并行,需要进行 all-gather 拼接;对于行并行,需要 all-reduce 求和
-
QKV 投影和 attn 输出投影怎么进行切分的?对于 GQA/MQA 情况下切分有什么影响?
- 对于标准的 MHA,设隐藏层维度为 h,头数为 N,则满足单头维度 =
- QKV 矩阵天然按照头维度进行切分,将若干个头均分到 TP 分组中,这样每张卡仅存储一部分 Q/K/V 对应的头投影,前向计算时分别计算,且无需立即进行 all-gather,因为注意力本身就是按头执行的
- 对于输出投影,则按照行切分,各卡的注意力结果映射为局部特侦,然后经过 all-reduce 求和得到完整结果
- 在 GQA 情况下,K/V 分组小于头数,此时需要小心地保证某些卡共享 K/V 矩阵;或者对 K/V 矩阵不进行切分 —— 导致大 TP 规模下负载倾斜比较明显
- 在 MQA 情况下,K/V 全局共享不能按照头进行切分,只能全员复制存储
-
MLP 怎么进行切分的?Gated 的情况下如何进行切分?为什么不能用另一种切分方式(激活函数受限)
-
对于标准 MLP 场景,升维投影按照列切分,而激活函数逐元素非线性,本地独立计算,降维投影按照行切分,最后 all-reduce sum 聚合输出
-
对于 Gated MLP 场景,gated 矩阵也按照列进行切分,保证逐元素乘法对位匹配,最后经过降维投影后,按照 all-reduce sum 聚合输出
-
A 列切 + B 行切的完整公式 —— 只需在最后进行一次 all-reduce 通信
-
A 行切 + B 列切的完整公式 —— 必须在 GELU 之前做 all-reduce
-
-
LayerNorm 为什么不做 TP 切分?LN 层的前向和反向需要 all-reduce 吗?
-
LayerNorm 计算公式
-
均值、方差是全局样本 / 全局隐藏维统计量,它是逐个 token 进行统计的
-
如果按维度 TP 切分,单卡局部统计≠全局统计,归一化彻底失效
-
和 是全局缩放偏移参数,拆分无数学意义
-
-
输入层和输出层如何进行切分?
- 输入层对词表按照行进行切分,每张卡分别查询词向量,能够找到的词返回对应的词向量,如果找不到就都置为 0;查找完毕后,进行一次 all-reduce 得到最终的输入
- 通过这种方式,每张 GPU 能够均摊词表
- 对于输出层,将原先为 [H,V] 的权重按列切分,每张卡持有 [H, V/tp],而输入为 [B, S, H],从而得到每张卡的结果为 [B, S, V/tp],即每张卡持有局部词表的 logits
- 这是为了 Loss 可以分布式计算,不需要全局汇聚 logits
-
Loss 如何计算?
- 首先,从输出层得到每张卡上拥有 [B, S, V/tp],显然可以先对其做一次 all-gather 拼接起来后得到完整结果,然后对每一行进行 softmax,即可得到对于当前位置而言每个词出现的概率,然后再用词概率和真值计算交叉熵损失
- 但是进行 all-gather 会引入额外的通信量 bsv
- 为了保证 softmax 计算稳定性,可以先进行归一化,先在每块 GPU 上按行求最大值,然后进行 all-reduce 得到每行的最大值,然后减去该最大值,通信量为 bs
- 在每块 GPU 上先按行求和,然后进行 all-reduce 得到每行最终的和,也即 softmax 的分母,通讯量为 bs
- 然后每块 GPU 上就可以维护各自部分的 softmax 值,然后与真值计算交叉熵损失,按行加总起来得到 GPU 上的标量损失
- 最后进行一次 all-reduce,得到总损失,通讯量为 N
-
损失的计算过程
-
标准单样本离散交叉熵:
-
z∈RV 表示全局 logits,y 真实标签(单个索引号)
-
TP 按词表把 z 切成多片 ,而真值标签 y 只会落在其中某一个 GPU 的局部词表分片里,其余卡对应位置项为 0 贡献,交叉熵只依赖真值对应那一项的归一化概率,全局求和等价于「仅含真值的那张卡单项贡献 + 其余卡 0」
-
所以各卡局部 loss 相加 = 全局完整 loss
-
-
-
Dropout 层
- Dropout 层在 TP 场景下需要切分吗?直接单机各自随机 mask 会出现什么问题?
- Dropout 不切分,TP 组必须共享同一个 mask
- 如果各自独立随机 mask,每张 GPU 的 mask 不一致,就导致最终 AllReduce 后,相当于 Dropout 率被错误提升到接近 100%,输出分布完全偏离数学期望,梯度方向错误,模型不收敛、震荡、最终崩塌
- TP 分组内不同卡独立 Dropout,为什么会导致分布不一致、梯度错误?
- 被丢弃的位置必须全局一致,而不是局部各自丢弃
- 某些 token 位置在某些卡保留、某些卡丢弃,经过 AllReduce 后,输出值被错误缩放,梯度被错误加权,数学期望 E [output] 不再等于输入,Dropout 失去正则化意义,变成噪声
- 标准 Transformer 里 Dropout(残差 dropout/attention dropout)在 TP 如何同步随机种子?
- TP 组内所有 GPU 使用完全相同的随机种子(RNG Seed)生成 Dropout Mask
- 三类 Dropout 全部同步:Attention Dropout、Residual Dropout(残差分支)、MLP Dropout
- 同步规则:
- 同一 TP 组,同一步 forward,同一种 Dropout,种子完全相同
- 不同 TP 组、不同 DP rank,可以不同
- 不同层、不同 step,种子正常递增
- 分布式 RNG(随机数生成器)怎么保证 TP 组 Dropout 掩码全局一致?
- TP 组内所有 GPU 共享同一个 RNG 状态
- 初始化时,TP 主卡(rank=0)生成种子,广播给同组所有卡
- 每次生成 Dropout mask 时:
- 所有卡使用相同种子 + 相同步长计数器
- 生成完全一样的随机数序列
- 得到完全一样的 mask
- 是否可以只在 rank0 生成 mask,再 broadcast 到所有 TP 卡?优缺点?
- 可以,但是不推荐这样做
- 因为先生成 [b,s,H/tp] 大小的 mask 再通信会增加 NVLink 的通信
- 使用多卡并行生成随机数更快,而且分布式 RNG 方案无通信、零开销、更高效
- TP+ZeRO 混合场景下,Dropout 随机种子管控边界在哪里?
- TP 组内:同一份层,必须同步 Dropout seed + 相同 mask
- DP/ZeRO 组间:不同数据,必须不同 mask(保证正则化多样性)
- Dropout 层在 TP 场景下需要切分吗?直接单机各自随机 mask 会出现什么问题?
-
通信
- 每行 / 每列切分后,需要哪些集合通信?all-reduce 还是 all-gather?
- 行切分需要进行 all-reduce 通信
- 列切分需要进行 all-gather 通信
- 为什么 Attention 和 MLP 之后需要 all-reduce?数学意义是什么?
- 每张卡只有部分求和项,不做 AllReduce,得不到全局加权和,下一层 LN/FFN 输入数学错误
- Gated MLP 的非线性逐元素运算允许分片独立计算,不需要拼接全局升维状态
- TP 能否做到计算和通信重叠?怎么做?
- TP 是层内强依赖:必须先算完局部矩阵乘,才能启动规约
- 纯首尾强同步,无法像 PP 那样大规模流水线重叠
- TP 每组通信量理论怎么计算?
- 单个 Transformer 层,前向 2 次 all-reduce,反向 2 次 all-reduce,共 4 bsh
- 每行 / 每列切分后,需要哪些集合通信?all-reduce 还是 all-gather?
-
显存
- TP 能省哪部分显存?不能省哪部分?
- 模型权重:Q/K/V、out proj、FC1/FC2 权重沿 hidden 维度切分到每张卡
- 与模型权重对应的优化器状态显存、梯度显存也随之切分
- 然而,对于全局输入激活、LN 权重和偏置等无法节省
- TP 为什么不能减少峰值激活值总量?
- 实际上,Attention/MLP 每个 GPU 计算得到的激活张量仍旧为 bsh 大小,进行 all-reduce 求和之后得到的全局激活也是 bsh 的,因此全局总激活元素并没有减少
- 不同 head 数下,TP 分组不均衡怎么解决?
- 模型结构优先保证,总注意力头数能被 TP 并行度整除
- GQA/MQA 场景:分组重映射 + Padding 补齐,对不整除的头分片做虚拟 padding 对齐 TP 分组,计算后截掉
- TP 并行度受什么硬件约束?
- 受单卡显存限制:虽然能够洁身参数 / 优化器显存,但是仍需要存储完整的 LN 层、全局输入激活等
- 机内通信带宽(NVLink)硬性限制:TP 是高频低延迟集合通信(每层 4 次 all-reduce),必须同节点、NVLink 互联;跨节点 PCIe 带宽太低,TP 性能骤降
- 通信延迟叠加阈值:TP 越大,Ring All-Reduce 通信延迟越高
- TP 能省哪部分显存?不能省哪部分?
-
TP 训练问题
- 推理场景 TP 和训练场景 TP 差异是什么?
- 通信逻辑不同
- 训练 TP:每层前向 2 次 AllReduce、反向 2 次 AllReduce,频繁规约激活 + 梯度强同步,同步开销大、依赖 NVLink 低延迟
- 推理 TP:仅必要节点做 AllGather,无反向梯度通信;KV Cache 按 TP 分片常驻显存,不需要梯度 / 优化器显存,通信量暴跌
- 显存构成瓶颈不同
- 训练:参数 + 梯度 + Adam 优化器状态 + 重计算激活四大块压显存
- 推理:KV Cache 是绝对瓶颈,权重分片分摊、无梯度与优化器开销
- 通信逻辑不同
- 大 bs 下 TP 为什么吞吐提升边际递减?
- 通信瓶颈放大:batch_size 越大,单次通信量 bsh 越大,计算随 BS 线性提升,但通信时延不会随着 batch_size 增长而分摊
- 单卡计算利用率饱和:大 batch_size 使单卡 GEMM 计算达到硬件峰值,继续扩展 TP 时,新增算力增益很小,但通信开销、kernel 启动开销线性增加
- 冗余常驻显存挤占算力资源:大 batch_size 下相对应的激活显存占用也越高
- TP 会引入哪些数值精度风险?
- 多次 all-reduce 累加舍入误差
- 推理场景 TP 和训练场景 TP 差异是什么?
CP
- 介绍一下 CP
- CP 是沿序列维度切分输入与所有中间激活的并行策略,专为超长上下文(128K/256K+)设计,核心是把长序列分片到多卡,降低单卡激活与 KV 显存占用
- CP 是为了解决 TP 的什么问题?
- 解决 TP 的长序列激活显存爆炸
- 解决 TP 下无法进行有效的通信 - 计算重叠,通过划分序列,计算量和通信量都按照 cp_size 降低,且 all-reduce 被拆分为 reduce-scatter 和 all-gather 后更方便进行计算通信重叠
- 切分
- LN 层怎么进行切分的?
- 由于 LN 是逐个 token 沿隐藏维度计算的均值和方差,因此需要访问完整的 hidden_dim 才能计算正确
- 而引入序列切分之后,可以天然地按照不同的序列计算 LN 中的均值和方差,无需进行跨卡同步
- 在反向过程中,梯度本地计算,无需跨卡规约均值方差梯度,只保留自身序列分片梯度
- 而在由 CP 域转向
- Dropout mask 怎么进行切分的?
- 每一张 GPU 仅生成本地子序列对应的 dropout mask:[b,s/cp,h]
- 前向时,各自用本地 mask 对局部 hidden 随机置零、缩放,完全独立,无同步
- 反向时,mask 不变,仅对本地分片传导梯度,天然匹配 CP 序列划分
- Embedding 层怎么进行切分的?
- 与 TP 的沿着词表 V 维度切分不同,在 CP 中只沿着序列长度的维度切分输入的 token 序列
- 对于全局 token 序列 [b,s],每卡持有 [b,s/cp] 的局部序列,CP 组内各卡用同一份 Embedding 表,得到局部的 embedding 即 [b,s/cp,h]
- 然后在 TP 域内,需要进行 all-reduce 通信,来获得局部序列的完整 embedding
- CP 中的 Attn 层是怎么进行切分的?每个 GPU 维护什么 Q/K/V 分块?
- 每个 GPU 持有的 QKV 大小均为 [b,s/cp,h/tp]
- 切分逻辑:Query (Q) 始终保留在本地节点。为了计算完整的 Attention,本地的 Q 需要与全局所有的 Key (K) 和 Value (V) 进行交互
- 维护方式:GPU 维护本地的 Q 分块不变;而 K 和 V 的分块会在 CP 通信组内进行轮转(如 Ring Attention),或者通过 All-to-All 将序列维度的切分转换为 Attention Head 维度的切分(如 DeepSpeed Ulysses)
- LN 层怎么进行切分的?
- 通信
- SP 引入了更多通信开销吗?SP+TP 之后的通信可以进行掩盖吗?
- 没有引入更多通信量。标准的 TP 前向和反向各需要一次 All-Reduce(通信量为 2×S×H),Megatron SP 将一次 All-Reduce 巧妙地数学拆解为一次 All-Gather 和一次 Reduce-Scatter,总通信量保持完全一致,但降低了显存峰值
- 前向传播 (All-Gather):在进入线性层之前,各卡持有的局部序列
[s/p, h]通过 All-Gather 拼成全局序列[s, h] - 反向传播 (Reduce-Scatter):计算完线性层的梯度后,各卡持有的全局序列梯度
[s, h]通过 Reduce-Scatter 归约并切分为局部梯度[s/p, h] - SP 允许将序列进一步划分为更小的 micro-chunks,当卡在计算 chunk i 的矩阵乘法时,可以通过硬件引擎(如 NCCL)异步进行 chunk i+1 的 All-Gather 通信,实现严格的通信 - 计算重叠
- all-gather p2p
- reduce-scatter p2p
- 反向传播
- LN 层为什么引入了额外的 all-reduce 通信?
- TP+SP 仍然存在的问题?
- Attention 复杂度瓶颈:尽管 SP 切分了 LN 和 Dropout 的序列,但在 Attention 模块中,Q 仍然需要和完整的 K、V 计算,单卡的 Attention 计算时间复杂度仍为 ,并没有随着卡数 p 的增加而减少
- KV 显存依然爆炸:每个 GPU 在 Attention 内部都需要短暂持有或重组完整的全局 K 和 V(即 B×S×H),在超长上下文(如 100K+)下,即便有 TP,显存依然会 OOM
- SP 引入了更多通信开销吗?SP+TP 之后的通信可以进行掩盖吗?
- TP+CP
- 无并行下的激活计算:全量激活占用,大小为 B×S×H,Attention 矩阵显存占用为 B×heads×S×S
- 纯 TP 的激活计算:由于输入序列未切分,进入 Attention/MLP 前的激活占用仍为 B×S×H(冗余存放),仅权重和中间映射(如 QKV 投影后)切分为 H/tp
- TP+SP 的激活计算:非 Attention/MLP 核心计算区(如 LN、Dropout)的激活降为 B×(S/tp)×H,但 Attention 计算时的 K、V 和注意力分数矩阵依然维持对全局序列 S 的依赖
- TP+SP+CP 的激活计算:序列被进一步极度切分,单卡的输入激活和 KV 缓存大小降至 B×(S/(tp×cp))×H,Attention 矩阵大小被严格限制在局部分块级别(如 (S/cp)×(S/cp)),彻底消除了 带来的单卡显存爆炸
- 引入 CP 后
- MLP 如何进行切分序列的?
- MLP 是基于 token 独立计算的
- 引入 CP 后,每张卡只负责通过本地的
[b, s/cp, h]序列分片计算 MLP 前向和反向,全程无需与其他卡通信,计算结果直接就是正确的局部切分形式
- CP 能不能减少激活总显存?原理是什么?
- 能,而且是呈倍数减少
- 原理是:通过长序列分片,每张卡仅保存
1/cp长度的前向激活值和 KV Cache - 同时,Attention 的 S×S 矩阵也被拆解为一个个小的 Block 分步计算并在计算后立即丢弃(结合 FlashAttention),使得激活显存从 骤降到 级别
- Ring Attention 的原理?通信量计算?
- 将全局序列分成
cp块,每张 GPU 持有局部的 、、 - 计算时,本地的 保持不动,各 GPU 将自身的 , 发送给环上的下一个 GPU,同时接收上一个 GPU 传来的 ,
- 收到新的 KV 后,计算局部的 Attention,并利用 FlashAttention 的 Online Softmax 公式更新本地的 Attention 结果
- 经过
cp-1轮传递, 遍历了所有的 KV - 通信量:每轮传递发送和接收一个 KV 块(大小为 2×S/cp×H),总共 cp−1 轮,单卡总通信量约为 2×S×H,与传统的 All-Gather 相当,但它是 P2P 通信
- 将全局序列分成
- CP 前向 / 反向分别触发哪些集合通信?
- Ring Attention 方案:前向和反向均触发
P2P (Send/Recv)环形通信 - Ulysses 方案:在 Attention 计算前后触发
All-to-All集合通信,进入 Attention 前,通过 All-to-All 将切分维度从 Sequence 转为 Attention Head;计算完后,再通过 All-to-All 转回 Sequence 维度
- Ring Attention 方案:前向和反向均触发
- CP 中的局部 Attention 计算完,如何拼接全局结果?通信数学意义?
- 采用 FlashAttention 的在线 Softmax 逻辑 (Online Softmax)
- 数学上,Softmax 的分母是所有元素的指数和,局部计算只能得到局部最大值 和局部指数和
- 在收到新的 KV 块计算时,结合上一次保存的全局最大值 进行自适应缩放(rescaling):更新后的局部结果乘以 ,然后再累加新的结果
- 最终所有块遍历完毕,即得到数学等价的全局精确 Attention
- CP 的通信量理论怎么计算?和 TP 通信量对比谁更大?
- 理论计算:Ring Attention 为 P2P 通信,单次 Send/Recv 数据量为 2×S/cp×H,总计 cp−1 次,总数据量 2SH;Ulysses 的 All-to-All 单次数据量也约为 SH
- 与 TP 对比:TP 每次 All-Reduce 数据量为 2SH(包含一发一收)
- 理论上通信量大小相当,但由于 CP 是在长序列下开启,单次通信的绝对包体会非常巨大;此外 TP 通常在机内高带宽 NVLink 执行,而 CP 常跨节点跨交换机执行,受限于以太网 / IB 带宽高延迟,CP 的通信瓶颈感官上更显著
- 如何把 CP 的通信隐藏在计算里做重叠掩盖?
- 采用 Block-wise (分块) 异步流水线,将本地的序列块进一步划分为多个 sub-blocks
- 当 GPU 在执行第 k 步的 Q 与当前 , 的 FlashAttention 核心计算时,后台并发调用 NCCL Send/Recv 将当前的 , 传给下个节点,并接收 ,
- 只要计算耗时 ≥ 传输耗时,通信时间就被完全隐藏
- CP 的负载不均衡问题怎么解决?Zig-Zag Ring Attention 的原理?
- 不均衡原因:在 Causal LLM(自回归)训练中,Attention 矩阵是下三角的。排在前面的 GPU 处理的是序列早期的 token,需要看的历史 KV 很少(大量被 mask 掉),计算极快;排在后面的 GPU 处理序列末尾的 token,需要算全量 KV,计算极慢,导致严重的等待
- Zig-Zag 原理:将序列分块进行对折 / 首尾交错分配
- 假设有 8 个块和 4 张 GPU
- 不均衡的做法是 GPU0 拿 0,1,GPU3 拿 6,7
- Zig-Zag 则是让 GPU0 处理首(轻)和尾(重)的组合(如 Block 0 和 Block 7),GPU1 处理 1 和 6... 使得每张卡分到的总计算面积(下三角中的有效像素数)在数学上严格相等,从而达到完美的负载均衡
- MLP 如何进行切分序列的?
- CP 训练
- 推理阶段 CP 和训练阶段 CP 最大差异是什么?
- 训练阶段需要保留每一层的全量计算图激活和局部 KV 分块,用于反向传播计算梯度。通信包含前向和反向两轮
- 推理阶段(Prefill 阶段)类似前向,但不保存计算图激活
- Decode 阶段(逐 token 生成)时,由于每次只生成一个 token,序列长度变为 1,此时 CP 退化,主要面临的是如何切分和读取庞大的 KV Cache(通常转为 TP 或者分布式 KV 管理如 KVCache / Chunked PagedAttention),此时的通信主要是路由生成的 token 给其他卡
- 多卡 CP 序列分片负载不均衡怎么解决?
- 主要通过上述的 Zig-Zag Ring Attention(或 Striped Attention 交错切分策略),在物理分发数据时就将序列的头部和尾部分配到同一张卡上
- CP 会引入哪些数值误差、收敛风险?
- 由于数学上采用了严格等价的 Online Softmax 拼接策略,CP 理论上是无损的
- 微小误差来源:浮点数的结合律问题 —— 累加顺序的改变(比如按分块累加 vs 一次性全局累加)在 FP16/BF16 下会产生极微小的精度截断差异
- 收敛风险:通常风险极低 —— 但如果 CP 实现中的 dropout 随机种子同步错误,或者位置编码错位,会导致严重的收敛崩坏
- CP 分片后,位置编码 RoPE 怎么同步对齐?会不会旋转错位?
- 不会旋转错位,前提是必须注入全局绝对位置 ID
- 在 CP 中,输入序列被切分成了独立的物理 tensor
[s/cp];但在计算 RoPE 时,不能只用0 到 s/cp的本地索引,而必须根据 GPU 的 CP Rank(即局部序列在全局序列中的位置)来生成 绝对位置索引 (Position IDs) - 例如,全局序列 1000,2 卡 CP,卡 0 使用位置索引 0
499 计算 RoPE;卡 1 计算位置索引 500999 计算 RoPE,这样旋转角度严格按照全局相对位置计算,拼接时能够完美对齐
- 推理阶段 CP 和训练阶段 CP 最大差异是什么?
PP
-
基础问题
- 流水线并行 PP 核心解决什么问题?和 TP/DP 本质区别?
- 解决单卡放不下整堆 Transformer 层数(模型深度维度)的问题
- 把整模型按 Transformer 层切块,分到不同 GPU Stage,突破模型深度显存上限
- PP 按什么粒度切分?层间切分为什么不破坏计算依赖?
- PP 按 Transformer Encoder/Decoder 完整 Layer 做层间切分
- Transformer 天然串行前向依赖,Stage 之间只传递层出口 hidden state
- PP 天然的气泡 bubble 是什么?怎么产生的?
- 流水线空闲气泡:部分 GPU 处于等待、无计算、硬件闲置的时间间隙,直接降低硬件利用率、拉高训练时间
- 模型分层驻留不同 Stage,前向必须逐级串行流水传递 micro-batch,在训练首尾阶段:
- 启动阶段:最早 Stage 开始算,后面所有 Stage 空等
- 收尾阶段:最早 Stage 算完无事,后面 Stage 还在收尾
- 多 micro-batch 重叠计算也无法完全填满:不同 Stage 计算耗时不均 + 首尾填充间隙 → 固定比例气泡
- PP 组数越多,气泡占比越大,相应的缓解手段是增大 micro-batch 数量,虚拟流水线,overlap 通信
- PP 单卡显存省在哪?哪些显存无法通过 PP 降低?
- 模型权重显存降低,相对应的优化器状态和激活值也降低
- 无法降低单 Transformer 层内部权重 / 计算峰值
- PP 为什么会引入跨 stage 激活 / 梯度通信?通信内容是什么?
- 数据流强串行依赖:
- 前向:上一个 Stage 最后一层输出 → 下一个 Stage 第一层输入
- 反向:下游 Stage 梯度 → 回传给上游 Stage 做参数更新
- 分层驻留不同 GPU,数据不在一起,必须点对点传输
- 在前向计算时,通信内容是 [b,s,h] 大小的隐状态
- 在反向传播时,通信内容是对应的梯度
- 因此,PP 一般使用点对点的 Send/Recv
- 数据流强串行依赖:
- 你的系统里怎么进行划分的?会引入不均衡切分吗?
- 全局统计总 Transformer 层数 L,按 PP 组数均匀基础切分
- Embedding 统一放 第一 Stage,LM Head 统一放 最后 Stage
- 每层做 FLOPs profiling,不是纯按数量均分,而是按计算量负载均衡分配层
- 显然,如果按照 L 划分,天然会引入负载不均
- Embedding + 最后一层 Norm+LM Head:首尾 Stage 计算 / 显存天然更重
- 不同模型深层 / 浅层 Transformer FLOPs、KV 缓存占用不一致
- 普通均分「层数相等」≠「算力相等」
- 流水线并行 PP 核心解决什么问题?和 TP/DP 本质区别?
-
符号定义
- K:流水线 Stage 数(GPU 数)
- M:micro-batch 数量
- 设 和 分别表示单个 micro-batch 在单个流水线 stage 上的前向和反向计算时间(一个常见的简化假设是 )
- 理想情况下,用于纯计算的时间应该是
-
AFAB(GPipe)
-
什么是 AFAB 调度?流程怎么走?
- 先完成所有 micro-batch 的前向传播,再按逆序完成所有 micro-batch 的反向传播,最后统一更新参数
-
GPipe(AFAB)气泡率理论公式怎么计算?
-
前向阶段:第 1 个微批次到达设备 K 需要经过 K−1 个设备的传递,耗时 ,设备 K 完成所有 M 个微批次的前向传播还需要 ,因此前向总耗时为
-
反向阶段:同理,反向传播从设备 K 传回设备 1,最后一个微批次完成反向传播的总耗时为
-
总时间为
-
气泡总时间为
-
气泡占比为
-
从公式看出,要降低气泡占比,必须增大 N,但 AFAB 的致命缺陷是显存占用极大,因为必须等所有 N 个前向做完才做反向,设备必须在显存中缓存 N 份激活值
-
-
AFAB 为什么大 micro-batch 才能压低气泡?小 batch 性能差?
- 因为气泡主要来自于首尾填充 / 排空的固定开销,且根据气泡占比可得,当 M 越大时,气泡占比越小
- 在小 M 下,气泡占比很高,GPU 大部分时间空闲,而且通信 / 调度 overhead 占比飙升
-
AFAB 需要累积完所有 micro-batch 再更新吗?梯度一致性?
- 必须累积完所有 M 个 micro-batch 的反向,再统一更新参数
- 在前向阶段,只计算并缓存激活值,不更新;而在反向阶段,只计算梯度并本地累积,不更新
- 所有反向完成 → 梯度求和 → 一次 step 更新
-
AFAB 显存瓶颈为什么高?大量激活缓存怎么来的?
- AFAB 必须在前向阶段缓存所有 micro-batch 的所有 Stage 激活,直到反向完成才能释放
- 当流水线填满后,需要同时缓存 M 个 micro-batch 的激活值,单个 stage 的激活显存为 [M,B_micro,S,H]
-
-
1F1B
-
1F1B 调度核心逻辑是什么?one forward one backward?
- 交替调度微批次前向 & 反向,不让所有前向跑完再集中反向
- 预热填充(warm-up):依次把前若干 micro-batch 一路前向灌满流水线
- 稳态 1F1B 核心区,每时间步:
- 某些 stage 执行下一个 micro 的 Forward
- 另一些 stage 执行更早 micro 的 Backward
- F/B 高度重叠,不再像 AFAB 全体先 F 后 B
- 排空收尾(cool-down):不再新注入,逐个把存量 micro 走完反向
-
对比 AFAB,1F1B 气泡、显存、收敛各有什么优劣?
-
预热(Warm-up)与排空(Cooldown):设备 1 需要先做 K 个前向(注:严格来说是取决于设备位置,设备 i 会缓存 K−i+1 个激活值),才能等来第一个反向
-
实际总耗时:尽管调度顺序变了,但关键路径的总长度并没有改变,第一个微批次走到底并传回设备 1 需要 的时间,此后设备 1 还需要做剩下的 M−1 个 1F1B
-
气泡总时间为
-
气泡占比为
-
可以看出,1F1B 的气泡时间与 AFAB 完全一样,都是 ,但 1F1B 实现了显存的降维打击
-
1F1B 只需要同时缓存一个 micro-batch 的激活值
-
-
1F1B 如何实现梯度实时复用?不用等全部 micro 跑完?
- 流水稳态里,前面 micro 早已完成前向,可以提前启动反向
- 每个 stage 维护本地梯度累加 buffer:每跑完一个 micro 的 backward → 立刻把该 micro 梯度累加到总梯度,不需要等全部 micro 前向结束,不需要等所有 stage 都 Forward 跑完
- 等到整个 global batch 对应的所有 micro 全部反向收尾完成,一次性 optimizer.step
-
1F1B 在少 stage、多 micro 场景下气泡变化趋势?
- 固定 P(stage 数少),增大 M(micro-batch 变多),气泡绝对总量基本不变(气泡主要来自首尾 warmup+cool-down,由 P 决定),但总有效计算时间随 M 线性增加,气泡开销被大量稳态计算摊薄,占比快速下降
-
1F1B 为什么更容易做通信计算重叠?
- 时间轴上 F/B 分散交错,空闲时隙碎片化且持续存在,每时隙总有部分 Stage 在算、部分在等数据,天然藏可填充间隙
- 跨 Stage 是 P2P 点对点通信(相邻 stage send/recv),1F1B 稳态持续产生上下游隐状态传递,通信流量平滑连续
- 可流水线嵌套重叠:当前 micro backward 梯度通信,下一个 micro forward 激活计算 ,两者硬件资源互不抢占(流 / 队列隔离)
-
-
Interleaved
-
什么是 VPP/Interleaved PP?虚拟分段怎么做?
- 不增加物理 GPU(P),把模型切分为 V = P × v 个虚拟 stage(v 为每个 GPU 的虚拟分段数)
- 每个物理 GPU 不负责连续层,而是持有 v 个分散的虚拟 chunk,形成 “交错” 的层分布
-
为什么增大虚拟分段数能降低流水线气泡?底层原理?
-
耗时切分:因为每个虚拟块的层数变为原来的 1/v,所以处理每个微批次的前 / 反向时间也变成了 和 ,单个虚拟块的完整耗时为 ,流水线填满和排空的时间也相应缩短为原来的 1/v
-
气泡产生机制:流水线气泡只产生在流水线 “填满前” 和 “排空时”
-
在 Interleaved 模式下,第一个微批次只需要经过 K 个虚拟块(而不是 K 个完整 GPU 阶段)就能到达最后一个 GPU,耗时从 缩短为 ,同样地,反向传播的首个微批次也能更快地回传到第一个 GPU,因此首尾的气泡时间从 缩短为 ,大幅降低了气泡时间
-
实际总耗时:总耗时等于处理所有 M 个微批次的理想时间,加上流水线两端的绝对气泡时间:
-
气泡时间:
-
气泡占比:
-
可以看到,直接缩减气泡 ——Interleaved 1F1B 在物理层面直接将气泡体积缩小为原来的 1/v
-
在 M 相同的情况下,原本气泡占用了 的算力,现在只占用 ,例如 v=2 时,气泡直接减半
-
需要注意的是,这种优化是以增加 GPU 间的通信次数(通信量翻了 v 倍)为代价的,因此 v 不能无限大,通常 是计算与通信 tradeoff 后的最优解
-
-
VPP 可以无限增大吗?带来的显存、通信、调度代价是什么?
- 显存代价:需要积压更多未进行反向传播的微批次激活,由于流水线的差异而导致同时被依赖的激活总数变多
- 通信代价:传统 PP 中每个 micro-batch 仅需 P-1 次跨 GPU 通信(物理 stage 间),而 VPP 中每个 micro-batch 需 V-1 = P×v-1 次跨 GPU 通信(虚拟 stage 间)
- 调度代价:需管理 v×P 个虚拟 stage 的 F/B 时序,调度逻辑复杂
- 负载均衡更难:v 增大易导致不同虚拟 chunk 计算量不均,引入新气泡
-
怎么实现通信计算重叠的?
- 计算 - 通信时间轴完全错开:
- GPU0 虚拟 stage0 做 mb5 前向(计算)
- 同时 GPU0 虚拟 stage4 做 mb1 反向→向 GPU3 发梯度(通信)
- 计算 - 通信时间轴完全错开:
-
-
ZeroBubble
- ZeroBubble 核心优化思路是什么?如何逼近零气泡?
- 拆分反向传播为 B(输入梯度)+ W(权重梯度),用 W 填充流水线空洞,配合重计算与异步更新逼近零气泡
- 把单步 Backward 拆成两个独立阶段:
- B 阶段(Backward-Input):计算输入梯度(dx),强依赖下一个 Stage 的 B,是流水线关键路径
- W 阶段(Backward-Weight):计算权重梯度(dw),仅依赖当前 Stage 的 B 结果,无跨 Stage 强依赖,可灵活调度
- 逼近零气泡
- B 前置、W 后置填充气泡:B 沿流水线反向串行推进,W 被调度到原本空闲的气泡时段执行
- 预热最大化 F:在显存限制内,预热阶段尽可能多排 F,减少首 B 前的气泡
- 优化器异步更新:各 Stage 完成自身所有 W 后即可更新,无需全局同步,消除尾端气泡
- ZeroBubble 为什么依赖更多重计算?算力开销增加多少?
- W 后置 → 对应 micro 的激活需更长时间驻留显存,为控制显存,必须增加重计算比例,用算力换显存
- 典型:完全重计算(F 不存激活,B 时重算),激活显存降至最低,但算力开销最大
- ZeroBubble 气泡理论计算公式?适用场景限制?
- 气泡从 变为
- ZeroBubble 核心优化思路是什么?如何逼近零气泡?
-
DualPipe
- DualPipe 双流水线设计原理?两组 micro batch 交错执行?
- 首先,在进行层分配时,分配规则是 V 形对称交错,每个 GPU 持有 2 层,且与对面 GPU 形成镜像:
- gpu0:layer0(最浅层) + layer7(最深层)
- gpu1:layer1 + layer6
- gpu2:layer2 + layer5
- gpu3:layer3 + layer4
- gpu4:layer3 + layer4(与 gpu3 完全对称)
- gpu5:layer2 + layer5(与 gpu2 完全对称)
- gpu6:layer1 + layer6(与 gpu1 完全对称)
- gpu7:layer0 + layer7(与 gpu0 完全对称)
- 通过这样排布
- 正向流在左侧 GPU 忙时,镜像流在右侧 GPU 忙
- 正向流在右侧 GPU 忙时,镜像流在左侧 GPU 忙
- 完美填满传统单向流水线的首尾气泡,让 GPU 利用率从~58% 提升到~94%
- 首先,在进行层分配时,分配规则是 V 形对称交错,每个 GPU 持有 2 层,且与对面 GPU 形成镜像:
- DualPipe 如何互相掩盖前向 / 反向气泡?
- DualPipe 气泡率理论推导?相比传统 PP 优势?
- 因为是从两端向中间逼近,流水线产生气泡的 “深度” 从 K−1 直接被折半为 K/2-1
- 气泡变为
- DualPipe 显存暴涨的核心原因是什么?
- 从只需维护一份参数副本,变为需要维护两份参数副本
- 同时,单卡的激活驻留数从 PP 个增长至 PP+1(多了一个镜像流的 micro 激活)
- DualPipe 双流水线设计原理?两组 micro batch 交错执行?
-
PP 与其他并行
- ZeRO-1/2/3 阶段与 PP 配合起来有什么影响?ZeRO-1 与 PP 配合如何?注意区分 weight 和 input 的区别
- ZeRO-1+PP:仅优化器状态在 DP 组内切分,仅在参数更新时,ZeRO-1 组内执行 All-Gather 恢复完整权重,与 PP 跨 Stage 通信没有冲突
- ZeRO-2+PP:
- 对 weight 梯度(dw):PP Stage 内完成本地梯度计算后,需在 ZeRO-2 DP 组内执行 Reduce-Scatter 分片梯度
- 对 input 梯度(dx):PP 跨 Stage 传递 dx 时,dx 是完整张量,不受 ZeRO-2 影响
- PP 跨 Stage 传递 dx(P2P 通信)与 ZeRO-2 组内 Reduce-Scatter(集体通信)可能抢占带宽,需调度重叠
- ZeRO-3+PP
- 兼容性最差:PP Stage 内的层权重被 ZeRO-3 分片,前向 / 反向计算时需频繁 All-Gather 权重,与 PP 跨 Stage 通信交织,延迟暴涨
- 通信爆炸:
- 前向:PP Stage 内计算前需 All-Gather 本层权重,计算后传递 hidden 到下一个 Stage
- 反向:PP Stage 内计算后需 Reduce-Scatter dw,再传递 dx 到上一个 Stage
- 极致显存收益:权重 / 梯度 / 优化器状态全部分片,单卡显存最低,但工程复杂度极高,仅极端显存受限场景使用
- PP+TP 通信域如何划分?跨组同步会不会冲突?
- 无本质冲突:TP 通信仅在同 PP Stage 内的 TP 组执行,PP 通信仅在跨 Stage 的同 tp_rank GPU 间执行(例如 (0,0) ↔ (1,0))
- 潜在带宽抢占:
- 同卡同时参与 TP 集体通信(All-Gather)和 PP P2P 通信(Send/Recv),需通过 CUDA 流 / 通信队列隔离
- 工业实现中,会将 PP 通信与 TP 通信绑定到不同的 NCCL 通信组,避免集体通信与 P2P 通信的死锁
- PP 为什么无法缓解激活爆炸?只能靠重计算、CP 优化?
- PP 仅切分模型深度:每个 Stage 只持有部分层,但单 Stage 内的激活显存仍与层数量成正比
- PP 无法减少单卡同时驻留的 micro-batch 激活数:稳态下仍需保留 PP 个 in-flight micro-batch 的激活
- 例如,PP=4,单卡仍需保留 4 个 micro-batch 的激活,与 PP=1 时单卡保留 1 个完整模型激活相比,总激活显存仅减少 4 / 层数,无法解决根本问题
- 大模型超长序列下,PP+CP 组合的调度难点是什么?
- CP 切分导致 KV 缓存全局依赖:Attention 计算需完整 K/V,CP 组内需 All-Gather K/V,增加通信开销
- PP+CP 嵌套调度复杂度:PP Stage 间传递 hidden,CP 组内 All-Gather K/V,TP 组内 All-Gather 特征,三维通信交织
- 激活驻留峰值暴涨:PP 需保留 PP 个 in-flight micro-batch,CP 需保留 CP 个序列分片,组合后单卡激活驻留数 = PP×CP,极易 OOM
- ZeRO-1/2/3 阶段与 PP 配合起来有什么影响?ZeRO-1 与 PP 配合如何?注意区分 weight 和 input 的区别
混合精度训练
- 混合精度训练(FP16/FP8/BF16)的原理,为何能提升训练速度,数值不稳定性的解决方法(如梯度缩放 Gradient Scaling)
- 显存占用降低、Tensor Core 专用加速、访存压力变小
- DeepSeek FP8 训练的流程是什么?
梯度检查点
- 梯度检查点(Gradient Checkpointing)的核心思想,以空间换时间的实现逻辑,如何平衡显存占用和训练速度,你在项目中的调参要点
- 如何控制启用梯度检查点的粒度
综合
- 在大规模分布式训练中,TP,CP,DP,PP 的组织如何进行组织?
- DP 通过多卡并行数据提升吞吐,PP 解决模型过深单卡放不下的问题,TP 针对 hidden 过大、attention/MLP 过宽进行切分,CP 则用于缓解 context 过长导致的 attention O (n²) 压力
- activation checkpoint 怎么控制粒度?计算量大致增加多少?
- Full Checkpoint:对整个 Transformer Layer 做 checkpoint,只存输入 embedding + 最终输出,中间所有激活全部丢弃
- Block Checkpoint / Sub-layer Checkpoint:把一层拆成几块分别 checkpoint,比如
Attn → MLP拆成两段,每段内部丢弃激活,段间保留少量激活,平衡显存与重算开销 - Operation-wise / Selective Checkpoint:只对显存大、计算便宜的 op 做 checkpoint,例如 GELU、LayerNorm、dropout;而矩阵乘法这种计算重、显存小的不重算,显存节省略少,但重算开销极低
- Full activation checkpoint:计算量 ≈ 变为原来 1.3 ~ 1.5 倍
- batch 里序列长度方差大,怎么处理?
- 动态 padding / 微 batch(Micro-batch):同一个 batch 内只放长度相近的样本,避免一个超长序列把整个 batch 的 padding 拉爆
- Packed Sentences / Sequence Packing:把多个短句子打包塞进一个序列,共用一个 batch 维度,用 segment position id /attention mask 区分不同句子
- Padding 存在哪些问题
- 显存浪费
- 计算浪费(矩阵乘法照样算 0)
- 影响 kernel 效率(访存不连续)
- 影响 FlashAttention 块划分效率
- FlashAttention 对变长序列的优化
- 按实际长度分块
- padding 块直接跳过
- 不写回无效部分,变长序列收益比等长更大
- 如何用 Profiler 精确算出:访存 bound / 通信 bound / 计算 bound 比例 + GEMM/Attention TFLOPS 实测值
- 计算占比 (Compute %):GEMM、FlashAttention、fused 算子等
- 访存占比 (Memory %):显存读写、加载权重、张量拷贝
- 通信占比 (Comm %):AllReduce / ReduceScatter / AllGather / NCCL
- 算子有效 TFLOPS:GEMM、Attention 实际算力利用率
LLM 训练
预训练
-
自回归任务的预训练损失函数是什么?代码实现?
-
损失函数
-
对每个位置预测下一个 token,只对真实 token 算 loss,padding 不计,整体是平均 token 损失
-
实现细节
- 只对有效 token 算 loss,padding 位置 label 设为
-100(PyTorch CrossEntropyLoss 默认忽略) - label 右移一位,如 input:
[sos, 1,2,3,4],label:[1,2,3,4,eos] - 按 token 平均,不是按样本平均,保证长短序列对梯度贡献一致
- 一般不用 label smoothing(大模型预训练很少用)
- 只对有效 token 算 loss,padding 位置 label 设为
-
代码实现
import torch import torch.nn as nn import torch.nn.functional as F # 假设: # logits: [batch_size, seq_len, vocab_size] 模型输出 # labels: [batch_size, seq_len] 真实token def pretrain_loss(logits, labels, ignore_index=-100): # 1. 右移一位:预测下一个token shift_logits = logits[:, :-1, :].contiguous() shift_labels = labels[:, 1:].contiguous() # 2. 展平为 [B*T, V] 和 [B*T] loss = F.cross_entropy( shift_logits.reshape(-1, shift_logits.size(-1)), shift_labels.reshape(-1), ignore_index=ignore_index ) return loss -
其他细节
-
为什么不用 MSE?分类任务用 CE 更稳定,MSE 梯度小、收敛慢
-
预训练 loss 一般多少正常?初始化 loss ≈ ln (vocab_size) ≈ 10
11,收敛后 1.52.5 左右(取决于数据、规模) -
perplexity(困惑度)和 loss 关系
-
-
-
掩码语言模型(MLM)的损失函数是什么?
-
损失函数
-
只对被 mask 掉的 15% 位置算损失
-
SFT
-
监督微调损失函数是什么?代码实现?
-
本质依然是下一个 token 预测的交叉熵损失,和预训练损失数学形式完全一样
-
唯一区别是,SFT 只在回答(response)部分算 loss,问题(prompt)部分不算 loss,这就是所谓的 Teacher Forcing + 掩码式损失
-
损失函数
-
代码实现
import torch import torch.nn.functional as F def SFT_loss(logits, labels, ignore_index=-100): # 右移一位,预测下一个token shift_logits = logits[:, :-1, :].contiguous() shift_labels = labels[:, 1:].contiguous() loss = F.cross_entropy( shift_logits.reshape(-1, shift_logits.size(-1)), shift_labels.reshape(-1), ignore_index=ignore_index ) return loss
-
-
什么是隐式 KL 正则?SFT 作为基准为什么能约束更新幅度?
-
不直接限制参数变化,而是约束当前策略 πθ 与冻结参考策略 πSFT 的概率分布距离:
-
SFT 约束的是行为 / 生成分布的距离,通过 KL 散度能够保证在偏好对齐或强化学习训练过程中,不会遗忘已经监督微调得到的语言能力
-
防止 RL 会为了高奖励 乱改语言能力、忘记知识、胡说八道,出现 奖励黑客(reward hacking)
-
-
模型 SFT 到什么程度可以进行 RL?判断指标是什么?
- SFT 的目标是让模型学会指令遵循,可以通过相关指标判断
- SFT 性能进入边际收益递减:训练 / 验证 loss 很低且平稳,loss 不再明显下降
- Reward 分布有梯度、不均匀:用 SFT 模型对同一 prompt 采样 N 个(如 8~16)回复,用 Reward Model(RM)打分,如果分数有高有低,分布较宽则可以使用 RL 进行优化,否则 RL 没有奖励信号,没有进一步的优化空间
- pass@k 远大于 pass@1:模型会做,但每次不一定做得对,RL 能显著抬升 pass@1
- SFT 的目标是让模型学会指令遵循,可以通过相关指标判断
-
SFT 进入边际收益递减时,loss 为什么不会再下降了?SFT 不会过拟合吗?灾难性遗忘呢?
-
为什么 SFT 不行,非得用 RL 做偏好微调,直接构造一个符合人类偏好的微调数据集 SFT 不行吗?
- SFT 不知道「好到什么程度」,只知道「要输出这个」,只能学「标准答案」,没有强弱、没有梯度、没有对比
- SFT 无法惩罚 “差的”,只能学习 “好的”,不擅长理解和遵循不要做什么,而模型应该拉大优劣差距
- SFT 学不到 “隐式偏好”,只能学 “显性文本”:人类偏好很多是无法写成文本的,这些东西无法通过 “模仿文本” 学到,只能通过奖励信号学到
-
SFT 拒绝采样进行偏好优化的原理,相比 PPO 等 RLHF 算法有什么好处和缺点?
- 主要步骤
- 基础模型:以监督微调(SFT)后的模型为起点(具备基础指令能力)
- 批量采样:对每个输入提示(Prompt),用随机解码(Temperature/Top-p)生成 K 个候选回复(K=4~16)
- 奖励筛选:用训练好的奖励模型(RM)给所有候选打分,保留高分样本、丢弃低分样本(常见策略:取单 Prompt 最高分(Top-1)、全局 Top-K、或按 Reward 阈值截断)
- SFT 再训练:用筛选出的高质量(Prompt, Response)对,再次做标准监督微调(交叉熵损失)
- 本质是把 “偏好筛选” 转化为新的 SFT 数据集,用模仿学习拟合高分分布
- 优点:工程实现简单、训练稳定、显存 / 计算成本低、速度快、无奖励黑客问题
- 缺点
- 采样效率低、数据浪费大,低分样本完全丢弃,未利用负样本信息
- 优化上限低于 PPO,被动拟合已有的高分样本,探索能力弱
- 依赖 RM 质量与采样多样性,RM 不准确可能导致训练变差
- 偏好信号弱,没有梯度和惊喜的 quan'zhong
- 主要步骤
-
微调后模型出现复读机怎么排查?灾难性遗忘怎么解决?
-
SFT 够用 vs 必须用 DPO/RL 怎么判断
-
大模型幻觉怎么规避?
-
LLM 微调阶段(全量 / 增量 / 参数高效微调)常见不稳定性问题及解决
- 微调过拟合 / 灾难性遗忘:微调数据量小 / 分布单一;预训练知识被微调任务覆盖;参数高效微调(PEFT)中适配器学习能力不足
- 训练震荡 / 收敛缓慢:学习率过大 / 调度不合理;批次大小(Batch Size)过小;数据分布异质;优化器选择不当
- 生成结果不稳定(重复生成 / 逻辑混乱 / 模式崩溃):微调任务与预训练任务差异大;解码策略不当;训练时未做生成式验证;损失函数单一
- 参数高效微调(PEFT)效果不稳定:LoRA 秩设置不合理;适配器与主模型融合差;仅在注意力层加适配器
-
SFT 和 RL 训练的区别
-
SFT 的目标函数
-
PPO/GRPO 的目标函数
-
其中,优势函数是策略的黑箱函数,会随着策略变化而变化,每更新一步,所有样本的优势都要重新计算
-
同时,优势函数是不可导的,只能通过采样 + 估计得到
-
因此,RL 优化本身是一个带 “动态、有噪、自相关目标” 的学习,不是在优化固定目标,而是在优化一个边跑边变、且只能采样观测的目标
-
目标不固定、只能采样估计没有解析形式、数据分布不断变化等原因,导致 RL 训练是不稳定的
-
RLHF
- RLHF 中一共需要维护几个模型
- Actor Model:待训练更新,生成 action/response,是主模型
- Critic Model:评估当前状态价值,计算广义优势 GAE,指导 Actor 更新
- Reward Model:偏好对齐专属,输入 chosen/rejected 序列输出标量奖励,冻结或轻量微调,提供外部奖励信号
- Reference Model:一般冻结 SFT 模型,用于计算 KL 散度正则,约束 Actor 不要偏离人类基座太远,防止崩坏、灾难性遗忘
- SFT
- SFT 本质不是为了 “对齐”,而是为了把策略初始化到一个合理分布,否则 RL 会直接崩掉
- 给 policy 一个 strong prior,让它一开始就在 “像人类说话” 的区域里,而不是在整个 token 空间乱探索
- SFT 实际上是在做一个强 prior,让 policy 已经具备基本对话能力、指令跟随能力以及合理的语言结构,这样 RL 阶段只需要做 “偏好微调”,而不是从零学行为
- Reward Model
- 它的作用是把人类的偏好(通常是 pairwise preference,比如 A 比 B 好)拟合成一个标量函数 r (x, y),这个函数本质上是在近似 “人类会更喜欢哪个回答”
- RM 不是在判断对错,而是在学习 “排序信号”,因此它更像一个 ranking model,而不是分类器
- Critic Model(Value Model)
- 在 PPO 阶段引入的,它学习的是状态价值 V (s),用来估计 “当前这个前缀继续下去大概能拿多少 reward”
- 它的核心作用不是提高性能,而是降低梯度方差,让训练稳定,否则 RL 在长序列下几乎不可用
- 同时,Value Model 不只是 “降方差工具”,它本质上就是在做 credit assignment 的近似
- 在 PPO 中,Value Model 通过估计每个 state 的期望回报,将全局 reward 转换为每个时间步的 advantage,从而实现一种 “隐式的 credit assignment”
- 但这种 credit assignment 是粗粒度的,无法精确定位到具体 token 或动作的贡献
- Reference Model
- 通常就是 SFT 模型的冻结版本,它的作用不是参与训练,而是提供一个 “行为锚点”,用于 KL 约束,防止 policy 在 RL 过程中偏离人类语言分布太远
- PPO
- 模型生成回答(rollout),Reward Model 给分,同时计算与 Reference Model 的 KL penalty,得到一个综合 reward,然后通过 PPO 更新 policy
- 这里其实是在做一个受约束的优化问题:既要提高 reward,又不能偏离 reference 太多
- RLHF 最大瓶颈是算力还是数据?
- 在早期阶段,瓶颈更偏向数据,尤其是高质量的人类偏好数据,因为 reward model 的上限直接决定 RL 的上限,reward 学歪了,RL 再强也只是 “优化错误目标”
- 但在实际大规模系统中,瓶颈往往转向算力,因为 RLHF 的训练范式极其低效:每一步都需要 rollout,而 rollout 本质是 inference,且无法像预训练那样高效并行和复用
- 更关键的一点是:RLHF 的 sample efficiency 很低,很多 token 是 “用来探索而不是学习” 的,所以单位算力产出的有效信息远低于 pretraining
- 因此在工业界,常见现象是 “算力成为直接瓶颈,而数据成为上限瓶颈”
- RLHF 三阶段(SFT→Reward Model→PPO)每阶段核心痛点是什么?
- SFT:优质标注数据稀缺、噪声较大;容易过拟合、泛化性差;容易导致灾难性遗忘
- Reward Model:偏好成对标注不一致、标注偏见;奖励坍缩,RM 区分度下降、输出分布集中
- PPO:奖励过拟合、分布漂移、训练不稳定
- LLM 中 PPO 的落地细节:如何构建奖励函数、价值网络的训练技巧、批次大小 / 学习率等超参调优
- Value 的学习率要比 Policy 的学习率大 2~5 倍
- clip_ratio = 0.1 ~ 0.2
- KL 惩罚系数 β 初始化:
0.05 ~ 0.1
- PPO 在 LLM 后训练中的痛点(如模式崩溃、奖励函数偏移、训练不稳定),你在项目中是如何解决的
- 为什么大模型对齐不用 DQN,而用 PPO?
- LLM 是连续高维动作空间(token 分布),DQN 这类价值方法不适用(DQN 是离散动作、低维动作空间的算法,只能输出有限个离散动作)
- 而且对齐需要稳定、安全、可控的策略更新,PPO 比 DQN 更适合大模型微调
- 为什么有了 llm as judge 还需要单独训练 reward model?
- 算力与延迟的限制
- LLM-as-Judge:如果直接用 Gemini-Pro 这类 70B + 的大模型作为 Judge,RL 训练过程中数百万次的推理调用会带来极高的 GPU 成本和延迟,直接卡死训练流程
- Reward Model:RM 通常是比 Policy Model 小得多的轻量模型(如 7B 甚至更小),只需一次前向传播就能输出标量奖励,速度快、吞吐量大,能满足 RL 内循环对低延迟、高吞吐反馈信号的需求
- 判别式 vs 生成式的能力差异
- LLM-as-Judge:本质是通过 Prompt 激发生成能力来打分,容易受到位置偏见(倾向选前面的答案)、长度偏见(倾向选长答案)影响;且输出通常是离散评分或文本判断,梯度信号不够平滑
- Reward Model:通过 Pairwise Ranking Loss 专门训练的判别式模型,会被强制拉大不同答案间的分数差距;输出连续实数值奖励,是数学上更友好的密集信号,对梯度下降更有利,在训练覆盖的分布内,排序和打分能力更敏锐
- OOD 悖论与业界折中方案
- RM 的弱点:RM 仅在有限偏好数据上训练,当 Policy Model 探索到分布外(OOD)的高分区域时,容易出现误判(如被 Reward Hacking 攻击)
- LLM-as-Judge 的优势:通用大模型见多识广,对 OOD 数据的鲁棒性更强,能识别出 “看似符合格式但毫无意义” 的内容
- RLAIF
- 用 LLM-as-Judge 标注大量偏好数据;
- 用这些数据训练轻量级 RM;
- 用 RM 跑 PPO 等 RL 算法;
- 定期用 LLM-as-Judge 检查 RM 质量,发现问题就补充数据重训 RM
- 算力与延迟的限制
奖励模型
-
Reward Model 怎么训练?损失函数是什么?
-
数据:使用同一个 prompt,让模型生成多个不同回复,由标注者对这些回复进行质量排序,得到偏好数据:,表示 优于
-
模型:一般基于预训练语言模型微调,在最后一层增加一个线性层,将隐状态映射为单个标量 r,作为该回复的奖励分数
-
训练目标:学习一个奖励函数 ,使得对于更好的回复 ,奖励高于更差的回复
-
损失函数(成对排序损失)
- 为 Sigmoid 函数
- 是模型对优质回复 的奖励
- 是对劣质回复 的奖励
- 期望表示在偏好数据集上取平均
-
训练细节
- 训练时冻结大部分底层 Transformer 层,只微调顶层和奖励头
- 避免奖励分数过大或过拟合,通常会加入权重衰减等正则化
- 训练好后 RM 固定参数,用于后续 PPO 阶段提供奖励信号
-
-
奖励模型的训练流程(偏好数据收集→成对排序训练→奖励分数预测),损失函数(如交叉熵、对比损失)选择?
-
用 SFT 模型初始化 RM,在最后加一个线性层输出 scalar score:
- 输入整个对话序列
- 取 最后一个 token 的 hidden state 过线性层
- 输出一个标量
r(x, y):越高表示回答越好
-
目标是让模型满足:
-
因此需要通过损失函数拉大同分差,训练 RM,一般使用成对交叉熵损失(Pairwise CE Loss)
- = chosen 奖励
- = rejected 奖励
- 是 sigmoid,把差值映射到 0~1
-
代码实现
import torch import torch.nn.functional as F def rm_pairwise_loss(rw: torch.Tensor, rl: torch.Tensor): # rw: [B] chosen reward # rl: [B] rejected reward logits = rw - rl loss = -F.logsigmoid(logits).mean() return loss -
Margin Loss(间隔损失,Hinge 版)
- 要求差值至少大于 1,否则惩罚,更鲁棒、梯度更温和,但不如 pairwise CE 常用
-
为什么不用普通交叉熵?RM 不是分类任务,是偏好排序任务,没有固定类别,只有相对好坏
-
为什么不用 MSE?MSE 拟合绝对分数,而偏好只关心相对顺序,pairwise loss 更直接
-
奖励分数范围?一般在 -5 ~ +5 左右,取决于归一化方式
-
RM 怎么避免 collapsed?数据多样性、权重衰减、梯度裁剪、只训顶层、归一化 reward
-
-
奖励模型的对齐偏差问题(如奖励稀释、人工标注偏好与实际业务需求不一致),如何缓解?
- 对齐偏差本质:RM 学到的奖励分布 ≠ 真实业务想要的偏好,典型表现就是奖励稀释、标注不准、目标跑偏
- 解决奖励黑客:精细化多维度标注、多目标奖励模型
- 解决人工标注与业务不一致:偏好蒸馏、领域自适应微调
-
奖励模型(RM)训练阶段核心对齐偏差 / 不稳定性问题及解决
- 奖励稀释(Reward Dilution)
- 人工标注偏好与实际业务需求不一致
- 奖励模型过拟合 / 偏好偏移:偏好对(chosen/rejected)数据量小;数据分布单一;训练时仅用成对排序损失,缺乏泛化约束
- 奖励模型分数崩塌(Score Collapse):奖励模型对所有输出的打分趋于一致,无法有效区分优劣;损失函数设计不合理(如仅用交叉熵排序)
- 奖励模型的信用分配偏差:多步决策 / 长文本任务中,奖励模型无法准确定位 “优质 / 低质” 的关键步骤,将整体奖励归因到错误环节
PEFT
-
LoRA 介绍,LoRA 的缺点是什么?后续有什么改进?LoRA 为什么有效?
- 冻结预训练模型权重 W,仅训练低秩矩阵 A、B 模拟权重更新 ,其中秩 (d、k 为原权重维度)
- 前向传播 ( 为缩放系数)
- 权重更新:( 为缩放系数)
- 参数量:仅 ,通常为原模型的 0.01%~1%
- 初始化
- A:高斯随机初始化(均值 0)
- B:零初始化 → 训练初期输出与原模型一致,避免破坏预训练知识
- 为什么有效
- 低秩假设成立:大模型权重更新 ΔW 本身是低秩的,仅需少量维度即可捕捉任务关键特征
- 保留通用能力:冻结主权重,不破坏预训练知识,避免灾难性遗忘
- 训练稳定:B 零初始化 + 缩放系数,梯度更平稳,不易爆炸 / 消失
- 推理无开销:训练后可合并
W + (α/r)·B·A,推理速度与原模型一致 - 多任务灵活:一个基座可挂载多个 LoRA,快速切换任务
- 缺点
- 表达能力受限(核心缺陷):秩 r 太小易欠拟合,太大则失去参数效率优势
- 超参数敏感、调参成本高:核心超参(r、α、目标层)需随任务调整,无通用最优值;初始化策略(A/B 初始化方式)影响收敛与最终效果
- 主要适配 Transformer 线性层(q_proj、v_proj 等),对非线性层(LayerNorm、激活)支持差
- 同等精度下,迭代步数比全量微调多 20%~30%
-
什么时候用 LoRA?LoRA 可以调整哪些超参数?LoRA 的 rank 怎么选?缩放系数是缩放什么?
-
超参数列表
参数 含义 推荐范围 经验法则 lora_rank 低秩矩阵维度,决定表达能力与参数量 4~64 简单任务 8 16;复杂任务 3264lora_alpha 控制更新强度, scaling = α/r2r~4r 常用 α=2r(如 r=8→α=16) target_modules LoRA 作用的层 q_proj、v_proj 优先注意力层,可扩展至 FFN 学习率 LoRA 专用学习率 1e-4~1e-3 比全量微调大 10~100 倍 dropout 防止过拟合 0.05~0.1 数据少则调高 -
rank 怎么选
- r 太小:欠拟合,无法捕捉复杂特征(如 r=2 时风格迁移完全失效)
- r 太大:参数量激增,性价比下降(r>64 后收益极小)
- 先设 r=8/16 快速验证任务可行性
- 若欠拟合(效果差)→ 增大 r
- 若过拟合(训练好、测试差)→ 减小 r 或加 dropout
-
缩放系数
- 缩放对象:低秩更新 B・A 的幅度,公式 ΔW = (α/r)・B・A
- 核心作用:平衡梯度大小,避免训练不稳定
- 推荐:α = 2r(α/r=2),在多数任务上效果最优
- 极端场景:任务难、更新弱 → 调大 α;任务简单、更新过强 → 调小 α
-
-
改进方法?
- 动态秩与自适应(解决固定秩缺陷)
- AdaLoRA:按层动态调整 r,用奇异值分析自动分配秩,资源利用率提升 20%+
- 动态剪枝 LoRA:训练中逐步剪除非关键维度,保留核心信息,参数量再降 50%
- 结构增强(提升表达能力)
- DoRA(权重分解):将权重拆分为幅度(magnitude)+ 方向(direction),分别学习,效果接近全量微调
- Dual LoRA:用 4 个低秩矩阵,解耦更新幅度与更新方向,复杂任务性能显著提升
- PiSSA(主奇异值适配):用预训练权重主奇异成分初始化 A,收敛更快、效果更稳
- MiLoRA(次成分适配):用次要成分初始化,更好保留通用知识
- 效率极致化
- QLoRA + LoRA:4/8 比特量化基座,LoRA 全精度训练,显存再降 70%+
- 稀疏 LoRA:块稀疏 / 重要性剪枝,推理速度提升 35%,内存减 40%
- 动态秩与自适应(解决固定秩缺陷)
-
为什么现在更多用 LoRA,而不是 prefix tuning
- LoRA:直接修改权重空间,能学到强任务特征、复杂领域知识、风格 / 格式迁移
- Prefix Tuning:只在上下文 / 特征空间加偏置,表达上限低,对 prefix 长度、初始化、学习率极敏感,容易不收敛、震荡、效果波动大;同时有额外的推理开销