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(LMArena)

整体介绍

  • 链接: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模型的性能
  • LMArena 是目前大家最相信的人类偏好排行榜

文本子榜详细介绍

  • 对于 文本子榜,LMArena 会报告两种分数:
    • 基础分(wo style control):arena.ai/zh/leaderboard/text/overall-no-style-control
      • 基于模型对战,人类原始打分结果得到的 Elo Rating 分(不做任何修改)
    • 风格分(w style control):arena.ai/zh/leaderboard/text/overall
      • 默认是打开 Style Control 的形式:https://arena.ai/zh/leaderboard/text
      • Style Control 是指在 基础分 的基础上,通过一些消偏模型将模型的回复长度、格式等对齐后得到的 “回复内容” 本身的得分
    • 实践中,基础分和风格分的相对值可以看出一个模型风格的好坏来
      • 如果一个模型的 基础分 > 风格分,说明模型风格不错(比如回复较短)
      • 如果一个模型的 风格分 > 基础分,说明模型回复可能风格不行(比如回复偏长)
  • 从之前的经验看,LMArena 对战并不是严格遵循相似能力模型对战,而是更多的让部分模型参战(比如靠前的 Gemini 系列/Claude 系列等的参战频率就比较高),至少看着模型的 vote 数量是不完全对齐的
  • LMArena 有很多细分的榜单,比如 Text 榜单下还有类似 Arena Hard V2 中提到的 Hard Prompts 榜单和 Creative Writing 榜单等

文本子榜 AutoEval & 人工测评

  • 可以提交自己的模型(付钱)让对方进行打分(提交包括模型名称,URL,API Key 等信息即可),AutoEval 提交后大概几个小时可以得到结果,一般包含三个文件:
    • autoeval_leaderboards__{model_name}__xxx.html: 包含参与本次对战的模型整体评分
    • autoeval_report__{model_name}__xxx.html: 包含参与本次对战的报告细节分数
    • {model_name}__1.jsonl: 包含本次对战的 Prompt 和 Response 详细细节
  • 人工测评:将模型真实部署到线上共人类真实响应,需要的时间较久,收费也更高
  • 注:不同榜单的收费也不一样

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——WizardLM(Evol-Instruct)

注:本文包含 AI 辅助创作

  • 参考链接:
    • 原始论文:(Evol-Instruct)WizardLM: Empowering large pre-trained language models to follow complex instructions, Microsoft & PKU, arXiv 20230424, 20230610, 20250527
    • 相关 GitHub 地址:github.com/nlpxucan/evol-instruct

Paper Summary

  • 整体说明:
    • 论文提出了一种为 LLM 生成多样化和复杂指令数据的进化算法 Evol-Instruct(使用 LLM 而非人类来创建大量不同复杂度指令数据的途径),并基于此数据集微调得到了 LLaMA(得到 WizardLM)
    • WizardLM 在一系列公认的基准测试中显著超越了典型的开源 LLM,如 Alpaca 和 Vicuna
    • WizardLM 在代码、数学、GPT-4 和人工评估方面均以显著优势超越基线模型
  • 背景 & 问题:
    • 使用开放域指令跟随数据训练 LLM 已经取得了巨大成功
    • 问题1:人工创建此类指令数据非常耗时且劳动密集
    • 问题2:人类可能难以生成高复杂度的指令
  • 基本思路:从一个初始指令集出发,论文使用提出的 Evol-Instruct 方法逐步将其重写为更复杂的指令
  • 模型训练:论文将所有生成的指令数据混合以微调 LLaMA(论文将得到的模型称为 WizardLM)
    • 自动评估和人工评估均一致表明, WizardLM 的性能优于基线模型,如 Alpaca(基于 Self-Instruct 训练)和 Vicuna(基于人工创建的指令训练)
    • 论文通过实验结果证明:由 Evol-Instruct 精心构建的指令跟随数据集的质量能够显著提升 LLM 的性能

Introduction and Discussion

  • LLM 已成为众多自然语言处理任务的首选方法 (2020; 2022; 2023)
  • LLM 在大规模文本数据上进行训练以预测后续 Token ,使其能够针对各种输入生成连贯流畅的文本
  • 然而,这些模型通常难以遵循用户指定的指令或目标,这限制了它们在现实场景中的实用性和适用性
  • NLP 界近期见证了众多努力,旨在训练 LLM 更好地遵循指令并变得更有帮助 (2023;)
    • 训练指令跟随语言模型的初步尝试 (2022; 2021; ) 基于一系列不同的 NLP 任务集合,并辅以少量手写指令
  • 这些封闭域指令存在两个主要缺点:
    • 首先,一个 NLP 数据集中的所有样本仅共享少数几个常见指令,严重限制了其多样性;
    • 其次,指令通常只要求完成一项任务 (但在现实生活中,人类的指令通常具有多个且多样的任务需求)
  • 通过使用真实用户生成的开放域指令数据,OpenAI 的 LLM(例如 InstructGPT (2022) 和 ChatGPT)取得了巨大成功
    • 这些开放域指令能够充分释放 LLM 的无限潜力 (2023; ),并使它们能够执行更复杂多样的任务
  • 但像 OpenAI 那样使用人类创建开放域指令数据集会遇到以下挑战:
    • 整个标注过程极其昂贵且耗时 (2023; )
    • 人工创建指令的难度级别分布偏向于简单或中等,困难指令较少(根据图 5a 中 ShareGPT (2023) 的难度统计)
    • 人类标注者容易疲劳,无法持续高强度工作以产生足够比例的高难度指令 (2023; )
  • 基于这些问题,开发一种能够以相对较低成本大规模自动生产开放域指令(尤其是更困难的指令)的方法,成为进一步推进指令微调语言模型(instruction-tuned language models)的关键 (2023; )
  • 在这项工作中,论文引入了 Evol-Instruct ,一种使用 LLM 而非人类来自动大规模生成不同难度级别开放域指令的新方法,以提升 LLM 的性能
  • 图 1 展示了 Evol-Instruct 的运行示例
  • 从一个简单的初始指令 “1+1=?” 开始,论文的方法随机选择深度演化(In-depth Evolving)(蓝色方向线)或广度演化(In-breadth Evolving)(红色方向线)来将简单指令升级为更复杂的指令或创建新指令(以增加多样性)
    • 深度演化包括五种操作类型:添加约束(add constraints)、深化(deepening)、具体化(concretizing)、增加推理步骤(increase reasoning steps)和复杂化输入(complicate input)
    • 广度演化是突变(mutation) ,即基于给定指令生成一个全新的指令
    • 这六种操作通过使用特定的提示词(prompt)来提示(prompting)一个 LLM 来实现
  • 由于演化后的指令是由 LLM 生成的,有时演化会失败
    • 论文采用一个指令淘汰器(instruction eliminator)来过滤失败的指令,这被称为淘汰演化(Elimination Evolving)
    • 论文重复这个进化过程若干轮,以获得包含各种复杂度的足够指令数据
  • 为了验证 Evol-Instruct 的有效性以及它创建的用于微调的指令是否超越人类创建的指令
    • 论文演化来自 Alpaca (2023) 数据(由机器创建)的指令,微调 LLaMA (2023) 模型,并全面比较微调后的模型 WizardLM 与在 ShareGPT(指令由人类创建)上训练的 Vicuna (2023)
      • Alpaca 数据总共有 \(52k\) 个样本,是使用 self-instruct (2022a) 从仅 \(175\) 个人工创建的种子指令生成的
      • 论文选择 Alpaca 数据作为演化的初始数据,这可以确保 WizardLM 的训练指令几乎没有人直接参与标注
    • 论文使用 OpenAI ChatGPT API 执行了四轮演化 ,最终获得 \(250k\) 条指令
    • 为了与 Vicuna 的 \(70k\) 真实用户数据进行公平比较
      • 论文从完整的 \(250k\) 数据中采样了 \(70k\) 条指令 ,并微调了 LLaMA 13B 模型
    • 由于原始 Alpaca 数据只有 \(52k\) 个样本,论文使用其 self-instruct 方法生成了额外的 \(18k\) 数据,并使用其代码重新训练了 LLaMA 13B 模型,得到 Alpaca 13B 作为论文的基线
    • 由于先前指令跟随测试数据集中困难指令比例较低 ,论文手动创建了一个新的难度平衡的测试数据集 ,命名为 WizardEval
  • 论文在广泛的 LLM 基准测试(涵盖推理、代码、数学、通用对话等)上评估了 Alpaca、Vicuna、ChatGPT 和 WizardLM
  • 论文的主要发现如下:
    • 论文引入了 Evol-Instruct ,一种通过自动大规模生成各种主题和难度级别的开放域指令来大幅提升开源 LLM 性能的新方法
    • 论文开发了 WizardLM 模型,其在一系列基准测试中显著超越了典型的开源 LLM,如 Alpaca 和 Vicuna
      • WizardLM 在代码、数学、GPT-4 和人工评估方面均以显著优势优于基线模型
    • 论文进行了一项初步研究,强调了指令复杂度在监督微调大规模预训练语言模型中取得出色性能的重要性

