昇腾 NPU 模型推理运行时错误诊断与修复实战:基于 CANN cann-recipes-infer 的 aicore timeout / HCCL / OOM 系统化排查指南
2026/9/18 16:32:16 网站建设 项目流程

昇腾 NPU 模型推理运行时错误诊断与修复实战:基于 CANN cann-recipes-infer 的 aicore timeout / HCCL / OOM 系统化排查指南

【免费下载链接】cann-recipes-infer本项目针对LLM与多模态模型推理业务中的典型模型、加速算法,提供基于CANN平台的优化样例项目地址: https://gitcode.com/cann/cann-recipes-infer

导读

本文是面向昇腾 NPU 的 LLM 与多模态模型推理运行时错误诊断方法论,来源于 cann-recipes-infer 仓库的.agents/skills/model-infer-runtime-debug技能,并辅以 executor 与 models 目录下的真实实现加以印证。文中系统覆盖 aicore timeout(507014)、HCCL 通信错误、显存 OOM、Shape 不匹配、算子约束违反以及推理卡住等高频故障,给出"先定位再修复"的二分法排查路径、常见算子约束速查表、四类修复策略与验证清单,并区分框架部署与独立部署两种进程模型。读完本文,你将能够独立完成 NPU 推理链路上从"报错定位"到"修复验证"的全过程,并掌握npu-smi+ 日志 + 检查点三类工具的组合用法。

诊断心法:先定位,再修复

NPU 运行时错误的排查核心是先定位再修复:通过二分法逐步缩小故障范围,避免在错误方向浪费时间。本文不覆盖精度问题(NaN、输出偏差等),精度调优请转向仓库中的model-infer-precision-debug技能(见 .agents/skills/model-infer-precision-debug/SKILL.md)。

部署模式适配:先确认你在哪条进程模型上

诊断流程对框架部署与独立部署两种模式均适用,但启动协议、通信组管理、KV cache 接入、权重加载四条链路的差异会直接决定错误的排查方向,必须先确认当前部署形态:

维度框架部署独立部署
启动协议executor/scripts/function.sh 中的launch多卡 fork python,通过LOCAL_RANK/RANK_ID环境变量区分每个 rank 的进程torchrun --nproc_per_node=N infer.py,由 torchrun 自动注入RANK/WORLD_SIZE/LOCAL_RANK/MASTER_ADDR/MASTER_PORT
通信组管理executor.core.config.comm_manager.CommManager一次性建组,模型类通过comm_manager.get_group(name)取组Runner 内 inlineParallelContext,对外接口对齐get_group(name)/get_group_name(name)/get_rank(name)
KV cache 接入由 executor/core/kv_cache 自动管理cache_entries/block_table[attn_type]/slot_mapping[attn_type]Runner 自管 KV tensor +block_table+slot_mapping
权重加载enable_online_split_weight: True时由ParallelLinear.weight_loader自动按 rank 切分Runner 自管,或调用 executor/model_loader/weight_utils.py 中的default_weight_loader

例如,出现 HCCL 错误时首先要确认走的是哪条建组路径:框架部署应核查CommManager.initialize()传入的group_stride/group_num参数;独立部署则应核查ParallelContext.build_parallel_context()init_comm_group的调用参数。以 executor/core/config/comm_manager.py 为例,CommManager对外暴露get_group(name)/get_rank(name)/get_group_name(name)/has_group(name)/register_group(...)五个接口,模型通过register_group声明业务通信组,由 manager 统一完成子组物化、签名缓存复用与 HCCL 组名捕获——这意味着所有 rank 必须以完全相同的顺序、相同的参数创建通信组,任何一条 rank 的参数不一致都会引发 HCCL 建组失败或后续通信死锁。

诊断路径总览

故障发生后,先根据"有无明确错误信息"分流,再按决策树逐级下钻:

