训练

整理各种面试中常见的八股问题,涵盖 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 层参数量相加:

    h2+h2+h2+h2+h+h+4h2+4h+4h2+h+h+h =12h2+10hh^2+h^2+h^2+h^2 + h+h + 4h^2+4h+4h^2+h+h+h \ = 12h^2 + 10h
  • 若有 L 层 Transformer,则 L 层总参数量为:

    L∗(12h2+10h)L * (12h^2 + 10h)
  • 模型中除 Transformer 层外,还包含 embedding 层和解码层,需单独计算:

    • embedding 层:将 token ID 映射为隐层向量
      • 权重维度:[vocab_size,h][vocab\_size, h]
      • 参数量:vocab_size×hvocab\_size \times h
    • 解码层(语言模型头):将隐层向量映射回词表概率
      • 权重维度:[h,vocab_size][h, vocab\_size]
      • 参数量:vocab_size×hvocab\_size \times h
      • 注:解码层通常与 embedding 层权重共享,实际仅计算一次
  • 整个模型总参数量 —— 结合 Transformer 层、embedding 层(权重共享):

    L∗(12h2+10h)+vocab_size×hL * (12h^2 + 10h) + vocab\_size \times h
  • 当模型层数 L 较大、隐层维度 h 较大时,12Lh212Lh^2 占绝对主导,可简化估算为:

    12Lh212Lh^2
  • 核心规律:

    • 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 层激活值及显存占用

    计算项维度显存大小(字节)
    Q=x⋅WQQ=x\cdot W_Q[b,a,s,ha]=[b,s,h]2bsh
    K=x⋅WKK=x\cdot W_K[b,a,s,ha]=[b,s,h]2bsh
    V=x⋅WVV=x\cdot W_V[b,a,s,ha]=[b,s,h]2bsh
    Q⋅KTQ\cdot K^T[b,a,s,s]2bas^2
    softmax(Q⋅KT)\text{softmax}(Q\cdot K^T)[b,a,s,s]2bas^2
    Dropout[softmax(Q⋅KT)]\text{Dropout}[\text{softmax}(Q\cdot K^T)][b,a,s,s]bas^2
    xself=Dropout[...]⋅Vx_{self} = \text{Dropout}[...] \cdot V[b, a, s, ha] = [b, s, h]2bsh
    xself⋅Wox_{self}⋅Wo[b,s,h]2bsh
    Dropout(xself⋅Wo)\text{Dropout}(x_{self}⋅Wo)[b,s,h]bsh
    xattn=LN[...]+xx_{attn} = \text{LN}[...] + x[b, s, h]2bsh
  • FFN 层激活值及显存占用

    计算项维度显存大小(字节)
    xattn⋅Wff1x_{attn}⋅W_{ff1}[b,s,4h]8bsh
    GeLU(xattn⋅Wff1)\text{GeLU}(x_{attn}⋅W_{ff1})[b,s,4h]8bsh
    xffn=GeLU(...)⋅Wff2x_{ffn}=\text{GeLU}(...)\cdot W_{ff2}[b,s,h]2bsh
    Dropout(xffn)\text{Dropout}(x_{ffn})[b,s,h]bsh
    LN[Dropout(xffn)+xattn]\text{LN}[\text{Dropout}(x_{ffn})+x_{attn}][b,s,h]2bsh
  • 单 Transformer 层总显存 —— 将 MHA 和 FFN 层显存相加:

    2bsh+2bsh+2bsh+2bas2+2bas2+bas2+2bsh+2bsh+bsh+2bsh+8bsh+8bsh+2bsh+bsh+2bsh=34bsh+5bas22bsh+2bsh+2bsh+2bas^2+2bas^2+bas^2+2bsh+\\ 2bsh+bsh+2bsh+8bsh+8bsh+2bsh+bsh+2bsh = 34bsh + 5bas^2
  • 若有 L 层 Transformer,则 L 层总中间激活值显存为:

    l∗(34bsh+5bas2)l * (34bsh + 5bas^2)
  • 模型中除 Transformer 层外,还包含 embedding 层和解码层,其中解码层无中间激活值,仅需分析 embedding 层:

    • embedding 层功能:将输入的 token ID 转为向量
    • 输出矩阵维度:[batch_size,seq_length,hidden_size]=[b,s,h][batch\_size, seq\_length, hidden\_size] = [b, s, h]
    • 显存占用:2bsh2bsh 字节
  • 整个模型的中间激活值总显存 —— 结合 Transformer 层和 Embedding 层,整个模型的中间激活值显存为:

    l∗(34bsh+5bas2)+2bshl * (34bsh + 5bas^2) + 2bsh
  • 当模型层数 L 较大时,可忽略 embedding 层的 2bsh2bsh,直接用 l∗(34bsh+5bas2)l∗(34bsh+5bas^2) 估算

  • 对于短序列(s≪hs \ll h)而言,bsh≫bas2bsh \gg bas^2,FFN / QKV activation 占主导

  • 对于长序列(s≫hs \gg h)而言,bsh≪bas2bsh \ll bas^2,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 三个线性层,共计 3×2×b×s×h×h=6bsh23×2×b×s×h×h=6bsh^2
        • 输出投影线性层,共计 2bsh22bsh^2
        • Q@K、Attn@V,共计 2×2×b×a×s×d×s=4bs2h2×2×b×a×s×d×s=4bs^2h
        • 因此共计 8bsh2+4bs2h8bsh^2+4bs^2h
      • FFN 层(线性层 ->GELU-> 线性层):2bs∗h∗4h+2bs∗4h∗h=16bsh22bs*h*4h+2bs*4h*h=16bsh^2
      • 单层 Transformer 的 FLOPS 为 24bsh2+4bs2h24bsh^2+4bs^2h
    • 前向计算的 FLOPS 共计 L⋅(24bsh2+4bs2h)L⋅(24bsh^2+4bs^2h)(当 s 远小于 h 时,有 24bsLh224bsLh^2)
    • 反向传播的 FLOPS 一般为前向的两倍,也即总 FLOPS 为 L⋅(72bsh2+12bs2h)L⋅(72bsh^2+12bs^2h)
    • 模型的总 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
  • 理论 FLOPS 计算

    • 单卡 4090,bfloat16 Tensor Core 理论峰值:≈ 165 TFLOPs
    • 4 张 GPU 总峰值:660 TFLOPs
  • MFU 为

    MFU=L⋅(72bsh2+12bs2h)⋅Gradient Accum Steps⋅NDP⋅Through output4⋅Single GPU TFLOPS≈34%\text{MFU}=\frac {L\cdot (72bsh^2+12bs^2h)\cdot \text{Gradient Accum Steps}\cdot N_{DP}\cdot\text{Through output}}{4\cdot\text{Single GPU TFLOPS}}\approx 34\%

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 永远不触发
        • 或错误触发,导致梯度数据错乱、部分未同步
      • 如果大量参数都大于桶大小,会导致每个大参数单独一个桶,通信次数变多,启动开销变高,速度变慢
  • 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 依次发送分片,最终所有卡都得到完整张量
  • 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 倍放大
  • 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 倍
  • 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) 轮点对点收发
  • Ring AllReduce 相比 Tree AllReduce 优势和劣势?什么数据量下自动切换?

    • Ring AllReduce 的每个节点收发负载完全对称,大规模带宽饱和效果好
    • 在大规模场景下,带宽开销主导,Ring 流水线吞吐更高
    • 小消息(一般 < 256KB ~ 1MB):切 Tree/Star AllReduce,规避 Ring 线性 hop 延迟,压低 latency
    • 大消息( > 1MB,梯度大桶):切 Ring AllReduce,打满带宽、流水线掩盖传输开销
  • Ring AllReduce 的 latency & bandwidth 理论计算公式?

    • 符号定义

      • α\alpha:单条点对点通信启动时延 (latency,s)
      • β\beta:单位数据传输反比,β=1/\beta=1/ 单链路带宽 (s/Byte)
      • SS:总数据量 (Byte)
      • NN:通信进程 / GPU 数量
    • Ring AllReduce 理论总时间

      • 每轮搬运数据量:S/NS/N

      • 总时延 = 启动时延总和 + 数据传输时延总和

        Tring=2(N−1)α+2(N−1)⋅β⋅SNT_{\rm ring} = 2 (N-1)\alpha + 2 (N-1)\cdot \beta \cdot \frac {S}{N}
      • 化简近似(N 大):

        Tring≈2Nα+2βST_{\rm ring} \approx 2N\alpha + 2\beta S
      • 时延项:随节点数线性涨

      • 带宽项:和节点数无关,只和总数据成正比(核心优势)

    • Tree AllReduce 理论总时间

      Ttree=2⌈log⁡2N⌉ α+2βST_{\rm tree} = 2\lceil \log_2 N \rceil \,\alpha + 2\beta S
      • 时延项:对数增长,小 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

    • 全局平均梯度:

      gˉ=1N∑rank=1Ngrank\bar g = \frac {1}{N}\sum_{rank=1}^N g_{rank}
    • grank:每张 GPU 本地产生的该参数梯度

    • Adam 一阶动量 / 二阶动量(逐元素):

      mt=β1mt−1+(1−β1)gˉ vt=β2vt−1+(1−β2)gˉ 2m_t = \beta_1 m_{t-1} + (1-\beta_1)\bar g\ v_t = \beta_2 v_{t-1} + (1-\beta_2)\bar g^{\,2}
    • 参数步进更新:

      wt+1=wt−η⋅mtvt+ϵw_{t+1} = w_t - \eta \cdot \frac {m_t}{\sqrt {v_t}+\epsilon}
    • 每一个参数 w 的更新,只依赖自己对应的那一个全局平均梯度 gˉ\bar g,不同参数之间解耦、互不干扰

    • 把全局模型参数按区间切分为 N 份:

      W=[W0,  W1,  …,  WN−1]W = [W_0,\;W_1,\;\dots,\;W_{N-1}]
    • GPU k 仅托管分片 Wk 常驻该分片的

      • FP32 master weight Wk,fp32
      • Adam 动量 mkm_k、二阶方差 vkv_k
    • 由上面逐元素独立原理更新 WkW_k,不需要其他分片的参数、不需要全局完整梯度,只需要 WkW_k 对应的全局聚合梯度分片 gˉk\bar g_k

  • 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 预取 + 计算重叠,掩盖反向通信延迟
  • 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 效果
  • 什么是 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梯度 AllReduce4M
    ZeRO-1梯度 AllReduce + 权重 AG6M
    ZeRO-2梯度 RS + 权重 AG4M
    ZeRO-3前向 AG + 反向 AG + 梯度 RS6M
  • 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 显存中:本地分片全局梯度 gˉshard\bar g_{\text{shard}}
        • 发起异步 D2H 拷贝:把该分片梯度从 VRAM → CPU DRAM
        • 拷贝期间 GPU 可预调度下一轮 / 清理临时缓存,不等待
      • 第二步:CPU 侧精度提升,即 FP16/BF16 梯度 → FP32

        • CPU 等待梯度分片就位
        • 执行类型强制转换:gfp32=cast(gfp16)g_{\text{fp32}} = \text{cast}(g_{\text{fp16}})
        • 匹配 CPU 上的 FP32 优化器状态精度(避免混合精度误差)
      • 第三步:CPU 内存执行 AdamW 完整数学更新

        • CPU 侧常驻 wmaster(fp32),m,vw_{\text{master}}(\text{fp32}), m, v,逐元素独立更新:

        • 动量平滑:

          mt=β1mt−1+(1−β1)gfp32m_t = \beta_1 m_{t-1} + (1-\beta_1) g_{\text{fp32}}
        • 二阶平方平滑:

          vt=β2vt−1+(1−β2)gfp322v_t = \beta_2 v_{t-1} + (1-\beta_2) g_{\text{fp32}}^2
        • 偏差校正 + 参数步进 + 权重衰减

          wmaster,t+1=wmaster,t−η⋅m^v^+ϵ−λww_{\text{master},t+1} = w_{\text{master},t} - \eta \cdot \frac {\hat m}{\sqrt {\hat v}+\epsilon} - \lambda w
        • 全程在 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 小包风暴
    • 哪些张量支持卸载、哪些严禁卸载?混合卸载策略怎么配置最优?

      • 支持安全卸载
        • 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 必须先 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,而梯度累积只增加迭代次数,不改变单微批输入尺寸
  • 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 和 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)
      PCIe32 GB/s微秒级次优,仅应急
      以太网 / RoCE(跨机)~100–200 GB/s微秒–毫秒级仅 DP/PP,不适合 TP
    • 综上,最佳实践是:TP 仅用单机内 NVLink;跨机并行用数据并行(DP)+ 流水线并行(PP),通信量小、可异步重叠,效率更高

  • 如何切分

    • 能够进行切分的数学意义?为什么需要进行 all-reduce 通信?

      • 设 Y=X⋅WY=X\cdot W,则
      • 按照列切分:W=[W1,W2],XW=[XW1,XW2]W=[W_1,W_2], XW=[XW_1,XW_2],结果需要拼接
      • 按照行切分:W=[W1W2],XW=X1W1+X2W2W=\begin {bmatrix} W_1\\W_2\end {bmatrix}, XW = X_1W_1+X_2W_2​,结果需要求和
      • 本质是线性变换可按维度解耦拆分计算,非线性操作不可随意拆分
      • 由此可知,对于列并行,需要进行 all-gather 拼接;对于行并行,需要 all-reduce 求和
    • QKV 投影和 attn 输出投影怎么进行切分的?对于 GQA/MQA 情况下切分有什么影响?

      • 对于标准的 MHA,设隐藏层维度为 h,头数为 N,则满足单头维度 =h//Nh//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 通信

        Y=GELU(X[A1 A2])[B1B2]=GELU([XA1  XA2])[B1B2]=[GELU(XA1)  GELU(XA2)][B1B2]=GELU(XA1)B1+GELU(XA2)B2\begin {align*} Y &= \text{GELU}(X [A_1\ A_2]) \begin {bmatrix} B_1\\B_2\end {bmatrix} \\ &= \text{GELU}([XA_1\ \ XA_2]) \begin {bmatrix} B_1\\B_2\end {bmatrix} \\ &= \big [\text{GELU}(XA_1)\ \ \text{GELU}(XA_2)\big] \begin {bmatrix} B_1\\B_2\end {bmatrix} \\ &= \text{GELU}(XA_1) B_1 + \text{GELU}(XA_2) B_2 \end {align*}
      • A 行切 + B 列切的完整公式 —— 必须在 GELU 之前做 all-reduce

        Y=GELU((X1 X2)[A1A2])[B1 B2]=GELU(X1A1+X2A2) [B1 B2]=[ GELU(X1A1+X2A2)B1   GELU(X1A1+X2A2)B2 ]\begin {align*} Y &= \text{GELU}\big ((X_1\ X_2)\begin {bmatrix} A_1\\A_2\end {bmatrix} \big) [B_1\ B_2] \\ &= \text{GELU}(X_1A_1 + X_2A_2)\ [B_1\ B_2] \\ &= \big [\ \text{GELU}(X_1A_1 {+} X_2A_2) B_1 \ \ \ \text{GELU}(X_1A_1 {+} X_2A_2) B_2\ \big] \end {align*}
    • LayerNorm 为什么不做 TP 切分?LN 层的前向和反向需要 all-reduce 吗?

      • LayerNorm 计算公式

        LN(x)=x−E[x]Var(x)+ε⋅γ+β\text{LN}(x) = \frac {x-\mathbb E [x]}{\sqrt {\text{Var}(x)+\varepsilon}}\cdot \gamma+\beta
      • 均值、方差是全局样本 / 全局隐藏维统计量,它是逐个 token 进行统计的

      • 如果按维度 TP 切分,单卡局部统计≠全局统计,归一化彻底失效

      • γ\gamma 和 β\beta 是全局缩放偏移参数,拆分无数学意义

    • 输入层和输出层如何进行切分?

      • 输入层对词表按照行进行切分,每张卡分别查询词向量,能够找到的词返回对应的词向量,如果找不到就都置为 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
    • 损失的计算过程

      • 标准单样本离散交叉熵:

        L=−log⁡p(y)=−log⁡(ezy∑v=1Vezv)\mathcal {L}=-\log p (y)= -\log\left (\frac {e^{z_y}}{\sum_{v=1}^V e^{z_v}} \right)
      • z∈RV 表示全局 logits,y 真实标签(单个索引号)

      • TP 按词表把 z 切成多片 z1,z2,…,ztpz_1,z_2,\ldots,z_{tp},而真值标签 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(保证正则化多样性)
  • 通信

    • 每行 / 每列切分后,需要哪些集合通信?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
  • 显存

    • 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:每层前向 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 累加舍入误差

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)
  • 通信
    • 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 计算时间复杂度仍为 O(S2)O (S^2),并没有随着卡数 p 的增加而减少
      • KV 显存依然爆炸:每个 GPU 在 Attention 内部都需要短暂持有或重组完整的全局 K 和 V(即 B×S×H),在超长上下文(如 100K+)下,即便有 TP,显存依然会 OOM
  • 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)),彻底消除了 O(S2)O (S^2) 带来的单卡显存爆炸
  • 引入 CP 后
    • MLP 如何进行切分序列的?
      • MLP 是基于 token 独立计算的
      • 引入 CP 后,每张卡只负责通过本地的 [b, s/cp, h] 序列分片计算 MLP 前向和反向,全程无需与其他卡通信,计算结果直接就是正确的局部切分形式
    • CP 能不能减少激活总显存?原理是什么?
      • 能,而且是呈倍数减少
      • 原理是:通过长序列分片,每张卡仅保存 1/cp 长度的前向激活值和 KV Cache
      • 同时,Attention 的 S×S 矩阵也被拆解为一个个小的 Block 分步计算并在计算后立即丢弃(结合 FlashAttention),使得激活显存从 O(S2)O (S^2) 骤降到 O((S/cp)2)O ((S/cp)^2) 级别
    • Ring Attention 的原理?通信量计算?
      • 将全局序列分成 cp 块,每张 GPU 持有局部的 QiQ_i、KiK_i、ViV_i
      • 计算时,本地的 QiQ_i 保持不动,各 GPU 将自身的 KiK_i, ViV_i 发送给环上的下一个 GPU,同时接收上一个 GPU 传来的 KjK_j, VjV_j
      • 收到新的 KV 后,计算局部的 Attention,并利用 FlashAttention 的 Online Softmax 公式更新本地的 Attention 结果
      • 经过 cp-1 轮传递,QiQ_i 遍历了所有的 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 维度
    • CP 中的局部 Attention 计算完,如何拼接全局结果?通信数学意义?
      • 采用 FlashAttention 的在线 Softmax 逻辑 (Online Softmax)
      • 数学上,Softmax 的分母是所有元素的指数和,局部计算只能得到局部最大值 mim_i 和局部指数和 lil_i
      • 在收到新的 KV 块计算时,结合上一次保存的全局最大值 moldm_{old} 进行自适应缩放(rescaling):更新后的局部结果乘以 emold−mnewe^{m_{old} - m_{new}},然后再累加新的结果
      • 最终所有块遍历完毕,即得到数学等价的全局精确 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 与当前 KjK_j, VjV_j 的 FlashAttention 核心计算时,后台并发调用 NCCL Send/Recv 将当前的 KjK_j, VjV_j 传给下个节点,并接收 Kj+1K_{j+1}, Vj+1V_{j+1}
      • 只要计算耗时 ≥ 传输耗时,通信时间就被完全隐藏
    • 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... 使得每张卡分到的总计算面积(下三角中的有效像素数)在数学上严格相等,从而达到完美的负载均衡
  • 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 使用位置索引 0499 计算 RoPE;卡 1 计算位置索引 500999 计算 RoPE,这样旋转角度严格按照全局相对位置计算,拼接时能够完美对齐

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 缓存占用不一致
        • 普通均分「层数相等」≠「算力相等」
  • 符号定义

    • K:流水线 Stage 数(GPU 数)
    • M:micro-batch 数量
    • 设 tft_f 和 tbt_b 分别表示单个 micro-batch 在单个流水线 stage 上的前向和反向计算时间(一个常见的简化假设是 tb≈2×tft_b \approx 2 \times t_f)
    • 理想情况下,用于纯计算的时间应该是 M⋅tfbM \cdot t_{fb}
  • AFAB(GPipe)

    • 什么是 AFAB 调度?流程怎么走?

      • 先完成所有 micro-batch 的前向传播,再按逆序完成所有 micro-batch 的反向传播,最后统一更新参数
    • GPipe(AFAB)气泡率理论公式怎么计算?

      • 前向阶段:第 1 个微批次到达设备 K 需要经过 K−1 个设备的传递,耗时 (K−1)⋅tf(K-1) \cdot t_f,设备 K 完成所有 M 个微批次的前向传播还需要 M⋅tfM \cdot t_f,因此前向总耗时为 (K−1+M)⋅tf(K-1+M) \cdot t_f

      • 反向阶段:同理,反向传播从设备 K 传回设备 1,最后一个微批次完成反向传播的总耗时为 (K−1+M)⋅tb(K-1+M) \cdot t_b

      • 总时间为

        M⋅tfb+(K−1)⋅tfb=(K+M−1)⋅tfbM\cdot t_{fb}+(K-1)\cdot t_{fb}=(K+M-1)\cdot t_{fb}
      • 气泡总时间为

        (K−1)⋅tfb(K-1)\cdot t_{fb}
      • 气泡占比为

        K−1K+M−1\frac {K-1}{K+M-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 需要 K⋅tfbK \cdot t_{fb} 的时间,此后设备 1 还需要做剩下的 M−1 个 1F1B

        T1F1B=K⋅tfb+(M−1)⋅tfb=(K−1+M)tfbT_{1F1B} = K \cdot t_{fb} + (M - 1) \cdot t_{fb} = (K - 1 + M) t_{fb}
      • 气泡总时间为

        (K−1)⋅tfb(K-1)\cdot t_{fb}
      • 气泡占比为

        K−1K+M−1\frac {K-1}{K+M-1}
      • 可以看出,1F1B 的气泡时间与 AFAB 完全一样,都是 (K−1)tfb(K−1) t_{fb},但 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,所以处理每个微批次的前 / 反向时间也变成了 tf/vt_{f}/v 和 tb/vt_{b}/v,单个虚拟块的完整耗时为 tfb/vt_{fb}/v,流水线填满和排空的时间也相应缩短为原来的 1/v

      • 气泡产生机制:流水线气泡只产生在流水线 “填满前” 和 “排空时”

      • 在 Interleaved 模式下,第一个微批次只需要经过 K 个虚拟块(而不是 K 个完整 GPU 阶段)就能到达最后一个 GPU,耗时从 (K−1)tf(K−1) t_f 缩短为 (K−1)tfv(K−1) t_{fv},同样地,反向传播的首个微批次也能更快地回传到第一个 GPU,因此首尾的气泡时间从 (K−1)tfb(K−1) t_{fb} 缩短为 (K−1)tfb/v(K−1) t_{fb}/v,大幅降低了气泡时间

      • 实际总耗时:总耗时等于处理所有 M 个微批次的理想时间,加上流水线两端的绝对气泡时间:

        TInterleaved=M⋅tfb+(K−1)tfbvT_{Interleaved} = M \cdot t_{fb} + (K - 1) \frac {t_{fb}}{v}
      • 气泡时间:

        BubbleInterleaved=K−1vtfbBubble_{Interleaved} = \frac {K - 1}{v} t_{fb}
      • 气泡占比:

        FractionInterleaved=K−1vtfb(M+K−1v)tfb≈K−1vM+K−1Fraction_{Interleaved} = \frac {\frac {K - 1}{v} t_{fb}}{(M + \frac {K - 1}{v}) t_{fb}} \approx \frac {K - 1}{vM + K - 1}
      • 可以看到,直接缩减气泡 ——Interleaved 1F1B 在物理层面直接将气泡体积缩小为原来的 1/v

      • 在 M 相同的情况下,原本气泡占用了 (K−1)tfb(K−1) t_{fb} 的算力,现在只占用 K−1vtfb\frac {K−1}{v} t_{fb},例如 v=2 时,气泡直接减半

      • 需要注意的是,这种优化是以增加 GPU 间的通信次数(通信量翻了 v 倍)为代价的,因此 v 不能无限大,通常 v∈{2,4}v \in \{2, 4\} 是计算与通信 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 气泡理论计算公式?适用场景限制?
      • 气泡从 (K−1)⋅tfb(K−1)\cdot t_{fb} 变为 (K−1)⋅(tfb−2tbw)(K−1)\cdot (t_{fb}−2t_{bw})
  • 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%
    • DualPipe 如何互相掩盖前向 / 反向气泡?
    • DualPipe 气泡率理论推导?相比传统 PP 优势?
      • 因为是从两端向中间逼近,流水线产生气泡的 “深度” 从 K−1 直接被折半为 K/2-1
      • 气泡变为 (K/2−1)(tfb+tb−3tbw)(K/2−1)(t_{fb}+t_b−3t_{bw})
    • DualPipe 显存暴涨的核心原因是什么?
      • 从只需维护一份参数副本,变为需要维护两份参数副本
      • 同时,单卡的激活驻留数从 PP 个增长至 PP+1(多了一个镜像流的 micro 激活)
  • 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

混合精度训练

  • 混合精度训练(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 训练

预训练

  • 自回归任务的预训练损失函数是什么?代码实现?

    • 损失函数

      L=−1T∑t=1Tlog⁡pθ(xt∣x1,…,xt−1)\mathcal {L}=-\frac {1}{T}\sum_{t=1}^{T}\log p_\theta (x_t\mid x_1,\dots,x_{t-1})
    • 对每个位置预测下一个 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(大模型预训练很少用)
    • 代码实现

      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) ≈ 1011,收敛后 1.52.5 左右(取决于数据、规模)

      • perplexity(困惑度)和 loss 关系

        PPL=exp⁡(loss)\text{PPL}=\exp (\text{loss})
  • 掩码语言模型(MLM)的损失函数是什么?

    • 损失函数

      L=−∑i∈Mlog⁡pθ(xi∣x\i)\mathcal {L}=-\sum_{i\in\mathcal {M}}\log p_\theta (x_i\mid x_{\backslash i})
    • 只对被 mask 掉的 15% 位置算损失

SFT

  • 监督微调损失函数是什么?代码实现?

    • 本质依然是下一个 token 预测的交叉熵损失,和预训练损失数学形式完全一样

    • 唯一区别是,SFT 只在回答(response)部分算 loss,问题(prompt)部分不算 loss,这就是所谓的 Teacher Forcing + 掩码式损失

    • 损失函数

      L=−1N∑i∈responselog⁡pθ(yi∣y<i)\mathcal {L} = -\frac {1}{N}\sum_{i\in\text{response}} \log p_\theta (y_i\mid y_{<i})
    • 代码实现

      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 的概率分布距离:

      DKL(πθ∥πref)D_{\text{KL}}(\pi_\theta\parallel\pi_\text{ref})
    • 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 进入边际收益递减时,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 的目标函数

      LSFT=−E(x,y)∼Dlog⁡pπ(y∣x)\mathcal {L}_{\text{SFT}} = -\mathbb {E}_{(x,y)\sim D} \log p_\pi (y|x)
    • PPO/GRPO 的目标函数

      LRL=Ex∼D, y∼πold  At(πold)⏟=At(x,y,πold)⋅log⁡pπ(y∣x) At=At(π)\mathcal {L}_{\text{RL}} = \mathbb {E}_{x\sim D,\,y\sim\pi_\text{old}} \; \underbrace {A_t (\pi_\text{old})}_{=A_t (x,y,\pi_\text{old})} \cdot \log p_\pi (y|x)\ A_t = A_t (\color {red}{\pi})
    • 其中,优势函数是策略的黑箱函数,会随着策略变化而变化,每更新一步,所有样本的优势都要重新计算

    • 同时,优势函数是不可导的,只能通过采样 + 估计得到

    • 因此,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,让模型生成多个不同回复,由标注者对这些回复进行质量排序,得到偏好数据:(prompt,yj,yk)(prompt, y_j, y_k),表示 yjy_j 优于 yky_k

    • 模型:一般基于预训练语言模型微调,在最后一层增加一个线性层,将隐状态映射为单个标量 r,作为该回复的奖励分数

    • 训练目标:学习一个奖励函数 rθ(x,y)r_θ(x, y),使得对于更好的回复 yjy_j,奖励高于更差的回复 yky_k

    • 损失函数(成对排序损失)

      LRM(θ)=−E(x,yj,yk)∼D[log⁡σ(rθ(x,yj)−rθ(x,yk))]\mathcal {L}_{\text{RM}}(\theta) = -\mathbb {E}_{(x,y_j,y_k) \sim \mathcal {D}} \left [\log\sigma\big (r_\theta (x,y_j) - r_\theta (x,y_k)\big) \right]
      • σ\sigma 为 Sigmoid 函数
      • rθ(x,yj)r_θ(x,y_j) 是模型对优质回复 yjy_j 的奖励
      • rθ(x,yk)r_θ(x,y_k) 是对劣质回复 yky_k 的奖励
      • 期望表示在偏好数据集上取平均
    • 训练细节

      • 训练时冻结大部分底层 Transformer 层,只微调顶层和奖励头
      • 避免奖励分数过大或过拟合,通常会加入权重衰减等正则化
      • 训练好后 RM 固定参数,用于后续 PPO 阶段提供奖励信号
  • 奖励模型的训练流程(偏好数据收集→成对排序训练→奖励分数预测),损失函数(如交叉熵、对比损失)选择?

    • 用 SFT 模型初始化 RM,在最后加一个线性层输出 scalar score:

      • 输入整个对话序列
      • 取 最后一个 token 的 hidden state 过线性层
      • 输出一个标量 r(x, y):越高表示回答越好
    • 目标是让模型满足:

      r(x,yw)>r(x,yl)r (x, y_w) > r (x, y_l)
    • 因此需要通过损失函数拉大同分差,训练 RM,一般使用成对交叉熵损失(Pairwise CE Loss)

      L=−log⁡σ(r(x,yw)−r(x,yl))\mathcal {L} = -\log\sigma\big (r (x,y_w) - r (x,y_l)\big)
      • rwr_w = chosen 奖励
      • rlr_l = rejected 奖励
      • σ\sigma 是 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 版)

      L=max⁡(0,1−(rw−rl))\mathcal {L} = \max (0, 1 - (r_w - r_l))
      • 要求差值至少大于 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 模拟权重更新 ΔW=B⋅A\Delta W = B\cdot A,其中秩 r≪min⁡(d,k)r \ll \min (d, k)(d、k 为原权重维度)
    • 前向传播 h=Wx+(α/r)⋅B⋅(A⋅x)h = Wx + (\alpha/r)\cdot B\cdot (A\cdot x)(α\alpha 为缩放系数)
    • 权重更新:ΔW=(α/r)・B・A\Delta W = (\alpha/r)・B・A(α\alpha 为缩放系数)
    • 参数量:仅 r∗(d+k)r*(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简单任务 816;复杂任务 3264
      lora_alpha控制更新强度,scaling = α/r2r~4r常用 α=2r(如 r=8→α=16)
      target_modulesLoRA 作用的层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 长度、初始化、学习率极敏感,容易不收敛、震荡、效果波动大;同时有额外的推理开销
这篇文档有帮助吗?

On this page

页面信息