Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

NLP——Megatron各种并行总结

  • 参考链接:
    • 大模型训练并行技术理解-DP/TP/PP/SP/EP - 哈密瓜的文章 - 知乎
    • MoE 训练到底是开 TP 还是 EP? - xffxff的文章 - 知乎

整体说明

  • 总体来说,各种并行策略包括了 DP、TP、PP、EP、CP/SP 等,并行意味着拆分,理解并行策略最重要的问题是回答并行策略到底在拆什么
    • DP,Data Parallelism:数据并行(切 batch/sample)
    • TP,Tensor Parallelism:张量并行(层内切权重矩阵)
    • PP,Pipeline Parallelism:流水线并行(层间切网络深度)
    • EP,Expert Parallelism:专家并行(仅 MoE 结构,切 Expert 层)
    • CP/SP,Context/Sequence Parallelism 序列并行(切输入长序列)
  • 各种并行策略的本质目标不外乎两个:
    • 将大批量的数据分发到不同的机器上,实现更高的并行度,缩短训练时间
    • 将一个巨大到无法在单个加速器(如 GPU)上训练的模型,高效地拆分到多个加速器上,以解决显存瓶颈并加速训练过程
  • 各种并行策略之间并非简单的“依赖”关系,而更多的是一种 正交(Orthogonal)和互补(Complementary) 的关系
    • 一般来说,它们可以独立应用,也可以组合使用,形成更复杂的混合并行策略

更详细一些的描述

  • DP(Data Parallelism) :核心是对模型进行复制,对数据进行拆分
    • DP 的拆分维度是数据(Batch),是最基础、最常见的并行方式;
    • DP 将整个模型完整地复制到每个计算设备上,然后将一个大的全局批次(Global Batch)数据切分成多个微批次(Micro-batch),每个设备分配一个微批次进行独立的前向和后向计算
    • 在所有设备完成梯度计算后,需要进行一次全局梯度同步(All-Reduce),将所有设备上的梯度规约(如求平均),然后每个设备用同步后的梯度更新自己的模型副本,以保证模型参数的一致性
    • 作用在模型全局,同步发生在设备之间
    • 对“单卡”来说不省任何权重,只提升系统吞吐;通常与 ZeRO-1/2/3 叠加才真正省显存
  • TP(Tensor Parallelism) :把“一层里的单个矩阵”按行或列切到多张卡上
    • TP 的拆分维度是模型参数(Tensor)
    • 核心思想是:当模型中的某个算子(Operator),尤其是线性层(Linear Layer)或注意力头(Attention Head)的权重矩阵过大时,TP 将其在水平或垂直方向上切分到多个设备上
    • 以一个线性层 \(Y = XA\) 为例,可以将权重矩阵 \(A\) 按列切分为 \([A_1, A_2]\),分别放到两个 GPU 上(注:也可以按行切分)
      • 输入 \(X\) 被广播到两个 GPU,各自计算 \(Y_1 = XA_1\) 和 \(Y_2 = XA_2\),最后将结果拼接 \([Y_1, Y_2]\) 得到完整的 \(Y\)
    • 重点:作用范围为 算子内部(Intra-Operator) ,它对模型的其它部分是透明的
    • 每张卡与同一层所有卡进行交互,延迟敏感
    • 单卡只存 1/TP 份权重 + 1/TP 份梯度,激活值也随 TP 线性下降
  • PP(Pipeline Parallelism) :把“网络按层切成若干 stage”,每个 stage 占连续的若干层,数据按 micro-batch 流水推进
    • PP 的拆分维度是模型结构(Layers)
    • 核心思想是当模型的层数非常深时,将模型的不同层(或层块,Stage)顺序地放置在不同的设备上,构成一个“流水线”
    • 数据在一个设备上完成前向计算后,将其激活值(Activations)传递给下一个设备继续计算
      • 为了减少设备空闲(即“流水线气泡” Pipeline Bubble),通常会将一个批次数据再切分成多个微批次(Micro-batches),让多个微批次在流水线中流动起来,实现类似 GPipe 或 Interleaved 1F1B 的调度
    • 重点:作用范围是 算子之间(Inter-Operator) ,跨越多个模型层
    • 只有相邻 stage 之间传激活,带宽要求比 TP 低
    • 每个 stage 只存自己那几层的权重与激活,层数越少显存越小;但流水会引入 bubble
  • EP(Expert Parallelism,MoE 场景专有) :把“不同的 Expert 网络”放到不同 GPU,Attention 部分参数复制
    • EP 的拆分维度 模型专家组件(Experts)
    • EP 是专门为 MoE(Mixture of Experts)架构设计的并行策略
      • Mo E模型中包含多个“专家”(通常是 FFN),一个门控网络(Gating Network)会为每个输入 Token 选择性地激活一个或少数几个专家
      • EP 将这些专家分布到不同的设备上:假设有 E 个专家,我们把它们均匀拆到 N 张卡上,每张卡只存 E/N 个专家权重
    • 基本流程:输入数据首先通过门控网络,然后根据门控结果,数据被路由(All-to-All 通信)到持有被激活专家的设备上进行计算,计算结果再被路由(All-to-All 通信)回来
      • 一张卡上难免会出现 本卡 Token 选中其他远端专家 的情况,于是必须做 跨卡 All-to-All 重排(先把 Token 发送到目标路由 Expert,走完专家再返回至 Token 所在机器)
      • 每 Token 前向过程需要 2 次 All-To-All,带宽压力最大
        • 第一次 All-to-All:把 token 特征发给真正拥有目标专家的卡;
        • 第二次 All-to-All:把 token 计算结果再发回原卡(返回 Token 所在机器)
    • EP 的作用范围是特定的 MoE 层内部,涉及跨设备的数据路由
    • 注:仅考虑 EP 时,虽然单张显卡存储 Expert 参数总量 / EP,但 Attention 部分直接全量存储
  • CP/SP (Context/Sequence Parallelism - 上下文/序列并行) :把“同一条超长序列”按 token 维度横切到多卡
    • CP/SP 拆分维度是输入序列(Sequence),将输入序列在长度维度上进行切分,分给不同的设备
    • CP/SP 的方法很多,实现上各有不同:
      • Megatron SP 核心是对 TP 进行补充:
        • TP 只针对 Attention 和 MLP,Dropout 和 LayerNorm 的 激活层未得到拆分,SP 按照 seq 维度进一步拆分了 Attention 和 MLP 之间的层上的激活值(即拆分 Attention/MLP 输入和输出结果),进一步降低单卡激活值存储
        • 注:这里 SP 中,Dropout 和 LayerNorm 的参数是全量存储的,并不进行拆分,仅拆分激活值
      • Megatron CP 是对 Megatron SP 的升级,SP 仅关注 Dropout 和 LayerNorm,Megatron CP 还针对注意力的做拆分,基本思想是在计算自注意力时,每个设备只负责计算其拥有的那部分序列的 Query 向量,但需要获取所有序列的 Key 和 Value 向量(计算 softmax)
        • 这需要在注意力计算中进行巧妙的通信(All-Gather),以收集完整的 K 和 V,它与 TP 协同工作,共同降低注意力计算的显存峰值
      • 还可以针对 MLP 做 CP 策略
    • 注意:序列并行切分维度是sequence length (序列长度),序列并行生效时,激活值也被切分了(激活值是训练时显存的大头),所以能极大节省显存

Tensor Parallelism

  • Tensor Parallelism 仅针对 MLP 和 Attention 做张量拆分,在 Attention 和 MLP 之间的部分,每张显卡上都存储了完整的(相同的)激活和参数
  • 核心实现思路:
    • 对 Attention 或 MLP 层进行张量拆分,在计算 Attention 或 MLP 时,每张卡独立计算自己的部分
    • 在 Attention 或 MLP 之前或之后的部分,每张卡上的各种数据/激活是完全一致的
    • 通信发生在每次 Attention/MLP 前后,保证 Attention/MLP 前后的输入或激活完全一致
  • 补充 Megatron-LM 论文中 Tensor Parallelism 的图片:
  • 具体来说,其交互方式为:
    • 可以按照不同方式拆分权重:按照行或列切分权重,分别需要不同的通信逻辑
    • 按行切分权重时:Forward 过程需要一次 All-Reduce;Backward 过程需要一次 All-Aather
    • 按列切分权重时:Forward 过程需要一次 All-Gather;Backward 过程需要一次 All-Reduce

Sequence Parallelism(序列并行)

  • 原始论文:Sequence Parallelism: Long Sequence Training from System Perspective, arXiv 2021, NUS
  • 参考链接:
    • 图解大模型训练系列:序列并行1,Megatron SP - 猛猿的文章 - 知乎
    • 图解大模型训练系列:序列并行2,DeepSpeed Ulysses - 猛猿的文章 - 知乎
    • 图解大模型训练系列:序列并行3,Ring Attention - 猛猿的文章 - 知乎
    • 图解大模型训练系列:序列并行4,Megatron Context Parallel - 猛猿的文章 - 知乎
    • [张量/序列并行]📚图解 DeepSpeed-Ulysses & Megatron-LM TP/SP - DefTruth的文章 - 知乎
  • Megatron 的 Sequence Parallelism 设计上是结合 Tensor Parallelism 一起使用的(MLP 和 Attention 做 TP,Dropout 和 LayerNorm 做 SP),核心目标是降低单卡激活值
  • DeepSpeed-Ulysses 可以对 MLP 和 Attention 也进行 SP,让单张卡只需要维护和计算部分 Attention Head 等结果
    • 补充背景:DeepSpeed Zero 1/2/3 的本质都是数据并行(形式上是模型并行),单张卡是需要过完整的 MHA 的,如果不做 Sequence Parallelism,长序列容易导致单卡显存压力过大
  • Ring Attention 让每张卡只需要维护自己那部分 Sequence chunk 的 MHA
    • 原始论文:Ring Attention with Blockwise Transformers for Near-Infinite Context, arXiv 2023, UC Berkeley
  • Megatron 的 Context Parallelism ,是在保持 Megatron SP 的基础上,引入 CP,这里的 CP 本质是对 Attention 做优化
    • Megatron 的 CP 也使用了类 Ring Attention 技术,相当于是 Megatron SP 的升级版本,在 Megatron SP 的基础上,增加了 Ring Attention,对 Attention 也做序列并行

Context Parallelism vs Sequence Parallelism

  • Sequence Parallelism 概念最早来源于 Megatron-LM 论文和代码中,这种技术就被称为 “Tensor Parallelism” across sequence dimension 或直接称为 “Sequence Parallelism”;上下文并行(Context Parallelism,CP)则是一个更广泛、更抽象的概念;
  • 在 Transformer 中讨论时,一般可以认为两者几乎是等价的,但是针对特定场景如 Megatron-LM 中,Context Parallelism 本质是升级版的 Sequence Parallelism
    • 注:在 Transformer 的论文和讨论中,“序列(Sequence)”和“上下文(Context)”这两个词本身就经常混用,都指模型一次处理的最大token长度

Expert Parallelism(专家并行)

  • 专家并行(Expert Parallelism, EP)是一种专为 MoE 设计的模型并行策略
  • Expert Parallelism 将 MoE 层中的不同专家(Expert)分布到不同设备上,每个设备只负责一部分专家的计算;
    • 输入 token 根据门控网络(Gating Network)动态路由到对应的专家设备,计算完成后再将结果聚合回原设备
  • 专家并行降低单卡显存的核心思想是:将模型中的不同“专家”(Experts)分布到不同的设备上,每个设备只负责维护分配给它的那一部分专家
  • 专家并行论文:GShard: Scaling Giant Models with Conditional Computation and Automatic Sharding, 2020, Google
  • EP 相关基本概念:
    • 专家并行组(ep_group) :一组设备共同托管一组专家,组内设备通过 All-to-All 通信协作
    • 数据并行组(dp_group) :在不同 ep_group 之间,相同专家的数据副本组成数据并行组,用于梯度同步

EP 具体详细流程

  • EP 的具体流程如下:专家并行 = 专家分布 + 动态路由 + All-to-All 通信 + 本地计算 + 结果还原
  • 1)EP 组划分与专家分配 :
    • 将不同的专家分配到不同的设备组上,每个设备组称为一个 EP 组,每个 EP 组包含多个机器和显卡
    • 例如,假设 MoE 层有 E 个专家,EP=4 那么可以将 E 个专家平均分配给 4 个 EP 组,每个EP组负责一部分专家
  • 2)路由决策计算 :
    • 输入数据首先经过路由层(Router),计算每个 Token 的路由分数/权重,每个设备本地运行共享的门控网络(通常在所有设备上复制)
    • 具体来说,通过公式 Gating_logits = Y @ W_router 计算,其中 Y 是输入张量,W_router 是路由层的权重
      • 如果 W_router 在张量并行(TP)组内切分,需要类似 AllGather 或 ReduceScatter 的通信来完成计算或收集结果,最终得到的 Gating_logits
    • 对 Gating_logits 应用 Top-K 和 Softmax 操作,得到每个 Token 的路由决策,即它应该被发送到哪 K 个专家以及对应的权重
  • 3)按照路由决策重排 :
    • 对输入的每个 token,门控网络决定其应被路由到哪些专家(Top-K 选择),即包含每个 token 到目标专家索引列表
    • 根据门控结果,将发往同一专家的 token 聚合到一个连续的内存块中
    • 这一步称为 permutation(重排) ,便于后续高效通信和计算
  • 4)All-to-All Dispatch(Token 分发) :
    • 使用 All-to-All 通信原语,将 token 从原始设备发送到目标专家所在的设备
    • 每个设备只接收它需要处理的 token 子集
    • 通信量取决于 batch 大小、专家数量和路由稀疏性
  • 5)本地路由与专家计算 :
    • 每个设备仅对其本地持有的专家进行前向计算(如 FFN)
    • 由于专家数量被切分,内存和计算压力显著降低
    • 计算是 并行进行 的,设备间无依赖
    • 每张卡对自己筛选出来的 Tokens 应用本卡负责的专家网络进行计算
      • 专家网络通常是标准的FFN(如 GeLU(W1 * x) * W2),并且专家网络的权重 W1、W2 在 TP 组(即 EP 组)内进行张量并行切分
  • 6)All-to-All Combine(结果收集)
    • 再次使用 All-to-All 通信,将专家计算结果发送回原始设备
    • 每个设备根据原始 token 的顺序,恢复输出张量的布局
  • 7)输出解码与加权求和
    • 将各专家的输出按门控权重加权求和,得到最终的 MoE 层输出
    • 输出与残差连接相加,继续进入下一层

通信成本

  • 通信成本总结
    并行维度 切分对象 单卡显存下降项 通信类型及量级 备注
    TP 单层权重(列切/行切) 模型权重 \(\propto\) 1/TP
    激活 \(\propto\) 1/TP
    每层 2×all-reduce,
    带宽要求高
    节点内(NVLink)最佳
    PP layer group 权重 \(\propto\) 1/PP
    激活 \(\propto\) 1/PP
    (开启gradient checkpoint 时)
    相邻 stage P2P,
    量小但需频繁
    可跨节点;bubble 占比=PP-1/PP
    EP FFN MoE Experts Expert 权重 \(\propto\) 1/EP
    Attention 权重完整复制
    每 token 2×all-to-all,
    非对称
    仅 MoE 模型;EP≥1
    CP 输入序列维度 激活 \(\propto\) 1/CP all-gather+reduce-scatter 一般超长上下文(>32k)才开
    DP batch 维度 无 每 step 1×all-reduce 与 TP/PP/EP 正交;可用 ZeRO 1/2/3 进一步省显存
  • EP 通信开销的详细描述:MoE 训练到底是开 TP 还是 EP? - xffxff的文章 - 知乎

常用组合约束

  • TP、CP、EP 都是“横切”同一组层内数据,通信模式全是 All-Reduce/All-To-All,必须放在同一高速域(NVLink / HBM),俗称一个“node”
  • PP 是“竖切”层,可以跨 node,通信量小,适合机间
  • DP 是“最外层复制”,可跨任意节点,业界常用 3D/4D/5D 混合:
    • 括号内必须落在一台 8-GPU 机器里,括号外可以跨机
  • TP/CP/EP 负责“省显存、限节点”,PP 负责“再省一层、可跨机”,DP 负责“加吞吐”
  • 补充:在 Megatron-LM 实现 中, 可以看到基本规则是:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # megatron/core/parallel_state.py def initialize_model_parallel()
    # 注:DP = world_size / (TP * PP * CP)
    model_size = tensor_model_parallel_size * pipeline_model_parallel_size * context_parallel_size
    if world_size % model_size != 0:
    raise RuntimeError(f"world_size ({world_size}) is not divisible by {model_size}")
    data_parallel_size: int = world_size // model_size

    # 注:EP 相关的并行不占用独立的进程组 DP_ep = world_size / (ETP * EP * PP),其中 ETP 一般等于 TP
    ## EP:expert_model_parallel_size (int, default = 1): The number of Mixture of Experts parallel GPUs in each expert parallel group. 这个是真正的 EP
    ## ETP:expert_tensor_parallel_size (int, default = tp_size): The number of GPUs to split individual tensors of expert. 这个是 Expert内部张量并行的并行度,一般来说就等于 TP 本身
    ## 总结:可以看出,EP 和 CP 不会同时出现在分母中,即两者没有同时出现?
    if expert_tensor_parallel_size is None:
    expert_tensor_parallel_size = tensor_model_parallel_size # 如果 EP 没有指定,默认取 EP = TP
    expert_tensor_model_pipeline_parallel_size = (
    expert_tensor_parallel_size * expert_model_parallel_size * pipeline_model_parallel_size
    )
    expert_data_parallel_size = world_size // expert_tensor_model_pipeline_parallel_size
    if world_size % expert_tensor_model_pipeline_parallel_size != 0:
    raise RuntimeError(
    f"world_size ({world_size}) is not divisible by expert_tensor_model_pipeline_parallel size ({expert_tensor_model_pipeline_parallel_size})"
    )

