首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >23-长上下文训 练为什么需要SequenceParallel

23-长上下文训 练为什么需要SequenceParallel

作者头像
anzhsoft
发布2026-07-23 20:52:47
发布2026-07-23 20:52:47
390
举报

当 micro-batch 已经很小,显存和计算压力仍然可能来自一条样本内部的 token 轴。 第23篇把第22篇的batch讨论推进到token维度:当micro-batch已缩小仍被长序列压垮时,系统必须引入Sequence/Context Parallel。文章沿FSDP/ Ulysses与Megatron/MCore两条源码路径,解释DPxSP mesh、输入切片、attention all-to-all、dynamic CP与输出恢复如何共同支撑长上下文训练。

第 22 篇把 mini-batch、micro-batch 和 dynamic batch size 拆开了:算法窗口、GPU 执行切片、token 重新装箱是三层不同控制。本文继续沿着 token 维度往下走,回答一个更尖锐的问题:如果 prompt/response 已经长到单条样本都很重,继续缩小 micro-batch 还够吗?

本文的核心判断是:长上下文训练不能只靠减少样本数,因为压力会从 batch 轴转移到 sequence/token 轴。verl 里这件事在两条后端路径上表现不同:FSDP 路径用 Ulysses sequence parallel 把同一个 micro-batch 的 token 分到 SP rank;Megatron/MCore 路径同时暴露 sequence_parallelcontext_parallel_size和 dynamic CP,把长序列压力接入 Megatron 的并行状态、pipeline schedule 和输出恢复。理解这一层,才能知道为什么第 22 篇的 dynamic batch size 只是装箱策略,而不是长上下文训练的全部答案。

先看整体地图。读图时注意:batch 轴控制“多少条样本”,token 轴控制“一条或一组序列内部有多长”;当 token 轴成为瓶颈时,系统必须让序列本身进入分布式坐标。

长上下文压力从 batch 轴转向 token 轴

这张图给第 23 篇定边界:dynamic batch size 可以把不同长度样本重新装箱,让 micro-batch 的 token 工作量更均衡;Sequence Parallel/Context Parallel 则进一步改变单个 micro-batch 内部的张量归属。前者解决“怎么分组”,后者解决“一个组里的 token 是否要跨 rank 执行”。

1. 缩小 micro-batch 不能切开 token 轴

很多性能调优会从 ppo_micro_batch_size_per_gpuppo_max_token_len_per_gpu入手,这在第 22 篇已经解释过。但长上下文还有一个更硬的边界:当 micro-batch 已经缩到 1 条样本,样本内部的 token 仍然会产生 embedding、attention、logits、loss mask 和输出恢复成本。此时继续减少样本数已经没有空间,必须处理 token 轴本身。

prepare_micro_batches()的源码正好暴露了这个边界。固定模式按 micro_batch_size_per_gpu切样本;dynamic 模式按 max_token_len_per_gpu * sp_size计算 micro-batch 的 token 上限,再调用 rearrange_micro_batches()重排样本(verl/workers/engine/utils.py:69-96)。这里的 sp_size很关键:它说明 token 预算不是孤立的 batch 配置,而要和后端的序列并行坐标一起解释。

所以第 23 篇不再问“一个 micro-batch 放几条样本”,而是问:一个 micro-batch 里的有效 token,能不能被多个 rank 共同承受?FSDP 和 Megatron 对这个问题的回答不一样。

2. FSDP 路径用 Ulysses 建出 DP x SP 坐标

先看 FSDP。它本来主要解决参数、梯度和 optimizer state 的 shard 问题;但在 verl 里,FSDP engine 还可以额外建一个 Ulysses sequence parallel mesh。读下面这张图时注意两点:第一,ulysses_sequence_parallel_size会改变 data parallel size 的计算;第二,micro-batch 的 token 上限会乘上 sp_size

FSDP Ulysses 如何改变 device mesh 与 token 预算

源码路径很短。FSDPEngineConfig暴露 ulysses_sequence_parallel_size,配置文件默认值是 1(verl/workers/config/engine.py:244-266verl/trainer/config/engine/fsdp.yaml:45)。FSDPEngine._init_device_mesh()先创建 FSDP 的 device mesh,再在 ulysses_sequence_parallel_size > 1时创建 mesh_shape=(dp_size, sp_size)mesh_dim_names=["dp", "sp"]的 Ulysses mesh,并保存 sp维度的 process group(verl/workers/engine/fsdp/transformer_impl.py:203-220)。

这不是附属信息,而是会改写 rank 语义。get_data_parallel_size()返回 world_size // ulysses_sequence_parallel_size;如果 Ulysses mesh 存在,DP rank 和 DP group 都来自 ulysses_device_mesh["dp"]verl/workers/engine/fsdp/transformer_impl.py:587-600)。进入 forward_backward_batch()后,engine 把 sp_size写进 TensorDict,并在 DP group 上 all-reduce loss_mask.sum()作为全局 token 数,再调用 prepare_micro_batches()verl/workers/engine/fsdp/transformer_impl.py:608-645)。

