长上下文训练 256k 不难:Halo 上下文并行(CP + Ulysses 注意力)原理与实测

发布时间:2026/10/26 17:52:36
长上下文训练 256k 不难:Halo 上下文并行(CP + Ulysses 注意力)原理与实测

【免费下载链接】halo

Halo is an open-source framework built by White Circle for training large language and multimodal models

项目地址:https://gitcode.com/gh_mirrors/halo34/halo
点击查看免费下载

长上下文训练一直是多卡训练里的硬骨头:序列一长,激活显存和注意力的平方复杂度就会把单卡"撑爆"。开源大模型训练框架Halo通过**上下文并行(Context Parallelism, CP)**加Ulysses 注意力,把 256k 长度的序列切到多张 GPU 上精确训练,实测峰值显存可降到单卡方案的约一半。本文讲清它的原理、使用方式和真实基准数据。

为什么长上下文训练这么难

多卡并行里,模型太大可以用张量并行(TP),MoE 专家太多可以用专家并行(EP),但序列太长是另一类瓶颈——它吃的是激活显存,不是参数显存。EP 切的是专家,对激活没帮助;所以 32K 以上的长序列训练,需要一条专门的路径:把序列本身切到多张卡上。

这就是 Halo 上下文并行(CP)存在的意义:每张 GPU 只持有seq_len / cp_size个 token 的激活,其余计算照常分摊,注意力层则靠通信"临时拼回"完整序列。官方说明见 agent-docs/parallelism/context-parallelism.md。

Ulysses 注意力:三次 all-to-all 换一次精确计算

朴素的"每卡切一段序列"有个致命问题:注意力要看全序列,切断了跨块依赖。Ulysses 的思路很聪明——在注意力核函数周围重新分配张量:

  1. 每个 rank 持有S/cp_size个 token、但全部注意力头;
  2. 两次 all-to-all 把张量换成"全量S个 token、但只有H/cp_size个头",供 Flash Attention 计算(Q 一次、融合后的 K|V 一次、输出再一次);
  3. 反向传播按同样三步反向执行。

多头注意力在"头"维度上是相互独立的,所以这种交换是精确的,不是近似。RoPE 在本地块上完成(优化路径),省掉了位置索引的全局搬运;原生 GQA 支持还省掉了repeat_kv的拷贝。

入口实现是 UlyssesCPModelWrapper,各类模型的注意力包装器在 src/distributed/context_parallel/layers/ 下,注册表 registry.py 直接从子类树派生支持列表。

图中展示cp 4、64 个 Q 头 / 8 个 KV 头的例子:交换前每卡只有对角块(本 rank 的一个序列块 × 全部头),交换后每卡拿到全部 S 个 token 的本 rank 16 个头,注意力计算即覆盖完整序列。

一次 CP 训练步的完整数据流

理解了注意力层,再看整步训练:

  • 入口split_sequence_for_cp把 batch 收窄为每 rank 一个连续序列块(S % cp_size == 0,collator 自动补齐);
  • Embedding 和所有 decoder 层(MLP / MoE 专家)都只在本块上跑,CP 对此无感知;
  • logits 保持分块,从不 all-gather;
  • Loss 做两处关键修正:边界 token(因果标签偏移会让每块少一个预测,非末块用最后一个 logit 预测下一块首 token)+全局归一化的 sum loss(loss_i = cp_size * local_sum / global_tokens,global_tokens是 CP 组内 all-reduce 出的非 mask token 总数),保证每个 token 对梯度的贡献完全相等。

一句话总结:只有注意力层看得到完整序列,其余所有层和 loss 都只处理本 rank 的块。GRPO 等训练目标的分块求和路径在 autograd.py 中提供 autograd 感知的cp_sum_rows。

上手:两条命令即可开启

纯 CP(任意支持的模型,长序列 SFT):

torchrun --nproc_per_node=4 scripts/training/sft.py \ examples/sft/qwen3/qwen3-4b-ultrachat.yaml --context_parallel_size=4 --packing=false

MoE 模型的 EP+CP(节点内 EP,ep_group_size等于 NVLink 域大小):

torchrun --nproc_per_node=8 scripts/training/sft.py \ examples/sft/gptoss/gptoss-20b-multinode-ep.yaml \ --expert_parallel_size=8 --ep_scope=node --context_parallel_size=2 --packing=false

开跑前的硬性前提 📋