附录:Megatron-LM 中的 expert_tensor_parallel_size 和 expert_model_parallel_size 的区别

  • 在 Megatron-LM(特别是针对 Mixture of Experts (MoE) 模型)项目中,expert_tensor_parallel_size 和 expert_model_parallel_size 都与 MoE 专家的并行化有关,但它们侧重于不同类型的并行:
  • expert_tensor_parallel_size (专家张量并行大小, 本质是专为专家配置的 TP):
    • 指的是应用于 单个 Expert(专家)内部 的 张量并行 (Tensor Parallelism, TP) 的大小
    • 像处理标准 Transformer 层一样,它将单个专家(通常是一个大的前馈网络 FFN)的权重矩阵分解到多个 GPU 上
      • 这减少了每个 GPU 上单个专家的内存占用
    • 相关 GPU 组成一个 张量并行组 (Tensor Parallel Group)
    • 通常在层内进行通信(例如 All-Reduce 操作)以完成计算
  • expert_model_parallel_size (专家模型并行大小,通常也称为专家并行 Expert Parallelism, EP):
    • 指的是所有 Experts在不同 GPU 上的分布 ,即 Expert Parallelism (EP) 的大小
    • 在 MoE 模型中,通常有大量的专家
      • 专家并行将不同的专家分配到不同的 GPU 上,使得每个 GPU 存储和计算一部分专家
      • 这有效地扩展了专家数量和模型总大小的上限
    • 相关 GPU 组成一个 专家并行组 (Expert Parallel Group)
    • 主要在 MoE 层的路由(Routing)过程中,涉及Token在不同专家(GPU)之间的发送和接收(例如 All-to-All 或 Shuffle 操作)
  • 在 Megatron-LM 的 MoE 实现中,可以同时使用这两种并行策略:
    • 先用 expert_model_parallel_size (EP) 将所有专家分布到多个 GPU 上
    • 再在每个 GPU 组内,使用 expert_tensor_parallel_size (TP) 将每个专家内部的计算进行张量分割

附录:数据并行下各种批次关系

  • micro_batch_size,有时简称 mbz,是单个 GPU 在一次前向-反向里真正处理的样本数;
  • gradient_accumulation_steps,有时简称 GAS,是同一张 GPU 在做一次参数更新前把 micro_batch 跑几遍并累加梯度
  • 一般来说:
    • global_batch_size = DP_size * mini_batch_size
    • global_batch_size = DP_size × micro_batch_size × grad_accum_steps

附录:使用注意事项

  • 实用建议:超长 MoE 先开 CP 把序列压下来,再在一层内部用 EP 分散专家;或干脆“二选一”
  • PP 的 stage 之间只传激活,不涉参数/梯度集合通信,通信量小,因此可以跨机器,所以分配 rank 时一般是最后考虑的;
    • 唯一要注意的是:micro_batches >= PP_size,否则流水线气泡 > 50%,吞吐腰斩
  • TP 是需要通信量最大的,一般限制在同一台机器内部,这样可以提升通信效率,分配 rank 时一般是最优先考虑的,TP 进程组 rank 也一般是连续成对的
  • ZeRO-1/2/3 本质仍是 DP,只是梯度/优化器/参数分片;
    • ZeRO-3 与 TP 同时开时,同一 TP 组内必须关闭参数分片 ,否则一次 MatMul 要跨 ZeRO 组做 All-Gather,延迟爆炸
  • DP 的本质是提升并行度,一般来说,DP 的目标是开启前后保证梯度更新是是一致的,实现时也是朝这个方向实现的,比如 DDP 或 Megatron 中,在聚合梯度时,都是按照 DP 求平均(先 all_reduce SUM,再除以 DP_size)
  • DP 的通信量不算太高,也可以跨机器实现

附录:Megatron 中 DP 之间聚合梯度是平均还是累加 ?

  • 和 DDP 中一样,Megatron 中 DP 之间的梯度聚合是分两步的:

    • 先调用 all_reduce 实现累加
    • 然后再调用除法(除以 DP_Size)实现平均
  • 简单代码阅读 github.com/NVIDIA/Megatron-LM/blob/main/megatron/training/training.py:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ...
    torch.distributed.all_reduce(
    val,
    group=mpu.get_data_parallel_group(with_context_parallel=True)
    )
    val /= torch.distributed.get_world_size(
    group=mpu.get_data_parallel_group(with_context_parallel=True)
    )
    loss_reduced[key] = val
    ...
  • 特别说明:如果是想做 Token 粒度的平均(每个样本的可学习 Token 数不一致),需要多维护一个 Token 数量的变量并执行一次 all_reduce 通信

    • 当然,为了实现与不做 DP 完全一致的效果,这里其实是应该对 Token 也做聚合,再做除法才行的

补充:DDP 中的 DP 间梯度聚合

  • DDP 中在 DP 间累积梯度后,做了平均,具体实现参见 github.com/pytorch/pytorch/blob/main/torch/csrc/distributed/c10d/reducer.cpp
    1
    2
    3
    4
    5
    // 取值与 DP_size 有关(注意: 这里的 size 就是 DDP 中的 world_size,也就是 DP_size)
    div_factor_ = process_group_->getSize();
    ...
    // 做除法
    bucket_view.div_(div_factor_);

不同并行配置下需要多少卡?

  • 一般来说,Megatron-LM 官方实现给的结论是,所有维度的并行相乘得到总的卡数
  • 部分框架实现下, 可以让 EP 复用 CP 或 DP 等, 实现 EP 不参与乘法得到总的卡数

VSCode——Debug使用笔记


整体说明

  • VSCode Debug 大型项目是有一定难度和门槛的

Debug 的前置准备

安装必要插件

  • 打开 VSCode,左侧栏扩展(快捷键Ctrl+Shift+X),搜索并安装:
    • Python(微软官方,核心插件)
    • Python Debugger(新版调试核心,微软官方)
    • 可选:Pylance(增强代码提示,大型项目必备)

确认 Python 解释器

  • 大型项目通常用虚拟环境,Debug 是从 terminal 中启动的,需要在 terminal 先配置好环境
  • 快捷键 Ctrl+Shift+P,输入 Python: Select Interpreter

初始化调试配置文件(launch.json)

  • VSCode 调试依赖 launch.json 配置,大型项目需自定义配置以适配项目结构,步骤如下:

  • 1)打开项目根目录(关键:必须打开根目录,而非单个文件)

  • 2)打开调试面板:左侧栏 运行和调试 (快捷键Ctrl+Shift+D), 点击 创建 launch.json 文件

  • 3)选择调试环境:弹出的下拉框中选 Python,再选 Python File(基础模板,后续修改)

    • 默认生成的 launch.json 在 .vscode 文件夹下
  • 4)自定义 launch.json(这一步是核心!适配大型项目),修改为适合大型项目的配置,示例如下(注释说明关键参数):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    {
    "version": "0.2.0",
    "configurations": [
    {
    "name": "Python: 项目主入口", // 配置名称,可自定义
    "type": "debugpy", // 调试器核心,固定值
    "request": "launch", // 启动调试(而非附加进程)
    "program": "${workspaceFolder}/src/main.py", // 项目主入口文件(替换为你的实际路径)
    "cwd": "${workspaceFolder}", // 调试时的工作目录(固定为项目根)
    "args": ["--env", "dev", "--config", "configs/dev.yaml"], // 主程序运行参数(按需添加)
    "justMyCode": false, // 大型项目建议关闭,可调试第三方库/依赖代码
    "env": { // 自定义环境变量(如数据库地址、密钥等)
    "PYTHONPATH": "${workspaceFolder}", // 关键!解决大型项目模块导入问题
    "ENV": "development"
    },
    "envFile": "${workspaceFolder}/.env", // 加载.env文件(可选,管理环境变量)
    "stopOnEntry": false, // 启动后是否立即暂停(新手可设为true,熟悉后改false)
    "console": "integratedTerminal", // 调试输出到VSCode集成终端(方便看日志)
    "subProcess": true // 关键!调试子进程/子模块(大型项目多进程必备)
    },
    // 可选:添加调试单个模块/测试文件的配置
    {
    "name": "Python: 调试单个模块",
    "type": "debugpy",
    "request": "launch",
    "module": "src.utils.data_process", // 调试指定模块(替代program)
    "cwd": "${workspaceFolder}",
    "env": {"PYTHONPATH": "${workspaceFolder}"}
    }
    ]
    }
    • PYTHONPATH:将项目根目录加入 Python 路径,解决 模块找不到 问题(大型项目多目录结构必配);
    • subProcess:开启后可调试项目中通过 subprocess 启动的子进程;
    • args:传递给主程序的命令行参数(如配置文件路径、环境参数)

设置断点

  • 大型项目调试时应该避免 全局断点 ,需针对性设置:

基础断点

  • 点击代码行号左侧的空白处,出现红色圆点即断点生效(调试时运行到此处会暂停)

高级断点(非常好用)

  • 使用:右键断点红点 -> Edit Breakpoint,会出现多个选项可选,默认是 Expression
    • Expression :
      • 右键断点红点 -> Edit Breakpoint -> Expression -> 输入 Python 表达式(仅当表达式为 True 时暂停)
      • 示例:调试循环处理数据时,设条件 i == 100(仅第 100 次循环暂停,避免逐行调试)
      • 注意这个配置很好用,不需要修改代码,且可以是任意的语句
    • Log Messages :
      右键断点红点 -> Edit Breakpoint -> Log Messages -> 输入日志内容(如 "处理数据:{data_id}" )
      • 调试时不暂停,仅输出日志(适合排查循环/批量处理问题,不中断程序)

启动调试

  • 第一步:
    • 确认 launch.json 中选中目标配置
    • 比如调试面板顶部下拉框选 launch.json 中已经配置好的选项
  • 第二步:
    • 点击调试面板的 绿色三角按钮
    • 启动后程序运行到断点会暂停,顶部出现调试控制栏,核心按钮(从左到右):
  • 第三步:一些调试操作说明
    • 继续(F5):运行到下一个断点;
    • 单步跳过(F10):执行当前行,不进入函数内部(适合快速跳过无关代码);
    • 单步进入(F11):进入当前行调用的函数内部(调试子模块核心);
    • 单步退出(Shift+F11):从当前函数退出到调用处;
    • 重启(Ctrl+Shift+F5):重新启动调试;
    • 停止(Shift+F5):结束调试

附录:调试时查看数据

  • VSCode 提供多个面板 查看 查看变量/数据状态

变量面板

  • 自动显示当前作用域的所有变量(局部变量、全局变量、内置变量)
  • 可展开复杂对象(如字典、类实例)查看内部属性
  • 右键变量 -> 添加到监视 ,固定关注核心变量

监视面板

  • 手动输入 Python 表达式(如 len(data_list)、user.id == 123),实时显示结果;
  • 大型项目建议添加 核心状态变量 (如配置是否加载、数据库连接是否正常)
  • 监控面板的变量会固定长期展示(变化也会体现出来)

附录:调试大型项目的进阶技巧(避坑+效率提升)

技巧1:解决 模块导入失败 问题

  • 大型项目多目录结构(如 src/、tests/、configs/)易出现 ModuleNotFoundError,除了配置 PYTHONPATH,还可以尝试
    • 在项目根目录创建 __init__.py(空文件即可,标记为Python包);
    • 调试单个模块时,用 module 参数替代 program(如 launch.json 中 "module": "src.utils.data_process",而非直接指定文件路径)

技巧2:调试多线程/多进程项目

  • 多线程 :调试面板左侧 调用堆栈 -> 展开 线程 列表,切换不同线程查看状态;
  • 多进程 :
    • 若用 gevent 协程,需在 launch.json 中添加 "gevent": true
    • 普通进程使用 subProcess: true(子进程调试);
  • 进阶:使用 附加到进程 调试(调试面板 -> 添加配置 -> Python: 附加到进程 ,选择运行中的Python进程)

技巧3:调试测试用例(大型项目单元测试必备)

  • 安装 pytest(pip install pytest);

  • launch.json 添加配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "name": "Python: test case",
    "type": "debugpy",
    "request": "launch",
    "module": "pytest",
    "args": ["tests/test_data_process.py::test_handle_data", "-v"],
    "cwd": "${workspaceFolder}",
    "env": {"PYTHONPATH": "${workspaceFolder}"}
    }
    • 启动后直接调试指定测试用例(精准定位单元测试失败问题)

技巧4:跳过无关代码(提升效率)

  • 大型项目调试时避免进入第三方库/无关模块:
    • 在 launch.json 中设置 "justMyCode": true(默认),调试时自动跳过 site-packages 中的第三方库代码
    • 若需调试自己写的子模块,确保模块路径在 PYTHONPATH 中,且代码在 ${workspaceFolder} 下

附录:其他问题总结

  • 若断点未触发:检查 launch.json 的 program 路径是否正确、断点是否在执行路径上、PYTHONPATH 是否配置

  • 若变量查看异常:确认当前作用域是否正确(如函数内部只能看局部变量)

  • 若调试卡顿:减少不必要的断点,优先用 日志断点 替代普通断点

  • 断点灰色(未生效):

    • 原因:文件不在项目根目录、解释器选错、launch.json 的 program 路径错误;
    • 解决:确认打开的是项目根目录,重新选择解释器,检查 program 路径是否为 ${workspaceFolder} 开头
  • ModuleNotFoundError:

    • 解决:配置 PYTHONPATH: "${workspaceFolder}",或在代码开头添加:
      1
      2
      3
      import sys
      from pathlib import Path
      sys.path.append(str(Path(__file__).parent.parent)) # 向上级目录添加到路径
  • 调试时终端无输出:

    • 解决:launch.json 中设置 "console": "integratedTerminal"(而非 internalConsole)

VSCode——一些常见的问题解决方案

本文总结一些 VSCode 使用过程中遇到的问题和解决方案


VSCode 不显示 Git 修改情况

  • VSCode 不显示 Git 修改情况(比如行号旁的颜色标记、文件状态图标、源代码管理面板无内容等),通常是 Git 配置、工作区设置或插件冲突 导致的

第一步:基础检查:确认项目已关联 Git 仓库

  • VSCode 仅对 Git 仓库目录 显示修改跟踪,先确认项目是否初始化 Git
  • 执行命令 git status

第二步:启用 VSCode 内置 Git 功能(核心步骤)

  • VSCode 内置 Git 支持,可能被手动禁用,需重新启用:
    • 1)打开 VSCode 设置
    • 2)搜索配置项 git.enabled ,确保勾选(值为 true)
    • 3)搜索配置项 git.decorations.enabled ,确保勾选(控制行号旁的修改标记、文件图标状态)
    • 4)重启 VSCode(关键!修改配置后需重启生效)

第三步:检查 Git 可执行文件路径(Git 未被 VSCode 识别)

  • 若 VSCode 找不到 Git 程序,会无法跟踪修改,需手动指定 Git 路径
  • 先确认本地已安装 Git(终端执行 git --version,若提示“不是内部命令”,需先安装)
  • 在 VSCode 设置中搜索/创建 git.path
    • 注意,这个字段可能找不到,需要在 settings.json 文件中手动加入
    • 比如添加 "git.path": "/usr/bin/git"
      • Windows:默认 C:\Program Files\Git\bin\git.exe 或 C:\Program Files\Git\cmd\git.exe;
        • Mac/Linux:默认 /usr/bin/git 或 /usr/local/bin/git(可通过 which git 命令查询);
  • 重启 VSCode,再查看是否生效

其他可能的问题

  • Git 版本过低:VSCode 对旧版 Git 兼容性较差,升级 Git 到 2.20+ 版本(官网下载最新版);
  • 权限问题:项目目录或 .git 文件夹无读写权限
    • Windows 右键目录 -> 属性 -> 安全 -> 允许“完全控制”;
    • Mac/Linux 执行 chmod -R 755 项目目录;
  • VSCode 版本过旧:升级 VSCode 到最新版(左下角齿轮 -> 检查更新)
  • 冲突插件或 VSCode Git 配置错误