这一段代码说明:Ulysses SP 不只是模型内部的 attention 技巧,它先改变了训练 engine 对 DP、loss 归一化和 micro-batch token 预算的理解。

3. Ulysses 让 forward 变成 slice、all-to-all、gather

有了 SP group 还不够,forward 也必须知道当前 token 属于哪个 SP rank。下面这张图把 FSDP/Ulysses 的运行链路画出来。看图时注意:输入先 pad/slice,attention 内部再把 sequence 和 head 维度做 all-to-all,输出最后还要 gather/unpad 回原来的 token 顺序。

Ulysses forward 的输入切片、注意力交换和输出恢复

FSDP 的 train/eval 上下文会先设置当前 Ulysses SP group,退出时再恢复原 group(verl/workers/engine/fsdp/transformer_impl.py:870-903)。LM head 的 prepare_model_inputs()在 remove padding 路径下把 nested input 展成 input_ids_rmpad,如果 use_ulysses_sp开启,就调用 ulysses_pad_and_slice_inputs()input_idsposition_ids、rolled labels 和 temperature(verl/workers/engine/fsdp/transformer_impl.py:935-981)。ulysses_pad_and_slice_inputs()本身先把长度 pad 到可被 SP size 整除,再按当前 SP rank 对 sequence 维度取切片(verl/utils/ulysses.py:282-334)。

注意力层还要再做一次更细的交换。gather_seq_scatter_heads()[batch, seq/sp, heads, ...]变成 [batch, seq, heads/sp, ...]gather_heads_scatter_seq()再把 attention 输出变回 [batch, seq/sp, heads, ...]verl/utils/ulysses.py:66-105)。Llama 和通用 monkey patch 的 Ulysses forward 都沿用这个模式,并且会校验 attention heads 必须能被 Ulysses size 整除(verl/models/transformers/llama.py:78-160verl/models/transformers/monkey_patch.py:119-153verl/utils/ulysses.py:337-341)。

输出侧也不能漏。prepare_model_outputs()在 SP 开启时用 gather_outputs_and_unpad()把 log_probs、entropy、distillation top-k 等结果 gather 回完整 token 序列,再按原始 cu_seqlens还原成 nested tensor(verl/workers/engine/fsdp/transformer_impl.py:1054-1155verl/utils/ulysses.py:247-279)。这就是为什么图前面要强调“切片”和“恢复”是一对:SP 如果只切不还原,loss 和后续指标就回不到算法 mini-batch 的语义。

4. Megatron 路径把长上下文写入并行状态

Megatron/MCore 路径的命名更容易混淆。sequence_parallelcontext_parallel_size、dynamic context parallel 都会影响长序列,但它们不是同一个开关。看下面这张图时注意:sequence_parallel贴着 TP 和 Megatron model config,context_parallel_size贴着长上下文切分和 CP group,dynamic CP 则会在 forward 时按序列长度再拆 micro-batch。

Megatron 中 SP、CP 和 dynamic CP 的位置

配置层先把边界说清楚。McoreEngineConfig同时暴露 tensor_model_parallel_sizecontext_parallel_sizedynamic_context_parallelmax_seqlen_per_dp_cp_ranksequence_parallel;其中 context_parallel_size的注释是 long sequences,sequence_parallel在 TP size 为 1 时会被自动改成 Falseverl/workers/config/engine.py:148-216)。Megatron 配置文件也把 context_parallel_sizesequence_parallel放成两个字段(verl/trainer/config/engine/megatron.yaml:29-44)。

初始化阶段,MegatronEngine._init_device_mesh()调用 mpu.initialize_model_parallel(),把 TP、PP、VPP、CP、EP/ETP 等 size 一次写进 Megatron parallel state;如果启用 dynamic CP,还要求当前 Megatron 支持该参数,并且必须设置 max_seqlen_per_dp_cp_rankverl/workers/engine/megatron/transformer_impl.py:131-158)。构建 transformer config 时,provider 会接收 context_parallel_sizesequence_parallelverl/workers/engine/megatron/transformer_impl.py:207-214)。

执行阶段,Megatron 的 forward_backward_batch()sp_size写成context_parallel_size,再按第 22 篇讲过的prepare_micro_batches()切 micro-batch;随后它把num_micro_batch写入每个 micro-batch,并把执行交给 Megatron pipeline schedule(verl/workers/engine/megatron/transformer_impl.py:599-674)。如果打开 dynamic CP,forward_step()会调用dynamic_cp_split_batch(),按最长序列和max_seqlen_per_dp_cp_rank估算local_cp_size,必要时在 DP-CP group 内选择子 batch;postprocess 再用dynamic_cp_merge_output()聚合输出(verl/workers/engine/megatron/transformer_impl.py:813-822963-992verl/utils/megatron_utils.py:1450-1525)。