Approach

  • 本节将详细阐述所提出的 Evol-Instruct 的细节
  • 如图 2 所示,该流程主要包含两个组件:
    • 指令演化器(Instruction Evolver)
    • 指令淘汰器(Instruction Eliminator)
  • 这些组件的细节将在第 3.2 节中介绍,指令微调方法将在第 3.3 节中描述

Definition of Instruction Data Evolution(指令数据演化)

  • 论文从给定的初始指令数据集开始演化:
    $$D^{(0)}=(I_{k}^{(0)},R_{k}^{(0)})_{1\leq k\leq N}$$
    • 其中 \(I_{k}^{(0)}\) 是 \(D^{(0)}\) 中的第 \(k\) 条指令
    • \(R_{k}^{(0)}\) 是第 \(k\) 条指令的相应响应
    • \(N\) 是 \(D^{(0)}\) 中的样本数量
  • 在每次演化中,论文通过使用 Evol-Instruct 提示词提示(prompting)一个 LLM
    • 将 \(D^{(t)}\) 中的所有 \(I^{(t)}\) 升级为 \(I^{(t+1)}\),然后使用该 LLM 为新演化出的 \(I^{t+1}\) 生成相应的响应 \(R^{t+1}\)
    • 从而,论文获得一个演化后的指令数据集 \(D^{t+1}\)
  • 通过迭代执行 \(M\) 次演化,我们可以顺序获得 \(M\) 个演化数据集:
    $$[D^{(1)}\cdots D^{(M)}]$$
  • 论文的工作专注于开放域指令数据,其中指令具有变化的输入和任务,指令部分和输入部分之间没有明确的区分

Automatic Instruction Data Evolution

  • 论文的指令演化流程包括三个步骤:
    • 1) 指令演化(instruction evolving)
    • 2) 响应生成(response generation)
    • 3) 淘汰演化(elimination evolving),即过滤未能成功演化的指令
Instruction Evolution
  • 论文发现 LLM 可以使用特定的提示词使给定的指令变得更加复杂和困难。此外,它们可以生成完全全新、复杂度相当但完全不同的指令。利用这一发现,我们可以迭代地演化一个初始指令数据集,提高其难度级别并扩展其丰富性和多样性。论文使用给定的初始指令数据集 \(D^{(0)}\) 初始化指令池(instruction pool)。在每个演化轮次(epoch)中,从前一轮次升级的指令被从池中取出。然后论文利用指令演化器(instruction evolver)来演化每个取出的指令,并利用指令淘汰器(instruction eliminator)来检查演化是否失败。成功演化的指令被添加到池中,而不成功的指令则原样放回,希望在下个演化轮次中能成功升级它们
Instruction Evolver
  • 指令演化器是一个使用 Evol-Instruct 提示词来演化指令的 LLM,有两种类型:深度演化和广度演化
