昇腾 NPU 图模式推理优化实战:cann-recipes-infer 中 npugraph_ex 与 GE 图模式的适配指南
【免费下载链接】cann-recipes-infer本项目针对LLM与多模态模型推理业务中的典型模型、加速算法,提供基于CANN平台的优化样例项目地址: https://gitcode.com/cann/cann-recipes-infer
本文基于 cann-recipes-infer 开源仓库的图模式适配技能文档(.agents/skills/model-infer-graph-mode/SKILL.md)及其三份参考指南,系统讲解如何在昇腾 NPU 上通过
torch.compile把 LLM 推理模型(重点是 Decode 阶段)适配到npugraph_ex(aclgraph 捕获回放)与 GE(Ascend IR)两种图模式,覆盖方案设计、图中断(Graph Break)与重编译修复、FA 融合算子参数配置、编译缓存以及验证测试全流程。读完本文,你将掌握一套可直接落地的图模式适配方法论:知道何时该用哪种模式、模型代码要满足哪些约束、actual_seq_lengths系列参数在不同图模式下应如何组织,并能结合仓库执行框架(executor/utils/graph_utils.py、executor/core/config/inference_config.py)理解底层编译接入与配置校验逻辑。
一、图模式适配的核心原则
在动手改造任何模型之前,先建立以下共识。这些原则来自技能文档的「重要原则」章节,是后续所有操作的前提:
- 前置条件:模型必须已在 NPU 上运行且已导入
torch_npu(导入后才会注册torch.npu设备相关 API)。 - 图模式仅适用于 Decode 阶段:Prefill 阶段输入长度动态变化,不适合图模式,保持 eager 执行。这是全篇最重要的原则。
- 保持模型完整性:不为适配图模式而简化模型逻辑,图模式改造应是对模型结构的适配而非阉割。
- NPU 不支持的后端:
aot_eager、inductor、cudagraphs等后端在昇腾 NPU 上不可用,不要沿用 CUDA 生态的编译路径。 - 固定 tensor 图外预创建:attention_mask、KV cache 等推理全程不变的 tensor 应在图外预创建,需要时用
torch._dynamo.mark_static()标记。 - LLM Decode 重编译注意:
kv_len/actual_seq_lengths_kv每步都会变化,npugraph_ex可能因此触发重编译,遇到hit recompile_limit时参考重编译解决方案排查(见第八章)。 - npugraph_ex decode 用 host list 长度字段:从
ForwardMetaData取actual_seq_lengths_list_kv/q、actual_seq_lengths_cu_list_kv/q(List[int]形态)传给静态图路径;普通 eager / GE 路径仍用对应的 Tensor 字段。这一点在仓库执行引擎中有直接实现,见第十章。 - 图模式验证项:确认 warmup 阶段首次编译功能正常;正式推理 decode 阶段直接复用 warmup 编译的图,不出现重编译(检查日志中是否有
recompile标识)。 - 编译缓存使用时机:先用常规图模式确认图可稳定捕获且无非预期重编译,再按官方指南使用
cache_compile降低冷启动 / 重复编译耗时。 - 精度问题调试:遇到精度问题可调用仓库中
model-infer-precision-debugskill 进行排查。
二、图模式适配工作流程
技能文档给出了一套四步走的标准化流程,特别强调方案设计必须先经用户确认再开发:
- 方案设计
- 分析模型结构:识别模型中可能阻碍图模式的代码模式(如
torch.cat扩展 KV cache、.item()调用、基于 Tensor 值的 Python 分支等); - 识别问题点:找出 Graph Break、重编译、动态 shape 等问题;
- 设计改造方案:明确需要修改哪些文件、修改的具体内容、对现有功能的影响评估;
- 输出设计方案文档:以 Markdown 格式呈现。
- 分析模型结构:识别模型中可能阻碍图模式的代码模式(如
- 方案确认:等待用户确认方案后再进行开发;有修改意见则返回第一步。
- 实施开发:按照确认的设计方案逐步实施代码修改。
- 验证测试:由 Agent 实际执行以下测试并记录真实结果:
- 编译验证:运行
torch.compile,检查是否成功,记录编译日志; - 功能验证:运行模型,对比图模式前后输出;
- 性能验证:记录 Prefill / Decode 阶段耗时;
- 编译缓存验证(可选):图结构稳定后可验证
cache_compile是否降低二次启动编译耗时; - 测试报告:整理测试环境、测试用例、对比数据。
- 编译验证:运行
三、图模式选型:npugraph_ex 与 GE 图模式
在开始适配前先确定使用哪种模式。如果用户未指定,默认采用 npugraph_ex。
| 场景 | 推荐模式 | 详细文档 |
|---|---|---|
| LLM 大语言模型 | npugraph_ex(优先阅读 LLM 指南) | .agents/skills/model-infer-graph-mode/references/llm-model-guide.md |
| 通用模型 | npugraph_ex | .agents/skills/model-infer-graph-mode/references/npugraph_ex-guide.md |
| 需要 GE 图模式 | GE(Ascend IR) | .agents/skills/model-infer-graph-mode/references/ge-graph-guide.md |
两种模式的核心差异如下表:
| 特性 | npugraph_ex 后端 (aclgraph) | GE 图模式 (Ascend IR) |
|---|---|---|
| 启用方式 | backend="npugraph_ex" | torchair.get_npu_backend() |
| 实现原理 | 捕获模式 (Capture & Replay) | FX 图转换为 Ascend IR,GE 引擎编译执行 |
| 成熟度 | 试验特性,暂不支持商用 | 更成熟稳定 |
| PyTorch 版本 | 需要 2.6.0+ | 无特殊要求 |
| 支持场景 | 在线推理 | 通用场景 |
| 类似技术 | torch.cuda.CUDAGraph | 传统图编译 |
| 配置方式 | options={}参数 | CompilerConfig对象 |
npugraph_ex 快速示例
import torch import torch_npu model = YourModel().to("npu") opt_model = torch.compile(model, backend="npugraph_ex", fullgraph=True, dynamic=False) # 注:LLM Decode 场景 actual_seq_lengths 每步变化时需 dynamic=True output = opt_model(input_tensor)关键约束:PyTorch 2.6.0+、仅支持在线推理、不支持随机数算子和动态控制流、forward 中不可使用.item()。
GE 图模式快速示例
import torch import torch_npu import torchair from torchair import patch_for_hcom patch_for_hcom() # 集合通信入图(有 TP/EP 并行时需调用) model = YourModel().to("npu") config = torchair.CompilerConfig() npu_backend = torchair.get_npu_backend(compiler_config=config) opt_model = torch.compile(model, backend=npu_backend) output = opt_model(input_tensor)仓库中的模式接入实现
从源码结构看,仓库执行框架把两种模式的编译入口统一封装在 executor/utils/graph_utils.py 的compile_model_forward()函数中,图编译前有一段通用准备:
import torchair as tng import torchair.ge_concrete_graph.ge_converter.experimental.patch_for_hcom_allreduce tng.patch_for_hcom() torch._dynamo.config.inline_inbuilt_nn_modules = False其中tng.patch_for_hcom()处理集合通信入图(PyTorch 2.6 及之后版本中通常可省略),inline_inbuilt_nn_modules = False避免内建模块被过度内联。随后按exe_mode分派:exe_mode == "npugraph_ex"时走torch.compile(model_forward, dynamic=enable_dynamic_graph, fullgraph=True, backend="npugraph_ex", options=compile_options);否则构建CompilerConfig并设置frozen_parameter、tiling_schedule_optimize、topology_sorting_strategy等实验性配置,再通过tng.get_npu_backend(compiler_config=compiler_config)获取 GE 后端。
执行模式的配置项定义在 executor/core/config/inference_config.py 的ModelConfig中:
| 配置项 | 默认值 | 说明 |
|---|---|---|
exe_mode | "eager" | 执行模式,仅支持eager、ge_graph、npugraph_ex三者之一,非法值会抛ValueError |
enable_cache_compile | False | 是否启用编译缓存 |
enable_static_kernel | False | 是否启用静态 kernel 加速,仅支持exe_mode='npugraph_ex',其他模式会报错 |
enable_dynamic_graph | True | 是否使用动态图编译;ge_graph模式下只支持静态图,该开关会被忽略并告警 |
此外,ModelConfig._validate()还有两个值得注意的副作用:当exe_mode == "npugraph_ex"或平台为 Ascend 950 时,会设置环境变量TASK_QUEUE_ENABLE=1(npugraph_ex 只支持 0 或 1);否则设为2(eager 下优化 host 性能的默认值)。真实配置示例可参考 models/deepseek_v4/config/ci_a3/deepseek_v4_flash_rank_128_128ep_w8a8.yaml,其中exe_mode: "npugraph_ex"、enable_static_kernel: True、enable_dynamic_graph: False。
四、npugraph_ex 后端使用详解
适用场景
- 在线推理场景:追求简单快速适配;
- 熟悉 CUDAGraph 模式:使用习惯类似;
- LLM Decode 阶段:固定 shape 的单 token 输入。
使用约束
| 约束项 | 说明 |
|---|---|
| PyTorch 版本 | 需要 2.6.0 及以上版本 |
| 支持场景 | 在线推理场景,不支持反向流程 capture |
| 随机数算子 | 不支持 capture(randn、dropout 等) |
| 动态控制流 | 不支持,需保证图静态 |
| Stream 同步 | 不支持 stream sync 操作 |
| 成熟度 | 试验特性,暂不支持商用产品 |
options 配置速查
opt_model = torch.compile( model, backend="npugraph_ex", fullgraph=True, options={ # ========== 调试 ========== "force_eager": False, # 强制 eager 模式调试 # ========== FX图优化 ========== "inplace_pass": True, # 原地操作优化 "input_inplace_pass": True, # 输入原地优化 "pattern_fusion_pass": True, # 算子融合 # ========== 内存优化 ========== "reuse_graph_pool_in_same_fx": True, # 图池复用 "clone_input": True, # 克隆输入 "clone_output": False, # 克隆输出 "use_graph_pool": None, # 图池配置 # ========== 性能优化 ========== "static_kernel_compile": False, # 静态Kernel编译 "remove_noop_ops": True, # 移除空操作 "frozen_parameter": False, # 冻结参数 # ========== 捕获控制 ========== "capture_limit": 64, # 重捕获次数限制 } )在仓库的compile_model_forward()中,npugraph_ex 的compile_options至少包含frozen_parameter=True、static_kernel_compile=enable_static_kernel、super_kernel_optimize=enable_superkernel等项,读者可以对照上表理解每个开关的定位。
核心 API
| API | 用途 |
|---|---|
compile_fx() | 自定义 backend |
register_replacement() | 自定义算子融合 |
cache_compile() | 编译缓存 |
limit_core_num() | 限核功能 |
常见问题
- 如何判断是否应该使用 npugraph_ex?适合:LLM decode 阶段、固定 shape 推理、简单快速适配;不适合:需要动态 shape、生产环境稳定性优先、训练场景。
- 报错"不支持 capture"怎么办?检查代码中是否包含随机数算子(randn、dropout)、动态控制流(基于 tensor 值的 if/while)、
.item()调用。 - 性能劣化怎么办?开启重编译日志
torch._logging.set_logs(recompiles=True),检查是否发生重编译,再参考 LLM 模型改造指南排查。
五、GE 图模式使用详解
GE 图模式通过 TorchAir 的CompilerConfig开启,将 FX 图转换为 Ascend IR 图,并通过 GE 图引擎实现图编译和执行。它更适合生产环境(稳定性优先)、通用场景(功能丰富)和复杂模型(需要更多配置选项)。
CompilerConfig 配置入口
config = torchair.CompilerConfig() # debug 类功能 config.debug.xxx = ... # export 类功能(离线导图) config.export.xxx = ... # dump_config 类功能 config.dump_config.xxx = ... # fusion_config 类功能 config.fusion_config.xxx = ... # experimental_config 类功能 config.experimental_config.xxx = ... # inference_config 类功能 config.inference_config.xxx = ... # ge_config 类功能 config.ge_config.xxx = ...其中experimental_config承载frozen_parameter(冻结参数)、tiling_schedule_optimize(tiling 调度优化)、topology_sorting_strategy(拓扑排序策略)等图内优化——这与仓库 executor/utils/graph_utils.py 中CompilerConfig()的用法一一对应。
核心 API
| API | 用途 |
|---|---|
CompilerConfig类 | 配置图模式功能 |
get_npu_backend() | 获取 NPU 后端 |
get_compiler() | 获取编译器 |
dynamo_export() | 导出模型 |
register_fx_node_ge_converter() | 注册转换器 |
register_replacement() | 自定义算子融合 |
两种模式选择建议
从仓库文档 docs/cann/zh/npu_graph_optimization.md 看,两种模式没有绝对优劣,当前建议是优先选择npugraph_ex,以降低适配成本、保留更接近 eager 的开发体验。一个值得注意的细节是:npugraph_ex 当前常保持dynamic=True,并不是因为图本身必须动态,而是与推理场景中部分 FIA 算子接口有关——部分actual_seq_lengths入参仍以list[int]形式传入,强行静态化容易触发重编译;后续算子接口补齐 Tensor 输入后,这类配置可以继续收敛。例如 deepseek_v4 不存在这类 list 输入,其配置便选择了静态图enable_dynamic_graph: False。
六、编译缓存使用建议
cache_compile适合在图模式已跑通、输入 shape / guard / 通信域稳定后启用,用于降低冷启动或多次拉起时的编译耗时;它不用于修复 Graph Break 或非预期重编译。启用时需按官方指南改造封装函数:
- npugraph_ex 使用
torch.npu.npugraph_ex.inference.cache_compile; - GE / Ascend IR 使用
torchair.inference.cache_compile; - 使用后原
torch.compile编译流程不再需要。
被缓存的函数应满足:是 module method、未被其他装饰器修饰、能形成 full graph,且同一缓存函数只能触发一次 Dynamo trace;Prefill / Decode 或 guard 不同的场景应拆分封装。若模型代码、输入规格、分布式 rank/world_size、CANN/torch_npu 版本发生变化,需重新生成或清理缓存。
仓库中的对应实现同样在 executor/utils/graph_utils.py:npugraph_ex 路径调用torch.npu.npugraph_ex.inference.cache_compile(model_forward, cache_dir=cache_dir, dynamic=enable_dynamic_graph, options=compile_options);GE 路径调用tng.inference.cache_compile(model_forward, cache_dir=cache_dir, config=compiler_config, dynamic=False, fullgraph=True, ge_cache=True)。cache_dir默认位于model_config.output_path/compile_cache下,还支持通过cache_namespace为独立的静态图 shape 指定子目录。无论哪种模式,缓存命中都取决于模型代码、输入规格(shape/dtype)、编译配置和cache_dir是否保持一致,任意一项变化都会导致缓存失效并重新编译。
七、LLM 模型适配要点
对于 LLM 推理模型,必须严格区分 prefill 和 decode 阶段。图编译后会生成静态计算图,任何动态行为都可能导致图中断(graph break)或重编译,因此改造的关键是识别并隔离动态因素。
Prefill 与 Decode 阶段限制
| 阶段 | 是否支持图模式 | 原因 |
|---|---|---|
| Prefill | 禁止使用 | 输入长度动态变化、首 token 生成逻辑复杂、shape 不固定 |
| Decode | 推荐使用 | 输入长度固定(通常为 1)、shape 稳定、适合图捕获 |
实现建议:将 prefill 和 decode 的 forward 逻辑分离成不同方法;仅对 decode 方法应用torch.compile图模式;prefill 阶段使用 eager 模式执行。
class YourModel: def prefill(self, input_ids, ...): """Prefill 阶段:使用 eager 模式""" # 输入长度动态变化,不适合图模式 return self._forward(input_ids, ...) def decode(self, input_ids, ...): """Decode 阶段:可使用图模式""" # 输入长度固定(通常为 1),适合图捕获 return self._forward(input_ids, ...) # 仅对 decode 方法应用图模式, model.prefill保持 eager model.decode = torch.compile(model.decode, backend="npugraph_ex", ...)核心改造原则
核心原则:将动态变化的东西提取为模型输入,模型内部尽量保证静态。
| 动态因素 | 问题表现 | 解决思路 |
|---|---|---|
| 内存地址变化 | Guard 失败、重编译 | 预分配固定大小,原地更新 |
| Shape 变化 | 图中断、多次编译 | 固定 shape 或通过参数控制 |
| Python 控制流 | Graph Break | 使用 Tensor 操作或模式参数 |
.item()调用 | 强制 Graph Break | 保持 Tensor 或外部传入 |
重编译问题定位与解决
如果图模式性能劣化,必须定位是否发生了重编译:
# 开启重编译日志 torch._logging.set_logs(recompiles=True) # 运行模型 output = compiled_model(input) # 如果发生重编译,会打印类似: # [recompiles] Recompiling function <func_name> for reason: <reason>重编译解决方案:
dynamic=False: 检测到重编译 │ └─→ 分析重编译原因 ├── 固定 shape 但仍重编译 → dynamic=False + skip_guard_eval_unsafe=True └── 输入 shape 变化 → dynamic=True区分 Prefill/Decode 实践指南
为模型添加独立的prefill()和decode()方法,通过is_prefill参数区分执行路径:
# === 模型层 === class MyModelForCausalLM(nn.Module): def forward(self, input_ids, position_ids, past_key_values, is_prefill=False, **kwargs): # is_prefill 控制不同执行路径 if is_prefill: # Prefill 专属:SP all-gather、取最后 token logits pass else: # Decode 专属:多流并行、原地更新 KV cache pass return logits def prefill(self, **kwargs): return self.forward(is_prefill=True, **kwargs) def decode(self, **kwargs): return self.forward(is_prefill=False, **kwargs) # === Runner 层 === class MyRunner: def model_inference(self, model_inputs, is_prefill=False): if is_prefill: return self.model.prefill(**model_inputs) else: return self.model.decode(**model_inputs) # 适合图模式八、模块级改造指南
1. KV Cache 模块改造
改造目标:消除 KV Cache 动态扩展导致的 shape 变化,实现固定大小 cache 的原地更新。
核心思路:① 预分配策略——在模型初始化时分配固定大小的 cache;② 原地更新原则——使用原地更新算子写入新值,避免重新分配;③ 有效长度控制——通过参数控制实际参与计算的长度;④ 返回优化——图模式下不返回 KV cache(已原地更新)。
# 问题模式:动态扩展 KV cache key = torch.cat([past_key, new_key], dim=1) # shape 变化! # 改造模式:固定大小预分配 + 原地更新 # 1. 初始化时预分配 def _init_kv_cache(self, batch_size, max_seq_len, device): cache_shape = (batch_size, 1, max_seq_len, head_dim) self.kv_cache = torch.zeros(cache_shape, dtype=dtype, device=device) # 2. forward 中原地更新 def forward(self, ..., kv_len, past_key_value): torch_npu.scatter_update_(past_key_cache, kv_len, new_key_states, dim=-2)常见问题:
| 问题现象 | 根因 | 解决方案 |
|---|---|---|
| 每次 decode 触发重编译 | torch.cat扩展 KV cache | 预分配固定大小,原地更新 |
| 内存占用过大 | 预分配浪费 | 结合 PagedAttention 按 block 管理 |
| 返回 KV cache 开销大 | 图模式下返回大量 tensor | 已原地更新,无需返回 |
2. Rotary Embedding 模块改造
改造目标:消除动态计算,实现静态图 cos/sin 查询。如果已经使用了融合算子、没有触发静态图的限制,则无需改造。
def forward(self, x, kv_len, is_prefill=True): if is_prefill: cos = self.cos_cached[:seq_len] # prefill:切片 else: cos = torch.index_select(self.cos_cached, dim=0, index=kv_len.view(-1)) # decode:索引 return cos.to(x.dtype), sin.to(x.dtype)核心思路:预计算 cos/sin(初始化时计算所有位置值并缓存)、索引查询(通过index_select或切片获取)、外层计算优化(在模型外层统一计算,传入各层)。
3. Attention 模块改造
改造目标:使 Attention 计算图模式友好,支持 Flash Attention 等融合算子。核心思路:优先使用 NPU 提供的融合 attention 算子;通过参数控制有效长度,避免大规模 attention mask;使用模式参数区分 prefill/decode 计算路径。融合算子选型可参考仓库中的model-infer-fusionskill(.agents/skills/model-infer-fusion/SKILL.md)。
4. Buffer/Parameter 模块改造
改造目标:避免 buffer/parameter 地址变化触发 guard 失败。核心思路:初始化时分配最大可能大小;使用copy_()、fill_()等原地操作;通过index_select、切片等只读方式访问。
5. 动态信息外部化设计
改造目标:将动态变化的信息从模型内部移到输入参数。
| 动态信息 | 内部计算 | 外部传入 |
|---|---|---|
| 位置索引 | position_ids = torch.arange(seq_len) | 作为参数传入 |
| 序列长度 | seq_len = hidden_states.size(1) | actual_seq_lengths参数 |
| 写入位置 | 内部计算kv_len | kv_len参数 |
| 模式切换 | 内部判断 | is_prefill参数 |
forward 签名设计参考:
def forward( self, input_ids: torch.LongTensor, # 位置相关(Tensor 形式支持图追踪) position_ids: Optional[torch.LongTensor] = None, kv_len: Optional[torch.IntTensor] = None, # KV 写入位置 # 序列长度(List[int] 传给 NPU 算子) actual_seq_lengths_kv: Optional[List[int]] = None, actual_seq_lengths_q: Optional[List[int]] = None, # 模式控制 is_prefill: bool = False, # KV Cache past_key_values: Optional[Tuple[torch.Tensor]] = None, # 预计算的 cos/sin(避免重复计算) cos: Optional[torch.Tensor] = None, sin: Optional[torch.Tensor] = None, ... ): pass6. 不要在 forward 中使用.item()
.item()将 Tensor 转换为 Python 标量会强制触发 Graph Break:
# 错误写法 - 会导致 Graph Break max_pos_id = position_ids.max().item() + 1 # 正确写法 - 使用静态参数或预计算 max_pos_id = MAX_SEQ_LEN # 作为常量传入7. 推荐配置
npugraph_ex 后端(推荐用于 LLM Decode):
import torch import torch_npu model = YourModel().npu() opt_model = torch.compile( model, backend="npugraph_ex", fullgraph=True, dynamic=False, # LLM decode 固定 shape options={ # FX图优化 "inplace_pass": True, "input_inplace_pass": True, "pattern_fusion_pass": True, # 内存优化 "reuse_graph_pool_in_same_fx": True, "clone_input": True, "clone_output": False, # 性能优化 "remove_noop_ops": True, } )GE 图模式:
import torch import torch_npu import torchair from torchair import patch_for_hcom patch_for_hcom() # 集合通信入图(有 TP/EP 并行时需调用) config = torchair.CompilerConfig() # 根据需要配置 inference_config, ge_config 等 npu_backend = torchair.get_npu_backend(compiler_config=config) opt_model = torch.compile(model, backend=npu_backend)九、问题定界流程
问题定界应优先基于本 skill 内置的知识进行独立分析,避免盲目复制其他模型的图模式配置:
问题发生 │ ├─→ aot_eager 验证 ──失败──→ 修复用户脚本 │ ↓ 正常 │ ├─→ force_eager/run-eagerly ──失败──→ 修复用户脚本 │ ↓ 正常 │ └─→ 图模式问题 ├── 重编译问题 → 阅读 LLM 指南 + npugraph_ex 指南 ├── Graph Break 问题 → 阅读 TorchAir 在线文档中的典型案例 └── 其他问题 → 阅读对应模式文档(npugraph_ex-guide.md 或 ge-graph-guide.md)调试知识来源(按优先级):① 本文档(SKILL.md)中的方法、原则和检查清单;② .agents/skills/model-infer-graph-mode/references/npugraph_ex-guide.md;③ .agents/skills/model-infer-graph-mode/references/llm-model-guide.md;④ .agents/skills/model-infer-graph-mode/references/ge-graph-guide.md;⑤ TorchAir 官方文档(按需查阅其在线文档中的案例与 FAQ 章节)。
十、图模式 + FA 融合算子快速 Debug
图模式与 FA 融合算子结合时,actual_seq_lengths参数的处理是最常见的出错点。
问题现象
- 编译报错:
actual_seq_lengths类型不匹配; - 运行时报错:重编译(recompile)触发;
- 性能问题:动态 shape 导致无法充分优化。
关键参数:actual_seq_lengths
FA 算子的actual_seq_lengths/actual_seq_qlen/actual_seq_kvlen参数在不同图模式下有不同的要求:
| 图模式 | FA 接口来源 | actual_seq_lengths 类型 | dynamic 设置 | 执行模式约束 | 说明 |
|---|---|---|---|---|---|
| GE 模式(推荐) | torchair FA 接口 | Tensor | dynamic=False | 仅支持 GE 图模式 | 最佳方案,静态图 |
| GE 模式(不推荐) | torch_npu FA 接口 | list[int] | dynamic=True+mark_static | 无限制 | 需额外配置,易出错 |
| npugraph_ex 模式 | torch_npu FA 接口 | list[int] | dynamic=True | 无限制 | 动态捕获模式 |
| npugraph_ex 模式 | torch_npu FA 接口 | Tensor(如有) | dynamic=False | 无限制 | 需确认接口是否支持 |
GE 模式配置方案一:torchair FA 接口(推荐)
import torch import torch_npu import torchair from torchair.ge_concrete_graph.ge_graph import mark_static # 使用 torchair 提供的 FA 接口 # actual_seq_lengths 为 Tensor 类型 attn_output = torchair.ops.npu_fused_infer_attention_score( query, key, value, actual_seq_qlen=actual_seq_qlen_tensor, # Tensor 类型 actual_seq_kvlen=actual_seq_kvlen_tensor, # Tensor 类型 # ... 其他参数 ) # 编译配置 opt_model = torch.compile(model, backend=npu_backend, dynamic=False)优点:dynamic=False可获得更好的静态图优化;无需额外的mark_static配置;图编译更稳定。
约束:TorchAir FA 接口仅支持 GE 图模式,不支持 Eager 模式和 npugraph_ex 模式调用。
GE 模式配置方案二:torch_npu FA 接口(不推荐)
import torch import torch_npu import torchair from torchair.ge_concrete_graph.ge_graph import mark_static # 使用 torch_npu 的 FA 接口 # actual_seq_lengths 为 list[int] 类型 attn_output = torch.ops.npu.npu_fused_infer_attention_score( query, key, value, actual_seq_lengths=[seq_len], # list[int] 类型 actual_seq_lengths_kv=[kv_len], # ... 其他参数 ) # 必须配置 dynamic=True # 并使用 mark_static 标记除 actual_seq_lengths 外的静态输入 mark_static(input_ids) # 静态输入 mark_static(position_ids) # 静态输入 mark_static(attention_mask) # 静态输入 # actual_seq_lengths 保持动态 # 编译配置 opt_model = torch.compile(model, backend=npu_backend, dynamic=True)缺点:需要配置dynamic=True,性能略逊于静态图;需要手动调用mark_static标记所有静态输入;配置繁琐,易遗漏导致问题。
npugraph_ex 模式配置方案一:list[int] 类型 + dynamic=True
import torch import torch_npu # 使用 torch_npu 的 FA 接口 # actual_seq_lengths 为 list[int] 类型 attn_output = torch.ops.npu.npu_fused_infer_attention_score( query, key, value, actual_seq_lengths=[seq_len], # list[int] 类型 actual_seq_lengths_kv=[kv_len], # ... 其他参数 ) # 必须配置 dynamic=True opt_model = torch.compile(model, backend="npugraph_ex", dynamic=True)npugraph_ex 模式配置方案二:Tensor 类型 + dynamic=False(如有接口支持)
import torch import torch_npu # 查询是否有 Tensor 类型的 actual_seq_lengths 接口 # 通过 subagent 调用 model-infer-fusion 查询 # 如果有支持的接口: attn_output = torch.ops.npu.npu_fused_infer_attention_score( query, key, value, actual_seq_lengths=actual_seq_lengths_tensor, # Tensor 类型 actual_seq_lengths_kv=actual_seq_kvlen_tensor, # ... 其他参数 ) # 可配置 dynamic=False opt_model = torch.compile(model, backend="npugraph_ex", dynamic=False)仓库中的 host list 转换实现
从源码看,仓库执行引擎 executor/core/engine/execution_engine.py 在 Decode 阶段为 npugraph_ex 做了专门的 host list 转换:当self.exe_mode == "npugraph_ex"且处于 decode(非 prefill)路径时,会将actual_seq_lengths_cu_kv/q、actual_seq_lengths_kv/q等 Tensor 通过detach().cpu().numpy().tolist()转为List[int]形态,再通过set_forward_metadata()写入 executor/utils/forward_metadata.py 中定义的actual_seq_lengths_cu_list_kv、actual_seq_lengths_cu_list_q、actual_seq_lengths_list_kv、actual_seq_lengths_list_q字段,供静态图路径使用;prefill 阶段这些 list 字段保持None。这与技能文档「npugraph_ex decode 用 host list 长度字段」的原则完全对应。
常见错误与修复
| 错误现象 | 根因 | 修复方案 |
|---|---|---|
| 编译报错:actual_seq_lengths 类型错误 | GE 模式下 torch_npu FA 接口传 Tensor | 改用 torchair FA 接口,或改用 list[int] + dynamic=True |
| 运行时频繁重编译 | dynamic=False 但 actual_seq_lengths 为 list[int] | 改用 Tensor 类型 + torchair 接口,或设置 dynamic=True |
| 性能不达预期 | dynamic=True 导致无法充分优化 | 尽量使用 torchair FA 接口 + dynamic=False |
| npugraph_ex 模式报错 | actual_seq_lengths 为 Tensor 但接口不支持 | 确认接口支持情况,或改用 list[int] + dynamic=True |
| Eager 或 npugraph_ex 模式调用 TorchAir FA 报错 | TorchAir FA 接口仅支持 GE 图模式 | 改用 torch_npu FA 接口,或切换到 GE 图模式 |
Debug 检查清单
在图模式 + FA 场景下,按以下清单逐一排查:
- 确认使用的图模式:GE 还是 npugraph_ex
- 确认 FA 接口来源:torch_npu 还是 torchair
- 若使用 torchair FA 接口,确认当前为 GE 图模式(不支持 Eager 和 npugraph_ex)
- 检查 actual_seq_lengths 类型:GE+torchair→Tensor;GE+torch_npu→list[int]+dynamic=True;npugraph_ex→list[int]+dynamic=True
- 检查 dynamic 配置是否与 actual_seq_lengths 类型匹配
- 若使用 GE + torch_npu FA + list[int],检查是否已 mark_static 标记所有静态输入
- 如有疑问,调用
model-infer-fusionskill 查询接口详情
- 如有疑问,调用
十一、进阶阅读与文档索引
围绕图模式,仓库还提供了以下可直接查阅的资料:
- 原理向:docs/cann/zh/npu_graph_optimization.md 详细解释了 eager 模式与图模式的执行差异、npugraph_ex 的「捕获一次、多次回放」原理(Dynamo compile → Guards → aclgraph Capture → Input 处理 → Replay)、编译缓存的落盘与命中机制,以及
ge_graph与npugraph_ex的选择建议。 - 技能文档:本主题的完整技能定义见 .agents/skills/model-infer-graph-mode/SKILL.md,三份参考指南分别对应 npugraph_ex-guide.md、llm-model-guide.md、ge-graph-guide.md。
- 框架代码:图编译统一入口 executor/utils/graph_utils.py;执行模式与图相关配置定义及校验 executor/core/config/inference_config.py;Decode 阶段 host list 长度字段的构造 executor/core/engine/execution_engine.py;元数据载体 executor/utils/forward_metadata.py。
- 真实配置样例:使用 npugraph_ex 并开启静态 kernel 的示例 models/deepseek_v4/config/ci_a3/deepseek_v4_flash_rank_128_128ep_w8a8.yaml。
- 图模式与增强特性叠加:图模式通常与编译缓存、静态 kernel、多流(docs/cann/zh/multi_stream_principles.md)、预取(docs/cann/zh/prefetch_principles.md)、superkernel(docs/cann/zh/super_kernel.md)等能力组合使用。推荐的推进顺序是:先跑通 eager 并完成功能与精度验证 → 打开图模式,消除 graph break 和 recompile → 对比 eager 与 graph 输出(至少覆盖一轮 Prefill 和多轮 Decode)→ 再按需开启
enable_cache_compile、enable_static_kernel、多流、限核或enable_superkernel。其中enable_static_kernel当前仅用于 npugraph_ex 相关路径,enable_superkernel当前主要在 ge_graph 模式下尝试。
最后再次强调图模式适配的收尾标准:确认 warmup 阶段首次编译功能正常,正式推理 decode 阶段直接复用 warmup 编译的图、日志中无recompile标识。只有满足这一条件,图模式的性能收益才会真正落在正式推理的关键路径之外。
【免费下载链接】cann-recipes-infer本项目针对LLM与多模态模型推理业务中的典型模型、加速算法,提供基于CANN平台的优化样例项目地址: https://gitcode.com/cann/cann-recipes-infer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考