MCore 模型侧也能看到 SP 的痕迹。value head 的 LinearForLastLayersequence_parallel=True时标记权重,并在 forward 后 gather sequence-parallel region;fused/overlap forward 里也会在输出层前 gather sequence-parallel hidden states(verl/models/mcore/bridge.py:35-90verl/models/mcore/model_forward_fused.py:282-284verl/models/mcore/model_forward_1f1b_overlap.py:190-192)。这说明 Megatron 的长上下文路径不是单点函数,而是 parallel state、model config、pipeline schedule 和输出后处理共同完成。

5. Sequence Parallel 买到的是容量,付出的是边界复杂度

把 FSDP/Ulysses 和 Megatron/MCore 放在一起看,Sequence Parallel 的收益并不神秘:它让 token 轴进入并行坐标,让一个 micro-batch 的长序列不必完全由单个 rank 承担。但代价也同样明确:输入要切、attention 要通信、输出要恢复,DP/CP/SP rank 的语义也会影响 loss 归一化和指标聚合。

下面这张图把收益和成本并排列出来。读图时重点看右侧:这些成本都不是论文术语,而是正文前面已经出现的源码边界。

Sequence Parallel 的收益与边界成本

因此,工程上不能把 ulysses_sequence_parallel_sizesequence_parallel=Truecontext_parallel_size>1理解成“打开长上下文模式”这么简单。它们会连带影响 micro-batch token 上限、data parallel group、attention all-to-all、输出 gather/unpad、pipeline schedule、dynamic CP split/merge,以及哪些 rank 真正产出可用于 loss 和指标的结果。

回到第 22 篇的结论:dynamic batch size 仍然重要,因为它决定样本如何按 token 预算装进 micro-batch;但当 token 预算本身要被多个 rank 分担时,Sequence Parallel/Context Parallel 才进入主路径。

小结:长上下文把训练引擎推向 token 维度

第 23 篇补的是第四组里的 token 维度并行:PPO/GRPO 的算法语义仍然由 mini-batch 和全局 loss 归一化维持,但执行层必须让长序列跨 rank 承担。FSDP 通过 Ulysses SP group、pad/slice、attention all-to-all 和 gather/unpad 完成这件事;Megatron/MCore 则把 SP、CP、dynamic CP 放进 parallel state、model config 和 pipeline schedule。

放回系列地图,第四组已经从参数 shard、Megatron 并行组合、engine 抽象、batch 执行单位一路走到长上下文。下一篇会把训练引擎重新接回 rollout:当 actor 更新完成后,Checkpoint Engine 如何把新权重同步给推理引擎。

本文源码索引

  • verl/workers/engine/utils.py:69-96prepare_micro_batches()如何把 sp_size计入 dynamic batch token 上限。
  • verl/workers/config/engine.py:244-266verl/trainer/config/engine/fsdp.yaml:45:FSDP 的 ulysses_sequence_parallel_size配置入口。
  • verl/workers/engine/fsdp/transformer_impl.py:203-220587-645:FSDP 如何创建 Ulysses dp x spmesh,并在 forward/backward 前写入 sp_size、全局 token 数和 DP size。
  • verl/workers/engine/fsdp/transformer_impl.py:870-903935-9811054-11551210-1239:FSDP train/eval 上下文、Ulysses 输入切片、输出 gather/unpad 与 forward step。
  • verl/utils/ulysses.py:66-105121-134247-334337-341363-394:Ulysses all-to-all、输入切片、输出恢复、head 数校验和 FSDP sharding manager。
  • verl/models/transformers/llama.py:78-160verl/models/transformers/monkey_patch.py:119-153:Transformer attention 内部如何插入 Ulysses sequence/head 交换。
  • verl/workers/config/engine.py:148-216verl/trainer/config/engine/megatron.yaml:29-44:Megatron/MCore 的 SP、CP、dynamic CP 配置边界。
  • verl/workers/engine/megatron/transformer_impl.py:131-158207-214599-674813-822963-992:Megatron parallel state 初始化、provider 配置、micro-batch schedule、dynamic CP split/merge 调用。
  • verl/utils/megatron_utils.py:1450-1525:dynamic CP 如何按最长序列估算 local_cp_size并合并输出。
  • verl/models/mcore/bridge.py:35-90verl/models/mcore/model_forward_fused.py:282-284verl/models/mcore/model_forward_1f1b_overlap.py:190-192:MCore 输出侧如何处理 sequence parallel hidden states。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 训推工坊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 缩小 micro-batch 不能切开 token 轴
  • 2. FSDP 路径用 Ulysses 建出 DP x SP 坐标
  • 3. Ulysses 让 forward 变成 slice、all-to-all、gather
  • 4. Megatron 路径把长上下文写入并行状态
  • 5. Sequence Parallel 买到的是容量,付出的是边界复杂度
  • 小结:长上下文把训练引擎推向 token 维度
  • 本文源码索引
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档