附录:修复文件/文件夹的 Git 跟踪状态(仅部分文件不生效时)

  • 若部分文件不显示修改,可能是 Git 未跟踪或被忽略
  • 检查 .gitignore 文件:确保目标文件没有被添加到 .gitignore(比如 node_modules/、dist/ 等目录会被默认忽略,修改这些目录的文件不会显示跟踪);
  • 手动跟踪未跟踪文件:终端执行 git add 文件名(或 git add . 跟踪所有文件),之后修改文件会显示红色(删除)、绿色(新增)、黄色(修改)标记;
  • 清除 Git 缓存(若文件曾被 .gitignore 忽略后需重新跟踪)
    1
    2
    3
    4
    5
    git rm --cached 文件名  # 单个文件
    # 或所有文件
    git rm -r --cached .
    git add .
    git commit -m "修复 Git 跟踪状态"

快捷键修改

  • File -> Preferences -> Keyboard Shortcuts
  • 可按照快捷键或名字搜索即可
    • 名称:
      • 如 前进/后退(中文)或 go back / go forward 表示前进或后退
      • 如 缩进/减少缩进 或 indent / outdent 表示缩进或者减少缩进
    • 快捷键:
      • 如 ctrl, cmd, tab, space,shift 等

Python——uv工具的使用


整体说明

  • uv 是一个快速的 Python 包管理器和项目管理工具,由 Astral 公司开发,旨在替代 pip、venv 等工具,提供更快的安装速度和更简洁的使用体验
  • uv 通常比 pip 快 10-100 倍
  • uv 内置虚拟环境:无需单独管理虚拟环境
  • uv 支持项目管理:原生支持 pyproject.toml,详情见附录
  • uv 保持了与 pip 相似的命令行接口,对于熟悉 pip 的用户来说很容易上手,同时提供了更现代、更高效的功能

安装 uv

  • 安装 uv 工具:

    1
    2
    3
    4
    5
    # 使用 pip 安装
    pip install uv

    # 或者使用官方安装脚本(推荐)
    curl -LsSf https://astral.sh/uv/install.sh | sh
  • 安装完成后,可以通过 uv --version 验证是否安装成功

  • 若提示没有命令,可能是需要配置环境变量,将下面的命令添加到 ~/.bashrc 中即可:

    1
    source $HOME/.local/bin/env

uv 基本用法介绍

虚拟环境管理

  • uv 内置了虚拟环境管理功能,无需单独使用 venv 或 virtualenv:

    1
    2
    3
    4
    5
    6
    7
    # 创建并激活虚拟环境(会在当前目录创建 `.venv` 文件夹)
    uv venv
    source .venv/bin/activate # Linux/macOS 激活环境,切换到当前环境下
    deactivate # Linux/macOS 退出激活

    # 直接在虚拟环境中运行命令(无需手动激活)
    uv run python --version
  • 若使用 source .venv/bin/activate 激活环境

    • 像 conda 一样,会切换到指定的虚拟环境下,直接使用 which python 可访问到当前项目的 python 文件
    • 但此时 pip 不会像 conda 一样替换,还是需要使用 uv pip 来使用,直接使用 which pip 得到的还是通用的 pip

IDEA 环境配置

  • 在使用 uv venv 创建了虚拟环境以后,可以使用 IDEA 直接选择 ./.venv/bin/python 作为解释器

类似 pip 的包安装与管理

  • uv 可以像 pip 一样安装和管理 Python 包:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 安装包
    uv pip install requests

    # 安装特定版本的包
    uv pip install requests==2.31.0

    # 从requirements.txt安装
    uv pip install -r requirements.txt

    # 升级包
    uv pip install --upgrade requests

    # 卸载包
    uv pip uninstall requests

    # 查看已安装的包
    uv pip list

    # 导出依赖到requirements.txt
    uv pip freeze > requirements.txt

uv 运行 python 文件

  • 使用 uv run 可以在虚拟环境中直接运行命令,无需手动激活环境 :
    1
    2
    3
    4
    5
    6
    7
    8
    # 运行Python解释器
    uv run python

    # 运行脚本
    uv run script.py

    # 运行命令行工具(如pytest)
    uv run pytest tests/

附录:uv 高级功能

缓存管理

  • uv 具有高效的缓存机制,可以手动管理缓存:
    1
    2
    3
    4
    5
    # 清理缓存
    uv cache clean

    # 查看缓存大小
    uv cache size

配置镜像源

  • uv 可以配置自己的 pip 源,配置国内镜像源加快下载速度,比如:

    1
    2
    # 设置 PyPI 镜像源
    export UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple/
    • 注:也可以永久添加到环境变量中方便使用
  • 临时指定镜像源的方式为:

    1
    uv add <package> --index-url https://pypi.tuna.tsinghua.edu.cn/simple/
  • 安装时输出源信息:

    1
    uv add requests --verbose # 注意:谨慎打开 `--verbose` 这个参数,会输出特别长的日志

构建和发布包

  • uv 支持构建和发布 Python 包到 PyPI:
    1
    2
    3
    4
    5
    # 构建包
    uv build

    # 发布包到 PyPI
    uv publish

附录:uv 管理 python 项目

  • uv 支持现代 Python 项目管理,包括 pyproject.toml:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 初始化新项目(创建 pyproject.toml)
    uv init my_project # 在当前目录下创建 my_project 文件夹并生成基本文件
    # 生成 README.md main.py pyproject.toml 等文件
    cd my_project

    # 添加依赖(会更新 pyproject.toml)
    uv add requests # 生产依赖,将 requests 添加到 pyproject.toml 的 dependencies 列表中同时安装 requests 及其依赖(注:requests 的依赖不会添加到 pyproject.toml 中)
    uv add --dev pytest # 开发依赖,仅开发阶段需要使用到的依赖(将 pytest 添加到 pyproject.toml 的 dev 列表中),pytest 就是最常见的开发依赖,prod 环境不需要

    # 安装项目依赖(根据 pyproject.toml)
    uv sync # 补充

    # 运行项目中的脚本
    uv run my_script.py

Python——OrderedDict类的使用


整体说明

  • OrderedDict 是 Python 标准库中的一个类,它位于 collections 模块下
  • 相对于普通字典,OrderedDict 能够记住元素插入的顺序,在迭代时,元素会按照插入的先后顺序被返回
  • OrderedDict 类对象的主要特点有:
    • 仍然是一个字典 :即仍然是 Key-Value 结构,且 Key 值不能重复
    • 保持插入顺序 :迭代时,元素会按照插入的顺序返回
    • 顺序敏感 :如果两个 OrderedDict 包含相同内容,但元素插入顺序不同,它们会被视为不相等
    • 支持移动操作 :可以通过 move_to_end() 方法将元素移动到开头或末尾
    • 删除操作保留顺序 :删除元素后,剩余元素的顺序保持不变

OrderedDict 的基本用法

  • OrderedDict 的一些常见操作示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    from collections import OrderedDict

    od = OrderedDict()
    od['a'] = 1
    od['b'] = 2
    od['c'] = 3
    od['b'] = 10

    # 迭代时保持插入顺序
    for key, value in od.items():
    print(key, value) # 输出: a 1, b 10, c 3

    print('---')
    # 移动元素到开始
    od.move_to_end('b', last=False)
    for key, value in od.items():
    print(key, value) # 输出: b 10, a 1, c 3

    print('---')
    # 移动元素到末尾
    od.move_to_end('b', last=True)
    for key, value in od.items():
    print(key, value) # 输出: a 1, c 3, b 10

    print('---')
    # 删除元素后结果不变
    del od['c']
    for key, value in od.items():
    print(key, value) # 输出: a 1, b 10

比较:与普通字典的区别

  • 在 Python 3.7 及以后的版本中,普通字典也会保持插入顺序,但 OrderedDict 仍然有其独特优势:
    • 明确的顺序语义 :使用 OrderedDict 可以更清晰地表达代码对顺序的依赖
    • 支持顺序相关的方法 :如 move_to_end()、popitem(last=False) 等,且这两个函数都可以通过 last 参数指定是操作第一个还是最后一个
    • 顺序敏感的比较 :两个字典即使包含元素相同,但元素顺序不同也会被视为不等

Math——证明笔记-对数似然的梯度期望为零


证明目标(对数似然的梯度期望为零)

  • 证明恒等式
    $$
    \mathbb{E}_{z \sim m_\theta(\cdot|x)} \left[ \nabla_\theta \log m_\theta(z \mid x) \right] = 0,
    $$
  • 这个恒等式是对数似然梯度的期望为零的性质,是强化学习和变分推断中的一个基本结果

证明过程

符号定义

  • 给定输入 \( x \),模型输出一个概率分布 \( m_\theta(z \mid x) \)(对离散 \( z \) 是概率质量函数,对连续 \( z \) 是概率密度函数)
  • 我们有归一化条件(离散):
    $$
    \sum_z m_\theta(z \mid x) = 1
    $$
  • 或(连续)
    $$
    \int_z m_\theta(z \mid x) , dz = 1
    $$

期望的定义

  • 对于连续情况(离散类似):
    $$
    \mathbb{E}_{z \sim m_\theta(\cdot|x)} \left[ \nabla_\theta \log m_\theta(z \mid x) \right]
    = \int_z m_\theta(z \mid x) \cdot \nabla_\theta \log m_\theta(z \mid x) , dz
    $$

代入梯度对数项

  • 因为
    $$
    \nabla_\theta \log m_\theta(z \mid x) = \frac{\nabla_\theta m_\theta(z \mid x)}{m_\theta(z \mid x)},
    $$
  • 所以:
    $$
    m_\theta(z \mid x) \cdot \nabla_\theta \log m_\theta(z \mid x) = \nabla_\theta m_\theta(z \mid x)
    $$
  • 于是期望变成:
    $$
    \int_z \nabla_\theta m_\theta(z \mid x) , dz
    $$

交换梯度与积分

  • 如果 \( m_\theta(z \mid x) \) 对 \( \theta \) 足够光滑,且积分与梯度可交换(通常成立),则:
    $$
    \int_z \nabla_\theta m_\theta(z \mid x) , dz
    = \nabla_\theta \int_z m_\theta(z \mid x) , dz
    $$

利用归一化条件

  • 归一化条件下
    $$
    \int_z m_\theta(z \mid x) , dz = 1 \quad \Rightarrow \quad \nabla_\theta 1 = 0
    $$
  • 因此:
    $$
    \mathbb{E}_{z \sim m_\theta(\cdot|x)} \left[ \nabla_\theta \log m_\theta(z \mid x) \right] = 0
    $$

附录:恒等式的直观解释

  • \( \nabla_\theta \log m_\theta(z \mid x) \) 称为 score function
  • 它的期望为零,是因为概率分布的总概率必须保持为 1
    • 改变参数 \(\theta\) 时,概率质量在不同 \(z\) 间重新分配,但增加某些地方的概率必然减少其他地方的概率,平均起来“变化方向”的期望为零
  • 这个性质在 REINFORCE 算法中用于引入基线(baseline)而不引入偏差,因为对任意只依赖 \(x\) 而不依赖 \(z\) 的 \(b(x)\):
    $$
    \mathbb{E} \left[ b(x) \cdot \nabla_\theta \log m_\theta(z \mid x) \right]
    = b(x) \cdot \mathbb{E} \left[ \nabla_\theta \log m_\theta(z \mid x) \right] = 0
    $$

NLP——LLM排行榜


整体说明

  • 目前,大模型(如LLM、多模态模型等)的评测和排名主要通过一些权威的基准测试和第三方平台进行
  • 本文记录并持续更新一些常见的在线排名网站和评测平台,涵盖不同领域的模型能力评估

LMSYS Chatbot Arena

  • 链接:https://lmarena.ai/
  • 领域:通用大模型排名
  • 基于人类反馈的实时对战排名(如GPT-4、Claude、Gemini等)
  • 采用Elo评分机制,反映用户偏好
  • 包含闭源模型
  • Chatbot Arena LLM Leaderboard: Community-driven Evaluation for Best LLM and AI chatbots
  • LmArena(原LMSYS)是一个由加州大学伯克利分校SkyLab和LMSYS研究团队开发的开源平台,专注于通过众包方式评估和比较不同AI模型的性能

OpenCompass

  • 链接:https://rank.opencompass.org.cn/home
  • 领域:通用大模型排名、多模态模型排名、对战排名均有
  • 包含豆包、Qwen、DeepSeek等
  • 司南 OpenCompass 是由上海人工智能实验室(Shanghai AI Lab)推出的一个开源、中立、全面的 LLM 评测体系,旨在对各类大模型进行系统性、标准化的能力评估与排名

LiveCodeBench

  • 链接:https://livecodebench.github.io/leaderboard.html
  • 领域:代码能力排名

Open LLM Leaderboard (Hugging Face)

  • 链接:https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard
  • 领域:通用大模型排名
  • 评估开源大模型在多项任务(如ARC、HellaSwag、MMLU等)上的表现
  • 涵盖模型:LLaMA、Falcon、Mistral等诸多模型,还有许多名字不出名的是基于其他模型微调后改名的
  • 仅评估开源模型

Stanford HELM (Holistic Evaluation of Language Models)

  • 链接:https://crfm.stanford.edu/helm/
  • 领域:通用大模型排名
  • 斯坦福的全面评测框架,覆盖准确性、公平性、鲁棒性等维度
  • 其中可选很多评估标注,比如MMLU,Finance等

C-Eval (中文评测基准)

  • 链接:https://cevalbenchmark.com/static/leaderboard.html
  • 领域:中文测评
  • 评估中文知识、推理能力的测试集,涵盖52个学科
  • 排名包含:GPT-4、ChatGLM、通义千问等

SuperCLUE (中文通用大模型评测)

  • 链接:https://www.superclueai.com/
  • 领域:中文测评
  • 中文版综合性评测,包括基础能力、专业任务等

MMBench

  • 链接:https://mmbench.opencompass.org.cn/leaderboard
  • 领域:多模态模型排名
  • 评估图文理解、生成能力的基准(如GPT-4V、Gemini Vision)

GLUE/SuperGLUE

  • 链接:https://gluebenchmark.com/
  • 领域:自然语言理解
  • 经典NLU任务评测,但近年逐渐被更大基准取代,都是一些比较老的模型评估

NLP——HF-Trainer使用总结

  • 参考链接:
    • 官方链接:HuggingFace Trainer 官网

HuggingFace Trainer 整体介绍

  • HuggingFace Trainer 是 transformers 库中提供的一个高级训练 API,旨在简化深度学习模型的训练流程
    • 尤其是对 Transformer 类模型的支持很丰富
  • Trainer 封装了训练循环的核心逻辑,支持多种常见任务(如文本分类、问答、翻译等),并内置了以下多种关键功能:
    • 自动处理单卡/多卡训练、分布式训练
    • 集成日志记录(TensorBoard、W&B等)
    • 支持模型保存、加载和断点续训
    • 内置评估机制,可自定义评估指标
    • 支持早停(Early Stopping)、学习率调度等训练策略
    • 兼容 datasets 库的数据集格式
    • 支持混合精度训练
    • 支持梯度累积(Gradient Accumulation),即在较小的批次上累积梯度,以模拟更大的批次大小
  • HuggingFace Trainer 为 PyTorch 模型提供了完整的训练和评估循环,极大地简化了训练过程,让用户可以专注于模型、数据集和训练参数的配置,而无需手动编写复杂的训练代码
  • Trainer 是一个 开箱即用(out-of-the-box) 的训练工具,它将训练中的各种细节全部封装起来:
    • 前向传播(Forward pass)
    • 计算损失(Loss calculation)
    • 反向传播(Backward pass)
    • 参数更新(Optimizer step)
    • 学习率调整(Learning rate scheduling)
    • 检查点保存(Checkpoint saving)
    • 日志记录(Logging)

对 Trainer 的定制和生态集成

  • Trainer 提供了全面的默认功能,也设计了高度的可定制性
  • 可以通过 子类化(subclassing) 或 重写(overriding) 其内部方法来满足特定的需求,例如:
    • 重写 get_train_dataloader() 方法以自定义数据加载器
    • 重写 compute_loss() 方法以使用自定义的损失函数
    • 重写 compute_metrics() 方法以计算和报告自定义的评估指标
  • Trainer 与 Hugging Face 的其他库(如 datasets 和 accelerate)无缝集成,这使得数据加载、预处理和模型部署变得更加流畅
    • Trainer 特别针对 transformers 库中的模型进行了优化,但也可以与用户自定义的 PyTorch 模型一起使用
  • Trainer 的设计理念是 “开箱即用” ,对于大多数任务,只需要提供 model、args 和 train_dataset 即可
    • 如果需要更精细的控制,可以逐步添加 eval_dataset、data_collator 和 compute_metrics
    • 对于更高级的定制,如自定义训练循环或高级优化策略,则可以利用 callbacks 和 optimizers 等参数