In-Depth Evolving
  • 通过五种类型的提示词来增强指令,使其更复杂和困难:

    • 添加约束(add constraints)、深化(deepening)、具体化(concretizing)、增加推理步骤(increased reasoning steps)和复杂化输入(complicating input)
  • 深度演化提示词的核心部分是“您的目标是将给定的提示词重写为一个更复杂的版本,以使那些著名的 AI 系统(例如 ChatGPT 和 GPT4 (OpenAI, 2023))更难处理。但重写后的提示词必须是合理的、可被人类理解并回应”

  • 论文要求 LLM 创建具有挑战性但合理且非 AI 任意想象的指令

  • 需要逐步增加难度以避免指令集中充斥极其复杂的指令,这会损害训练模型的泛化性能

  • 为了控制难度增加,论文使每次演化“更难一点”,并限制最多添加 10 到 20 个单词

  • 在上述五种演化中,除了复杂化输入(complicating input)外,其他都可以在没有任何上下文示例(in-context examples)的情况下实现

  • 论文展示添加约束的提示词如下(深化、具体化和增加推理步骤的提示词将在附录 A-C 中详述)

  • 示例 3.1:深度演化中添加约束的提示词(Prompt for Adding Constraints of In-Depth Evolving)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    I want you act as a Prompt Rewriter.
    Your objective is to rewrite a given prompt into a more complex version to make those famous AI systems (e.g., ChatGPT and GPT4) a bit harder to handle. But the rewritten prompt must be reasonable and must be understood and responded by humans.
    Your rewriting cannot omit the non-text parts such as the table and code in #Given Prompt#:. Also, please do not omit the input in #Given Prompt#.
    You SHOULD complicate the given prompt using the following method:
    **Please add one more constraints/requirements into #Given Prompt#**
    You should try your best not to make the #Rewritten Prompt# become verbose, #Rewritten Prompt# can only add 10 to 20 words into #Given Prompt#. ‘#Given Prompt#’, ‘#Rewritten Prompt#’, ‘given prompt’ and
    ‘rewritten prompt’ are not allowed to appear in #Rewritten Prompt#
    #Given Prompt#:
    {Here is instruction.} #Rewritten Prompt#:

    我希望您扮演一个提示词重写器(Prompt Rewriter)
    您的目标是将给定的提示词重写为一个更复杂的版本,以使那些著名的 AI 系统(例如 ChatGPT 和 GPT4)更难处理。但重写后的提示词必须是合理的、可被人类理解并回应
    您的重写不能省略 #给定提示词# 中的非文本部分,例如表格和代码。同时,请不要省略 #给定提示词# 中的输入
    您**应该**使用以下方法使给定提示词复杂化:
    **请向 #给定提示词# 中添加一个更多的约束/要求**
    您应尽力避免使 #重写后的提示词# 变得冗长,#重写后的提示词# 只能在 #给定提示词# 的基础上增加 10 到 20 个单词。“#给定提示词#”、“#重写后的提示词#”、“给定提示词”和“重写后的提示词”不允许出现在 #重写后的提示词# 中
    **#给定提示词#:**
    {这里是指令。}
    **#重写后的提示词#:**
  • 对于复杂化输入(complicating input),论文将使用上下文演示(in-context demonstration)。由于演示较长,论文在下面提供一个简要模板,完整提示词详见附录 D

  • 示例 3.2:深度演化中复杂化输入的提示词(Prompt for Complicating Input of In-Depth Evolving)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    I want you act as a Prompt Rewriter.
    Your objective is to rewrite a given prompt into a more complex version to make those famous AI systems (e.g., ChatGPT and GPT4) a bit harder to handle. But the rewritten prompt must be reasonable and must be understood and responded by humans.
    You must add [XML data] format data as input data in [Rewritten Prompt]
    #Given Prompt#:
    {Here is instruction of Example 1.}
    #Rewritten Prompt#:
    {Here is rewritten instruction of Example 1.} ... N -1 Examples ...
    You must add [#Given Dataformat#] format data as input data in [Rewritten Prompt] #Given Prompt#:
    {Here is instruction of Example N.}
    #Rewritten Prompt#:

    我希望您扮演一个提示词重写器(Prompt Rewriter)
    您的目标是将给定的提示词重写为一个更复杂的版本,以使那些著名的 AI 系统(例如 ChatGPT 和 GPT4)更难处理。但重写后的提示词必须是合理的、可被人类理解并回应
    您必须在 [重写后的提示词] 中添加 [XML 数据] 格式的数据作为输入数据
    **#给定提示词#:**
    {这里是示例 1 的指令。}
    **#重写后的提示词#:**
    {这里是示例 1 重写后的指令。}
    ... N -1 个示例 ...
    您必须在 [重写后的提示词] 中添加 [#给定数据格式#] 格式的数据作为输入数据
    **#给定提示词#:**
    {这里是示例 N 的指令。}
    **#重写后的提示词#:**
In-Breadth Evolving
  • 广度演化旨在增强主题覆盖度、技能覆盖度以及整体数据集的多样性
  • 开放域指令微调数据集(例如 Alpaca、ShareGPT 等)通常规模较小,缺乏主题和技能多样性
  • 为了解决这个问题,论文设计了一个提示词,基于给定指令生成一个全新的指令,要求新指令更加长尾(more long-tailed)。论文的广度演化提示词如下:
  • 示例 3.3:广度演化的提示词(Prompt for In-Breadth Evolving)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    I want you act as a Prompt Creator.
    Your goal is to draw inspiration from the #Given Prompt# to create a brand new prompt.
    This new prompt should belong to the same domain as the #Given Prompt# but be even more rare.
    The LENGTH and difficulty level of the #Created Prompt# should be similar to that of the #Given Prompt#. The #Created Prompt# must be reasonable and must be understood and responded by humans.
    ‘#Given Prompt#’, ‘#Created Prompt#’, ‘given prompt’ and ‘created prompt’ are not allowed to appear in #Created Prompt#.
    #Given Prompt#:
    {Here is instruction.} #Created Prompt#:

    我希望您扮演一个提示词创建者(Prompt Creator)
    您的目标是从 #给定提示词# 中汲取灵感,创建一个全新的提示词
    这个新提示词应该与 #给定提示词# 属于同一领域,但应更加罕见(rare)
    #创建出的提示词# 的长度和难度级别应与 #给定提示词# 相似
    #创建出的提示词# 必须是合理的、可被人类理解并回应
    “#给定提示词#”、“#创建出的提示词#”、“给定提示词”和“创建出的提示词”不允许出现在 #创建出的提示词# 中
    #给定提示词#:
    {这里是指令。}
    #创建出的提示词#:
Response Generation
  • 论文使用与演化相同的 LLM 来为演化后的指令生成相应的响应
  • 生成提示词是 “{Here is instruction.}”,论文将其输入到 ChatGPT-3.5 的请求中,并将返回的文本正文解析为响应
Elimination Evolving(淘汰演化)
  • 论文将以下四种情况归类为指令演化失败:
    • 1)演化后的指令与原始指令相比未提供任何信息增益
      • 论文使用 ChatGPT 来进行此判断,详情请参阅附录 G
    • 2)演化后的指令使得 LLM 难以生成响应
      • 论文发现当生成的响应包含“抱歉(sorry)”且长度相对较短(即少于 80 个单词)时,通常表明 LLM 难以响应演化后的指令
      • 因此我们可以使用此规则进行判断
    • 3)LLM 生成的响应仅包含标点符号和停用词(stop words)
    • 4)演化后的指令明显复制了演化提示词中的某些词语

