序列并行:解决超长序列的显存瓶颈
张量并行(TP)将模型参数切分到多个GPU,流水线并行(PP)将模型层切分到多个GPU,但两者都没有解决序列长度带来的显存问题。
当序列长度达到128K甚至1M时,即使使用TP和PP,每个GPU上存储的激活值仍然是 O(seq_len × hidden_dim)。序列并行(Sequence Parallelism, SP)正是为了解决这个问题——将序列维度也切分到多个GPU上。
序列并行的核心思想
基本切分
将序列维度切分到N个GPU上,每个GPU只处理 seq_len/N 个token:
原始序列: [t0, t1, t2, ..., t_{L-1}]
GPU 0: [t0, t1, ..., t_{L/N-1}]
GPU 1: [t_{L/N}, ..., t_{2L/N-1}]
...
GPU N-1: [t_{L-L/N}, ..., t_{L-1}]
通信需求
序列切分后,LayerNorm和Dropout等逐token操作可以独立进行(无需通信),但注意力计算需要完整的序列信息——每个token的Query需要与所有token的Key/Value计算注意力。
这是序列并行的核心挑战:如何在不重新聚合完整序列的前提下完成注意力计算?
DeepSpeed-Ulysses
原理
DeepSpeed-Ulysses使用All-to-All通信来实现序列并行注意力:
class UlyssesAttention(nn.Module):
def __init__(self, d_model, n_heads, n_gpus):
super().__init__()
self.n_gpus = n_gpus
self.local_heads = n_heads // n_gpus
# 每个GPU只存储部分注意力头的参数
self.W_q = nn.Linear(d_model, self.local_heads * (d_model // n_heads))
self.W_k = nn.Linear(d_model, self.local_heads * (d_model // n_heads))
self.W_v = nn.Linear(d_model, self.local_heads * (d_model // n_heads))
self.W_o = nn.Linear(self.local_heads * (d_model // n_heads), d_model)
def forward(self, x):
# x的形状: [batch, local_seq_len, d_model]
# 每个GPU持有序列的一部分
# 1. 本地计算QKV
Q_local = self.W_q(x) # [batch, local_seq, local_heads * head_dim]
K_local = self.W_k(x)
V_local = self.W_v(x)
# 2. All-to-All通信:重新排列序列和头维度
# 从 [batch, local_seq, local_heads, head_dim]
# 到 [batch, global_seq, local_heads, head_dim]
Q_global = all_to_all(Q_local, split_dim=1, concat_dim=2)
K_global = all_to_all(K_local, split_dim=1, concat_dim=2)
V_global = all_to_all(V_local, split_dim=1, concat_dim=2)
# 3. 本地注意力计算(现在每个GPU有完整的序列,但只有部分头)
attn_out = flash_attention(Q_global, K_global, V_global)
# 4. All-to-All通信:将结果按序列切分回去
attn_local = all_to_all(attn_out, split_dim=2, concat_dim=1)
# 5. 输出投影
output = self.W_o(attn_local)
return output
通信分析
Ulysses需要2次All-to-All通信(前向)+ 2次All-to-All(反向)。
All-to-All的通信量:每个GPU发送/接收 batch × seq_len × hidden_dim / n_gpus 个元素。
当 n_gpus 较大时,通信量线性增长,成为瓶颈。
Megatron SP(Sequence Parallelism)
原理
Megatron-LM的序列并行与DeepSpeed-Ulysses不同,它不切分注意力计算,而是切分LayerNorm和Dropout:
class MegatronSP(nn.Module):
def forward(self, x):
# x按序列维度切分:[batch, local_seq, d_model]
# 1. LayerNorm:本地独立计算(无需通信!)
x_norm = layer_norm(x) # 逐token操作,天然支持序列并行
# 2. QKV投影:本地计算
Q = self.W_q(x_norm)
K = self.W_k(x_norm)
V = self.W_v(x_norm)
# 3. 注意力:需要All-Gather恢复完整序列
Q_full = all_gather(Q, dim=1) # [batch, seq, local_heads * head_dim]
K_full = all_gather(K, dim=1)
V_full = all_gather(V, dim=1)
# 4. 注意力计算
attn_out = attention(Q_full, K_full, V_full)
# 5. Reduce-Scatter:按序列切分回去
attn_local = reduce_scatter(attn_out, dim=1)
# 6. 输出投影
output = self.W_o(attn_local)
# 7. Dropout:本地独立计算
output = dropout(output)
return output + x # 残差连接
关键洞察
Megatron SP的核心洞察是:LayerNorm和Dropout是逐token操作,天然支持序列并行。这些操作在TP中会在所有GPU上重复计算,而SP将它们切分到不同GPU,节省了显存。
注意力计算虽然需要All-Gather,但只占整体计算的一小部分(当FFN较大时)。
Ulysses vs Megatron SP
| 特性 | DeepSpeed-Ulysses | Megatron SP |
|---|---|---|
| 切分维度 | 注意力头 | 序列维度 |
| 通信模式 | All-to-All | All-Gather + Reduce-Scatter |
| LayerNorm | 重复计算 | 切分(节省显存) |
| 适用场景 | 注意力密集型 | FFN密集型 |
| 通信量 | O(seq_len × d / N) | O(seq_len × d / N) |
组合使用
两者可以组合——Ulysses切分注意力头,Megatron SP切分序列维度:
# 组合配置
parallel_config = {
"tensor_model_parallel_size": 4, # TP=4
"pipeline_model_parallel_size": 2, # PP=2
"sequence_parallel_size": 4, # SP=4 (Megatron方式)
"ulysses_parallel_size": 2, # UP=2 (Ulysses方式)
}
# 总GPU数 = 4 × 2 × 4 × 2 = 64
Ring Attention与序列并行
Ring Attention(之前文章介绍过)也是一种序列并行方案,但它通过环形通信避免实例化完整注意力矩阵。
Ring Attention + SP
将Ring Attention与Megatron SP结合:
- 序列切分:SP将序列切分到多个GPU(Megatron方式)
- 环形通信:Ring Attention在切分后的序列上做环形注意力
这种组合可以将序列长度扩展到极长(1M+ tokens)。
2026年序列并行进展
混合序列并行
根据模型不同层的特点,动态选择Ulysses或Megatron SP:
- 浅层:使用Megatron SP(LayerNorm占比大)
- 深层:使用Ulysses(注意力头数多)
动态序列并行
根据当前序列长度动态调整是否启用SP:
def dynamic_sp(model, input_ids, seq_parallel_threshold=8192):
"""动态序列并行"""
seq_len = input_ids.shape[1]
if seq_len > seq_parallel_threshold:
# 长序列:启用序列并行
return model.forward_sp(input_ids)
else:
# 短序列:标准计算(避免通信开销)
return model.forward(input_ids)
序列并行 + MoE
在MoE模型中,序列并行切分序列后,专家路由需要在全局序列上操作。这需要额外的All-to-All通信来同步路由决策。
实践建议
- 启用条件:序列长度 > 8K 时考虑启用SP
- 与TP/PP配合:SP通常作为第三维并行(在TP和PP之外)
- 通信优化:使用NCCL的All-Gather和Reduce-Scatter,确保通信-计算重叠
- 梯度检查点:SP下梯度检查点仍然有效,但需要注意检查点保存的是本地序列部分
结语
序列并行是超长序列训练的关键技术。DeepSpeed-Ulysses和Megatron SP提供了两种不同的切分思路,两者可以组合使用以发挥最大效用。随着上下文长度从32K向1M+迈进,序列并行将成为大模型训练的标配。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。