Trainer 类使用示例

安装依赖

  • 首先安装必要的库:
    1
    pip install transformers datasets evaluate accelerate

完整代码示例

  • Trainer 抽象了整个训练循环,只需要提供模型、训练参数、数据集和数据整理器(data_collator),然后调用 trainer.train() 就可以开始训练
  • 以加载一个预训练好的 DistilBERT 模型为例,实现 IMDb 情感分析(正面/负面分类):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    from datasets import load_dataset
    from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    TrainingArguments,
    Trainer,
    DataCollatorWithPadding
    )
    import evaluate
    import numpy as np

    dataset = load_dataset("imdb") # 加载数据集(IMDb情感分析:正面/负面分类),包含train和test拆分

    model_name = "distilbert-base-uncased" # 可选择自己的模型
    tokenizer = AutoTokenizer.from_pretrained(model_name) # 加载分词器
    model = AutoModelForSequenceClassification.from_pretrained( # 用 Classification 类加载预训练模型
    model_name,
    num_labels=2 # 二分类任务(正面/负面)
    )
    # Trainer支持多种任务,只需替换对应的模型(如 `AutoModelForQuestionAnswering` 用于问答)和数据集即可

    def preprocess_function(examples): # 数据预处理函数(分词)
    return tokenizer(examples["text"], truncation=True, max_length=512)
    tokenized_dataset = dataset.map(preprocess_function, batched=True) # 对数据集应用预处理

    accuracy = evaluate.load("accuracy") # 定义评估指标(准确率)

    def compute_metrics(eval_pred): # 定义指标计算函数
    predictions, labels = eval_pred
    predictions = np.argmax(predictions, axis=1) # 取概率最大的类别
    return accuracy.compute(predictions=predictions, references=labels)

    # 重点:配置训练参数(TrainingArguments)
    training_args = TrainingArguments(
    output_dir="./save", # 模型保存路径
    learning_rate=2e-5, # 学习率
    per_device_train_batch_size=8, # 每个设备的训练批次大小
    per_device_eval_batch_size=8, # 每个设备的评估批次大小
    num_train_epochs=3, # 训练轮数
    weight_decay=0.01, # 权重衰减系数(防止过拟合)
    eval_strategy="epoch", # 每轮结束后评估,旧版本中该参数名为 eval_strategy,新版本中为 evaluation_strategy
    save_strategy="epoch", # 每轮结束后保存模型
    load_best_model_at_end=True, # 训练结束后加载最优模型,确保最终保存的是验证集上表现最好的模型
    logging_dir="./logs", # 日志保存路径
    logging_steps=100, # 每100步记录一次日志
    )

    trainer = Trainer( # 重点:初始化Trainer
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"], # 训练集
    eval_dataset=tokenized_dataset["test"], # 评估集
    tokenizer=tokenizer, # 传入 Tokenizer
    compute_metrics=compute_metrics, # 评估指标函数
    data_collator=DataCollatorWithPadding(tokenizer=tokenizer) # 使用动态padding
    )

    trainer.train() # 重点:开始训练

    # 测试训练结果:使用最优模型进行预测
    sample_text = "This movie is amazing! The acting was incredible and the plot was gripping."
    inputs = tokenizer(sample_text, return_tensors="pt").to(model.device)
    outputs = model(**inputs)
    predicted_class = outputs.logits.argmax().item()
    labels = ["negative", "positive"]
    print(f"预测结果:{labels[predicted_class]}")

启动命令

  • 根据需要启动 trainer_demo.py 的命令需要根据训练需求(如单卡训练、多卡训练、调试模式等)选择不同的方式
基础启动方式(单卡训练)
  • 直接用Python命令运行即可:
    1
    python trainer_demo.py

多卡训练(使用 accelerate)

  • 为了满足分布式训练,无需修改代码 ,通过 accelerate 工具(已在依赖中安装)启动即可,即 accelerate launch 命令即可启动多卡训练:

    1
    accelerate launch --num_processes=2 trainer_demo.py
  • 更多 accelerate launch 命令启动的用法见:/Notes/PyTorch/PyTorch——HF-Accelerate使用总结

调试模式(单步执行)启动

  • 如需调试代码(如设置断点),可使用 debugpy 或 Python 内置调试器:

    1
    python -m debugpy --wait-for-client --listen 5678 trainer_demo.py
    • 会在5678端口等待调试客户端连接(如 VS Code、PyCharm)
    • 适合排查训练逻辑或数据处理问题

启动混合精读训练

  • 启用 FP16/FP8 混合精度训练,有两种方法

  • 方法一:可在启动命令中指定 ,详情参考 accelerate launch 用法

    1
    accelerate launch --mixed_precision=fp16 trainer_demo.py
    • --mixed_precision 可选值:no(默认)、fp16、bf16、fp8(需特定GPU支持)
  • 方法二:可在 TrainingArguments 中配置:

    1
    2
    3
    4
    training_args = TrainingArguments(
    ...
    fp16=True, # 启用FP16混合精度
    )
    • 此时用基础命令启动即可:python trainer_demo.py

附录:Trainer 类的详细参数说明

核心参数(通常必须提供)

  • model: 训练或评估的 PyTorch 模型
    • 通常是 transformers 库中继承自 PreTrainedModel 的模型,比如 BertForSequenceClassification
    • 也可以传入一个自定义的 PyTorch 模型,但它需要与 Trainer 的其他组件兼容
  • args: 是一个 TrainingArguments 类的实例
    • 封装了所有的训练超参数,例如学习率、批次大小、训练轮次、日志目录等,是控制训练行为的主要方式
  • train_dataset: 训练数据集
    • 通常是 torch.utils.data.Dataset 或 Hugging Face datasets 库中的 Dataset 对象
    • 提供了这个参数后,就可以调用 trainer.train() 方法开始训练
  • eval_dataset: 评估数据集
    • Trainer 会定期在这个数据集上运行评估,以监控模型性能,并作为最佳模型的选择依据

数据处理相关参数

  • data_collator: 数据整理器
    • 作用是接收数据集中的多个样本,并将它们组合成一个批次(batch)
    • 对于 NLP 任务,它通常会负责对文本序列进行填充(padding)和张量化(tensorizing)
    • 如果不提供,Trainer 会使用一个默认的整理器,但通常只适用于固定长度的输入
    • 对于变长序列,需要提供一个 DataCollatorWithPadding
  • tokenizer: 分词器
    • 用于与 data_collator 协同工作,通常用于处理文本数据
    • 提供分词器可以让 Trainer 在内部自动处理一些数据预处理工作,比如与 DataCollatorWithPadding 结合使用来填充序列
  • model_init: 一个用于初始化模型的函数
    • 如果想使用 Trainer 的超参数搜索(hyperparameter search)功能 ,或者在每次训练前都重新初始化模型,而不是使用固定的 model 实例,就可以使用这个参数
    • 该函数应该不带参数,并返回一个模型实例

训练与评估相关参数

  • compute_metrics: 一个计算评估指标的函数
    • 这个函数接收一个 EvalPrediction 对象(包含预测结果和标签),并返回一个字典,其中包含在评估过程中报告的指标(例如准确率、F1 分数等)
  • callbacks: 一个 TrainerCallback 实例列表
    • 回调函数让你可以在训练循环的特定点(如每个训练步、每个 epoch 结束时)执行自定义逻辑,比如提前停止训练、在 TensorBoard 中记录额外信息等
  • optimizers: 一个包含 优化器和学习率调度器 的元组 (optimizer, lr_scheduler)
    • 这里配置的自定义优化器优先级比 TrainingArguments 中默认配置的更高

其他高级参数

  • preprocess_logits_for_metrics: 一个预处理逻辑的函数
    • 这个函数会在计算指标之前对模型的输出(logits)进行预处理
    • 例如,对于分类任务,你可能希望在计算准确率之前应用 argmax 来获取预测的类别索引
  • args.deepspeed: 这个参数不是直接给 Trainer 的,而是 TrainingArguments 的一部分
    • 可以传入一个 DeepSpeed 配置文件的路径或字典,从而启用 DeepSpeed 进行大规模分布式训练,以实现模型并行、梯度检查点等高级功能

附录:Trainer 中优化器设置

  • 在 HuggingFace Trainer 中设置优化器有两种主要方式:使用内置优化器或自定义优化器

使用内置优化器(简单方式)

  • Trainer 内置了常见优化器(如 Adamw_hf、adamw_torch 等),可通过 TrainingArguments 直接配置,无需额外代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from transformers import TrainingArguments

    training_args = TrainingArguments(
    output_dir="./saves",
    optim="adamw_torch", # 优化器设置,可选: adamw_hf, adamw_torch_fused, adafactor等,默认值为 adamw_hf
    learning_rate=2e-5, # 学习率,默认值 5e-5
    weight_decay=0.01, # 权重衰减(仅AdamW等支持),默认值为 0
    adam_beta1=0.9, # Adam优化器的beta1参数,默认值 0.9
    adam_beta2=0.999, # Adam优化器的beta2参数,默认值 0.999
    adam_epsilon=1e-8, # Adam优化器的epsilon参数,默认值 1e-8
    )
  • 常用内置优化器 :

    • adamw_hf:HuggingFace 实现的 AdamW(默认值)
    • adamw_torch:PyTorch 原生 AdamW
    • adamw_torch_fused:PyTorch 融合版 AdamW(速度更快)
    • adafactor:适合大模型的 Adafactor 优化器(无需设置学习率)

自定义优化器(灵活方式)

  • 如果需要使用 Trainer 不包含的优化器(如RAdam、SGD等),可通过重写 Trainer 类的 create_optimizer 方法实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class CustomOptimizerTrainer(Trainer):
    def create_optimizer(self): # 重写 Trainer 类
    params = [p for p in self.model.parameters() if p.requires_grad] # 定义需要优化的参数(排除冻结层)
    optimizer = optim.SGD( # 自定义优化器(这里以SGD为例,也可替换为其他优化器)
    params,
    lr=5e-5, # 学习率
    momentum=0.9, # SGD动量参数
    weight_decay=0.01
    )
    return optimizer # 返回优化器对象

    training_args = TrainingArguments(
    output_dir="./custom_optimizer_results",
    # ...
    # 注意:自定义优化器时,无需在TrainingArguments中设置optim参数
    )

    # 使用自定义 Trainer 初始化
    trainer = CustomOptimizerTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    tokenizer=tokenizer,
    )

    # 训练
    trainer.train()
  • 常用在需要对不同层设置不同学习率(如微调时固定底层,只训练顶层),可在 create_optimizer 中对参数分组:

    1
    2
    3
    4
    5
    6
    # PyTorch 官方库 `optim` 支持的分不同参数设置不同的学习率
    params = [
    {"params": model.pretrained_layer.parameters(), "lr": 1e-5}, # 底层参数
    {"params": model.classifier.parameters(), "lr": 1e-4} # 分类头参数
    ]
    optimizer = optim.AdamW(params, weight_decay=0.01)
  • 配合优化器使用时,可通过 TrainingArguments 的 lr_scheduler_type 配置调度策略(如线性衰减、余弦退火等)

  • 自定义优化器需兼容 PyTorch 的优化器接口(继承 torch.optim.Optimizer),否则可能导致训练异常

NLP——Megatron框架的使用

  • 参考链接:
    • 原始论文:Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism, arXiv 20200313, NVIDIA
    • 官网主页:Megatron Core User Guide
    • GitHub开源地址:github.com/NVIDIA/Megatron-LM

Megatron 整体说明

  • Megatron 是由 NVIDIA 开发的一个用于训练 LLM 的高性能框架
  • Megatron 专为分布式训练优化,支持模型并行、数据并行和混合精度训练,能够在数千个 GPU 上高效运行
  • Megatron 集成了多种优化技术,包括张量并行、管道并行、激活检查点等,显著提升了超大规模模型的训练效率
  • Megatron 通常与 DeepSpeed 结合使用,形成 Megatron-DeepSpeed 框架,进一步增强训练能力

Megatron 安装

  • Megatron 的安装需要结合 NVIDIA 的环境和依赖,以下是详细的安装步骤:
  • 推荐在 Linux 系统上安装

安装依赖项 PyTorch

  • 根据 CUDA 版本安装对应的 PyTorch:
    1
    2
    3
    4
    5
    # 对于CUDA 11.8
    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

    # 对于CUDA 12.1
    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

安装 Megatron

  • 下载源码并安装
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    git clone https://github.com/NVIDIA/Megatron-LM.git
    cd Megatron-LM

    pip install -r requirements.txt

    # 安装apex(用于混合精度训练)
    git clone https://github.com/NVIDIA/apex
    cd apex
    pip install -v --disable-pip-version-check --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./
    cd ..

    # 安装其他依赖
    pip install sentencepiece regex datasets

验证安装是否成功

  • 创建一个简单的Python脚本来验证 Megatron 是否正确安装:
    1
    2
    3
    4
    5
    6
    import torch
    from megatron import get_args
    from megatron.model import GPTModel

    # 能导入相关包,说明 Megatron 已成功安装
    print("Megatron安装成功!")

Megatron 数据处理

  • 详情参考:github.com/NVIDIA/Megatron-LM

  • 数据预处理负责将 .jsonl 的文本数据 tokenize 并处理成 Megatron 可以直接读取的数据格式(.bin 和 .idx 类型的文件),减少训练时的数据处理时间

  • 准备 .jsonl 文件,文件格式如下:

    1
    2
    {"text": "Your training text here..."}
    {"text": "Another training sample..."}
  • 数据预处理:

    1
    2
    3
    4
    5
    6
    7
    python tools/preprocess_data.py \
    --input data.jsonl \
    --output-prefix processed_data \
    --tokenizer-type HuggingFaceTokenizer \
    --tokenizer-model /path/to/tokenizer.model \
    --workers 8 \
    --append-eod
    • output-prefix:输出文件的前缀
    • append-eod:是否添加 EOD Token?
    • 注意:还可以根据需要设置 split_sentences 参数,对文档进行拆分成 sentence 再做 tokenize

Megatron 训练开源标准大模型

  • Megatron 支持对一些标准的开源大模型进行训练,比如 GPT2,此时不需要修改代码
  • 这种标准流程包含以下两部分:
    • 需要处理数据为 Megatron 支持的格式
    • 使用命令行启动任务
  • 本文暂不对这部分进行详细讲解

Megatron 训练自定义模型

核心思路

  • 需要做到如下事情
    • 1)先把 Megatron 自带的 GPT/BERT/T5 的「壳」理解透
    • 2)再把自己的网络结构「套」进去
    • 3)最后复用 Megatron 的并行、优化器、数据管道即可
  • 基本思路:
    • 使用 Megatron 训练自定义模型,不需要改 Megatron 核心 ,只实现 3-4 个钩子即可
    • 一些底层的高阶功能,如并行、混合精度、检查点 全部复用官方实现
    • 任何模型(CNN、RWKV、RetNet…)只要包装成 MegatronModule 并返回 loss ,都能用 Megatron 训练

目录结构

  • 目录结构如下,建议自建文件都放到统一的新文件夹 my_model 下
    1
    2
    3
    4
    5
    6
    7
    8
    Megatron-LM/
    ├─ megatron/ # 官方代码不动
    ├─ examples/ # 官方示例
    ├─ my_model/ # 我们自己的
    │ ├─ __init__.py
    │ ├─ model.py # 自定义网络
    │ ├─ layer.py # 自定义层
    │ └─ train.py # 入口脚本

自定义模型的 4 个关键钩子说明

  • Megatron 的训练循环入口是 pretrain(),它通过 4 个可插拔函数 决定「数据长什么样、模型长什么样、前向怎么算、验证看啥指标」:
    钩子名称 作用 示例文件
    model_provider 返回 nn.Module my_model/model.py
    train_valid_test_dataset_provider 返回三个 Dataset my_model/data.py
    forward_step_func 定义一次前向/损失 my_model/train.py
    process_non_loss_data_func TensorBoard 画额外指标(可选) my_model/train.py

模型钩子(model_provider)

  • 新建 my_model/model.py:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    from megatron.model.module import MegatronModule
    from megatron import get_args

    class MyCustomModel(MegatronModule):
    def __init__(self, num_tokentypes=0):
    super().__init__()
    args = get_args()
    self.embed = nn.Embedding(args.padded_vocab_size, args.hidden_size)
    # 这里换成自己的自定义层
    self.backbone = ConvNextBackbone(args.hidden_size, args.num_layers)
    self.lm_head = nn.Linear(args.hidden_size, args.padded_vocab_size)

    def forward(self, input_ids, position_ids, attention_mask, labels=None):
    x = self.embed(input_ids)
    x = self.backbone(x, attention_mask) # [b, s, h]
    logits = self.lm_head(x) # [b, s, V]

    if labels is None:
    return logits
    loss = F.cross_entropy(logits.view(-1, logits.size(-1)), labels.view(-1))

    # Megatron 的 GPTModel 实现也是在 Model.forward 直接返回 loss 的
    return loss

    def model_provider(pre_process=True, post_process=True):
    """给 Megatron 调用的工厂函数,负责返回 MegatronModule 类的模型对象"""
    return MyCustomModel()