Finetuning The LLM On The Evolved Instructions

  • 所有演化完成后,论文将初始指令数据集与所有轮次中演化得到的指令数据合并,并对样本进行随机打乱,以创建用于微调的数据集
    • 这种处理方式确保了数据集中不同难度级别的指令均匀分布,从而最大化模型微调的平滑性
  • 为了证明性能提升并非源于合并后数据量的增加,而是源于论文提出的新方法 Evol-Instruct,论文从合并后的数据中随机抽取与训练基线模型(例如 Vicuna)等量的数据,作为论文最终的微调数据
  • 论文选择 Vicuna 的提示词(prompt)作为论文微调所用的提示词,其具体格式为:“A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user’s questions. USER: Hi ASSISTANT: Hello. USER: Who are you? ASSISTANT: I am WizardLM ……”

Experiment

  • 论文通过自动评估和人工评估两种方式对 WizardLM、Alpaca、Vicuna 和 ChatGPT 进行了评估

Baselines

  • (1) ChatGPT 是由 OpenAI 开发的一款 AI 聊天机器人,能够以自然且引人入胜的方式与用户互动
    • 它建立在 GPT-3.5 和 GPT-4 等 LLM 之上,并基于海量的互联网文本数据进行训练
  • (2) Alpaca 是由斯坦福大学开发的开源指令遵循模型
    • 为了公平比较,论文使用 Alpaca 采用的 Self-Instruct 方法将指令数量从 52k 扩展到 70k,并将原始的 David+003 响应替换为 ChatGPT 的响应
    • 论文基于这份新的 Alpaca 数据,从 LLaMA 13B (2023) 重新训练了 Alpaca 13B
  • (3) Vicuna 基于 LLaMA,并在从 ShareGPT 收集的 70k 用户共享对话上进行了微调
    • 它是目前最先进、最通用的开源指令遵循模型之一
    • 论文使用来自 FastChat 的 13B-v1.1 模型
  • (4) 基于 Llama 13B 训练的开源模型,包括 Baize (2023)、CAMEL (2023a) 和 Tulu (2023)

Experiment detail

  • 为了构建数据集,论文使用 Alpaca 的 \(52k\) 指令数据集进行初始化,并迭代执行 \(M\) 轮演化,其中 \(M=4\)
  • 在每一轮演化中,对于每条指令,论文以相等概率从总共六个演化提示(即五个来自深度演化,一个来自广度演化)中随机选择一个
    • 论文使用 Azure OpenAI ChatGPT API5 执行上述过程
    • 然后,论文利用 ChatGPT 生成响应
    • 最终,论文获得了 \(250k\) 条指令
  • 为了公平比较,论文从 \(250k\) 数据中以相等概率随机采样 \(70k\) 数据作为 WizardLM 的最终训练数据,与 Vicuna 的训练数据量相同
    • 论文使用温度为 1 来生成响应,并将生成的最大 token 数设置为 2048
    • 此外,论文将频率惩罚设置为零,top-p 设置为 \(0.9\)
    • 总共,论文请求 API \(52k\times 4\times 3=624k\) 次以构建完整的数据集
  • 论文使用预训练的 LLaMA 13B (2023) 来初始化论文的模型
    • 论文采用 Adam 优化器,初始学习率为 \(2\times 10^{-5}\),最大 token 数为 2048,每个 GPU 的批次大小为 4
  • 论文在 8 个 V100 GPU 上使用 Deepspeed Zero-3 训练了论文的模型 3 个 epoch ,耗时 140 小时
  • 对于推理,论文对 WizardLM 和基线模型使用贪心搜索,并将最大生成长度设置为 2048

Automatic Evaluation

  • 为了全面概述论文的 WizardLM 的性能,论文在多个 LLM 基准测试中对论文的模型与既定基线进行了比较
  • HuggingFace 的 OpenLLM 排行榜 :(2023) 包括 MMLU (2020)、ARC (2018)、HellaSwag (2019) 和 TruthfulQA (2022)
    • MMLU 包含一系列多项选择的学术问题
    • ARC 是一组小学科学问题
    • HellaSwag 是一个常识推理测试
    • TruthfulQA 衡量模型再现错误陈述的倾向
    • 论文采用了 OpenLLM 的评估代码 (2021)
  • 代码生成 (Code Generation)
    • 论文使用广泛使用的 HumanEval (2021) 基准测试,该测试包含 164 个编码问题,通过报告 pass@1 指标来评估 LLM 在函数级别的代码编写能力
  • 数学推理 (Math Reasoning)
    • 论文使用 GSM8k (2021) 来评估模型的数学能力,GSM8k 包含 1319 个小学数学测试数据
    • 论文采用 4-shot 测试并报告 pass@1
  • GPT-4 评估 (GPT-4 Evaluation)
    • 论文采用了两个广泛认可的 GPT-4 评估基准,包括 AlpacaEval (2023c) 和 MT-Bench (2023)
    • 论文还使用 GPT-4 在论文后续提出的 WizardEval 上评判 LLM
  • 如图 3 和表 1 所示,与其他相同规模的开源模型相比, WizardLM 在大多数基准测试中都具有显著的性能优势。特别是在数学、代码和 GPT-4 评估方面,它相比 Alpaca、Vicuna、Baize、CAMEL 和 Tulu 取得了显著提升