问题发生 │ ├── 有明确错误信息 → 通用诊断流程 │ ├── aicore timeout (507014) → 二分法定位 │ │ ├── 定位到阶段 → 层 → 模块 → 算子 │ │ └── 查表「常见算子约束」→ 修复策略 → 验证 │ ├── OOM → 检查参数量 / batch_size / seq_len / 中间 buffer │ ├── Shape 不匹配 → 检查 TP/EP 切分维度 / Prefill vs Decode 分支 │ └── HCCL timeout → 检查各 rank 代码路径一致性 / 通信组创建 │ └── 无明确错误 → npu-smi 状态检查 ├── 部分 rank 缺失 / HBM 不均 → 多卡部署诊断 │ ├── 权重加载 crash → TP 切分越界 / vocab pad │ ├── Config 字段缺失 → Config 兼容性 │ └── 静默失败 → 逐 rank 检查日志 ├── 有进程 + HBM 不变 + CPU 100% → 推理卡住诊断 - 模型构造阶段 ├── 有进程 + HBM 不变 + CPU 0% → 推理卡住诊断 │ ├── 检查残留进程 │ ├── eager 也卡 → sync+print 定位(通用流程第二步) │ └── 仅 graph 卡 → 比较 eager/graph 代码路径差异 └── 0 进程 → 查 rank 0 日志 → 通用诊断流程

npu-smi 状态检查:无报错时的第一现场

当没有明确错误信息时,先运行npu-smi info读取设备状态,用一张表完成初步定性:

npu-smi 表现含义诊断路径
前 N 进程存在,后面缺失,HBM 不均高位 rank 在权重加载时 crash→ 多卡部署诊断
8 进程,HBM 均匀,AICore 全 0%所有 rank 卡在通信等待→ 推理卡住诊断
有进程,HBM 不变 ~3GB,CPU 100%+CPU 密集操作(模型构造/权重初始化)→ 推理卡住诊断 - 模型构造阶段
有进程,HBM 不变 ~3GB,CPU 0%死锁/通信等待→ 推理卡住诊断
8 进程,HBM 持续增长权重加载中等待
0 进程全部 crash查 rank 0 日志 → 通用诊断流程

值得注意的是,框架部署的launch_infer_task会为每个 rank 分配 CPU 核心绑定(taskset -c),且 rank 0 的 stdout 通过tee输出、其余 rank 只写日志文件(executor/scripts/function.sh)。因此"终端无输出"并不能代表推理状态,必须以npu-smi或日志文件为准。

通用诊断流程

适用于有明确错误信息的场景(aicore timeout、HCCL error、OOM、shape mismatch 等)。整体按分类 → 定位 → 查表 → 修复 → 验证线性推进。

第一步:错误分类

拿到错误信息后,先判断属于哪一类。不同类别的根因和排查路径完全不同,分类错误会浪费大量时间。

A. aicore timeout(错误码 507014)

特征AclrtSynchronizeDeviceWithTimeouterror code is 507014aicore timeoutfftsplus aivector errorKernel task happen error, retCode=0x25

含义:某个 NPU 算子在设备上执行时超时未返回。这是最常见也最难排查的错误——因为报错的位置(synchronize 调用处)通常不是出错的位置(某个具体算子)。

常见根因

  • 算子入参违反硬件约束(如 A2 上 MC2 要求experts_per_rank <= 24,见下文「常见算子约束」)
  • 算子 shape 超出硬件限制(如单次 matmul 的 M/N/K 维度超限)
  • 死锁:部分 rank 走了不同的通信路径,导致集合通信永远等不齐
  • 内存越界:slot_mapping/block_table索引超出 KV cache 分配范围
B. HCCL 通信错误

特征HCCL_CONNECT_TIMEOUTHCCL errorAllReduce/AllToAll timeoutHCCL_EXEC_TIMEOUT

含义:分布式通信操作超时或失败。

常见根因

  • 各 rank 进入通信操作的顺序/次数不一致(代码分支导致部分 rank 跳过某次通信,常见于 modeling forward 内有if forward_metadata.is_prefill等分支)
  • 通信组创建时参数错误:框架部署核查CommManager.initialize()group_stride/group_num;独立部署核查ParallelContext.build_parallel_context()init_comm_group调用参数
  • MoE EP 场景缺moe_ep_group_namedispatch_v2/combine_v2算子要求 HCCL group name,框架部署用comm_manager.get_group_name("moe_ep_group"),独立部署用parallel_ctx.get_group_name("moe_ep_group")。这一点在 models/deepseek_v4/models/modeling_deepseek.py 中有直接体现——MoE 推理会同时声明moe_ep_group(供all_to_all_single使用)与moe_ep_group_mc2(供 dispatch/combine 取 HCCL group name),两者缺一不可
  • 网络问题(跨节点时HCCL_IF_IP配置错误)
  • HCCL_BUFFSIZE不足(大 batch / 大 hidden_size 的 dispatch/combine 失败)——调高至 512MB 试试。仓库中CommManager读取环境变量HCCL_BUFFSIZE,默认值为_DEFAULT_HCCL_BUFFSIZE_MB = 200(executor/core/config/comm_manager.py),大模型场景建议显式调大
  • 某些 rank 已经 crash 但其他 rank 还在等它参与通信
