vllm-ascend 通信优化:SP/FlashComm1/FlashComm2
在大规模模型的推理过程中,通信效率成为性能瓶颈之一。本文面向 vLLM + 昇腾 NPU 场景下的推理工程师、性能优化人员与运维人员,系统梳理了 `vllm-ascend` 中的三套递进式通信优化方案:**SP(Sequence Parallelism)**、**FlashComm1(FC1)** 与 **FlashComm2(FC2)**。本文将详细介绍这些方案的设计思路、数学等价性、代码实现以
作者:昇腾实战派
前言
在大规模模型的推理过程中,通信效率成为性能瓶颈之一。本文面向 vLLM + 昇腾 NPU 场景下的推理工程师、性能优化人员与运维人员,系统梳理了 vllm-ascend 中的三套递进式通信优化方案:SP(Sequence Parallelism)、FlashComm1(FC1) 与 FlashComm2(FC2)。本文将详细介绍这些方案的设计思路、数学等价性、代码实现以及各自的适用场景,帮助读者在实际业务中做出合理的选型决策。
名词表
| 缩写 | 全称 | 含义 |
|---|---|---|
| TP | Tensor Parallelism | 张量并行 |
| SP | Sequence Parallelism | 序列并行 |
| FC1 / FC2 | FlashComm v1 / v2 | 昇腾侧通信优化方案 |
| AR / RS / AG | AllReduce / ReduceScatter / AllGather | 集合通信原语 |
| MLA | Multi-head Latent Attention | DeepSeek 系列采用的注意力变体 |
| MoE | Mixture of Experts | 混合专家模型 |
| OTP / ODP | o_proj TP / o_proj Data Parallel | FC2 特有的通信子组 |
| VL | Vision-Language | 多模态模型 |
| Pass | Graph Pass | 图编译阶段的改图 Pass |
1. 背景:为什么需要 SP / FlashComm
在 TP 张量并行下,每一层通常需要进行一次 AllReduce 以汇总各卡上的局部结果。随着模型参数量和序列长度的增加,这一步逐渐成为性能瓶颈:
- 通信量大:完整 hidden state 在 TP 组内全量同步,带宽成本线性增长。
- 时机过早:
AllReduce之后紧跟的算子(如LayerNorm、o_proj)未必需要“完整结果”,却被迫等待通信完成。 - 无法与计算融合:
AllReduce作为一个独立算子,难以与前后 matmul 形成流水线。
SP / FC1 / FC2 沿着同一条思路迭代优化:
将
AllReduce拆分为ReduceScatter + AllGather,然后尽量将AllGather往后推,甚至省略。
- SP 解决“拆”的问题(基础版);
- FC1 解决“推”和“融”的问题(增强版);
- FC2 针对
o_proj这一具体路径,重新编排参与通信的 rank(专项版)。
2. SP:基础版通信图重排
2.1 核心思路
经典 TP 路径:
AllReduce → LayerNorm
SP 改写为:
ReduceScatter → LayerNorm → AllGather
每张卡只处理自己负责的 token 分片,带来以下收益:
- 分片状态下可先进行部分计算,缩短等待时间;
- 某些场景下(如量化)等效通信量更小;
- 为后续
RS + MM融合等更深优化留出空间。
2.2 数学等价性
前提:AllReduce 在实现上本就是 ReduceScatter + AllGather 的组合形式;LayerNorm / RMSNorm 是逐 token 算子,每个 token 的归一化只依赖自身 hidden 维。
结论:
ReduceScatter + AllGather与AllReduce数学等价 —— 切分和拼接维度一致时,最终完整张量相同。- 把 token 分到不同卡上单独做
LayerNorm,与先汇总再逐 token 计算等价。
注意:由于浮点累加顺序的差异,SP 与原始路径的数值会存在极小偏差。在训推一致场景中需评估该偏差是否在可接受范围内。
2.3 代码逻辑
SP 在 vllm-ascend 中主要通过 Graph Pass 改写计算图完成(对应 enable_sp_by_pass,仅图模式生效)。以下以 Pass 路径为准;其他 runtime 分支的差异见 2.4 节。
原始 pattern:
x = self._all_reduce(input)
result, _, residual = torch.ops._C_ascend.npu_add_rms_norm_bias(x, residual, weight, None, self.eps)
替换后的 replacement:
reduce_scatter = self._reduce_scatter(input)
residual = torch.ops.vllm.maybe_chunk_residual(reduce_scatter, residual)
result, _, residual = torch.ops._C_ascend.npu_add_rms_norm_bias(reduce_scatter, residual, weight, None, self.eps)
all_gather = self._all_gather(result)
对于 Qwen3-VL 这类带 deepstack_input_embeds 的中间层,改写方式类似:
原始图:
all_reduce(hidden_states) → add(deepstack_input_embeds) → layernorm
SP Pass 改写后:
reduce_scatter(hidden_states) → add(chunk(deepstack_input_embeds)[tp_rank]) → layernorm → all_gather
调用链大致为:
启用 pass_config.enable_sp
→ GraphFusionPassManager.configure()
→ 将 SequenceParallelismPass / SequenceParallelismMoePass 追加到 passes 列表
→ SequenceParallelismPass.is_applicable_for_range()(按 token 数判定是否应用)
→ SequenceParallelismPass.__call__()
→ self.patterns.apply(graph)
→ 将图中的 all_reduce pattern 替换为 reduce_scatter + 逐 token 算子 + all_gather
→ graph capture / replay 阶段执行改写后的图
2.4 适用场景与限制
最佳匹配场景:VL 模型 + ACL Graph + 非量化路径。
限制与踩坑点:
- Pass 路径仅在图模式生效;
enable_sp()还会聚合enable_shared_expert_dp等 runtime 信号,部分 SP 行为在 eager 模式下也会激活 —— 不要把 “SP” 与 “图模式” 简单等号。 - 不支持量化路径(量化场景建议直接用 FC1)。
- token 数需能被
tp_size整除,由model_runner_v1._pad_for_sequence_parallelism()做 padding 对齐。 - token 数阈值:dense 模型
SP_MIN_TOKEN_NUM_DEFAULT = 1000,MoE 模型为 1。低于阈值时 Pass 不改写,短 prompt / 小 batch 场景静默失效。 - 浮点累加序差异:训推一致场景需评估。
3. FlashComm1:增强版 SP
FC1 是 SP 的升级形态 —— 在“拆”的基础上进一步将通信往后推,并叠加 NPU 定制融合 kernel(如 torch_npu.npu_mm_reduce_scatter_base):
SP = 通信图重排
FC1 = 通信图重排 + NPU 定制融合 + 更晚的通信时机
3.1 核心思路
将通信尽可能往后推。具体做法:
- MLA 模型:将
AllGather延后到QKV projection之后; - MoE 模型:将
AllGather延后到Gating + DynamicQuant之后 —— gating 筛选路由的 token,DynamicQuant 压缩 hidden state 位宽,延后通信即降低带宽压力(走SequenceParallelismMoePass)。
直觉很简单:如果后续某一步本就会把张量压小或筛掉一部分数据,就没必要太早把完整张量准备好。
3.2 数学等价性
前提:FC1 往后推的算子通常满足以下三点:
- 逐 token 独立;
- 不会把不同 token 混合计算;
- 多为线性投影、gating、量化前处理等可以在 shard 上先做的操作。
结论:在上述前提下,将这些算子前移至分片阶段,与原路径数学等价。
3.3 代码逻辑
关键落点一:vllm_ascend/ops/register_custom_ops.py
# 不开 FC1
_maybe_pad_and_reduce_impl(x)
→ tensor_model_parallel_all_reduce(x)
# 开启 FC1
_maybe_pad_and_reduce_impl(x)
→ F.pad(x)
→ tensor_model_parallel_reduce_scatter(x, 0)
# 后续 gather
_maybe_all_gather_and_maybe_unpad_impl(x)
→ tensor_model_parallel_all_gather(x, 0)
→ unpad
关键落点二:vllm_ascend/ops/linear_op.py 的 SequenceRowParallelOp.matmul_and_reduce()
# 不开 FC1
output_parallel = self.layer.quant_method.apply(...)
return tensor_model_parallel_all_reduce(output_parallel)
# 开启 FC1
# 融合路径:需同时满足 mmrs_fusion=True 且 quant_method 属于
# UnquantizedLinearMethod 或 AscendW8A8LinearMethod
return torch_npu.npu_mm_reduce_scatter_base(...)
# 其余情况:退化为 reduce_scatter
output_parallel = self.layer.quant_method.apply(...)
return tensor_model_parallel_reduce_scatter(output_parallel, 0)
mmrs_fusion 由 ascend_forward_context.py 设置(默认 tp_world_size <= 8)。未命中融合条件时会自动退化为 reduce_scatter,具体踩坑点见 3.4 节。
AllGather 补齐点分布在三处:
- 列并行线性层入口 ——
SequenceColumnParallelOp.apply_impl()在真正 matmul 之前调用:
torch.ops.vllm.maybe_all_gather_and_maybe_unpad(input_, label=need_all_gather)
- MLA 路径 ——
vllm_ascend/attention/mla_v1.py中的_mla_preprocess()在拿到q_c和kv_no_split后调用:
torch.ops.vllm.maybe_all_gather_and_maybe_unpad(...)
- Runner 收尾 ——
vllm_ascend/worker/model_runner_v1.py在模型 forward 结束后,若flash_comm_v1_enabled为真,会调用:
self._all_gather_hidden_states_and_aux(...)
启用入口:VLLM_ASCEND_ENABLE_FLASHCOMM1=1 → ascend_forward_context 计算 flash_comm_v1_enabled / pad_size → 线性层与 MLA 路径进入上述落点。
3.4 适用场景与限制
适用场景:
non-VL模型;- dense / MoE 均有较强支持;
- 与量化路径兼容性较好。
| 方案 | VL + Dense | VL + MoE | non-VL + Dense | non-VL + MoE |
|---|---|---|---|---|
| SP | graph | graph | ✗ | ✗ |
| FC1 | ✗ | ✗ | eager / graph | eager / graph |
限制与踩坑点:
- 对 MoE 模型需要走
SequenceParallelismMoePass,部分新模型需手动确认 Pass 覆盖。 - 存在 token 数阈值:
ascend_forward_context.py中num_tokens > 1000才启用,短 prompt / 小 batch 场景会静默不生效。 - 融合路径有限制:
SequenceRowParallelOp.matmul_and_reduce()命中npu_mm_reduce_scatter_base的前提是mmrs_fusion=True(默认tp_world_size <= 8)且 quant_method 属于UnquantizedLinearMethod或AscendW8A8LinearMethod。TP > 8 或使用 W4A8 等其他量化方式时将自动退化为非融合reduce_scatter。排查性能退化时优先检查此处。 - VL 模型支持仍在推进中,以实际版本支持列表为准。
4. FlashComm2:专项化的通信重组方案
FC2 不再是通用的图重排,而是针对 o_proj 这条具体路径做的专项优化。
4.1 背景:o_proj 为何值得单独优化
典型 attention 链路:
hidden_states
→ q_proj / k_proj / v_proj
→ attention(q, k, v)
→ attn_output
→ o_proj
→ 输出回主干
在大模型、长序列、Prefill 场景下,o_proj 往往是 attention 链路中最昂贵的一层:token 数多、hidden size 大,既有 matmul 又有跨卡通信。FC2 正是为压榨这一环的性能而设计。
4.2 核心思路
能不能先把
o_proj的输入重新编队,再用更适合o_proj的小组去算?
步骤:
- 重排输入:按目标 rank 对 token 小块重新排列;
all_to_all搬运:把输入送到更适合o_proj的 rank 上;- 小组计算:在新的
OTP小组内做o_proj。
4.3 OTP & ODP:两个特有通信组
FC2 为此引入了两个新的通信子组:
flashcomm2_otp(o_proj TP):真正一起计算o_proj的小组 —— “算账”;flashcomm2_odp(o_proj DP):把数据重排并送达OTP的通信组 —— “搬货”。
4.4 ODP + all_to_all 的数据重排
Flashcomm2OProjRowParallelOp 的输入输出形状:
Input shape = [N, H / global_tp]
Output shape = [N / (global_tp / otp_size), H]
经过 all_to_all 后,单卡看到的输入形状变为:
[N / odp_size, H / otp_size]
当 otp_size < global_tp_size 时:token 数减少、特征维度变宽。
一个具体例子:假设 global_tp=4、otp_size=2,初始状态:
- rank0 持有特征片 A,形状
[N, H/4] - rank1 持有特征片 B
- rank2 持有特征片 C
- rank3 持有特征片 D
将 token 均分为 4 段 T0 T1 T2 T3,则:
- rank0:
[T0A, T1A, T2A, T3A] - rank1:
[T0B, T1B, T2B, T3B]
Step 1 本地重排
get_flashcomm2_reorgnized_batch_ids() 在 otp_size=2 时给出 [[0, 2], [1, 3]]。rank0 将本地张量重排为两包:
发给目标 0: [T0A, T2A]
发给目标 1: [T1A, T3A]
rank1 同理。
Step 2 ODP 组内 all_to_all
假设一个 ODP 组是 [0, 1],交换后:
rank0: [T0A, T2A, T0B, T2B]
rank1: [T1A, T3A, T1B, T3B]
Step 3 拼接特征维
rank0 将 [T0A, T2A, T0B, T2B] 整理为 [T0AB, T2AB]:
- token 只剩
T0, T2→ token 数变为N/2; - 每个 token 的特征由 A、B 拼接 → 宽度变为
H/2。
最终:
rank0: [N/2, H/2] # [T0AB, T2AB]
rank1: [N/2, H/2] # [T1AB, T3AB]
另一个 ODP 组 [2, 3] 对称地得到 rank2: [T0CD, T2CD]、rank3: [T1CD, T3CD]。
4.5 收益来源
1)单卡 GEMM 的 K 轴更大
原始全局 TP 切法:
[N, H/G] @ [H/G, H]
FC2 重排后:
[N/(G/P), H/P] @ [H/P, H]
若 P < G,单卡 matmul 的输入宽度从 H/G 增大到 H/P,GEMM 计算密度更高。
2)ReduceScatter 参与者更少
FC2 中真正做 o_proj 规约的是 OTP 组而非全局 TP 组,参与集合通信的 rank 变少、链路更短。
3)可与 FC1 叠加省掉尾部 AllGather
单独启用 FC2 时 o_proj 后仍需 AllGather;与 FC1 叠加后这一步可以后移或直接省掉。
4.6 数学等价性
前提:
- batch 重组只改排列顺序,不改变数值;
all_to_all只改变“数据所在的 rank”,不改变“计算什么”;o_proj的权重和计算公式没有变化;ReduceScatter / AllGather是布局转换,不影响最终数学结果。
结论:FC2 与原始 TP 路径在数学上等价(同样存在浮点累加序带来的极小偏差)。
4.7 代码逻辑
VLLM_ASCEND_FLASHCOMM2_PARALLEL_SIZE=<size>
→ flashcomm2_enable() / get_flashcomm2_config_and_validate()
→ 初始化 flashcomm2_otp / flashcomm2_odp 并行组
→ 线性层替换为 Flashcomm2OProjRowParallelOp / Flashcomm2OshardQKVParallelOp
→ 按 reorganized_batch_ids 重排(原布局 [N, H/global_tp])
→ ODP 组内 all_to_all → reshape 为 [N/odp_size, H/otp_size]
→ OTP 组内 matmul + reduce_scatter
→ 若未开启 FC1,则全局 all_gather 收尾
4.8 适用场景与限制
推荐场景:Prefill bound —— 长序列、大模型、o_proj 占比高。
硬限制(来自 get_flashcomm2_config_and_validate()):
- 不支持 D 角色(Decode-only / kv_consumer)部署:
kv_transfer_config.is_kv_consumer=True时会直接AssertionError启动失败。 - PD 混部需谨慎:
kv_transfer_config is None时仅会给出 warning,但官方注明“may lead to decode performance degradation”。 - 不能与
finegrained_tp_config.oproj_tensor_parallel_size同时启用,两者互斥。 global_tp_size必须 严格大于 且能被flashcomm2_oproj_tensor_parallel_size整除。
踩坑点:
- 强烈建议与 FC1 同时启用:
get_flashcomm2_config_and_validate()会对未启用 FC1 的情况主动告警 —— “It is recommended to enable FLASHCOMM1 simultaneously when starting FLASHCOMM2 for optimal performance”。 - 对 batch / token 数的整除性要求更严(需同时满足
odp_size、otp_size,以及chunk_num整除条件)。 - 与 FC1 叠加时,
AllGather的具体落点依赖flash_comm_v1_enabled,调试时需关注 forward context 的取值。
5. 横评
| 维度 | SP(基础版) | FlashComm1(增强版) | FlashComm2(专项版) |
|---|---|---|---|
| 主要目标 | AR → RS + AG,拆分通信 | 通信后推 + 定制融合 | 针对 o_proj 通信重组 |
| 实现形态 | graph pass(仅图模式) | runtime + custom op + fused kernel(eager/graph 皆可) | 专用线性层 + 专用并行组 |
| 典型场景 | VL、结构特殊模型 | non-VL 主路线;部分 VL+MLA | Prefill、大模型、o_proj 路径 |
| 量化支持 | 不支持 | 支持较好 | 常与量化 / 层分片一起使用 |
| 方案叠加 | 独立使用 | 与 SP 互斥 | 推荐与 FC1 叠加 |
| 维护成本 | 中(Pass 随模型适配) | 中高(custom op × 量化) | 高(新通信组 + shape 管理) |
6. 使能方法
6.1 SP(VL Pass 路线)
vllm serve Qwen/Qwen3-VL-2B-Instruct \
--tensor-parallel-size 2 \
--compilation-config '{"pass_config":{"enable_sp":true}}'
6.2 FC1
export VLLM_ASCEND_ENABLE_FLASHCOMM1=1
vllm serve <your-model> \
--tensor-parallel-size 2
6.3 FC2
推荐配合 FC1 同时启用(否则会触发告警,且性能不理想):
export VLLM_ASCEND_ENABLE_FLASHCOMM1=1
export VLLM_ASCEND_FLASHCOMM2_PARALLEL_SIZE=2
vllm serve <your-model> \
--tensor-parallel-size 8 \
--additional-config '{"layer_sharding":["o_proj"]}'
详细约束与硬限制见 4.8 节。layer_sharding 可选(仅支持 ["o_proj"]),启用后降低显存占用。
7. 模型支持列表
以下为经实际验证可用的型号,不保证穷尽所有组合。
7.1 SP
| 模型系列 | 具体型号 |
|---|---|
| Qwen3-VL | Qwen3-VL 系列(含 Qwen3-VL-2B-Instruct 等) |
7.2 FlashComm1
| 模型系列 | 具体型号 |
|---|---|
| DeepSeek | DeepSeek-V3.1、DeepSeek-V3.2 |
| Qwen3 | Qwen3-Next-80B-A3B-Instruct、Qwen3-235B-A22B、Qwen3-Dense、Qwen3-VL-235B-A22B-Instruct |
| GLM | GLM4.x、GLM5、GLM-4.7 |
| Kimi | Kimi-K2.5 |
| MiniMax | MiniMax-M2.5 |
Qwen3.5 需等待 vllm-ascend#8004 合入。
7.3 FlashComm2
| 模型系列 | 具体型号 |
|---|---|
| DeepSeek | DeepSeek-V3 / R1 |
| Qwen3 | Qwen3 MoE |
8. 选型建议
| 场景 | 推荐 |
|---|---|
| VL 模型 | SP |
| non-VL dense / MoE | FC1 |
| 长序列 / Prefill 瓶颈 | FC1 + FC2 |
| 量化路径 | FC1 |
三套方案递进而非互斥:SP 奠定“拆”,FC1 在此之上“推 + 融”,FC2 针对 o_proj 做“重组”。先判断模型形态与部署约束,再按场景层层叠加。
更多推荐

所有评论(0)