Human evaluation

  • 为了评估 WizardLM ,论文在论文精心制作的测试平台 WizardEval 上进行了人工评估,该测试集包含 218 条来自不同来源(如在线开源项目(Github, ShareGPT)、平台(Twitter)和论坛(Reddit, Discord)的真实世界人类指令
    • 数据包含 29 项技能和领域,代表了人类的主要需求,例如代码生成(Coding Generation)、Math、Reasoning、复杂格式(Complex Formats)、写作(Writing)、广泛学科(Extensive Disciplines)等
  • 如图 3(a) 和附录图 6 所示,论文还分别分析了 WizardEval 的难度和技能分布,这表明 WizardEval 能够处理比 Self-Instruct 和 Vicuna 测试集更复杂和要求更高的场景的评估
  • 论文在 WizardLM-13b 和基线模型之间进行了盲法成对比较
    • 论文招募了 10 名受过良好教育的标注员
    • 向每位标注员展示来自 Alpaca-13b、Vicuna-13b、 WizardLM 和 ChatGPT 的四条响应,这些响应被随机打乱以隐藏其来源
  • 然后,标注员根据以下标准(详细定义请参阅附录 K)判断哪个响应更好:
    • (1) 相关性(Relevance)
    • (2) 知识性(Knowledgeable)
    • (3) Reasoning
    • (4) 计算(Calculation)
    • (5) 准确性(Accuracy)
  • 然后,他们应将四个响应从 1 到 5 排名(1 表示最好),并允许对可比较的实例给出相同的分数
    • 为了估计胜率,论文比较了每对模型之间的获胜、失败和平局频率
  • 如图 4 (b) 所示
    • WizardLM 取得了比 Alpaca 和 Vicuna 好得多的结果,这证明了 Evol-Instruct 方法的有效性
    • 所有的 Kappa 分数均大于 0.6,这表明标注员之间具有良好的一致性

Ablation Study

  • 使用不同的数据(种子、大小)、演化模型和基础模型大小进行训练 (Training with different data (seed, size), evol model, and base model size)
    • 为了研究不同数据种子、演化模型、演化数据集规模、预训练模型对论文提出的方法的影响,论文进行了以下实验:
      • a)使用 70k ShareGPT 作为种子数据获得 WizardLM-13b (ShareGPT Seed);
      • b)使用 LlaMA-2-70B-Chat 替代 ChatGPT 作为演化执行模型获得 WizardLM-13b (LlaMA-2-70B-Chat Evol);
      • c)论文在更大规模的预训练模型 Llama-1 65B 和 Llama-2 70B 上训练,分别获得 WizardLM-65b 和 WizardLM-70b;
      • d)使用完整的 250k 演化数据获得 WizardLM-13b (250K);
      • e)使用与 LlaMA 系列完全不同的基础模型 Mistral-7B,获得 WizardLM-7b (Mistral);
      • f)为了比较更多样化的指令数据,论文选择 Supernatural Instructions (2022b) 并随机抽取 70k 数据训练 llama-13b 获得 LlaMA-13b (SNI)
    • 完整结果如表 2 所示:
      • 为了探究 WizardLM-13b (ShareGPT Seed) 在 GSM8k 上表现较差的原因,论文分别从 ShareGPT 和 Alpaca 数据中随机采样 2000 条指令,然后使用 ChatGPT 判断(提示词请参阅附录 G)一条指令是否与“数学”相关,论文发现 ShareGPT 仅包含 4.3% 的数学数据,而 Alpaca 数据包含 11.8% 的数学数据,因此作者认为较少的数学数据导致 WizardLM-13b (ShareGPT Seed) 的 GSM8k 性能较差
    • 表2结果表明:
      • (i) ShareGPT 是比 Alpaca 更好的 evol-instruct 种子;
      • (ii) 更大的演化数据规模可以提高模型能力;
      • (iii) 论文提出的 Evol-Instruct 方法不依赖于 ChatGPT,其他强大的开源模型如 Llama-2 也是 ChatGPT 的良好替代品;
      • (iv) 论文的演化数据也显示出比 Supernatural Instructions 更好的微调性能
      • 此外,在不同预训练基础(例如 Llama-1 65B、Llama-2、Mistral-7B)上的结果表明,论文的 Evol-Instruct 可以广泛应用于各种预训练模型
  • 深度演化分析 (Analysis of In-depth Evolving)
    • 图 4(a) 和 4(b) 展示了一项消融研究,调查了数据演化轮数的影响
    • 为了研究演化过程的深度,论文使用 ChatGPT 来判断指令的难度级别。使用的提示词请参阅附录 E
    • 图 4(b) 显示了使用每轮演化数据微调的模型在(第 4.3 节中的九个自动基准测试上的)平均分数
    • 从 \(C0\) 到 \(C4\) 的每轮数据大约为 \(52k\)
    • 从该图的趋势可以看出,随着训练指令数据复杂度的逐渐增加,微调模型的性能也同步提高
    • 为了探究 ChatGPT 难度评分的正确性,论文还使用 GPT-4 和人工来测量指令难度,附录 I 表 3 中的详细结果表明 ChatGPT、GPT-4 和人工标注员之间具有良好的一致性
  • 广度演化分析 (Analysis of In-breadth Evolving)
    • 论文旨在检查指令的语义广度
    • 论文使用 t-SNE (2008) 和 k-means (1979) 算法将指令的 BERT 嵌入划分为 20 个簇
    • 附录 F 中的图 6 显示了聚类情况,突出了论文的方法与 ShareGPT 和 Alpaca 相比具有更优越的分散性,表明论文的指令具有更大的主题多样性

Related Work

Closed domain instruction tuning

  • 早期的指令跟随训练工作 (2021; 2023) 关注 LM 的跨任务泛化能力,其中 LM 在广泛的公共 NLP 数据集上进行微调,并在不同的 NLP 任务集上进行评估
    • T5 (2020) 做出了最早的尝试,使用统一的文本到文本(text-to-text)格式共同训练自然语言处理(NLP)任务,如问答、文档摘要和情感分类
    • 诸如 FLAN (2021)、ExT5 (2022)、T0 (2022) 和 KnowDA (2022c) 等工作将 NLP 任务的数量增加到大约一百个,并为每个任务精心设计了几个指令 (2023;)
    • 诸如 ZeroPrompt (2022) 和 FLAN-T5 (2022) 等工作将任务数量提升至数千个
  • 这些研究一致表明,使用多样化的 NLP 任务指令微调 LM 可以增强它们在新任务上的性能
  • 但使用这些封闭形式指令(即指令通常仅针对单个 NLP 任务,且输入数据形式简单)训练的 LLM 在真实用户场景中往往表现不佳

Open domain instruction tuning

  • 论文的工作属于这一研究路线
  • OpenAI 雇佣了许多标注员并编写了许多带有相应正确答案的指令
    • 这些人工创建的指令形式多样,任务类型丰富
    • 基于这个数据集,OpenAI 将 GPT-3 (2020) 训练成 InstructGPT (2022),它可以处理各种真实用户指令,并导致了 ChatGPT 的成功
  • Orca (2023) 不仅学习来自 LLM 的表层响应文本,还捕获复杂的推理过程信号
  • 由于 OpenAI 的这些杰出工作并未开源,Alpaca (2023) 和 Vicuna (2023) 随后基于开源 LLM LLaMA (2023) 积极探索了开放域指令微调
  • Alpaca 使用了一个包含 \(50k\) 条指令的数据集,这些指令是从有限(例如 175 个样本)的手写指令种子集中生成的
  • 论文的工作与 InstructGPT 和 Vicuna 的不同之处在于:
    • 论文使用 AI 生成的数据进行指令微调
    • 与 Alpaca 的 self-instruct (2022a) 生成方法不同, Evol-Instruct 可以控制生成指令的难度和复杂度级别

