← 返回首页

算子与 vLLM 参数:一条链讲透

启动命令里的每个参数,最终都落到 device 上一串具体的 kernel。这篇用 GLM-5.1 的真实启动参数 + 真实 op_statistic.csv,讲清"参数 → 框架执行路径 → 算子 → profiling"这条链,以及怎么反向从算子排行反推配置。

vLLM 源码 · 2026-10-16 发布 · 参数→算子链路实战笔记

一句话总结:启动命令里的每个参数,最终都会变成 NPU 上一串具体的 kernel。读懂 参数 → 框架路径 → 算子 → profiling 这条链,就能正向预测一个参数会动哪些算子,也能反向从算子排行反推它跑的是什么配置。
新手提示:本文出现的 op_statistic.csv 是算子级的聚合统计(每个算子一共调用了多少次、耗时多少),kernel_details.csv 是每一次调用的明细。阅读时可以按"先看第 1 节的四种影响方式 → 再看第 3、4 节的例子 → 最后看第 5 节的反推"这个顺序,跳过第 2 节的大表也不影响理解。

0. 三层视角:参数、路径、算子

同一次推理可以从三层看:配置层(启动敲进的参数)、框架层(vllm / vllm-ascend 据此组织的执行路径)、算子层(NPU 上真正跑的 kernel)。profiling(op_statistic.csv / kernel_details.csv)就是算子层的观测窗口。

配置层 · 敲进的参数 --quantization ascend · enable_fused_mc2 --enable-chunked-prefill · mtp ... 框架层 · 执行路径 模型的 forward 组织:走哪条分支、 调哪些算子、batch/shape 怎么切 算子层 · device kernel DispatchFFNCombine · QuantBatchMatmulV3 SparseFlashAttention · allgather ... profiling 观测窗口 op_statistic.csv(聚合) kernel_details.csv(逐次) 实线:参数落到算子的主链(配置→框架→算子→profiling);虚线:框架路径在算子层与 profiling 里的投影。改参数 = 改框架路径 = 改算子清单。
图 1 · 三层视角:参数改的是框架路径,profiling 看到的是路径的最终投影
为什么要打通这条链

1. 参数影响算子的四种方式

所有参数对算子的影响,归纳起来就四种,每种都能在这次的真实数据里找到例子。先记住这四个分类,后面所有排查都会用到它。

方式含义真实例子(GLM-5.1)
① 换实现同一份计算逻辑,换不同 kernel(甚至融合成一个大算子)enable_fused_mc2=1:MoE 的 dispatch+FFN+combine 融合成 DispatchFFNCombine 一个 kernel(占 40.5%);关掉则变成 MoeDistributeDispatchV2 + 专家 MatMul + CombineV2 三个
② 换 shapekernel 名不变,但调用的 shape / 次数变了--enable-chunked-prefill + CP 切片:3.5K prompt 的 MoE 调用 shape 是 220~222×6144 的 chunk,不是整条 3.5K;num_speculative_tokens=3:decode 每 step 1~4 个 token(shape [1~4,6144])
③ 增删算子开了新路径,出现原本没有的 kernelenable_dsa_cp(DSA 稀疏注意力)→ 出现 SparseFlashAttention(9.3%)+ Indexer 相关小 kernel(IndexCheck 等);--quantization ascend → 出现 QuantBatchMatmulV3(10.9%)+ AscendQuantV2(2.6%)
④ 改执行方式kernel 清单不变,改的是怎么调度执行cudagraph_mode FULL_DECODE_ONLY:decode 走图模式,kernel 名一个不变,变的是 host 侧 launch 方式(间隔更密、CPU 开销小);--async-scheduling、multi stream 同理
推论排查"改了参数为什么没效果"时先归类:如果是 ①③ 类参数,op_statistic.csv 里必须能看到 kernel 清单变化——看不到就是参数没生效(没被解析、名字不匹配、走了回退分支);如果是 ② 类,要去看 kernel_details 的 Input Shapes;如果是 ④ 类,kernel 层面根本看不出差别,得看 host 侧(api_statistic)或端侧指标。

2. 实测对照:GLM-5.1 参数 → 算子清单

先把我们环境的 op_statistic.csv(1009 prefill 窗口,15 秒)摆出来,再做逐行归因。看不懂的可以只扫一眼第一列和最后一列,主要看谁占大头。