数据钩子(train_valid_test_dataset_provider)

  • 把 .jsonl/txt 转成 Megatron 的 IndexedDataset:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # my_model/data.py
    from megatron.data.dataset_utils import build_train_valid_test_datasets
    from megatron import get_args

    def train_valid_test_dataset_provider(train_val_test_num_samples):
    args = get_args()
    return build_train_valid_test_datasets(
    data_prefix=args.data_path,
    splits_string=args.split,
    train_valid_test_num_samples=train_val_test_num_samples,
    seq_length=args.seq_length,
    masked_lm_prob=0.15 if args.task == 'BERT' else 0.0,
    seed=args.seed,
    skip_warmup=True,
    )

前向钩子(forward_step_func)

  • 定义前向过程(包含 loss 计算,需要返回 loss)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # my_model/train.py
    from megatron.training import get_model
    from megatron.utils import average_losses_across_data_parallel_group
    from megatron import get_args

    def forward_step(data_iterator, model):
    """一次 micro-batch 的前向"""
    args = get_args()
    tokens = next(data_iterator)['text'].long().cuda()
    labels = tokens[:, 1:].contiguous()
    tokens = tokens[:, :-1]
    position_ids = torch.arange(tokens.size(1), device=tokens.device).unsqueeze(0)
    attention_mask = (tokens != args.pad_token_id).unsqueeze(1).unsqueeze(2)

    # 因为传入 labels 参数时,模型的 forward 已经计算出来 loss 了,这里可以不需要自己写参数
    loss = model(tokens, position_ids, attention_mask, labels)
    reduced = average_losses_across_data_parallel_group([loss])

    # 第一个返回值必须是 loss,第二个返回值可以是任意想要记录的辅助信息
    return loss, {'lm loss': reduced[0]}

将钩子传入 pretrain() 函数

  • 调用 pretrain(),传入前面定义的钩子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # my_model/train.py
    from megatron.training import pretrain

    if __name__ == '__main__':
    pretrain(
    train_valid_test_dataset_provider, # 用于提供训练、验证和测试数据集的函数或模块
    model_provider, # 用于构建模型的函数,调用它可返回模型实例
    ModelType.encoder_or_decoder, # 或 encoder_decoder
    forward_step, # 定义模型前向传播步骤的函数,包括输入处理、模型计算和损失计算等
    process_non_loss_data_func=None # tensorboard 的 额外指标,process_non_loss_data_func 在这里暂未实现
    )

启动训练

  • 单节点 8 卡示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    torchrun --nproc_per_node=8 my_model/train.py \
    --tensor-model-parallel-size 2 \
    --pipeline-model-parallel-size 2 \
    --num-layers 12 \
    --hidden-size 768 \
    --data-path data/my_corpus \
    --seq-length 1024 \
    --micro-batch-size 4 \
    --global-batch-size 64 \
    --train-iters 50000 \
    --lr 1e-4 \
    --save checkpoints/myconvnext

附录:使用中遇到的常见问题

  • 显存 OOM 问题:
    • 解决方案:降 micro-batch、开 --recompute-activations、或加大 TP/PP
  • loss 不收敛 问题:
    • 检查学习率、warmup、初始化;确认 pad_token_id 设置正确
  • 多机多卡时卡死不动:
    • 确认 MASTER_ADDR/MASTER_PORT 一致,NCCL 版本统一等

附录:pretrain 函数的详细说明

  • Megatron-LM 中的 pretrain 函数是模型预训练的核心入口,定义在 megatron/training.py 文件中

pretrain() 函数参数

  • train_valid_test_dataset_provider:用于提供训练、验证和测试数据集的函数或模块
  • model_provider:用于构建模型的函数,调用它可返回模型实例
  • model_type:模型的类型,如ModelType.encoder_or_decoder
  • forward_step_func:定义模型前向传播步骤的函数,包括输入处理、模型计算和损失计算等
  • valid_forward_step_func:可选参数,用于验证阶段的前向传播函数
  • args_defaults:可选参数,包含默认的参数设置

pretrain() 函数内部执行的主要流程

  • 第一步,初始化Megatron :
    • 调用 initialize_megatron 函数,初始化 Megatron-LM 所需的分布式环境,包括设置分布式通信后端、初始化分布式进程组、配置日志记录等
    • 还会调用get_args()与get_timers()函数获取配置参数与计时器,并设置 PyTorch JIT 融合选项,同步启动时间
  • 第二步,设置模型、优化器和学习率计划 :
    • 调用setup_model_and_optimizer函数,传入model_provider和model_type,返回模型、优化器以及学习率调度器
  • 第三步,获取训练/验证/测试数据集 :
    • 根据 args.virtual_pipeline_model_parallel_size 是否为 None 来判断是否需要进行虚拟流水线模型并行处理
    • 如果不进行虚拟流水线模型并行,则直接调用build_train_valid_test_data_iterators函数,获取训练、验证和测试数据迭代器
  • 第四步,调用train函数训练模型 :
    • 判断 args.do_train 和 args.train_iters 是否满足条件,若满足则调用 train 函数执行训练过程
    • train 函数接收多个参数,包括前向传播步骤函数、模型、优化器、学习率调度器、训练数据和验证数据迭代器等,并返回最后一次迭代的索引和到目前为止执行的浮点运算次数

附录:如何修改优化器

  • Megatron-LM 里“指定/切换优化器”有两种主流做法:
    • 第一种:不动源码,靠命令行参数(最简单,官方已内置)
    • 第二种:改源码,注册自定义优化器(想换 Lion、RAdam 等第三方优化器时用)

第一种:命令行直接切换(无需改代码)

  • 注:Megatron 从 2024-10 之后的版本开始,把 optimizer 也暴露成了 CLI 参数:
    主要参数 取值 说明
    --optimizer adam, sgd 默认 adam,会自动选用 Apex 的 FusedAdam
    --adam-beta1 0.9
    --adam-beta2 0.95
    --adam-eps 1e-8
    --sgd-momentum 0.9 只在 --optimizer sgd 时生效
    --weight-decay 0.1 通用
    --clip-grad 1.0 梯度裁剪
  • 示例:把优化器换成 SGD + momentum 的启动脚本如下:
    1
    2
    3
    4
    5
    torchrun --nproc_per_node=8 pretrain_gpt.py \
    ... \
    --optimizer sgd \
    --sgd-momentum 0.9 \
    --weight-decay 1e-4

第二种:源码级自定义优化器(以 Lion 为例)

  • 当你想用官方未内置的优化器(Lion、AdaFactor、RAdam等)时,只要三步:

  • 第一步:在 megatron/core/optimizer/ 里新建文件 lion.py,并定义自己的优化器类

    1
    2
    3
    4
    import torch
    class Lion(torch.optim.Optimizer): # 继承 Optimizer 类
    def __init__(self, params, lr=1e-4, betas=(0.9, 0.99), weight_decay=0.0):
    ...
    • 问题:需要特殊处理的优化器,比如可能涉及其他更多超参数的优化器,还需要考虑
  • 第二步:在 megatron/core/optimizer/__init__.py 的 _get_megatron_optimizer() 中注册新的优化器

    1
    2
    elif opt == 'lion':
    optimizer = Lion(param_groups, lr=args.lr, weight_decay=args.weight_decay)
  • 第三步:启动脚本里添加新的优化器选项

    1
    --optimizer lion
  • Megatron 会自动把上述自定义的 Lion 优化器包装到 DistributedOptimizer(或 DeepSpeed ZeRO,如果启用)里,梯度同步、fp16/bf16 主参数、checkpoint 保存/加载全部复用现有逻辑


附录:CPU Offload & 显存优化

  • Megatron 支持把优化器状态卸载到 CPU 以减少显存:
    1
    2
    --optimizer-cpu-offload \
    --optimizer-offload-fraction 0.8 # 80% 状态放 CPU

附录:Megatron 使用代码简单示例

  • 下面是一个使用 Megatron 训练 GPT 模型的示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    import os
    import torch
    from megatron import get_args
    from megatron import mpu
    from megatron.initialize import initialize_megatron
    from megatron.training import setup_model_and_optimizer
    from megatron.model import GPTModel
    from megatron.training import train_step
    from megatron.data.gpt_dataset import build_train_valid_test_datasets

    def main():
    # 初始化Megatron
    initialize_megatron(extra_args_provider=None, args_defaults={
    'tokenizer_type': 'GPT2BPETokenizer',
    'micro_batch_size': 4,
    'global_batch_size': 32,
    'lr': 0.00015,
    'min_lr': 0.00001,
    'lr_decay_style': 'cosine',
    'weight_decay': 0.1,
    'clip_grad': 1.0,
    'lr_warmup_fraction': 0.01,
    'num_layers': 24,
    'hidden_size': 1024,
    'num_attention_heads': 16,
    'seq_length': 1024,
    'max_position_embeddings': 1024,
    'vocab_size': 50257, # GPT-2 vocab size
    'tensor_model_parallel_size': 2,
    'pipeline_model_parallel_size': 2,
    'pipeline_model_parallel_split_rank': 0,
    'fp16': True,
    'bf16': False,
    'seed': 1234,
    })

    args = get_args()

    # 构建数据集
    train_ds, valid_ds, test_ds = build_train_valid_test_datasets(
    data_prefix=args.data_path,
    data_impl=args.data_impl,
    splits_string=args.split,
    train_valid_test_num_samples=[args.train_samples, args.valid_samples, args.test_samples],
    seq_length=args.seq_length,
    seed=args.seed,
    skip_warmup=(not args.mmap_warmup)
    )

    # 设置模型和优化器
    model, optimizer, lr_scheduler = setup_model_and_optimizer()

    # 训练循环
    iteration = 0
    max_iterations = args.train_iters

    while iteration < max_iterations:
    loss = train_step(model, optimizer, lr_scheduler, train_ds)
    if torch.distributed.get_rank() == 0 and iteration % args.log_interval == 0:
    print(f"迭代: {iteration}/{max_iterations}, 损失: {loss.item()}")
    iteration += 1

    if __name__ == "__main__":
    main()
  • 通常使用以下命令启动训练(假设每台机器使用 4 个 GPU):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 使用torchrun启动分布式训练
    torchrun --nproc_per_node=4 --master_port=12345 your_script.py \
    --data-path /path/to/your/dataset \
    --vocab-file /path/to/vocab.json \
    --merge-file /path/to/merges.txt \
    --save /path/to/save/checkpoints \
    --load /path/to/load/checkpoints \
    --num-layers 24 \
    --hidden-size 1024 \
    --num-attention-heads 16 \
    --seq-length 1024 \
    --max-position-embeddings 1024 \
    --micro-batch-size 4 \
    --global-batch-size 32 \
    --lr 0.00015 \
    --min-lr 0.00001 \
    --lr-decay-style cosine \
    --lr-warmup-fraction 0.01 \
    --weight-decay 0.1 \
    --clip-grad 1.0 \
    --fp16 \
    --seed 1234

附录:Megatron 中间数据 decode 查看

  • 示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    import os
    import torch

    # 读取 tokens.bin(假设为 int32 类型,根据预处理配置调整 dtype)
    tokens_path = "./processed_tokens_document.bin"

    dtype = torch.int32 # 假设是int32,根据实际预处理配置调整(如int64)
    bytes_per_token = dtype.itemsize # int32->4字节,int64->8字节

    # 获取文件总大小(字节)
    file_size = os.path.getsize(tokens_path)
    # 计算总token数(总字节数 / 每个token的字节数)
    total_tokens = file_size // bytes_per_token
    print(f"文件总大小:{file_size} 字节,总token数:{total_tokens}")

    from transformers import AutoModelForCausalLM, AutoTokenizer
    model_name = "./path_to_model/"
    # load the tokenizer and the model
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    # 抽查的 Token 数量
    block_size = 10000

    # 分块读取并解码
    with open(tokens_path, "rb") as f:
    for start in range(0, total_tokens, block_size):
    # 计算当前块的结束位置(不超过总token数)
    end = min(start + block_size, total_tokens)
    current_block_size = end - start

    # 读取当前块的二进制数据(字节数 = token数 × 每个token的字节数)
    f.seek(start * bytes_per_token) # 移动到当前块的起始位置
    block_bytes = f.read(current_block_size * bytes_per_token)

    # 将二进制数据转为torch tensor(token索引)
    block_tokens = torch.frombuffer(block_bytes, dtype=dtype)

    # 解码当前块为文本
    block_text = tokenizer.decode(block_tokens.tolist(), skip_special_tokens=False) # skip_special_tokens=True 会缺失 Special Token

    print(f"处理块 {start//block_size + 1}/{(total_tokens + block_size -1)//block_size}")
    print(block_text)
    break # 打开 break 可不断循环读取

附录:ckpt 文件清理

  • 使用 Megatron-LM 训练模型时,为了保证可恢复,常常会出现存储多个 ckpt 的情况,一般是一定的步骤就存储一个 ckpt
  • 当实验完成后一般仅保留最后一个即可
  • Meagtron-LM 的每个 ckpt 中,都完整存储着从这个 ckpt 启动继续训练所需的所有文件,包括模型权重文件等
    1
    2
    distrib_optim.pt:分布式优化器状态分片,训练恢复时用
    model_optim_rng.pt:随机数生成器状态,训练恢复时用,可能包含模型权重等,根据具体场景可能部分

脚本编写

  • 脚本编写的基本要求为:
    • 删除某个目录(用参数传入)下所有满足条件的文件夹(包括子文件夹):
      • 1)创建日期在 “2024-08-01” 到 “2024-08-10” 之间的
      • 2)文件名以 iter_000x 命名,且x是100 的整倍数,比如 iter_0000600 或 iter_0001000 等
      • 3)当前同级目录下还存在以 iter_000x 命名,且 x 比自己大的文件夹
    • 删除前要求如下:
      • 删除每个文件夹时,先询问是否删除,必须等待回应才继续(同时打印被删除的文件夹及其同级的其他文件夹和文件),Y表示删除,N表示不删除,直接Enter表示不删除;
      • 注意:为了安全起见,一定要收到 “Y” 作为输入再删除,否则不删除,避免误删
  • 下面是一个脚本实现(大模型实现,经过本人部分修改),用于帮助清理 ckpt(已经测试过可以使用):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    #!/bin/bash

    # 用法: ./clean_x00_steps_in_ckpt.sh /path/to/target_dir
    TARGET_DIR="$1"

    if [ -z "$TARGET_DIR" ]; then
    echo "❌ 请传入目标目录路径"
    exit 1
    fi

    if [ ! -d "$TARGET_DIR" ]; then
    echo "❌ 目录不存在: $TARGET_DIR"
    exit 1
    fi

    # step_interval
    step_interval=100

    # 日期范围
    START_DATE="2025-08-01"
    END_DATE="2025-11-10"

    # 转换为时间戳方便比较
    start_ts=$(date -d "$START_DATE" +%s)
    end_ts=$(date -d "$END_DATE" +%s)

    # 遍历所有匹配 iter_000x 的文件夹(递归)
    find "$TARGET_DIR" -type d -regextype posix-egrep -regex '.*/iter_000[0-9]+' | while read -r dir; do
    basename=$(basename "$dir")

    # 提取数字部分
    num=$(echo "$basename" | sed -E 's/iter_0+([0-9]+)/\1/')

    # 判断是否是 step_interval 的倍数
    if (( num % step_interval != 0 )); then
    continue
    fi

    # 获取创建日期(Linux & macOS兼容)
    if [[ "$OSTYPE" == "darwin"* ]]; then
    create_date=$(stat -f "%SB" -t "%Y-%m-%d" "$dir")
    else
    create_date=$(stat -c %w "$dir")
    if [ "$create_date" = "-" ]; then
    create_date=$(stat -c %y "$dir" | cut -d' ' -f1)
    fi
    fi

    # 转换为时间戳
    create_ts=$(date -d "$create_date" +%s 2>/dev/null)
    if [ -z "$create_ts" ]; then
    continue
    fi

    # 日期范围判断
    if (( create_ts < start_ts || create_ts > end_ts )); then
    continue
    fi

    # 检查同级目录是否存在更大的 iter_000y
    parent_dir=$(dirname "$dir")
    bigger_exist=false
    for sibling in "$parent_dir"/iter_000*; do
    if [ -d "$sibling" ]; then
    sib_num=$(echo "$(basename "$sibling")" | sed -E 's/iter_0+([0-9]+)/\1/')
    if (( sib_num > num )); then
    bigger_exist=true
    break
    fi
    fi
    done

    if [ "$bigger_exist" = false ]; then
    continue
    fi

    # 符合条件 -> 询问是否删除
    # 打印当前目录及同级目录内容
    echo "----------------------------------------"
    echo "📂 待删除文件夹: $dir ✅"
    echo "----------------------------------------"
    echo "同级目录内容:"
    ls -l "$parent_dir"
    echo "----------------------------------------"
    read -p "是否删除? (Y/N, 回车默认不删除): " choice < /dev/tty # 必须使用 < /dev/tty 以确保从交互界面接收到一个输入

    if [[ "$choice" =~ ^[Yy]$ ]]; then
    rm -rf "$dir" && echo "✅ 已删除 $dir"
    else
    echo "跳过 $dir"
    fi
    done