附录 A:Deepening Prompt(深化 Prompt)

  • 示例 A.1:用于深度演化的深化提示 (Prompt for Deepening of In-Depth Evolving)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    I want you act as a Prompt Rewriter.
    Your objective is to rewrite a given prompt into a more complex version to make those famous AI systems (e.g., ChatGPT and GPT4) a bit harder to handle. But the rewritten prompt must be reasonable and must be understood and responded by humans.
    Your rewriting cannot omit the non-text parts such as the table and code in #Given Prompt#:. Also, please do not omit the input in #Given Prompt#.
    You SHOULD complicate the given prompt using the following method:
    If #Given Prompt# contains inquiries about certain issues, the depth and breadth of the inquiry can be increased.
    You should try your best not to make the #Rewritten Prompt# become verbose, #Rewritten Prompt# can only add 10 to 20 words into #Given Prompt#. ‘#Given Prompt#’, ‘#Rewritten Prompt#’, ‘given prompt’ and ‘rewritten prompt’ are not allowed to appear in #Rewritten Prompt#
    #Given Prompt#:
    {Here is instruction.} #Rewritten Prompt#:

    我希望你扮演一个提示词重写器(Prompt Rewriter)。
    你的目标是将给定的提示词(prompt)改写成更复杂的版本,以使那些著名的人工智能系统(例如 ChatGPT 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。
    你的改写不能省略 #给定提示词#(#Given Prompt#)中的非文本部分,例如表格和代码。此外,请不要省略 #给定提示词# 中的输入部分。
    你应当通过以下方法来使给定提示词复杂化:
    如果 #给定提示词# 包含对某些问题的询问,可以增加询问的深度和广度。
    你应尽力避免使 #改写后的提示词#(#Rewritten Prompt#)变得冗长,#改写后的提示词# 只能在 #给定提示词# 的基础上增加 10 到 20 个词。禁止在 #改写后的提示词# 中出现“#给定提示词#”、“#改写后的提示词#”、“given prompt”或“rewritten prompt”这些短语。
    **#给定提示词#:**
    {这里是指令。}
    **#改写后的提示词#:**

附录 B: Concretizing Prompt(具体化 Prompt)

  • 示例 B.1:用于深度演化的具体化提示 (Prompt for Concretizing of In-Depth Evolving)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    I want you act as a Prompt Rewriter.
    Your objective is to rewrite a given prompt into a more complex version to make those famous AI systems (e.g., ChatGPT and GPT4) a bit harder to handle. But the rewritten prompt must be reasonable and must be understood and responded by humans.
    Your rewriting cannot omit the non-text parts such as the table and code in #Given Prompt#:. Also, please do not omit the input in #Given Prompt#.
    You SHOULD complicate the given prompt using the following method:
    Please replace general concepts with more specific concepts.
    You should try your best not to make the #Rewritten Prompt# become verbose, #Rewritten Prompt# can only add 10 to 20 words into #Given Prompt#. ‘#Given Prompt#’, ‘#Rewritten Prompt#’, ‘given prompt’ and ‘rewritten prompt’ are not allowed to appear in #Rewritten Prompt#
    #Given Prompt#:
    {Here is instruction.} #Rewritten Prompt#:

    我希望你扮演一个提示词重写器(Prompt Rewriter)。
    你的目标是将给定的提示词改写成更复杂的版本,以使那些著名的人工智能系统(例如 ChatGPT 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。
    你的改写不能省略 #给定提示词# 中的非文本部分,例如表格和代码。此外,请不要省略 #给定提示词# 中的输入部分。
    你应当通过以下方法来使给定提示词复杂化:
    请将一般性概念替换为更具体的概念。
    你应尽力避免使 #改写后的提示词# 变得冗长,#改写后的提示词# 只能在 #给定提示词# 的基础上增加 10 到 20 个词。禁止在 #改写后的提示词# 中出现“#给定提示词#”、“#改写后的提示词#”、“given prompt”或“rewritten prompt”这些短语。
    **#给定提示词#:**
    {这里是指令。}
    **#改写后的提示词#:**

附录 C:Increased Reasoning Steps Prompt

  • 示例 C.1:用于深度演化的增加推理步骤提示 (Prompt for Increased Reasoning Steps of In-Depth Evolving)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    I want you act as a Prompt Rewriter.
    Your objective is to rewrite a given prompt into a more complex version to make those famous AI systems (e.g., ChatGPT and GPT4) a bit harder to handle. But the rewritten prompt must bereasonable and must be understood and responded by humans.
    Your rewriting cannot omit the non-text parts such as the table and code in #Given Prompt#:. Also, please do not omit the input in #Given Prompt#.
    You SHOULD complicate the given prompt using the following method:
    If #Given Prompt# can be solved with just a few simple thinking processes, you can rewrite it to explicitly request multiple-step reasoning.
    You should try your best not to make the #Rewritten Prompt# become verbose, #Rewritten Prompt# can only add 10 to 20 words into #Given Prompt#. ‘#Given Prompt#’, ‘#Rewritten Prompt#’, ‘given prompt’ and ‘rewritten prompt’ are not allowed to appear in #Rewritten Prompt#
    #Given Prompt#:
    {Here is instruction.} #Rewritten Prompt#:

    我希望你扮演一个提示词重写器(Prompt Rewriter)。
    你的目标是将给定的提示词改写成更复杂的版本,以使那些著名的人工智能系统(例如 ChatGPT 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。
    你的改写不能省略 #给定提示词# 中的非文本部分,例如表格和代码。此外,请不要省略 #给定提示词# 中的输入部分。
    你应当通过以下方法来使给定提示词复杂化:
    如果 #给定提示词# 可以通过几个简单的思考过程解决,你可以将其改写成明确要求多步推理的形式。
    你应尽力避免使 #改写后的提示词# 变得冗长,#改写后的提示词# 只能在 #给定提示词# 的基础上增加 10 到 20 个词。禁止在 #改写后的提示词# 中出现“#给定提示词#”、“#改写后的提示词#”、“given prompt”或“rewritten prompt”这些短语。
    **#给定提示词#:**
    {这里是指令。}
    **#改写后的提示词#:**

附录 D:Complicate Input Prompt

  • 示例 D.1:用于演化的复杂化输入提示 (Prompt for Complicate Input of Evolving)

    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
    我希望你扮演一个提示词重写器。你的目标是使用数据格式将给定的提示词改写成更复杂的版本,以使那些著名的人工智能系统(例如 chatgpt 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。  
    你必须在 [改写后的提示词] 中添加 [XML 数据] 格式的文本作为输入数据。

    **#给定提示词#:**
    我正在使用这段 php 代码来获取 xml 数据

    **#改写后的提示词#:** 我有这个 xml 文件,我想获取 xml 数据以自动填充 HTML 表格,代码可以运行,但会导致表格内容重复。

    以下是 xml 数据:

    <root>
    <stats>
    <item>
    <day>2017-11-01</day>
    <impressions>2192</impressions>
    <money>1.96790003</money>
    </item>
    <item>
    <day>2017-11-02</day>
    <impressions>2824</impressions>
    <money>3.208500033</money>
    </item>
    <item>
    <day>2017-11-03</day>
    <impressions>3680</impressions>
    <money>3.321799981</money>
    </item>
    </stats>
    <total>
    <impressions>8696</impressions>
    <money>8.498200044</money>
    </total>
    <filter>
    <dateFrom>2017-11-01</dateFrom>
    <dateTo>2017-11-03</dateTo>
    <groupBy>day</groupBy>
    <format>xml</format>
    </filter>
    </root>

    我正在使用这段 php 代码来获取 xml 数据,但这段代码是从整个 xml 数据中获取,导致表格字段重复。

    <?php
    \$dom = new DOMDocument;
    \$dom -> load('http://example.com/', \$dateselected . '&dateTo =', \$dateselected2 . '&format=xml');
    \$day = \$dom->getElementsByTagName('day');
    \\$impressions = \$dom->getElementsByTagName('impressions');
    echo ( "<table>");
    foreach(\$day as \$node1) {
    foreach(\$impressions as \$node2) {
    echo '<tr>';
    echo "<td>", \$node1 -> textContent . "<td>";
    echo "<td>", \$node2 -> textContent . "<td>";
    echo "<td>", \$node2 -> textContent *0.5/1000 . "<td>";
    echo '</tr>';
    }
    }
    echo( "<table>");
    ?>

    有人能提示我如何修复这个问题吗?谢谢

    ####
  • 示例 D.2:用于演化的复杂化输入提示 (Prompt for Complicate Input of Evolving)

    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
    我希望你扮演一个提示词重写器。你的目标是使用数据格式将给定的提示词改写成更复杂的版本,以使那些著名的人工智能系统(例如 chatgpt 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。  

    你必须在 [改写后的提示词] 中添加 [SQL 数据库] 格式的文本作为输入数据。

    **#给定提示词#**
    实现 SQL 查询结果

    **#改写后的提示词#**(必须包含一个具体的 SQL 数据库作为输入):
    有一个名为 messages 的表格,包含的数据如下所示:

    | Id | Name | Other_Columns |
    |----|------|---------------|
    | 1 | A | A_data_1 |
    | 2 | A | A_data_2 |
    | 3 | A | A_data_3 |
    | 4 | B | B_data_1 |
    | 5 | B | B_data_2 |
    | 6 | C | C_data_1 |

    如果我运行查询 `select * from messages group by name`,我将得到以下结果:

    | 1 | A | A_data_1 |
    | 4 | B | B_data_1 |
    | 6 | C | C_data_1 |

    哪个查询会返回以下结果?

    | 3 | A | A_data_3 |
    | 5 | B | B_data_2 |
    | 6 | C | C_data_1 |

    也就是说,应返回每个组中的最后一条记录。目前,我使用的查询是:

    SELECT *
    FROM (SELECT *
    FROM messages
    ORDER BY id DESC) AS x
    GROUP BY name

    但这看起来非常低效。是否有其他方法可以实现相同的结果?

    ####
  • 示例 D.3:用于演化的复杂化输入提示 (Prompt for Complicate Input of Evolving)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    我希望你扮演一个提示词重写器。你的目标是使用数据格式将给定的提示词改写成更复杂的版本,以使那些著名的人工智能系统(例如 chatgpt 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。  

    你必须在 [改写后的提示词] 中添加 [python 代码] 格式的文本作为输入数据。

    **#给定提示词#**
    转换 python 代码

    **#改写后的提示词#**(必须包含一个具体的 python 代码作为输入):
    我有以下 Python 代码:

    ```python
    cursor.execute("INSERT INTO table VALUES var1, var2, var3,")

    其中 var1 是整数,var2 和 var3 是字符串。
    如何编写变量名而不让 Python 将它们作为查询文本的一部分?

    ####

    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
    * **示例 D.4:用于演化的复杂化输入提示 (Prompt for Complicate Input of Evolving)**  
    ```md
    我希望你扮演一个提示词重写器。你的目标是使用数据格式将给定的提示词改写成更复杂的版本,以使那些著名的人工智能系统(例如 chatgpt 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。
    你必须在 [改写后的提示词] 中添加 [HTML 页面] 格式的文本作为输入数据。

    **#给定提示词#**
    滚动整个 HTML 页面

    **#改写后的提示词#**(必须包含一个具体的 HTML 页面作为输入):
    我希望能够滚动整个页面,但不显示滚动条。
    在 Google Chrome 中,可以使用:


    ::-webkit-scrollbar {
    display: none;
    }

    但 Mozilla Firefox 和 Internet Explorer 似乎不支持这种方式。
    我也在 CSS 中尝试了:

    overflow: hidden;

    这样可以隐藏滚动条,但我无法再滚动了。有没有办法可以在隐藏滚动条的同时仍然能够滚动整个页面?

    请仅使用 CSS 或 HTML。

    ###
  • 示例 D.5:用于演化的复杂化输入提示 (Prompt for Complicate Input of Evolving)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    我希望你扮演一个提示词重写器。你的目标是使用数据格式将给定的提示词改写成更复杂的版本,以使那些著名的人工智能系统(例如 chatgpt 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。  

    你必须在 [改写后的提示词] 中添加 [Shell 命令] 格式的文本作为输入数据。

    **#给定提示词#**
    Shell scp 文件

    **#改写后的提示词#**(必须包含一个具体的 Shell 命令作为输入):
    我正在尝试从远程服务器 scp 一个文件到我的本地机器。只有端口 80 是可访问的。
    我尝试了:

    ```shell
    scp -p 80 username@www.myserver.com/root/file.txt .

    但出现了这个错误:cp: 80: No such file or directory
    如何在 scp 命令中指定端口号?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    * **示例 D.6:用于演化的复杂化输入提示 (Prompt for Complicate Input of Evolving)**  
    ```md
    我希望你扮演一个提示词重写器。你的目标是使用数据格式将给定的提示词改写成更复杂的版本,以使那些著名的人工智能系统(例如 chatgpt 和 GPT4)更难处理。但改写后的提示词必须是合理的,且必须能被人类理解并回应。

    你必须在 [改写后的提示词] 中添加 [JSON 数据] 格式的数据作为输入数据,添加 [JSON 数据] 代码作为输入代码。

    改写后的提示词必须是一个问题式指令。

    **#给定提示词#:**
    给定一个客户购买历史的 JSON 数据集,我们如何计算客户在同一商店进行重复购买的概率?我们能否利用条件概率公式:\(P(A|B)=P(A\cap B)/P(B)\),其中 A 表示客户进行重复购买的事件,B 表示客户再次在同一商店购买的事件?此外,我们如何应用这个公式来识别最有可能进行重复购买的客户群体?你能提供一个使用给定 JSON 数据集实现这个公式的示例吗?

    改写后的提示词必须是一个问题式指令。

    **#改写后的提示词#**(必须包含一个具体的 JSON 数据作为输入):

附录 E:Difficulty Judge Prompt

  • 示例 E.1:用于判断指令难度的提示 (Prompt for Juding the Difficulty of Instructions)
    1
    2
    3
    4
    5
    6
    我们希望您评估并评定以下问题的难度和复杂性。您应给出一个从 1 到 10 的整体分数,分数越高表示难度和复杂性越高。您必须仅给出分数,不提供任何其他理由。  

    **## 问题:**
    { 这里是指令。 }

    **## 分数:**

附录 F:Equal Prompt

  • 示例 F.1:用于判断两个指令是否等价的提示 (Prompt for Determining whether Two Instructions are Equal)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    以下是两个给 ChatGPT AI 的指令,您认为它们是否彼此等价,需满足以下要求:  

    1. 它们具有相同的约束和要求。
    2. 它们具有相同的询问深度和广度。

    第一个提示:{这里是第一个指令。}

    第二个提示:{这里是第二个指令。}

    您的判断(仅回答:等价 或 不等价。无需解释原因。):

附录 G:Math Judgement Prompt

  • 示例 G.1:用于判断指令是否与数学相关的提示 (Prompt for judging whether an instruction is math related)
    1
    2
    3
    请判断以下问题是否是一个数学问题,并仅返回 True 或 False,不提供任何解释。  

    问题:{指令}

附录 H:WizardEval Analysis

  • 论文收集了 Evol-Instruct 测试集,其中包含来自各种来源的真实世界人类指令,例如在线开源项目、平台和论坛
  • 论文分析了数据并识别出 29 种不同的技能,这些技能代表了人类的主要需求,例如代码生成与调试、数学、推理、复杂格式、写作、广泛学科等等
  • 图 6 展示了论文测试集中实例和技能的分布情况
  • 论文的测试集包含 218 个实例,每个实例都是针对特定技能的指令
  • 论文将论文的测试集与 Vicuna 的测试集进行了比较,后者是用于评估指令遵循模型的基准数据集
  • 论文发现 Vicuna 的测试集只有 80 个实例和 9 种技能,比论文的测试集小得多且多样性低得多
  • 图 4a 显示了测试数据的难度和复杂性如何在不同实例间变化
  • 论文的测试数据具有更均匀的分布,这意味着它包含不同难度和复杂性级别的指令
  • 另一方面,Vicuna 和 Alpaca 的分布则存在偏差,这意味着它们主要包含低难度和低复杂性的指令
  • 这表明这两个语料库无法处理对更复杂和要求更高场景的评估

附录 I:Different difficulty Annotators

  • 论文仅使用 ChatGPT 来事后分析生成指令的“难度”分布,但论文并未使用此分析结果来指导数据生成或模型训练
  • 为了探索 ChatGPT 执行难度分析的能力,论文采样了 600 条指令,并使用更强大的 GPT-4 模型和 5 位受过良好教育的人类标注者一起进行难度评估
  • 评估结果见表 3。结果表明,ChatGPT、GPT-4 和人工标注在难度变化趋势上表现出高度的一致性
  • 为了研究 ChatGPT 难度评分的正确性,论文增加了一个新的实验来测量 ChatGPT 与人类之间在难度判断上的一致性:
    • 论文每次以相等概率从六个数据集(Alpaca、ShareGPT、C1 到 C4)中随机选择两条指令,组成一对
    • 总共论文选择了 300 个指令对
    • 然后,论文请 ChatGPT 和 5 位受过良好教育的人类标注者判断在一个指令对中哪一条更难,人类之间的 Kappa 分数为 0.68,ChatGPT 与人类(多数投票)之间的 Kappa 分数为 0.66,这表明 ChatGPT 和人类标注者之间具有良好的一致性

附录 J: Cluster Scatter Plot(聚类散点图)

  • 广度演化旨在增强主题覆盖度、技能覆盖度和整体数据集的多样性。为了(定性分析)检查不同数据集的广度(多样性),论文首先使用 BERT
  • 对每条指令进行编码并获得其 768 维的嵌入向量,然后使用名为 t-SNE 的降维算法将嵌入维度降至 2 维,最后论文应用聚类算法 k-means 将每个数据集的指令划分为 20 个簇,以便进行直观的可视化
  • 如图 7 所示,论文数据集的数据点比 ShareGPT 和 Alpaca(Self-Instruct)的数据点更加分散,这表明论文的指令具有更好的主题多样性

附录 K: Human Evaluation Aspects

  • 标注者从以下五个维度判断哪个回答更好:
    • (1) 相关性 (Relevance): 评估模型正确理解上下文和问题语义含义的能力
    • (2) 知识性 (Knowledgeable): 模型是否能够准确使用各种详细的知识来解决问题
    • (3) 推理能力 (Reasoning): 评估模型执行正确推理过程或设计有效推理概念以解决问题的能力
    • (4) 计算能力 (Calculation): 评估模型是否能在数学、生物、化学和物理领域对所提供的公式进行准确的数学计算
    • (5) 准确性 (Accuracy): 评估模型对于给定指令是否能在相应领域正确执行

附录 L: Performance details of different checkpoints

  • 在论文中,论文使用 3 个训练周期 (epoch) 训练论文的模型,并且在上文的“第 4 节 实验”中仅报告了最终检查点的性能,以与之前的工作保持一致
  • 如下表 4 所示,论文报告了模型在不同周期(2.5, 2.75, 3)的检查点性能
    • 对于 13B 模型,我们可以看到除了 GSM8k 之外,在每个基准测试上表现最好的始终是 WizardLM-13b (ShareGPT Seed)
    • 对于 65b/70b 模型,论文也看到 WizardLM-70b 在所有基准测试中都是最好的
    • 因此,作者认为这主要是由模型训练过程中在某些基准测试上的波动引起的

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 解析为字符串或其他类型,而不是小数类型
1…141516…63
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

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