OP Type              Core Type       Count  Total(ms)  Avg(us)  Ratio(%)
DispatchFFNCombine   MIX_AIC        12007     4574.4    380.9    40.5   ← enable_fused_mc2
QuantBatchMatmulV3   MIX_AIC        54036     1378.4     25.5    12.2   ← --quantization ascend (W8A8)
SparseFlashAttention MIX_AIC        12469     1284.8    103.0    11.4   ← enable_dsa_cp (DSA)
AivKernel            (vector聚合)   78382     1174.7     15.0     8.5   ← 各类 vector 小算子聚合名
TransposeBatchMatMul AI_CORE        24939      549.6     22.0     4.9   ← 注意力里的矩阵变换
allgatherAicpuKernel AI_CPU           249      467.4   1877.2     4.1   ← TP16 / SP 通信
MatMulV2             AI_CORE        17212      407.5     23.7     3.6   ← 未融合的普通矩阵乘
AscendQuantV2        AI_VECTOR_CORE 41104      356.1      8.7     3.2   ← W8A8 的激活量化
ForeachAddList       AI_VECTOR_CORE 12008      213.6     17.8     1.9   ← MoE 残差加(见 4.2)
reduce_scatterAicpu  AI_CPU           171      172.4   1008.1     1.5   ← TP16 / SP 通信
启动参数落到的算子本窗口占比
enable_fused_mc2: 1(additional_config)DispatchFFNCombine(MC2 融合:dispatch + 专家 FFN + combine)40.5%
--quantization ascend(W8A8C8 权重)QuantBatchMatmulV3(A8×W8 矩阵乘)+ AscendQuantV2(激活量化)+ 名字里带 WeightNz(NZ 权重格式)12.2% + 3.2%
enable_dsa_cp: true(DSA 稀疏注意力 + CP)SparseFlashAttention + Indexer 系列(IndexCheck 等小 kernel,未进 top10)11.4%
--tensor-parallel-size 16 + SP / --enable-expert-parallelallgather / reduce_scatter AicpuKernel(EP 的 all-to-all 在 communication.json 里看)5.6%
--enable-chunked-prefill + CP 切片不新增算子,决定 FFNCombine / attention 的 Input Shapes(220~222×6144)—(②换 shape)
--speculative-config deepseek_mtp num=3decode 每 step 1+3 个 token 并行验证 → shape [1~4,6144];本窗口 FFNCombine decode 形状调用 11622 次—(②换 shape)
--compilation-config cudagraph_mode FULL_DECODE_ONLYkernel 清单不变,decode 的 host launch 走图模式—(④改执行)

一个细节:kernel 名里的前缀也有信息量——aclnn 开头是经 AscendC 算子库下发的标准算子,无前缀的(DispatchFFNCombine、SparseFlashAttention)是自定义融合算子包里的,对应 CANN 自定义算子包(我们对比 staticKey 的那几个文件)。

3. ① 换实现的实例:enable_fused_mc2 开关前后

这是最能说明"参数 = 算子清单开关"的例子。MoE 一层 FFN 前后有两套实现:拆开的 3 个算子,或者融合成的 1 个大算子。

MoE 层 FFN 段:同一份计算,两种算子清单 enable_fused_mc2 = 0(拆分) MoeDistribute DispatchV2(通信) 专家 FFN MatMul/GMM(计算) MoeCombineV2(通信) 3 个 kernel · 2 次算子间往返 · 通信算子单独计时 enable_fused_mc2 = 1(融合,我们的配置) DispatchFFNCombine MIX_AIC · dispatch+FFN+combine 一体 1 个 kernel · 占窗口总耗时 40.5%(实测) 代价:黑盒大算子——它慢了没法在框架层再拆优化,只能找算子团队(我们这次 TTFT 劣化正是它的大 chunk 路径回退)。
图 2 · 融合开关直接改写 op_statistic 的第一行:从三个中算子变成一个 40.5% 的大算子

4. ② 换 shape 的实例:shape 里藏着配置信息

4.1 prefill 的 220~222 是怎么来的

压测输入 3.5K token,经 CP=16 切片(每卡约 219 token)+ chunked prefill 按块组 batch 后,落到每卡单次 MoE 调用就是 [220~222, 6144] 的 chunk。所以看到 FFNCombine 的 Input Shapes 首维在 220 附近,就能反推:输入约 3.5K、CP=16、chunked prefill 开着。反过来,decode 的 shape 首维 = 1 + draft 数:mtp num=3 时每 step 最多 4 个 token(1 个真实 + 3 个 draft 并行验证),窗口里看到的是 [1~3, 6144]。