前提说明
头数整除cp_size必须同时整除 Q 头和 KV 头数(GPT-OSS 64Q/8KV:CP=8 ✅,CP=3 ❌)
序列长度必须是cp_size的倍数(sft.py 自动设置pad_to_multiple_of,无需手动 collator)
拓扑cp_size不能超过 NVLink 域(all-to-all 必须留在 NVLink 内)
注意力实现必须是 Flash Attention(FA2/FA3/FA4);eager/sdpa会被拒绝
仅右 paddingUlysses 路径走稠密因果核,左 padding 批次会在 forward 时直接报错
与 packing 互斥packing/padding_free在 CP 下被拒绝

支持的模型包括 Qwen3 dense/MoE、Qwen3-VL 文本分支、GPT-OSS、GLM-4 MoE Lite、Mistral4(MLA 路径)、Bailing/Ling 2.0、Cohere2 MoE;线性注意力、序列轴卷积等结构(如 Qwen3.5/3.6 完整检查点、LFM-2、Gemma 4)暂不支持 CP。

实测:256k 不是数字游戏

以下数据来自官方基准(gpt-oss-20b,8× B300,b1,GC 开启):

256k 序列实测对比(tok/s/GPU · 峰值显存 GiB):

配置吞吐峰值显存
stock TRL (z3)4,463148
Halo EP1 z3(稠密)5,728112
Halo EP8+CP8 (DP1)2,39577
Halo dense CP-only (z3)1,83656

三个结论:

  • 稠密 EP1 z3 赢吞吐:256k 下比 stock TRL 快 28%,显存还少 36 GiB;
  • 64k 之后 CP 越大越好:128k 处 EP8+CP8 在吞吐和显存上同时优于 EP8+CP4——注意力是二次复杂度,序列减半带来的收益远大于多一次 all-gather 的开销;
  • dense CP-only 是全榜最省显存(128k→256k 仅 32–56 GiB),且不依赖 DeepEP。

再看 EP8+CP8 在 16k–64k 的表现:

SeqLentok/s/GPUTFLOPS峰值显存
16,3847,14924924.6 GiB
32,7689,03140229.9 GiB
65,5368,86256641.9 GiB

注意TFLOPS 随序列变长而上升——更长的序列摊薄了 Ulysses all-to-all 的固定成本,显存则只从 25 GiB 缓慢涨到 42 GiB。CP 的本质是一笔划算的交易:用少量 per-GPU 吞吐换取便宜的长上下文。完整表格见 throughput-benchmarks.md 与 halo-vs-stock-trl.md。

CP 与 EP/TP 怎么搭配

  • data_parallel_size = world_size / cp_size,CP 直接缩减 DP(EP 则正交不减 DP);
  • EP+CP 是合法组合:CP 在注意力层做 all-to-all,EP 在 MoE 块做 DeepEP dispatch/combine,位置不同互不冲突。8 卡节点上的合法形状如ep8/cp2 → DP4、ep8/cp4 → DP2、ep8/cp8 → DP1;
  • TP+CP 被拒绝:两者对同一组连续 rank 做两次不兼容的划分,配置期即报错;
  • 所有组合由 parallelism_config.py 中的白名单在配置期校验,跑错的组合会在加载模型前就被明确拒绝。

注意事项与限制 ⚠️

  • 训练器:仅 SFT、SMPO、offline GRPO(offline GRPO 要求全量微调)声明支持 CP,其余训练器会在构造时拒绝;
  • 不支持在线生成:generate()会直接报错(每 rank 只有一个块,Ulysses 注意力没有 KV-cache 路径),评测请用保存的检查点另起进程;
  • 文本 only:多模态输入(pixel_values)在 CP 下被拒绝;
  • torch.compile对 CP 无收益(all-to-all 在每个注意力层打断计算图),保持默认关闭即可。

小结

Halo 把长上下文训练抽象成一个干净的问题:序列切块 + Ulysses 精确注意力交换 + 全局归一化的分块 loss。前提校验严格(配置期拒绝一切不合法组合)、实现精确(非近似)、实测扎实(256k 显存减半、TFLOPS 随序列增长)。如果你正被 128k+/256k 的 OOM 卡住,--context_parallel_size加一个参数可能就够用了。更多细节可查阅 上下文并行指南 和 并行策略总览。

【免费下载链接】halo

Halo is an open-source framework built by White Circle for training large language and multimodal models

项目地址:https://gitcode.com/gh_mirrors/halo34/halo
点击查看免费下载

相关推荐

上一篇:Arco Design React 表单实战:使用 Form 实现带二次密码校验的注册表单
下一篇:Windows窗口美化终极指南:打造个性化毛玻璃特效完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考