附录:使用问题记录

问题:lr 配置需要注意加小数点

  • 在 yaml 文件中进行 lr 等配置时,使用类似 8e-6 的配置可能会出现问题,使用 8.0e-6 会更保险
  • 原因:某些 yaml 配置文件解析器可能将 8e-6 解析为字符串或其他类型,而不是小数类型

NLP——LLM对齐微调相关总结

本文简单录对齐微调的一些方法,部分方法的详细内容可以在本人其他博客搜索到


PPO

  • PPO 是最原始的 RLHF 方法

CPPO(Continual Proximal Policy Optimization)

  • 原始论文:CPPO: Continual Learning for Reinforcement Learning with Human Feedback, ICLR 2024, Harbin Institute of Technology (Shenzhen):截止到 20250612,cited by 25
  • 其他容易误解论文:CPPO: Accelerating the Training of Group Relative Policy Optimization-Based Reasoning Models, arXiv 202503, Xiamen University:截止到 20250612,cited by 12
    • 不厚道,命名与别人相同,容易造成读者误解

REINFORCE

  • 不需要 Value Model,蒙特卡罗法评估奖励
  • 对 PPO 的简化

REINFORCE++

  • 原始论文:REINFORCE++: An Efficient RLHF Algorithm with Robustness to Both Prompt and Reward Models, 20250104-20251110, Jian Hu & Jason Klein Liu & Wei Shen
  • REINFORCE++ 方法介绍
  • 本质可以理解为 REINFORCE 方法(不是基于 Prompt 组的,有一个基于历史全局的基线)
  • 在 REINFORCE 的基础上,记录历史平均奖励作为基线,判断模型是否在进步(相比 GRPO,基线不是 Prompt 粒度的,而是历史)
  • 使用历史奖励的均值和方差做归一化,类似 Batch Normalization(论文认为 GRPO 的方法会出现 Prompt 粒度的有偏问题)
  • REINFORCE++ 方法出现在 ReMax, GRPO 和 RLOO 之后,对比如下:
  • 其他讨论:
    • REINFORCE++ 使用的是 k2 KL 散度估计而不是 k3,似乎觉得 k3 有偏差,论文参考了博客 Rethinking KL Regularization in RLHF: From Value Estimation to Gradient Optimization 中的内容
      • 但是论文的这个认知待确认,原本的理解应该是 k3 无偏,k2 有偏才对,且 k2 和 k3 的偏差似乎是差不多的,只是 k3 的计算稍微更复杂一些

ReMax

  • 参考链接:ReMax: A Simple, Effective, and Efficient Reinforcement Learning Method for Aligning Large Language Models, 2023, ICML 2024, 香港中文大学,南京大学
  • 本质是 REINFORCE 方法
  • 使用当前策略下每个状态下概率最高的动作对应的样本的奖励作为基线(每一步都贪心决策)
  • 注意:概率最高的动作会持续走到最后直到拿到一个完整的 rollout,再来计算奖励
  • 注意:与 GRPO 和 RLOO 不同,每次仅采样两个样本(其中一个是目标样本,另一个是贪心决策的样本)
  • ReMax 在 RLOO 之前一点点提出,算是并行的工作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # Algorithm 1: ReMax for Aligning LLMs
    1 Input: reward_model(rm), language_model(lm)
    2 for prompt in datasets:
    3 seq=lm.sample(prompt, greedy=False)
    4 seq_max=lm.sample(prompt, greedy=True)
    5 rew=rm(prompt, seq)−rm(prompt, seq_max)
    6 logp=lm.inference(prompt, seq)
    7 loss=−(logp.sum(dim=−1)*rew).mean()
    8 lm.minimize(loss)
    9 Output: language_model

GRPO(Group Relative Policy Optimization)

  • 每次生成一组样本,并对这组样本进行归一化

RLOO(REINFORCE Leave-One-Out)

  • 参考链接:LLM RLHF 2024论文(四)RLOO - sanmuyang的文章 - 知乎
  • 类似于 GRPO(发表时间也类似),每次生成一组样本
  • 与 GRPO 的主要区别是:减去的是其他样本(不包含当前样本)的均值,且没有除以方差
  • RLOO 没有使用限制 KL 散度约束?

DPO(Direct Preference Optimization)

  • DPO 及其相关改进参见 NLP——LLM对齐微调-DPO 和 NLP——LLM对齐微调-DPO相关改进

RAFT(Reward rAnked FineTuning)

  • RAFT,即 Reward rAnked FineTuning,出自 Raft: Reward ranked finetuning for generative foundation model alignment, 2023.
  • RAFT 是 RL-free 方法,其核心步骤包括:
    • 数据收集:可以利用正在训练的生成模型、预训练模型或它们的混合模型作为生成器,提升数据生成的多样性和质量
    • 数据排序:利用与目标需求对齐的分类器或者回归器,筛选出最符合人类需求的样本
    • 模型微调:利用筛选出的样本对模型进行微调,使训练后的模型与人类需求相匹配

RAHF(Representation Alignment from Human Feedback)

  • (RAHF)Aligning Large Language Models with Human Preferences through Representation Engineering, 2024, Fudan
  • RAHF(Representation Alignment from Human Feedback)的训练流程聚焦于通过表示工程实现大语言模型与人类偏好的对齐,整体方法描述如下

步骤一:指导LLM理解人类偏好

  • 单模型对比指令微调(RAHF-SCIT) (Single Large Language Model through Contrastive Instruction Tuning (SCIT))
    • 使用对比指令(如“生成受人类偏好的响应”和“生成不受人类偏好的响应”)对单个LLM进行微调
    • 训练目标:通过最小化损失函数,使模型在给定正偏好指令时提高生成偏好响应的概率,给定负偏好指令时降低该概率
    • 优势:通过同一模型学习偏好与非偏好的差异,避免特征空间不一致问题
  • 双模型监督训练(RAHF-Dual)
    • 分别微调两个LLM:
      • 偏好模型 :使用偏好响应数据进行监督训练,学习生成符合人类偏好的输出
      • 非偏好模型 :使用非偏好响应数据训练,学习生成不符合偏好的输出
    • 特点:通过不同模型分别捕捉偏好与非偏好的表征,但需注意双模型特征空间可能存在的偏差

步骤二:收集模型 Activity Pattern

  • 输入处理 :将查询-响应对与偏好/非偏好指令拼接,输入模型以获取中间层隐藏状态(即内部表征)。为确保长度一致,对指令和响应进行 padding 处理
  • 差异向量计算 :提取偏好刺激(\(A_{p^{+}, \pi, l}\))和非偏好刺激(\(A_{p^{-}, \pi, l}\))下的隐藏状态,计算差值 \(v_l = A_{p^{+}, \pi, l} - A_{p^{-}, \pi, l}\),该向量表征人类偏好相关的 Activity Pattern 差异

步骤三:构建最终对齐模型

  • LoRA适配器微调 :利用低秩适配器(LoRA)拟合差异向量 \(v_l\),通过均方误差(MSE)损失函数将差异向量融入模型表征:
    $$
    \mathcal{L}_{Align} = \left| A_{p, \pi_{LoRA}, l} - (A_{p, \pi_{base}, l} + \alpha v_l) \right|_2
    $$
    其中 \(\alpha\) 控制差异向量的干预强度,通过调整该超参数平衡模型原始能力与偏好对齐效果
  • 目标层选择 :优先选择模型中间层(如LLaMA2-7B的第10、20层)进行操作,因中间层更易捕捉与偏好相关的全局表征,避免顶层任务特异性或底层表征不完整的问题

SimpleRL

  • 原始论文:SimpleRL-Zoo: Investigating and Taming Zero Reinforcement Learning for Open Base Models in the Wild, HKUST & TikTok Meituan, 20250524 & 202508076
  • 没有提出新的方法,就是原始的 GRPO 方法,但是对 GRPO 方法进行了微调
  • 与 DAPO 类似,SimpleRL 将 GRPO 公式中的长度归一化挪到更外层的循环中去
  • SimpleRL 这篇论文提出了适用于多种开源基础模型的 Zero RL 训练方法
    • 注:Zero RL:指直接基于基础模型训练的方法,这种方法不依赖 SFT
    • SimpleRL 的贡献在于通过优化奖励设计、数据难度匹配等关键策略,实现 Zero RL 训练方法下推理能力提升
  • 具体设计:
    • 训练方法:直接从基础模型出发进行强化学习,不进行任何前置的 SFT,采用GRPO算法作为训练核心,仅依赖基于正确性的规则奖励和简单训练设置
    • 关键训练组件
      • 算法:采用移除目标函数中长度归一化的 GRPO 算法,优化计算效率,无需单独价值模型,直接通过组归一化奖励估计优势函数
      • 奖励函数:仅基于答案正确性设计,正确答案奖励+1,错误奖励0,摒弃严格格式奖励(如强制答案入盒),避免限制模型探索
      • 数据处理:将 GSM8K 和 MATH 数据集按难度分为 Easy(GSM8K+MATH lv.1)、Medium(MATH lv.1-4)、Hard(MATH lv.3-5)三类,每类含约 8000 个样本,根据模型能力匹配对应难度数据
      • 模型与 Prompt:覆盖 10 种不同家族和规模的模型(Llama3-8B、Mistral 系列、Qwen2.5 系列等),对指令跟随能力弱的模型采用简单提示(仅要求分步推理),能力强的模型采用复杂提示
  • 对 GRPO 目标函数作为微小修改(实际上这种改法与 DAPO 一致)
    $$
    \mathcal{J}_{\text{GRPO}}(\theta)=\underbrace{\frac{1}{\color{red}{\sum_{i=1}^{G}\left|o_{i}\right|}} \color{red}{\sum_{i=1}^{G} \sum_{t=1}^{\left|o_{i}\right|}} min \left[r_{i, t}(\theta) \hat{A}_{i}, clip\left(r_{i, t}(\theta) ; 1-\epsilon, 1+\epsilon\right) \hat{A}_{i}\right]}_{\text{Clipped policy update } }-\underbrace{\beta \mathbb{D}_{KL}\left[\pi_{\theta} | \pi_{ref }\right]}_{\text{KL penalty } }
    $$
    • 理解:这样可以消除长度归一化对模型响应长度的不当约束,更贴合 Zero RL 训练中“鼓励模型自由探索合理推理长度”的需求
  • 总结思考: SimpleRL 关键优化策略
    • 1)摒弃刚性格式奖励,优先保证响应可验证性,避免抑制模型探索
    • 2)严格匹配训练数据难度与模型固有能力,难度不匹配会导致训练崩溃或效果不佳
    • 3)调整探索相关超参数:采用较大采样量(N≥8)和合适温度(训练温度 1.0-1.2),稳定训练过程
    • 4)避免传统 SFT 冷启动:传统 SFT 会限制模型探索能力,降低RL阶段的推理行为涌现潜力

SEED-GRPO(Semantic Entropy EnhanceD GRPO)

  • SEED-GRPO: Semantic Entropy Enhanced GRPO for Uncertainty-Aware Policy Optimization, 20250518, Zhejiang University

VAPO(Value-model-based Augmented Proximal Policy Optimization)

  • VAPO: Efficient and Reliable Reinforcement Learning for Advanced Reasoning Tasks, arXiv 202504, ByteDance Seed
  • 字节 Seed 团队的作品,是对 DAPO 的进一步改进

VC-PPO(Value-Calibrated PPO)

  • (VC-PPO)What’s Behind PPO’s Collapse in Long-CoT? Value Optimization Holds the Secret, arXiv 20250303, ByteDance Seed
  • 核心贡献:
    • Pretrained value:开始 RL 前先预训练价值网络
    • Decoupled-GAE:计算 Advantage (for Actor 损失)时和 计算 Target Reward(for Critic 损失)时使用不同的 \(\lambda\)

ORZ(Open-Reasoner-Zero)

  • 原始论文:Open-Reasoner-Zero: An Open Source Approach to Scaling Up Reinforcement Learning on the Base Model, arXiv 20250401, StepFun & THU
  • 第一个开源的 LLM 上面向推理的 zero RL 实现(即从 Base Model 直接进入 RL)
  • 相关链接
    • GitHub: https://github.com/Open-Reasoner-Zero/Open-Reasoner-Zero
    • HuggingFace: https://huggingface.co/Open-Reasoner-Zero
  • 效果:
    • ORZ-32B 在 GPQA Diamond 基准上优于 DeepSeek-R1-Zero-Qwen-32B,训练步骤仅为其 1/30
    • ORZ-32B 在 AIME 2024 上获得 48.1分(同 Size 模型上,后来字节的 VAPO 做到了 60分)
    • ORZ-7B 做到了 17.9 分
  • 注:包含很多训练经验,值得一看

GVPO(Group Variance Policy Optimization)

  • 原始论文:GVPO: Group Variance Policy Optimization for Large Language Model Post-Training, arXiv 20250319, HKUST
  • 核心贡献:
    • GVPO 推到了一个 RL 约束优化问题的唯一最优解
    • 提出一种灵活的采样分布避免了 on-policy 和 重要性采样
  • 结果:用 Qwen-7B 模型为基线,在 AIME 2024 上,做到了 20.72分(注:比 ORZ 和 GRPO 等都高)

GPG(Group Policy Gradient)

  • 原始论文:GPG: A Simple and Strong Reinforcement Learning Baseline for Model Reasoning, arXiv 20250501, AMAP Alibaba:AMAP是高德地图的简称
  • 效果明显优于 GRPO
  • GPG 方法的特点:移除所有花里胡哨的组件(问题:效果真的好吗?)
  • GPG 方法的训练算法:
  • 各种方法的损失函数比较:
  • 补充趣事:GPG 对 Dr.GRPO 的批判:

    In addition to these methods to improve efficiency and stability, a very recent and concurrent work Dr.GRPO [31] studies the details of reward and loss normalization and states GRPO tends to generate more tokens. However, although it reveals the reward bias in the advantage function, we observe that its performance did not significantly outperform GRPO.


ORPO(Odds Ratio Preference Optimization)

  • 原始论文:ORPO: Monolithic Preference Optimization without Reference Model, arXiv 20240314, KAIST AI:截止到 20250616 日,cited by 244
    • 注:KAIST AI 是韩国科学技术院(KAIST)的一个机构
  • 核心思路:
    • 偏好对齐的同时考虑 SFT 损失

ECPO(Early Clipped GRPO)

  • 来源于快手的 OneREc OneRec Technical Report,用于 LLM4Rec 领域的偏好对齐
  • 具体来说,对于用户 \( u \),论文使用旧策略模型生成 \( G \) 个物品。每个物品与用户一起输入偏好奖励模型,得到 P-Score 作为奖励 \( r_i \)。优化目标如下:
    $$
    \mathcal{J}_{\text{ECPO}}(\theta) = \mathbb{E}_{u \sim P(U), \{o_i\}_{i=1}^G \sim \pi_{\theta_{old} } } \left[ \frac{1}{G} \sum_{i=1}^G \min \left( \frac{\pi_\theta(o_i|u)}{\color{red}{\pi’_{\theta_{old} }}(o_i|u)} A_i, \text{clip} \left( \frac{\pi_\theta(o_i|u)}{\color{red}{\pi’_{\theta_{old}} }(o_i|u)}, 1 - \epsilon, 1 + \epsilon \right) A_i \right) \right], \\
    A_i = \frac{r_i - \text{mean}(\{r_1, r_2, \ldots, r_G\})}{\text{std}(\{r_1, r_2, \ldots, r_G\})},\\
    \color{red}{\pi’_{\theta_{old} }(o_i|u) = \max \left( \frac{\text{sg}(\pi_\theta(o_i|u))}{1 + \epsilon + \delta}, \pi_{\theta_{old} }(o_i|u) \right), \quad \delta > 0,}
    $$
    • \(\text{sg}\) 表示停止梯度操作(stop gradient operation)
    • \(\delta\) 是一个大于 0 的超参数
  • ECPO 对 GRPO(Group Policy Relative Optimization)(2024) 进行了修改,使其训练过程更加稳定
    • 如图 6 所示,在原始 GRPO 中,允许负优势(negative advantages)的策略比率(\(\pi_\theta / \pi_{\theta_{old} }\))较大,这容易导致梯度爆炸
    • 因此,论文预先对具有较大比率的策略进行截断,以确保训练稳定性,同时仍允许相应的负优势生效
    • \(\delta\) 越大,可容忍的策略比率越大,意味着可容忍的梯度越大,这可以根据实际需求确定
  • 在 OneRec 中,论文将 \(\delta\) 设为 0.1,表示允许负优势的策略比率略微超过 \(1 + \epsilon\)

