作者:昇腾实战派

前言

在大规模模型的推理过程中,通信效率成为性能瓶颈之一。本文面向 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 之后紧跟的算子(如 LayerNormo_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 维。

结论

  1. ReduceScatter + AllGatherAllReduce 数学等价 —— 切分和拼接维度一致时,最终完整张量相同。
  2. 把 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.pySequenceRowParallelOp.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_fusionascend_forward_context.py 设置(默认 tp_world_size <= 8)。未命中融合条件时会自动退化为 reduce_scatter,具体踩坑点见 3.4 节。

AllGather 补齐点分布在三处:

  1. 列并行线性层入口 —— SequenceColumnParallelOp.apply_impl() 在真正 matmul 之前调用:
   torch.ops.vllm.maybe_all_gather_and_maybe_unpad(input_, label=need_all_gather)
  1. MLA 路径 —— vllm_ascend/attention/mla_v1.py 中的 _mla_preprocess() 在拿到 q_ckv_no_split 后调用:
   torch.ops.vllm.maybe_all_gather_and_maybe_unpad(...)
  1. Runner 收尾 —— vllm_ascend/worker/model_runner_v1.py 在模型 forward 结束后,若 flash_comm_v1_enabled 为真,会调用:
   self._all_gather_hidden_states_and_aux(...)

启用入口:VLLM_ASCEND_ENABLE_FLASHCOMM1=1ascend_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.pynum_tokens > 1000 才启用,短 prompt / 小 batch 场景会静默不生效
  • 融合路径有限制SequenceRowParallelOp.matmul_and_reduce() 命中 npu_mm_reduce_scatter_base 的前提是 mmrs_fusion=True(默认 tp_world_size <= 8)且 quant_method 属于 UnquantizedLinearMethodAscendW8A8LinearMethodTP > 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 的小组去算?

步骤:

  1. 重排输入:按目标 rank 对 token 小块重新排列;
  2. all_to_all 搬运:把输入送到更适合 o_proj 的 rank 上;
  3. 小组计算:在新的 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=4otp_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 数学等价性

前提

  1. batch 重组只改排列顺序,不改变数值;
  2. all_to_all 只改变“数据所在的 rank”,不改变“计算什么”;
  3. o_proj 的权重和计算公式没有变化;
  4. 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_sizeotp_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 做“重组”。先判断模型形态与部署约束,再按场景层层叠加

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