C. OOM(显存不足)

特征NPU out of memoryTried to allocate X GiBENOMEM

含义:NPU HBM 不够。

常见根因

  • 模型参数 / KV cache / activation 总和超过单卡显存
  • 中间 tensor 未及时释放(常见于 MoE EP all-to-all 中间 buffer)
  • batch_size 或 seq_len 超出预期
  • migrator 阶段单卡 OOM:编排层会标记"需多卡",并行化(parallel-impl skill)后再补采基线,不是必修问题
D. Shape 不匹配

特征RuntimeError: shape mismatchexpected size X but got Ymat1 and mat2 shapes cannot be multiplied

含义:tensor 维度不对。

常见根因

  • TP 切分后维度未正确除以tp_size:核对ParallelLineartp_size/tp_rank(标准做法是用comm_manager.get_rank("attn_tp_group")/parallel_ctx.get_rank("attn_tp_group")等取值,避免硬编码)
  • KV cache 的num_head字段:必须用num_kv_heads_per_rank = max(num_kv_heads // attn_tp_size, 1),与 parallel-impl skill 的attn_tp_size改动联动
  • EP 切分后每卡专家数计算错误:检查experts_per_rank = num_experts // ep_size(models/deepseek_v4/models/modeling_deepseek.py 正是按此公式计算)
  • Prefill/Decode 分支传入了错误 shape 的 tensor(阶段分支统一用forward_metadata.is_prefill
  • packed sequence 协议错误:modeling 内hidden_states被 reshape 成[B, S, H](变长 batch 不能简单 reshape,应保持[TotalTokens, hidden_size]二维,详见 model-infer-kvcache 技能)
  • 多 attn_type 混合模型:误把block_table当 plain Tensor 传给 FA,应该取block_table[self.attn_type](dict 索引)
E. 算子约束违反

特征:不直接报约束错误,通常表现为 aicore timeout 或 SIGABRT。需要通过二分法定位后,查阅算子文档确认。

含义:NPU 自定义算子对入参有隐含约束(数据类型、维度范围、硬件平台限制),违反时行为未定义。

第二步:二分法定位(sync+barrier 检查点法)

这是核心方法。当错误发生在torch.npu.synchronize()时,错误信息只告诉你"设备上有东西出错了",不告诉你具体哪个算子。通过插入检查点逐步缩小范围。

关键陷阱:NPU 操作是异步的。model.prefill()返回不代表 prefill 完成——只有torch.npu.synchronize()返回才代表所有已提交的操作完成。所以报错位置(synchronize 调用处)不一定是出错位置(某个具体算子)。确保检查点覆盖所有可能的异步操作。

2.1 定位到阶段(Prefill vs Decode):在 runner 的model_inference方法中,prefill 和 decode 调用之间插入同步检查:

# 在 prefill 之后、decode 之前 torch.npu.synchronize() logging.info("prefill passed")

2.2 定位到层:确认出错阶段后(如 Decode),在每个 decoder layer 之间插入检查点:

for i, layer in enumerate(self.layers): hidden_states = layer(hidden_states, ...) torch.npu.synchronize() logging.info(f"layer {i} passed")

如果 Layer 0 就超时——问题在第一层内部;如果 Layer 2 超时——问题在第 2 层或其子模块。

2.3 定位到模块:进入出错层,在各子模块之间插入检查点:

# 在 DecoderLayer.forward 中 hidden_states = self.input_layernorm(hidden_states) torch.npu.synchronize(); logging.info(" norm passed") hidden_states = self.self_attn(hidden_states, ...) torch.npu.synchronize(); logging.info(" attention passed") hidden_states = self.mlp(hidden_states) # MoE torch.npu.synchronize(); logging.info(" MoE passed")

2.4 定位到算子:进入出错模块,在每个 NPU 算子调用之间插入检查点。此时通常可以定位到具体算子及其入参,结合「常见算子约束」判断根因。

2.5 注意事项

  • 先用torch.npu.synchronize()做单卡定位,确认是哪个算子出错。不要一开始就加dist.barrier()——barrier 会在某 rank 已崩溃时导致其他 rank 永久挂起
  • 怀疑多 rank 不同步时,用dist.barrier()torch.distributed.monitored_barrier()确认各 rank 是否走到同一位置。monitored_barrier在超时后会报告哪个 rank 未到达
  • 查看所有 rank 的日志,确认各 rank 到达的检查点是否一致(不一致说明有条件分支差异 → 通信死锁)
  • 不要一次插入太多检查点——每次 sync 有开销,且大量 sync 可能改变时序。先粗粒度,再细粒度
  • 保留日志:把每轮定位的检查点输出保存,供后续分析

第三步:常见 NPU 算子约束速查

以下约束来自本仓实际调试经验,定位到具体算子后应回到当前调用的 API 签名和官方文档核对,不要跨算子套用规则。违反约束的典型表现是 aicore timeout。

MoE 相关算子
算子约束违反表现解决方案
npu_moe_distribute_dispatch_v2(MC2)A2:experts_per_rank = moe_expert_num // (ep_world_size - shared_expert_rank_num) <= 24;A3 无此 24 限制aicore timeout,Decode 阶段挂死experts_per_rank > 24时回退到 double_routing 路径(npu_moe_init_routing_v2+ manual all_to_all)
npu_moe_distribute_combine_v2(MC2)同上同上同上
npu_moe_init_routing_v2无 EP 最小限制,但moe_chunk_max_len为 0 可能导致空 tensorSIGABRT 或 shape error设置合理的 moe_chunk_max_len(如 1024)
npu_moe_gating_top_kk不能超过专家总数静默错误输出或 crash检查 reduced model 的 moe_topk 是否已调整

以 models/deepseek_v4/models/modeling_deepseek.py 为例,可以看到 MoE 推理中存在两条路径:npu_moe_distribute_dispatch_v2/npu_moe_distribute_combine_v2(MC2 高性能路径)与moe_infer_double_routing(回退路径,走npu_moe_init_routing_v2+ 手动all_to_all_single)。源码中moe_ep_group同时服务于两条路径的集合通信,而 MC2 路径额外依赖moe_ep_group_mc2的 HCCL group name——这正是上述约束在真实代码中的落点。

FA(Flash Attention)算子
约束说明
head_num须匹配实际 Q headsMLA 模型中 head_num = num_attention_heads,不是 kv_heads
sparse_mode+atten_mask按路径分类标准 LLM TND PA 路径 Prefill+Decode 统一 sparse_mode=3 + causal mask;滑窗层统一 sparse_mode=4 + band;MLA absorb Prefill 3 / Decode 0+None
atten_mask shapesparse_mode=3/4时固定[2048, 2048]bool(用executor.utils.common_utils.get_init_attn_mask(2048, device)构造)
input_layout按 API 和阶段区分npu_fusion_attention用 BSH/SBH/BSND/BNSD/TND;npu_fused_infer_attention_score用复合 layout 如 TND_NTD(PA + MLA 默认推荐)、BSND_NBSD(KVP 场景),以实际 API 签名为准
Decode PA 模式下block_tableshape[batch, max_blocks],值不能超出 cache 分配的 block 数;框架部署是Dict[attn_type → Tensor],attention 内取block_table[self.attn_type]
FA v1 + BSH + Q_S=1 Decode 算子级限制op 内部忽略sparse_mode/pre_tokens(实测 sparse_mode 0 vs 4 等价);滑窗模型长序列正确性靠模型层actual_seq_lengths_kv截断
NZ 格式 cache需要torch_npu.npu_format_cast转换,且 hidden_dim 须为 16 的整数倍

关于actual_seq_lengths参数,要以"当前调用算子的签名与官方 API 文档"为准,不跨算子套用规则:

  • torch_npu.npu_fused_infer_attention_score/torch_npu.npu_fusion_attentionactual_seq_lengths*List[int](文档为 int64 语义)
  • torch_npu.npu_kv_quant_sparse_flash_attentionactual_seq_lengths_query/kvTensor(文档为 int32)

仓库中get_init_attn_mask的实现位于 executor/utils/common_utils.py,它基于torch.tril生成上三角掩码并支持valid_len参数,可直接用于构造 FA 所需的[2048, 2048]bool 掩码。

通信算子
约束说明
all_to_all_single的 split sizesinput_splits 和 output_splits 之和须分别等于 input 和 output 的第 0 维
通信组内所有 rank 须同时到达任何条件分支差异都可能导致部分 rank 跳过通信 → 死锁
HCCL group 创建顺序所有 rank 须以相同顺序创建相同的通信组

第四步:修复策略模式

定位根因后,从以下四类模式中选择合适的修复策略。

模式 1:算子回退(Operator Fallback)——当某个高性能算子在当前硬件/配置下不可用时,回退到功能等价但限制更少的替代算子:

# 示例:A2 上 experts_per_rank > 24 时 MC2 算子不可用,回退到 double_routing def forward(self, hidden_states, is_prefill): topk_indices, topk_weights, _ = self.router(hidden_states) if is_prefill: return self.moe_infer_double_routing(hidden_states, topk_indices, topk_weights) else: experts_per_rank = self.num_experts // (self.moe_ep_size - self.shared_expert_rank_num) if experts_per_rank > 24: # A2 MC2 dispatch_v2/combine_v2 requires experts_per_rank <= 24; fall back return self.moe_infer_double_routing(hidden_states, topk_indices, topk_weights) return self.moe_infer_dispatch_combine(hidden_states, topk_indices, topk_weights)
  • 适用场景:硬件约束、EP/TP size 不满足算子要求
  • 注意:回退通常有性能损失,需记录在优化报告中

模式 2:参数修正(Parameter Fix)——算子入参计算错误(如 FAhead_num用了 KV heads 而非 Q heads、TP 切分后维度未除以 tp_size)。定位到具体算子后,逐个核对入参与算子文档/参考模型的差异。

  • 适用场景:shape 不匹配、dtype 错误、维度计算错误

模式 3:路径统一(Path Unification)——部分 rank 走不同代码路径导致通信死锁:

# 错误:不同 rank 可能在不同 step 进入 prefill/decode if is_prefill: self.all_reduce(...) # rank 0 执行 # rank 1 已进入 decode,不执行 all_reduce → 死锁 # 修复:确保所有 rank 同步状态后再分支 dist.barrier() is_prefill_tensor = torch.tensor([int(is_prefill)], device="npu") dist.broadcast(is_prefill_tensor, src=0) is_prefill = bool(is_prefill_tensor.item())
  • 适用场景:多 rank 死锁、HCCL timeout

模式 4:配置降级(Configuration Downgrade)——配置组合不兼容时,调整 YAML 参数(如降低moe_chunk_max_len、减小batch_size)。

  • 适用场景:OOM、性能异常

第五步:验证清单

修复后,按以下清单逐项验证:

  • 单步验证:在出错位置前后加 sync+barrier,确认不再超时
  • 全流程验证:移除所有调试检查点,运行完整 warmup + inference
  • 全 rank 验证:检查所有 rank 的日志,确认全部成功(grep "model run success" log_*.log
  • 输出一致性:各 rank 的最终输出应一致(对 DP 模式,同 DP group 内的 rank 输出应一致)
  • 性能记录:记录修复后的 Prefill/Decode 耗时,与修复前对比
  • 回退文档:如果使用了算子回退,记录性能影响和未来可恢复条件

特定场景诊断

以下场景有独立的诊断路径,不走通用流程。通过入口 npu-smi 表判断后直接跳转。

多卡部署诊断

多卡部署引入了一类单卡不存在的问题:权重加载切分、vocab 对齐、部分 rank 崩溃、config 兼容性。共同特点:部分 rank 成功、部分 rank 失败,错误出现在权重加载阶段而非 forward 阶段。

启动协议区分:框架部署用executor/scripts/function.sh::launch多卡 fork python(每个 rank 单独起进程,通过LOCAL_RANK/RANK_ID环境变量区分);独立部署用torchrun --nproc_per_node=N infer.py(torchrun 自动注入 RANK / WORLD_SIZE / LOCAL_RANK / MASTER_ADDR / MASTER_PORT)。卡住 / crash 时先确认对应启动方式的进程模型。

权重加载 TP 切分越界

典型错误

RuntimeError: start (105984) + length (35328) exceeds dimension size (131125)

或:

ValueError: 131125 is not divisible by 8

根因ColumnParallelLinear/VocabParallelEmbedding将权重沿 output 维度切分为对应*_tp_size份。如果 checkpoint 的实际维度不等于模型参数的维度,或者不能被对应*_tp_size整除,高位 rank 的narrow()操作就会越界。仓库中的 executor/model_loader/weight_utils.pydefault_weight_loader会在param.size() != loaded_weight.size()时抛出ValueError并打印"default weight load failed"日志,这正是此类错误的落点之一。

常见场景

场景说明修复
多模态模型文本推理embed_tokens用 full vocab (282624) 但lm_head用 text+special (131125),两者维度不同lm_head 单独使用text_vocab_plus_multimodal_special_token_size
vocab 不整除 lmhead_tp_size131125 % 8 ≠ 0向上 pad 到最近的lmhead_tp_size倍数:padded = ((raw + lm_tp - 1) // lm_tp) * lm_tp
padded 参数 vs 原始 checkpoint模型参数 131128(padded)但 checkpoint 权重只有 131125加载时先 pad checkpoint weight 再传给 weight_loader

修复模板(vocab pad + weight pad)

# 1. 创建 lm_head 时 pad vocab size,注意切分用的 lmhead_tp_size 与 attn_tp_size 可能不同 lm_head_raw = getattr(config, "text_vocab_plus_multimodal_special_token_size", config.vocab_size) self.lm_head_vocab_size = ((lm_head_raw + lmhead_tp_size - 1) // lmhead_tp_size) * lmhead_tp_size # 2. 加载权重时 pad checkpoint tensor if "lm_head" in name and loaded_weight.shape[0] < self.lm_head_vocab_size: padded = torch.zeros(self.lm_head_vocab_size, *loaded_weight.shape[1:], dtype=loaded_weight.dtype, device=loaded_weight.device) padded[:loaded_weight.shape[0]] = loaded_weight loaded_weight = padded

排查技巧:如果只有高位 rank (rank 3-7) crash 而低位 rank (0-2) 正常,几乎一定是切分越界——因为低位 rank 的start_idx还在合法范围内。

Config 兼容性问题

从 HuggingFace 原始模型加载时,config.json 可能缺少代码期望的字段,或字段格式不同。

问题表现修复
缺少rope_parametersTypeError: 'NoneType' object is not subscriptableat RotaryEmbedding initConfig__init__中当rope_parameters=None时构造 default:{"rope_type": "default", "rope_theta": self.rope_theta}
vocab_size含多模态 tokenEmbedding 过大 / lm_head 维度不匹配区分vocab_size(embedding 用)和text_vocab_plus_multimodal_special_token_size(lm_head 用)
num_hidden_layers歧义Dual-sublayer 架构下 HF 可能存 28(attention 层数)但物理层只有 14用 property 覆盖:@property num_hidden_layers -> return self.num_layers
auto_map指向原始模型代码加载时尝试 import 原始 modeling 文件传入自定义 config class 到from_pretrained

最佳实践:在 config class 的__init__末尾为缺失字段补 defensive defaults。对有模型差异的参数(如rope_theta),从 config.json 读取而非硬编码默认值,缺失时报错。

部分 Rank 静默失败

有时 rank crash 了但错误被吞掉,存活的 rank 卡在下一个dist.barrier()或集合通信上,看起来像"推理卡住"。

诊断流程

# 1. npu-smi 确认进程数 npu-smi info | grep "Process id" # 2. 逐 rank 检查日志末尾 for i in $(seq 0 7); do echo "=== Rank $i ===" tail -5 logs/log_${i}.log done # 3. 检查是否所有 rank 都到达了同一个里程碑 grep "model run success" logs/log_*.log # 或 grep "Loading weights took" logs/log_*.log
多卡权重加载检查清单

在发起多卡推理前,逐项确认:

  • embed_tokens 维度:checkpoint 里 embed_tokens.weight.shape[0] 和 config.vocab_size 一致
  • lm_head 维度:checkpoint 里 lm_head.weight.shape[0] 可能小于 embed_tokens(多模态模型常见)
  • *vocab 整除对应_tp_size:lm_head 和 embed_tokens 的 output_size 都能被各自lmhead_tp_size/embed_tp_size整除(不能则 pad)
  • per-rank head 数:attn_tp_size 改动后,num_kv_heads_per_rank = max(num_kv_heads // attn_tp_size, 1),cache_entries.num_head 字段同步更新
  • ignore 规则覆盖_keys_to_ignore_on_load_unexpected包含所有不需要的权重前缀(ngram、visual、audio、mtp 等)
  • config 字段完整rope_parametersnum_layerszero_expert_num等代码必需字段在 config.json 中有或有 default
  • 模型路径绝对路径:相对路径可能在不同 rank 的 cwd 下解析不同
  • enable_online_split_weight 与 weight_loader 实现:YAML 设 True 时 ParallelLinear / FusedMoEGMM 的 weight_loader 必须正确处理 tp_rank / ep_rank 切分

推理卡住诊断

推理卡住与 crash 不同——进程仍在运行但不产出结果。需要区分真卡住和假卡住。先通过入口 npu-smi 表判断状态,再按以下路径排查。

eager 模式复现
卡住 → 换 eager YAML 跑同样模型 ├── eager 也卡 → bug 在模型代码 → 用 sync+print 定位 ├── eager 正常 → bug 在图模式适配 │ ├── 看报错:ERR03007 / graph break → 找不兼容的算子 │ └── 无报错但慢/卡 → 比较 eager 和 graph 模式代码路径差异 └── 两者都正常但上次卡了 → 检查是否有残留进程占 NPU
模型构造阶段卡住(CPU 密集)

症状:日志停在 HCCL init 之后,HBM 不增长,但 CPU 100%+。

原因机制修复
nn.Embedding大 vocab 初始化大 vocab 的nn.Embeddingnormal_()填充随机值,多个 embedding 累计耗时可达数分钟no_init_weights()上下文包裹,跳过将被 checkpoint 覆盖的参数初始化
VocabParallelEmbedding瞬态内存继承nn.Embeddingsuper().__init__(full_vocab, dim)先创建全 vocab tensor,再用 per-partition tensor 替换,瞬态 CPU 内存远超最终占用nn.Module+torch.empty(per_partition_size)直接创建 per-partition 权重,避免瞬态全 vocab 分配
post_init()遍历所有参数HuggingFacePreTrainedModel.post_init()调用_init_weights对每个参数做初始化,大模型参数量多时很慢对从 checkpoint 加载的大参数模块,用no_init_weights()跳过

诊断技巧

# 区分真卡和 CPU 密集慢 ps aux | grep "infer.py" | awk '{print $2, $3"%CPU", int($6/1024)"MB"}' # CPU 100%+ = 还在算(慢但没死) # CPU 0% = 真卡住(等通信/IO)

修复模板(大 embedding 模块)

from transformers.modeling_utils import no_init_weights # 用 no_init_weights 包裹,避免对即将被 checkpoint 覆盖的参数做随机初始化 with no_init_weights(): self.large_embeddings = LargeEmbeddingModule(config, ...)
残留进程导致 HCCL 卡住

症状:新启动的推理卡在 HCCL init,不报错不超时。

根因:上次推理的进程未正确退出,仍占用 NPU 设备或 HCCL 端口。

修复

# 1. 杀死残留进程 ps aux | grep "infer.py" | grep -v grep | awk '{print $2}' | xargs kill -9 # 2. 等待 NPU 释放 sleep 3 # 3. 确认 NPU 完全空闲 npu-smi info | grep "No running" # 期望输出 8 行 "No running processes found" # 4. 重新启动推理

预防:每次启动推理前,先检查并清理残留进程。实际上框架部署的启动脚本本身就内置了这一防护——executor/scripts/function.sh 的check_launch会在启动前用pgrep/ps检测是否已有infer.py/server.py进程在运行,若有则直接中断并提示"script was interrupted and exited",从源头避免残留进程导致的 HCCL 卡住。

附录:运行监控与提效

以下命令示例基于当前仓默认的日志路径和启动脚本约定,executor 或日志组织变更后需相应调整。

核心原则

判断完成状态看进程,不看日志流。多卡推理通过infer.sh后台启动 N 个进程再wait。rank 0 的 stdout 通过tee到终端,可能缓冲截断;其他 rank 只写日志文件(见 executor/scripts/function.sh 中tee&>的分流处理)。

完成检测方法

方法命令说明
查 NPU 进程npu-smi info \| grep "running process"无进程 = 已结束
查非 rank0 日志grep "model run success" logs/*/log_1.logRank 1-7 日志不经 tee,完整写入。不要只查 rank 0
查所有 rankfor i in $(seq 0 7); do grep -l "model run success" logs/*/log_${i}.log; done \| wc -l期望输出 = world_size

日志关键字判断阶段

grep "Loading safetensors" logs/*/log_1.log # 正在加载权重 grep "Loading weights took" logs/*/log_1.log # 加载完成 grep "inference time cost of prefill" logs/*/log_1.log # 推理已开始 grep "model run success" logs/*/log_1.log # 推理已结束

这些关键字与仓库实际日志输出一一对应,例如 executor/utils/common_utils.py 中的obtain_mtp_stats会打印"Finished inference, number of loop step is ..."与模型平均推理耗时,executor/utils/common_utils.py 的detokenize_outputs会打印 "Inference decode result" 输出。

一条命令获取全局状态

echo "=== NPU ===" && npu-smi info | grep -E "Process|HBM" | head -5 && \ echo "=== Log ===" && tail -3 logs/*/log_1.log && \ echo "=== Done? ===" && grep -c "model run success" logs/*/log_*.log 2>/dev/null

常见误判场景

现象原因正确做法
终端无新输出,以为在运行Rank 0 的 tee 管道已关闭或缓冲未刷npu-smi或查 rank 1-7 日志
Rank 0 日志没有 "model run success"tee 截断查 rank 1 的日志
sleep 120后还没完成模型大/首次编译慢npu-smi判断存活,再看 HBM 变化判断阶段
grep 日志找不到关键字进程 crash 了没写 successnpu-smi确认进程不在 → 查tail -20 log_*.log看错误

Agent 工作模式

  1. 启动推理bash infer.sh,前台或run_in_background
  2. 不要盲目 sleep——用run_in_background等通知,或前台直接看输出
  3. 10 分钟无进展 → 主动排查:推理启动超过 10 分钟仍无model run success输出,不要继续等待,按「推理卡住诊断」流程排查(npu-smi→ 判断状态 → 分流)
  4. 超时后第一步查npu-smi,不要先查日志
  5. 检查完成时查 rank 1(不是 rank 0)——rank 1 日志完整无截断
  6. 一次检查多个信号npu-smi+tail log_1.log+grep "model run success" log_*.log并行执行

总结

NPU 推理运行时错误的排查本质是一场"缩小范围"的游戏:先用npu-smi定性(crash / 卡住 / 通信等待),再用torch.npu.synchronize()检查点二分定位到具体算子,随后对照算子约束速查表确认根因,最后从四类修复模式中选择策略并完成六项验证。仓库中的 .agents/skills/model-infer-runtime-debug/SKILL.md 将这套方法论沉淀为可复用的 Agent 技能,配合 executor/core/config/comm_manager.py(通信组管理)、executor/scripts/function.sh(启动协议与残留进程防护)、executor/model_loader/weight_utils.py(权重加载)以及 models/deepseek_v4/models/modeling_deepseek.py(MoE 算子路径与回退)等源码实现,开发者可以在实际故障面前快速定位根因、完成修复并形成可复用的排查经验。

【免费下载链接】cann-recipes-infer本项目针对LLM与多模态模型推理业务中的典型模型、加速算法,提供基于CANN平台的优化样例项目地址: https://gitcode.com/cann/cann-recipes-infer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询