CISPO(Clipped IS-weight Policy Optimization)

  • 原始论文:MiniMax-M1: Scaling Test-Time Compute Efficiently with Lightning Attention, arXiv 20250616, MiniMax
  • 名字说明:Clipped IS-weight Policy Optimization 中 IS 表示 Importance Sampling
  • 效果:相对 DAPO,实现两倍加速
  • 核心思路:不再裁剪 token 更新(影响是否反传梯度),而是裁剪重要性采样权重
    • 注:思路和最近的 ECPO 有点相近
    • 理解:这种做法是不对的,相当于忽略了 PPO 本身的优势

GPPO(Gradient-Preserving Clipping Policy Optimization)

  • Klear-Reasoner: Advancing Reasoning Capability via Gradient-Preserving Clipping Policy Optimization, Klear, arXiv 20250812
  • Hugging Face地址:https://huggingface.co/Suu/Klear-Reasoner-8B
  • GitHub 地址:https://github.com/suu990901/KlearReasoner/tree/main
  • GPPO 方法用于解决传统强化学习(如PPO、GRPO)中的剪辑机制(Clipping)存在两个关键问题:
    • 高熵token梯度被抑制 :超出上阈值(\(1+\epsilon\))的高熵 token(对应关键探索行为)的梯度被直接丢弃,限制模型探索能力
    • 负样本收敛延迟 :低于下阈值(\(1-\epsilon\))的次优轨迹梯度被截断,导致模型难以从负样本中学习,收敛速度减慢
  • GPPO 不丢弃任何token的梯度,即使是超出剪辑范围的 token,其梯度也会被纳入反向传播计算图。通过有界且温和的梯度传播 ,平衡训练稳定性与有价值梯度信息的保留:
    • 对高熵token(超出上阈值),保留其梯度以增强探索;
    • 对次优轨迹(低于下阈值),保留其梯度以加速负样本学习
  • GPPO 的优势:
    • 增强探索能力 :保留高熵token的梯度,避免过早终止探索;
    • 加速负样本学习 :利用次优轨迹的梯度,减少重复采样,加快收敛;
    • 稳定训练 :通过有界梯度控制,避免梯度爆炸,维持训练稳定性
  • 实验表明,GPPO 在数学(AIME)和编程(LiveCodeBench)任务上的性能优于传统剪辑方法(如 GRPO w/ Clip-Higher)和并发方法(如 CISPO)
  • GPPO 损失函数(基于 GRPO 的 token-level 损失修改而来),公式如下:
    $$\mathcal{L}^{GPPO}(\theta)=\mathbb{E}_{x \sim \mathcal{D}}\left[\frac{1}{\sum_{j=1}^{M} T_{j}} \sum_{j=1}^{M} \sum_{t=1}^{T_{j}} min \left(\delta \tilde{A}^{(j)}, clip\left(\delta, \frac{1-\epsilon_{l}}{sg(\delta)} \delta, \frac{1+\epsilon_{h}}{sg(\delta)} \delta\right) \overline{A}^{(j)}\right)\right]$$
    • \(\delta = r_{t}^{(j)}(\theta)\) :token 级重要性采样比(当前策略与旧策略的概率比);
    • \(sg(\cdot)\) :停止梯度(stop-gradient)操作,确保 \(\frac{\delta}{sg(\delta)}\) 数值上恒为1,前向计算不变;
    • \(\epsilon_l, \epsilon_h\) :剪辑的下、上阈值(如 \(\epsilon_l=0.2, \epsilon_h=0.28\));
    • \(\tilde{A}^{(j)}\) :组相对优势(group-relative advantage),通过组内奖励标准化计算;
    • \(\sum_{j=1}^{M} T_j\) :所有token的总长度,用于归一化
  • GPPO 梯度表达式,(梯度计算保留所有 token 的贡献),公式如下:
    $$\nabla_{\theta} \mathcal{L}^{GPPO}(\theta) = \mathbb{E}_{x \sim \mathcal{D}}\left[\frac{1}{\sum_{j=1}^{M} T_{j}} \sum_{j=1}^{M} \sum_{t=1}^{T_{j}} \mathcal{F}_{j, t}(\theta) \cdot \phi_{\theta}\left(a_{j, t}, s_{j, t}\right) \cdot \tilde{A}^{(j)}\right]$$
    • 其中,\(\mathcal{F}_{j, t}(\theta)\)(梯度权重函数)定义为:
      $$\mathcal{F}_{j, t}(\theta) = \begin{cases}
      1-\epsilon_{l} & \text{if } \delta<1-\epsilon_{l} \text{ and } \tilde{A}^{(j)}<0, \\
      1+\epsilon_{h} & \text{if } \delta>1+\epsilon_{h} \text{ and } \tilde{A}^{(j)}>0, \\
      \delta & \text{otherwise}
      \end{cases}$$
    • \(\phi_{\theta}(a_{j,t}, s_{j,t})\) :策略网络输出的 logits 关于参数 \(\theta\) 的导数(减去基线项);
      • 当 \(\delta\) 超出剪辑范围时,\(\mathcal{F}_{j,t}(\theta)\) 被约束为 \(1-\epsilon_l\) 或 \(1+\epsilon_h\),确保梯度有界;
    • 当 \(\delta\) 在范围内时,直接使用 \(\delta\),保留原始梯度

Reflect-Retry-Reward

  • 原始论文:Reflect, Retry, Reward: Self-Improving LLMs via Reinforcement Learning, 20250530, Writer,Writer 是一家美国 AI 公司
  • Reflect-Retry-Reward 机制的基本框架:
  • 基本流程:
    • 第一次生成结果
      • 如果成功则不进行任何训练
      • 如果失败则生成 Self-reflection token,重新将带着 Self-reflection token 的任务继续输入模型
    • 第二次生成结果
      • 如果成功则进行训练,并对刚刚 Self-reflection token 增加赋予奖励?
      • 如果失败则不进行训练?

DFT(Dynamic Fine-Tuning)

  • 今天在损失函数上添加一个权重,将 SFT 的损失函数对齐 RLHF
    • 不能做到像 RL 一样探索,但是能尽量让 SFT 的损失函数贴近 RL
  • 注意:
    • SFT 的原始目标是最大化专家数据集的似然函数,故而其极大似然法推导出来的损失交(即叉熵损失)
    • 经过修改以后,SFT 的损失函数已经不太能说得上其含义了,目标是最大化专家数据对应的奖励?

iw-SFT(Importance Weighted Supervised Fine-tuning)

  • iw-SFT 是 Importance weighted supervised fine-tuning,详情见论文:Supervised Fine Tuning on Curated Data is Reinforcement Learning (and can be improved), 20250717
  • 待补充

CHORD(Controllable Harmonization of On-and Off-Policy Reinforcement Learning via Dynamic Weighting)

  • 原始论文:(CHORD)On-Policy RL Meets Off-Policy Experts: Harmonizing Supervised Fine-Tuning and Reinforcement Learning via Dynamic Weighting, arXiv 20250815, Alibaba Group
  • 解读博客:【千问大模型官方】先SFT后RL但是效果不佳?你可能没用好“离线专家数据”!
  • CHORD(Controllable Harmonization of On-and Off-Policy Reinforcement Learning via Dynamic Weighting)是一种结合了 RL 和 SFT 的方法,可以在 Token 粒度上识别重要性,从而调整学习损失函数或权重
  • CHORD 提出了思路相似的两种变体 CHORD-\(\mu\) 和 CHORD-\(\phi\),实验结果如下:

CHORD-\(\mu\)

  • CHORD-\(\mu\) 通过动态调整全局系数 \(\mu\) 来平衡 off-policy 专家数据(SFT 损失)和 on-policy 探索(GRPO 损失)的影响,其混合损失函数表达式为:
    $$
    \mathcal{L}_{\text{Hybrid}}(\theta) = (1-\mu) \mathcal{L}_{\text{GRPO}}(\theta) + \mu \mathcal{L}_{\text{SFT}}(\theta)
    $$
  • \(\mathcal{L}_{\text{GRPO}}(\theta)\) 是基于 GRPO 的 RL 损失函数,定义为:
    $$
    \mathcal{L}_{\text{GRPO}}(\theta) = -\frac{1}{\sum_{i=1}^{\hat{B}} \sum_{k=1}^{K} |\tau_{i,k}|} \sum_{i=1}^{\hat{B}} \sum_{k=1}^{K} \sum_{t=1}^{|\tau_{i,k}|} \min\left(r_{i,k,t}(\theta) A_{i,k}, \text{clip}(r_{i,k,t}(\theta), 1-\epsilon, 1+\epsilon) A_{i,k}\right)
    $$
    • 式中 \(r_{i,k,t}(\theta)\) 为 token-level 重要性采样比率,\(A_{i,k}\) 为优势值,\(\hat{B}\) 为批量提示数,\(K\) 为每个提示的候选响应数
  • \(\mathcal{L}_{\text{SFT}}(\theta)\) 是监督微调损失函数,定义为:
    $$
    \mathcal{L}_{\text{SFT}}(\theta) = -\frac{1}{\sum_{i=1}^{B} |y_i^*|} \sum_{i=1}^{B} \sum_{t=1}^{|y_i^*|} \log \pi_{\theta}(y_{i,t}^* | x_i, y_{i,< t}^*)
    $$
    • 式中 \(B\) 为批量大小,\(y_i^*\) 为专家响应序列,\(\pi_{\theta}\) 为模型策略
  • \(\mu \in [0,1]\) 是动态衰减的全局系数 ,初始值较高(侧重 SFT),随训练逐步降低(侧重 RL)

CHORD-\(\phi\)

  • CHORD-\(\phi\) 在 CHORD-\(\mu\) 的基础上引入 token-level 加权函数 \(\phi(\cdot)\),进一步细化对 off-policy 数据的控制,其损失函数表达式为:
    $$
    \mathcal{L}_{\text{Hybrid-}\phi}(\theta) = (1-\mu) \mathcal{L}_{\text{GRPO}}(\theta) + \mu \mathcal{L}_{\text{SFT-}\phi}(\theta)
    $$
  • \(\mathcal{L}_{\text{SFT-}\phi}(\theta)\) 是带 token 加权的 SFT 损失函数,定义为:
    $$
    \mathcal{L}_{\text{SFT-}\phi}(\theta) = -\mathbb{E}_{(x,y^*) \sim \mathcal{D}_{\text{SFT}}} \left[ \sum_{t=1}^{|y^*|} \phi(y_t^*; \pi_{\theta}) \cdot \log \pi_{\theta}(y_t^* | x, y_{<t}^*) \right]
    $$
  • 加权函数 \(\phi(y_t^*; \pi_{\theta})\) 基于模型对专家 token 的生成概率 \(p_t = \pi_{\theta}(y_t^* | x, y_{< t}^*)\) 定义为:
    $$
    \phi(y_t^*; \pi_{\theta}) = p_t (1 - p_t)
    $$
    • 该函数呈抛物线形,在 \(p_t=0.5\) 时权重最大,对高概率(\(p_t \to 1\))和低概率(\(p_t \to 0\))的 token 均降权,平衡探索与稳定性

BAPO(Balanced Policy Optimization with Adaptive Clipping)

  • 原始论文:BAPO: Stabilizing Off-Policy Reinforcement Learning for LLMsvia Balanced Policy Optimization with Adaptive Clipping, 20251021, Fudan
  • GitHub 链接:github.com/WooooDyy/BAPO
  • BAPO 用于解决 Off-policy RL 训练中 “优化失衡(imbalance in optimization)” 和 “熵坍缩” 两大问题,通过动态调整裁剪边界实现正 / 负样本贡献平衡与熵保留,最终提升训练稳定性、数据效率与模型性能
    • 优化失衡 :负优势样本(Advantage < 0)在策略梯度中占主导,抑制有效行为且易引发梯度爆炸;
    • 熵坍缩 :PPO 类方法的固定对称裁剪机制会系统性阻断“熵增更新”(排除低概率正样本、过度惩罚低概率负样本),导致策略过度利用(Exploitation)而丧失探索能力(Exploration)
  • BAPO 上述问题,提出动态非对称裁剪机制 ,核心目标是:
    • 平衡正/负样本对损失的贡献;
    • 保留低概率正样本以维持熵,过滤过度负样本以避免梯度爆炸;
    • 无需复杂手动调参,适配不同离线场景(样本重放、部分轨迹生成、不同数据陈旧度)
  • BAPO 核心优势
    • 稳定性:在不同数据陈旧度(2×、4×)、部分轨迹生成场景下,训练奖励持续上升,熵保持稳定(无坍缩),梯度范数可控;
    • 通用性:适配 DeepSeek-R1、Llama3.2 等不同底座模型,无需针对模型规模重新调参
    • 注:论文中看起来得分似乎没有比 GRPO 高太多

BAPO 目标函数(动态裁剪改进)

  • BAPO 保留 PPO 的“最小化裁剪项”结构,但将固定对称边界替换为动态非对称边界(\(c_{low}\) 为下边界,\(c_{high}\) 为上边界),目标函数为:
    $$
    J^{BAPO}(\theta) = \mathbb{E}_{y \sim \pi_{\theta_{rollout} }(\cdot | x)} \sum_{t=1}^{T} min\left(r_t \cdot A_t, clip\left(r_t, c_{low}, c_{high}\right) \cdot A_t\right)
    $$
    • 其中,\(c_{low}\) 和 \(c_{high}\) 不再是固定值,而是通过每批次数据动态调整 ,核心约束是“正样本对策略梯度损失的贡献达到目标阈值 \(\rho_0\)”

动态裁剪边界调整规则

  • BAPO 每轮训练(Step)中,通过迭代调整 \(c_{low}\) 和 \(c_{high}\),满足“正样本贡献目标 \(\rho_0\)”,具体规则如下:

  • (1)调整目标约束

    • 设 \(\rho\) 为当前批次中正样本对策略梯度损失的实际贡献占比,需满足:
      $$
      \rho \geq \rho_0
      $$
      • 其中 \(\rho_0\) 为预设目标(实验中设为 0.4),确保正样本不被负样本压制
  • (2)边界调整范围与步长

    • 下边界 \(c_{low}\):取值范围 \([a^-, b^-]\)(实验中设为 [0.6, 0.9]),调整步长 \(\delta_2\)(实验中设为 0.02);
    • 上边界 \(c_{high}\):取值范围 \([a^+, b^+]\)(实验中设为 [1.2, 3.0]),调整步长 \(\delta_1\)(实验中设为 0.05)
  • (3)迭代调整逻辑

    • 1)初始化:\(c_{low} = a^-\),\(c_{high} = a^+\);
    • 2)若 \(\rho < \rho_0\) 且 \(c_{low} + \delta_2 \leq b^-\):
      • 优先提升 \(c_{high}\)(若 \(c_{high} + \delta_1 \leq b^+\)),纳入更多低概率正样本(\(r_t\) 较大的正样本);
      • 若 \(c_{high}\) 已达上限,则提升 \(c_{low}\),过滤更多过度负样本(\(r_t\) 过小的负样本);
    • 3)重复步骤 2,直至 \(\rho \geq \rho_0\) 或边界达上限

一些关于熵的理论基础讨论

  • BAPO 通过纳入低概率正样本维持熵,其理论基础是“熵-裁剪规则”(Entropy-Clip Rule):策略熵的变化由未裁剪样本的“对数概率与优势值的协方差”决定,公式推导如下(详细证明见附录 B):
    $$
    \Delta \mathcal{H}(\pi_{\theta} | x, y_{ < t}) \approx -\eta \cdot Cov_{y_t \sim \pi_{\theta} } \left( log \pi_{\theta}(y_t), A_t \cdot \mathcal{X}(y_t) \right) + C
    $$
  • 其中:
    • \(\Delta \mathcal{H}\):策略熵的变化量;
    • \(\eta\):学习率;
    • \(\mathcal{X}(y_t)\):指示函数,\(\mathcal{X}(y_t)=1\) 表示样本未被裁剪,\(\mathcal{X}(y_t)=0\) 表示被裁剪;
    • \(C\):与 \(y_t\) 无关的常数
  • 关键结论:
    • 低概率正样本(\(\pi_{\theta}(y_t) \to 0\),\(A_t > 0\))未被裁剪时,会增大协方差,进而提升熵;
    • BAPO 动态提升 \(c_{high}\) 可纳入更多此类样本,避免熵坍缩