4.2 用"调用次数对齐"认亲:谁跟着谁走

kernel 之间的次数关系能暴露它们的归属路径,这次窗口里有两对精准对齐:

对齐关系说明什么
ForeachAddList 12008 次 ≈ DispatchFFNCombine 12007 次每次 MoE 调用后跟一次残差/列表加法——ForeachAddList 是 MoE 路径的随从,FFNCombine 走融合路径,它就同步存在
0815 窗口:ScatterNdUpdate 10611 次 == SparseFlashAttention 10611 次每次注意力计算后一次 KV cache 散射写入——KV 写入跟着 attention 走,次数一致说明每层每 chunk 写一次

方法:把候选 kernel 的 Count 列放一起比,成对出现的就在同一条执行路径上。版本升级后某个 kernel 从 top10 消失(如 1009 窗口 ScatterNdUpdate 不在了),先查它是不是被融合/改名,再看调用次数去哪了——不要直接当成"优化掉了"。

5. 反向:从 op_statistic.csv 反推配置

拿到一份陌生环境的 profile,按下面的三步决策树走:先认头部 kernel 认模型和路径,再看 shape 认输入和阶段,最后看通信算子认并行布局。

top kernel 是什么? DispatchFFNCombine / MoeDistributeDispatch / Combine → MoE 模型,fused_mc2 开/关 QuantBatchMatmulV3 (+WeightNz / AscendQuant) → 量化 W8A8(+NZ 权重) SparseFlashAttention + Indexer 系列 → DSA 稀疏注意力路径 第二步看 kernel_details 的 Input Shapes:首维 220 至 222 → prefill 大 chunk(3.5K 输入 + CP16 + chunked prefill); 首维 1 至 4 → decode(MTP num=3 → 最多 4)。shape 分布还能反映窗口里 prefill/decode 的比例 第三步查通信算子占比:allgather / reduce_scatter / all_to_all 高 → 并行度大或通信没被计算盖住 (我们 0815→1009:reduce_scatter 1489→172ms,是通信优化的痕迹,不是负载变了) 口诀:先认头部 kernel 认模型和路径,再看 shape 认输入和阶段,最后看通信算子认并行布局
图 3 · 三步反推:认 kernel → 看 shape → 查通信
练习(可直接拿我们文件验证)打开 /home/ywx1279670/profile/1010_cc8_prefill2/**/op_statistic.csv:第一行 DispatchFFNCombine 40.5% → MoE + fused_mc2;第二行 QuantBatchMatmulV3 + 第三行 SparseFlashAttention → W8A8 量化 + DSA;去 kernel_details 按 Input Shapes 分组,首维 220~222 和 1~3 两簇 → prefill 窗口里混着 decode 调用。三个结论全是配置层事实,没用任何额外信息。

6. 验证"参数真的生效":三板斧

改完参数不确定有没有生效?按成本从低到高,依次用这三招验证:

手段看什么本例
① 服务日志回显启动日志 non-default args 段有没有新参数(最快,零成本)enable_prefill_mc2: True 回显确认配置进去了
② kernel 清单 diff开关前后各采 15s,diff op_statistic 的 OP Type 清单与占比(①③ 类参数必变;② 类看 shape;④ 类不变)fused_mc2 关/开 → DispatchFFNCombine 行消失/出现;enable_prefill_mc2 开后 TTFT 753-768 持平 → 生效但无收益
③ 端侧指标交叉TTFT / TPOT 方向与算子变化自洽FFNCombine 大 chunk +8% ↔ TTFT 差 15-20%(它占 40.5%,量级吻合)

注意 ② 的坑:有的开关生效后 kernel 名不变(改 shape/调度),diff 清单会一无所获——先判断参数属于第 1 节的哪一类,再决定看哪张表。

7. 常见疑问

姊妹篇:《Profiling 实战解读:从采集、读数到把变慢钉到单个算子》——本篇引用的 op_statistic.csv 怎么采、怎么逐行读、怎么归因,姊妹篇逐步拆过。

注:本篇所有 kernel 名、次数、占比均来自 GLM-5.1(TP16×DP2、EP32、W8A8C8、MTP=3、DSA-CP)2026-10 排查时 1009 prefill 窗口 op_statistic.csv 真实数据;0815 对照数据来自 1010_cc8_0815_prefill。不同模型/配置的算子清单会有差异,映射关系(哪个参数出哪个 kernel)以实际 profile 为准。

↑ 回到顶部
华杉的技术博客 · 苏ICP备2026063680号-1
首页