BAPO 训练流程:Algorithm 1

  • BAPO 每轮训练包含“样本生成-动态裁剪-策略更新”三步,具体流程如下:
  • 1)初始化输入:
    • 初始 LLM 策略 \(\pi_{\theta}\)、训练数据集 \(\mathcal{D}\)、奖励函数 \(R\)、数据陈旧度上限 \(E\);
    • 裁剪边界范围 \([a^-, b^-]\)(\(c_{low}\))和 \([a^+, b^+]\)(\(c_{high}\))、步长 \(\delta_1/\delta_2\)、正样本贡献阈值 \(\rho_0\)
  • 2)样本生成与过滤(每轮 Step s):
    • 更新行为策略:\(\pi_{\theta_{rollout} } \leftarrow \pi_{\theta}\);
    • 从 \(\mathcal{D}\) 采样批次数据 \(\mathcal{D}_s\),基于 \(\pi_{\theta_{rollout} }\) 生成 G 条响应 \(y_i\);
    • 计算每条响应的奖励(基于 \(R\))和优势值 \(A_t\)
  • 3)动态调整裁剪边界(适配不同陈旧度):
    • 初始化 \(c_{low} = a^-\),\(c_{high} = a^+\);
    • 循环:若 \(\rho < \rho_0\) 且 \(c_{low} + \delta_2 \leq b^-\):
      • 若 \(c_{high} + \delta_1 \leq b^+\),则 \(c_{high} \leftarrow c_{high} + \delta_1\);
      • 否则,\(c_{low} \leftarrow c_{low} + \delta_2\)
  • 4)策略更新:
    • 通过最大化 \(J^{BAPO}(\theta)\) 更新 \(\pi_{\theta}\),完成一轮训练

Trianing-free GRPO

  • 原始论文:Training-Free Group Relative Policy Optimization, 20251009, Tencent Youtu-Agent Team
  • 亮点:不修改模型参数,仅通过改进上下文来提升模型推理能力
  • Training-free GRPO 保留了传统 GRPO 的“多轮学习”框架,但将“参数更新”替换为“经验知识迭代优化”,核心流程可分为 4 步:
  • 1)初始化:经验库与基础配置
    • 初始化外部经验库 :存储领域相关的“语义优势知识”,初始为空或包含少量基础经验;
    • 固定LLM参数:使用冻结的大模型(如 DeepSeek-V3.1-Terminus )作为基础策略,避免参数更新;
    • 配置训练参数:仅需少量训练样本(如 100 个)、3-5轮迭代(epoch)、每组生成 5 个输出(group size=5)
  • 2)Rollout 与奖励计算(复刻传统 GRPO):对每个查询(query)执行并行输出生成 :
    • 基于当前经验库 \(\varepsilon\),让 LLM 生成一组输出(rollout,如 5 个不同推理轨迹),即策略 \(\pi_\theta(o_i | q, \varepsilon)\);
    • 使用奖励模型(R)对每个输出 \(o_i\) 打分,得到 scalar reward \(r_i = R(q, o_i)\)(如数学推理的“答案正确性”、网页搜索的“任务完成率”)
  • 3)群体语义优势计算(核心创新):传统 GRPO 通过数值优势( \(\hat{A}_i = \frac{r_i - mean(r)}{std(r)}\))指导参数更新,而Training-free GRPO 替换为语义优势(自然语言形式的经验知识),具体步骤:
    • 轨迹总结 :用同一LLM对每个输出 \(o_i\) 生成结构化总结 \(s_i\),包含“推理步骤、工具使用、错误点(若有)”;
    • 语义优势提炼 :基于总结 \(\{s_1,…,s_G\}\) 和当前经验库,让 LLM 分析“成功/失败原因”,提炼通用经验(如“几何题需验证解是否在边界内”),形成语义优势 \(A_{text}\);
    • 筛选有效群体 :仅对“存在明显优劣差异”的群体(即 \(std(r) \neq 0\))提炼语义优势,避免无意义经验
  • 4)经验库优化(无参数更新的“策略优化”):通过语义优势 \(A_{text}\) 迭代更新经验库,LLM 生成 4 类操作指令:
    • Add(添加) :将新提炼的有效经验直接加入经验库;
    • Delete(删除) :移除经验库中过时或低质量的经验;
    • Modify(修改) :基于新经验优化现有经验的通用性(如扩展“几何题验证”到“代数题验证”);
    • Keep(保留) :经验库无需调整时维持现状
  • 更新后的经验库会作为“token 先验”注入下一轮 LLM 调用,引导模型输出向高奖励方向偏移,实现“参数冻结下的策略优化”

SRL(Supervised Reinforcement Learning)

  • 原始论文:Supervised Reinforcement Learning: From Expert Trajectories to Step-wise Reasoning, 20251029, Google Cloud AI Research, UCLA
  • SRL 是一种针对复杂多步推理任务的 LLM 训练框架,核心是将问题拆解为序列决策过程,通过分步专家动作引导和密集奖励信号实现高效学习
    • 用于弥补SFT(刚性逐 token 模仿易过拟合)和 RLVR(依赖最终结果奖励、稀疏信号难学难问题)的缺陷
    • 核心思路是把专家解决方案分解为一系列“逻辑动作”,模型先生成内部推理独白,再输出每步动作,基于与专家动作的相似度获得分步奖励

整体流程概述

  • 整体流程图
  • 动作化问题构建
    • 将专家(优秀的大模型)轨迹 \( \mathbf{y} \) 拆解为步骤动作序列 \( \mathbf{y} = \{\mathbf{y}_{\text{step}_n}\}_{n=1}^N \),每个动作代表一个有意义的决策步骤(如数学中的代数运算、软件任务中的命令执行)
    • 构建分步训练数据:从单个专家解决方案生成 \( N-1 \) 个部分轨迹,输入提示 \( x_{\text{step}_k} = [x, \mathbf{y}_{\text{step}_1}, …, \mathbf{y}_{\text{step}_{k-1} }] \),目标是预测下一步动作 \( \mathbf{y}_{\text{step}_k} \)
  • 分步推理与奖励计算
    • 模型生成格式:
      $$ \mathbf{y}’ \sim p_{\theta}(\cdot | x_{\text{step}_k}) = [\mathbf{y}_{think}’, \mathbf{y}_{\text{step}_k}’] $$
      • 其中 \( \mathbf{y}_{think}’ \) 是内部推理独白(用特定标签封装),\( \mathbf{y}_{\text{step}_k}’ \) 是预测动作
    • 序列相似度奖励公式:
      $$ R(\mathbf{y}_{\text{step}_k}’, \mathbf{y}_{\text{step}_k}) = \frac{2 \sum_{(i,j,n) \in \text{MatchingBlocks}} n}{|S_1| + |S_2|} $$
      • \( S_1 \) 为模型预测动作序列,\( S_2 \) 为专家动作序列
      • \( \text{MatchingBlocks} \) 是两序列中非重叠匹配块的集合,\( n \) 为每个匹配块的长度
      • 若输出格式错误,奖励为 -1,最终奖励范围 \( r \in [0,1] \cup \{-1\} \)
  • 动态采样策略
    • 过滤奖励方差接近零的样本,保留标准偏差超过阈值 \( \epsilon \) 的样本:
      $$ \sqrt{\frac{\sum_{i=1}^G (r(o_i, \mathbf{y}) - \bar{r})^2}{G} } > \epsilon $$
      • \( G \) 为生成的轨迹数量,\( r(o_i, \mathbf{y}) \) 是第 \( i \) 条轨迹的奖励,\( \bar{r} \) 为样本平均奖励
  • 采用 GRPO 目标函数优化策略,仅基于逻辑动作计算奖励,不约束内部推理独白,兼顾动作一致性与推理灵活性

核心优势

  • 密集奖励:即使所有轨迹均错误,仍能通过分步动作相似度提供有效学习信号
  • 灵活推理:避免SFT的刚性模仿,允许模型发展自身推理风格
  • 跨域通用:在数学推理和软件工程代理任务中均表现优异

性能表现

  • 数学推理任务:在 AMC23、AIME24 等竞赛级基准上,SRL 平均性能超 SFT 和 RLVR,SRL+RLVR pipeline 实现最优(平均28.3%)
  • 软件工程任务:在 SWE-Bench 上,Oracle 设置下 resolve rate 达 14.8%,较 SFT 基线提升 74%;端到端设置下性能翻倍

AWPO

  • 原始论文:AWPO: Enhancing Tool-Use of Large Language Models through Explicit Integration of Reasoning Rewards
  • AWPO(Advantage-Weighted Policy Optimization)是用于工具集成方向的 LLM,其核心思想是 通过显式地集成推理奖励(reasoning rewards)来提升模型在复杂任务中的推理和工具调用能力 ,同时避免与基于结果的奖励(outcome rewards)发生冲突
  • 背景:
    • 现有的基于 RL 的工具使用 LLM 训练方法通常仅依赖 可验证的结果奖励(如工具调用的格式正确性、执行结果匹配度),而 忽视了推理过程的质量(如逻辑连贯性、步骤合理性、工具选择恰当性)
    • 若直接简单混合推理奖励与结果奖励可能导致:优化目标冲突、 训练不稳定、 性能提升有限 等问题
  • AWPO 建立在 策略改进上界理论 之上,

核心:奖励设计与优势计算

  • 结果奖励 \(R^{\text{out} }\) :基于规则计算,包括格式正确性(精确匹配)和执行正确性(工具名、参数名、参数值的相似度)
  • 推理奖励 \(R^{\text{reasoning} }\) :由 LLM-as-a-Judge 模型评估生成推理链的逻辑连贯性、工具选择合理性、参数设置准确性等,得分范围 \([0, 1]\)
  • 混合奖励 :
    $$
    R^{\text{mix} } = R^{\text{out} } + R^{\text{reasoning} }
    $$
  • 优势计算 :分别计算基于结果奖励和混合奖励的归一化优势:
    $$
    A^{\text{out} }_{g,j} = \frac{R^{\text{out} }_{g,j} - \bar{R}^{\text{out} }_{g} }{\widehat{\sigma}^{\text{out} }_{g} + \epsilon}, \quad
    A^{\text{mix} }_{g,j} = \frac{R^{\text{mix} }_{g,j} - \bar{R}^{\text{mix} }_{g} }{\widehat{\sigma}^{\text{mix} }_{g} + \epsilon}
    $$
  • 最终加权优势 :结合门控权重与难度权重:
    $$
    A^{\text{hyper} }_{g,j} := d_{g}\left[ (1 - w_{g}^{\text{mix} }) A^{\text{out} }_{g,j} + w_{g}^{\text{mix} } A^{\text{mix} }_{g,j} \right]
    $$

创新1:方差感知门控,Variance-Aware Gating

  • 用于自适应调节推理奖励的权重,避免在结果奖励方差不足时引入噪声
  • 对于每组样本,计算混合奖励与结果奖励的标准差比值:
    $$
    r_{g} := \frac{\widehat{\sigma}_{g}^{\text{mix} } }{\widehat{\sigma}_{g}^{\text{out} }+\widehat{\sigma}_{g}^{\text{mix} }+\varepsilon_{\text{std} } }
    $$
  • 最终得到 门控权重 :
    $$
    w_{g}^{\text{mix} } := \mathbf{1}(\bar{R}_{g}^{\text{out} } < R_{\text{out} }^{\text{max} }) \cdot \mathbf{1}(r_{g} < \varepsilon_{\text{mix} }) \cdot r_{g}
    $$
    • 仅当结果奖励未饱和且混合奖励方差相对可控时,才引入推理奖励信号

创新2:Difficulty-Aware Weighting(难度感知加权)

  • 优先从中等难度样本组中学习,避免过于简单或过于困难的样本主导优化过程
  • 根据结果奖励的组内均值设定权重:
    $$
    d_{g} := \alpha_{\text{base} } + (\alpha_{\text{prio} } - \alpha_{\text{base} }) \cdot \mathbf{1}(\tau_{\text{low} } < \bar{R}_{g}^{\text{out} } < \tau_{\text{high} })
    $$
    • 中等难度区间 \((\tau_{\text{low} }, \tau_{\text{high} })\) 内的样本获得更高权重 \(\alpha_{\text{prio} }\)

创新3:Dynamic Clipping

  • 根据混合信号依赖程度动态调整 PPO 裁剪范围,在高方差信号下收紧信任域以控制噪声风险
  • 裁剪半径随批次平均混合权重自适应调整:
    $$
    \varepsilon := \varepsilon_{\min} + (1 - \bar{w}_{\mathcal{B} })(\varepsilon_{\max} - \varepsilon_{\min})
    $$
    • 当模型更多依赖高方差的推理奖励时(\(\bar{w}_{\mathcal{B} }\) 大),裁剪范围收紧,防止梯度更新过大

Self-Rewarding

  • 原始论文:Self-Rewarding Language Models, ICML 2024, Meta
  • Self-rewarding 是用模型自身替代独立外部奖励模型(RM),以自评估生成响应并提供奖励信号、驱动迭代对齐的范式,核心是单模型兼具生成(Actor)与评估(Judge)能力,降低对人类偏好标注的依赖
  • 论文贡献:
    • 提出了一体化框架,让模型同时具备指令生成、响应生成与自我评估能力,无需分离的奖励模型
    • 验证了迭代式自奖励训练的可行性,实现模型在两大核心能力上的协同提升
    • 为突破人类反馈瓶颈、实现模型持续自我改进提供了新路径
  • 方法流程:
    • 初始化:SFT
    • 自指令生成(Self-Instruction Creation):
      • 为每个指令生成多个候选响应(注:论文中似乎 Instruction 也是模型自己生成的)
      • 模型通过 “LLM-as-a-Judge” 提示自我评估候选响应,给出 0-5 分评分(基于相关性、完整性、实用性等5个维度)
    • 模型训练:从生成的候选响应中筛选出最高分(获胜者)和最低分(失败者)组成偏好对,通过直接偏好优化(DPO)训练下一轮模型
    • 迭代优化:重复上述步骤

DLER

  • 原始论文:DLER: Doing Length pEnalty Right - Incentivizing More Intelligence per Token via Reinforcement Learning, 20251016, NVIDIA
  • DLER(Doing Length pEnalty Right)是一种通过 RL 优化推理语言模型效率的训练方案,核心目标是在不损失准确率的前提下最大化“每 token 智能度”(准确率与响应长度的比值),解决现有长链推理模型输出冗长、延迟高的问题
  • DLER 的核心创新在于:无需复杂的长度惩罚设计,通过优化 RL 训练过程即可实现最优的准确率-效率权衡
    • 长度缩减:相较于原始模型(如 DeepSeek-R1-7B),DLER 将响应长度削减 69%-77%,DA-DLER 进一步降至 80%;
    • 准确率提升:在 MATH、AIME-24 等 5 个推理基准上,DLER 不仅恢复原始模型准确率,还实现 1%-3% 的提升;
    • 推理效率:并行推理时,DLER-7B 生成多轮响应的 latency 降低6 2%,且准确率提升 28%;
    • 泛化性:兼容多种长度惩罚函数(如 Cosine、Laser),且简单截断惩罚的效果优于复杂惩罚,同时训练成本更低
  • 现有基于RL的长度优化方法常采用复杂长度惩罚函数,但存在三大关键问题:
    • 1)优势估计偏差:GRPO 的分组奖励归一化在截断惩罚下产生显著奖励噪声,导致优势估计偏差
    • 2)熵崩溃:重要性采样比率裁剪会过滤掉低概率、高熵的推理过渡 token,限制推理路径探索
    • 3)奖励信号稀疏:大量训练样本因响应超截断长度被分配零奖励,导致训练信号失衡
  • DLER 整合四大关键技术,针对性解决上述挑战:
    • 1)批次级奖励归一化(Batch-wise Reward Normalization) :将GRPO的分组级优势归一化改为批次级归一化,缓解奖励噪声导致的偏差,优势计算方式为:
      $$A_{i, t}^{norm }=\frac{A_{i, t}-mean_{batch}\left(A_{i, t}\right)}{std_{batch}\left(A_{i, t}\right)}$$
      • 其中 \(A_{i, t}=R_{i}’-mean(\{R_{i}’\}_{i=1}^{G})\),\(R_i’\) 为包含正确性奖励与长度惩罚的总奖励
    • 2)更高裁剪阈值(Higher Clipping Threshold) :解耦GRPO中上下裁剪阈值,提高上阈值(\(\epsilon_{high}\)),保留高熵探索性token的梯度更新,避免熵崩溃
    • 3)动态采样(Dynamic Sampling) :过滤所有rollout均为零奖励(过难样本)或全为正奖励(过易样本)的训练样本,重新采样至目标批次大小,构建均衡的训练信号
    • 4)简单截断惩罚(Simple Truncation Penalty) :采用最简洁的长度惩罚机制——对超过固定长度限制的响应分配零奖励,避免复杂惩罚函数带来的训练不稳定

DLER 扩展变体

  • 难度感知DLER(DA-DLER) :根据问题难度动态调整截断长度,模型已可靠解答的简单问题进一步缩短截断长度,复杂问题保留更长token预算,额外降低11%-15%的响应长度
  • 更新选择性权重融合(Update-selective Weight Merging) :针对公开训练数据质量不足导致的准确率下降,融合原始基线模型与DLER训练模型的权重(保留Top25%最大参数更新量并缩放),在恢复基线准确率的同时保持47%的长度缩减
1…131415…62
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

618 posts
52 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4