Skip to content

Device Error Codes_zh

ChaoZheng109 edited this page Jul 3, 2026 · 1 revision

设备错误码排查指南(Worker 直接运行场景)

来源:本页镜像自仓库文档 docs/troubleshooting/device-error-codes_zh.md。 请以仓库版本为准并在其中修改,wiki 页随后同步,避免两处内容漂移。

本文适用于通过 Worker.run(...) 直接运行 tensormap_and_ringbuffer (以下简称 trb / rt2)运行时的上层调用方。文中逐一说明每个设备侧错误码的 含义、触发场景,以及遇到该错误码时应如何定位问题。


1. 错误如何从设备侧传递到上层调用方

trb 运行时将编排(orchestrator)与调度(scheduler)两个环节均放在 AICPU 上执行。 一旦设备侧出现致命情况,运行时会将对应的错误码 latch(锁存) 至设备共享内存 (SM)头部;host 侧随后在 validate_runtime_impl 收尾阶段读回并打印。因此,上层 调用方可观测到的信息分为以下两部分。

1.1 抛出的异常

当返回码非 0 时,Worker.run(...) 内部会抛出如下异常:

RuntimeError: run failed with code <rc>

其中 <rc> 的具体含义取决于运行环境:

环境 <rc> 的含义
sima5sim / a2a3sim 直接等于运行时状态 -N,其中 N 即下表所列错误码。例如 -1 表示 SCOPE_DEADLOCK,-100 表示 SCHEDULER_TIMEOUT。
onboard(真实硬件) 常被 CANN 看门狗(op-execute 超时 / stream-sync 超时)提前触发,从而被掩盖为通用的 507018。此时 <rc> 不再等于 -N不可直接作为错误码解读。

关键提示:在 onboard 环境下,507018run_prepared failed / aclrtSynchronizeStreamWithTimeout failed)是一个泛化的 host 侧错误码, 多种不同的设备侧机制最终都会表现为这同一个码。因此,不应仅凭 507018 便判定为"死锁"或"内存溢出"——真正的设备错误类别记录在下文所述的日志行中。

1.2 自诊断日志行(两种环境均会输出)

无论 sim 还是 onboard,只要设备侧锁存了错误码,host 日志中都会出现如下一行:

[ERROR] PTO2 runtime failed: orch_error_code=<O> sched_error_code=<S> runtime_status=<R>
  • orch_error_code 非 0,表示错误属于 编排错误(1–11),详见第 3 节。
  • sched_error_code 非 0,表示错误属于 调度错误(100+),详见第 4 节。
  • 二者至多有一个非 0;runtime_status 为统一归并后的 -N

sched_error_code=100(调度器检测到无前进进展并超时),日志中还会紧接着 输出一行子分类及定位信息,使调用方无需翻阅设备日志即可判断停滞点:

[ERROR] PTO2 scheduler timeout sub_class=<S1..S5> (detail=N) completed=c/t \
        running=r ready=k waiting=w orch_done=d stuck_task_id=id stuck_core=core

排查的第一步始终是:在 host 日志中检索 orch_error_code=sched_error_code=sub_class=,先完成错误归类,再对照下文的表格逐项定位。即便 onboard 环境 将异常码掩盖为 507018,上述几行日志仍会照常打印。


2. 错误码总览

名称 简要含义 大致归属
0 NONE 无错误
1 SCOPE_DEADLOCK 编排 单个 scope 内的任务数达到任务窗口上限,slot 无法回收 用户编排
2 HEAP_RING_DEADLOCK 编排 ring 的任务 slot 与 heap 双双耗尽,无法继续提交任务 用户编排 / 配置
3 FLOW_CONTROL_DEADLOCK 编排 任务窗口被阻塞但 heap 未满(典型场景为同一 ring 上的嵌套) 用户编排
4 DEP_POOL_OVERFLOW 编排 依赖(fanin)溢出池无法分配到条目 用户编排 / 配置
5 INVALID_ARGS 编排 编排 API 收到非法参数 用户编排
6 (已废弃) 旧的 per-task fanin 溢出,现已并入 4
7 REQUIRE_SYNC_START_INVALID 编排 require_sync_start 请求的 block 数超过该类核总数,必然死锁 用户编排
8 TENSOR_WAIT_TIMEOUT 编排 等待 tensor 数据就绪超时(生产者未完成 / 消费者未释放) 用户 kernel / 编排
9 EXPLICIT_ORCH_FATAL 编排 编排代码主动调用 rt_report_fatal() 上报错误 用户编排(主动)
10 SCOPE_TASKS_OVERFLOW 编排 scope 任务记录缓冲被填满(实践中 ring 通常先满,几乎不可达) 运行时内部
11 TENSORMAP_OVERFLOW 编排 tensormap 条目池被阻塞,last_task_alive 无法前进 运行时内部 / 极端规模
100 SCHEDULER_TIMEOUT 调度 调度器检测到无前进进展并超时(详见子分类 S1–S5) 视子分类而定
101 ASYNC_COMPLETION_INVALID 调度 异步完成条件非法(completion_type / counter 地址错误) 用户 kernel(async)
102 ASYNC_WAIT_OVERFLOW 调度 异步等待列表被填满(在途 async 完成数超过上限 64) 用户 kernel(async)
103 ASYNC_REGISTRATION_FAILED 调度 收到非法的 async 完成消息类型(通常已被 102 提前拦截) 运行时内部

编排错误(1–11)由 AICPU 编排线程检测,写入 orch_error_code,以先写入者为准。 调度错误(100+)由调度线程检测,写入 sched_error_code,同样以先写入者为准。


3. 编排错误(1–11)排查细则

此类错误几乎都与资源容量与编排结构之间的匹配关系有关。共同的调参入口是 CallConfig.runtime_env 中的三项 ring 配置(也可通过同名环境变量 PTO2_RING_TASK_WINDOW / PTO2_RING_HEAP / PTO2_RING_DEP_POOL 覆盖):

  • ring_task_window:每个 ring 的任务槽数(须为 2 的幂,且 ≥4)。
  • ring_heap:每个 ring 的 heap 字节数(≥1024)。
  • ring_dep_pool:每个 ring 的依赖溢出池容量(≥4)。

问题在于:这三项该调到多大,不应凭猜测。错误码本身只告诉调用方"某类资源已耗尽", 却不指出是哪一类资源、由哪个 scope 触发。下述 scope_stats 工具正是用于补齐这一 信息,进而将容量调整到合适大小。

用 scope_stats 定位资源瓶颈(错误码 1 / 2 / 4 通用)

scope_stats 是 trb 运行时专属的 opt-in DFX 功能:它为编排中的每一个 PTO2_SCOPE 区域记录四类资源——task window 槽位、heap 字节、dep pool 条目、tensormap 条目——的 高水位,从而回答"是哪一类资源逼近容量、由哪个 scope 驱动"这一问题。这恰好是错误码 1、2、4 定位所需的信息,因此三者共用同一套排查手段。

  • 开启方式(Worker 直跑):在 CallConfig 上置位 enable_scope_stats,并指定 输出目录 output_prefix

    from simpler.task_interface import CallConfig
    
    cfg = CallConfig()
    cfg.enable_scope_stats = True
    cfg.output_prefix = "outputs/my_run"   # 输出根目录
    worker.run(callable, args, cfg)
  • 输出位置<output_prefix>/scope_stats/scope_stats.jsonl(NDJSON 格式)。 首行为运行元数据,其中 task_window_max / heap_max / dep_pool_max 为各 ring 的容量上限、tensormap_max 为 tensormap 容量;其余每行是某个 scope 在 begin 或 end 边界处的一次采样。

  • 对失败的运行同样有效:即便本次运行以 fatal 收场,元数据行会标记 "fatal": true, 且该点之前落盘的记录仍然保留。因此可直接对触发错误码 1 / 2 / 4 的 workload 开启 scope_stats,观察究竟是哪一类资源、在哪个 scope 撞上了上限。

  • 快速判读:某个 scope 的资源高水位等于该 scope 在 end 边界与 begin 边界之间 的差值(即 task_window / heap / dep_pool 各自的 *_end − *_start)。将其与 元数据中对应 ring 的 *_max 相比,占比最接近 1 的那一类资源即为瓶颈所在。

  • 可视化报告:执行 python simpler_setup/tools/scope_stats_plot.py <output_prefix>/scope_stats/scope_stats.jsonl 会生成自包含的 HTML 报告,其中的 Top Peaks 表直接列出每类资源最接近容量的 ring 与 scope,无需手动解析 JSONL。

  • 据此调参:定位到瓶颈资源及其所在 scope 后,按下表调整 CallConfig.runtime_env 中的对应项,或从结构上拆分对应的 scope:

    瓶颈资源 对应错误码 调整方式
    task window 1 调大 ring_task_window,或将该 scope 拆分为多个小 scope
    heap 2 调大 ring_heap,或减小单任务的参数 / 中间 tensor 占用
    dep pool 4 调大 ring_dep_pool,或减少单任务的显式依赖数量

工具的完整用法、报告字段与内部机制详见 docs/dfx/scope-stats.md

1 — SCOPE_DEADLOCK

  • 触发条件:同一 scope 内提交的任务数 ≥ 任务窗口容量。由于 scope 内任务持有的 fanout 引用要到 scope_end 时才会释放,slot 无法回收,导致整个 ring 死锁。
  • 排查步骤
    1. 统计发生停滞的 scope 内共提交了多少任务,确认其是否 ≥ ring_task_window; 开启 scope_stats(见上文"用 scope_stats 定位资源瓶颈")可直接读出各 scope 的 task window 高水位,无需人工统计。
    2. 或调大 ring_task_window,或将大 scope 拆分为多个小 scope,使 slot 能在 scope_end 处及时回收。

2 — HEAP_RING_DEADLOCK

  • 触发条件:ring 的任务分配器报告分配失败——任务 slot 与 heap 空间同时耗尽, 无法继续提交任务。
  • 排查步骤:此类问题通常源于单任务的参数或中间 tensor 占用过大,或 ring_heap 配置过小。请先查看 host 日志中打印的 Ring buffer sizes: task_window=... heap=... dep_pool=...,对照单个 scope 的实际 需求,相应调大 ring_heap。开启 scope_stats(见上文"用 scope_stats 定位资源 瓶颈")可进一步定位到 heap 高水位究竟落在哪个 scope,从而据实调整容量。

3 — FLOW_CONTROL_DEADLOCK

  • 触发条件:任务窗口被阻塞、但 heap 未满(典型场景为同一 ring 上的嵌套结构: 外层任务等待内层任务,而内层任务又无法获得 slot)。ring 分配器在空转约 500ms (由 PTO2_ALLOC_DEADLOCK_TIMEOUT_CYCLES 控制)仍无法回收 slot 后,报出此码。
  • 排查步骤:检查是否在同一 ring 上进行了自依赖或嵌套提交;可调大 ring_task_window,或将嵌套结构改为跨 ring 提交。此码与错误码 1 的区别在于: 错误码 1 为单个 scope 被填满,错误码 3 为流控或嵌套导致的阻塞。

4 — DEP_POOL_OVERFLOW

  • 触发条件:某个任务的显式依赖(fanin)数量过多,溢出池无法分配到条目。
  • 排查步骤:定位挂载了超大 set_dependencies() 列表的任务;减少单任务依赖数量, 或调大 ring_dep_pool。开启 scope_stats(见上文"用 scope_stats 定位资源瓶颈") 可读出各 scope 的 dep pool 高水位,据此判断该调大容量还是精简依赖连线。

5 — INVALID_ARGS

  • 触发条件:编排 API 收到非法参数。常见来源包括:
    • alloc_tensors() 未提供任何 TensorCreateInfo,或提供了非 output 的创建信息;
    • 在手动 scope 内再开启自动 scope(嵌套非法);
    • set_dependencies() 引用了不存在或尚未提交的 task id;
    • submit_*L0TaskArgs 自带 error 标志。
  • 排查步骤:此类错误属于编排代码缺陷,而非容量问题,调整 ring 大小无效。 应回到 orchestration .cpp 中,检查上述 API 的调用参数。

7 — REQUIRE_SYNC_START_INVALID

  • 触发条件:任务调用了 require_sync_start(),且请求的 block_num 超过目标核 类型的物理核总数(对 AIV 为 AIV 总数 total_aiv_count;对 MIX / AIC 为 cluster 总数 total_cluster_count,即每个 cluster 一个 AIC)。判定基准是该类核在硬件上的 物理总数,而非当前空闲核数——sync-start 允许等待正在忙碌的核执行完毕后再一同 起跑,因此只要 block_num 不超过物理总数即为可满足;唯有当它连全部核都无法容纳时 才必然死锁,运行时据此在提交阶段直接拒绝。
  • 排查步骤:将该任务的 block_num 降至不超过目标核类型的物理核总数;或重新确认 是否确有 sync-start 的必要。

8 — TENSOR_WAIT_TIMEOUT

  • 触发条件:等待某个 tensor 数据就绪超时(默认 15s,由 PTO2_TENSOR_DATA_TIMEOUT_MS 控制,且已做频率缩放)。原因或为生产者任务始终未完成(kernel 卡死或执行过慢), 或为消费者未释放 fanout 引用计数
  • 排查步骤
    1. 首先确认是否存在某个生产 kernel 真正 hang(可参考错误码 100 的子分类 S1)。
    2. 核对依赖连线是否正确:消费者确实声明了对该 tensor 的依赖,且会正常退出并释放 引用。
    3. 若 kernel 仅为执行缓慢而非真正死锁,可临时调大 PTO2_TENSOR_DATA_TIMEOUT_MS 加以验证。

9 — EXPLICIT_ORCH_FATAL

  • 触发条件:编排代码主动调用了 rt_report_fatal(PTO2_ERROR_EXPLICIT_ORCH_FATAL, ...)。这是提供给编排作者的主动 上报机制,不依赖任何运行时资源的耗尽。上报后,所有后续编排 API 调用都会短路为 no-op。
  • 排查步骤:此错误由编排代码主动埋设,依据 rt_report_fatal 调用处的 message 自行排查即可。

10 — SCOPE_TASKS_OVERFLOW / 11 — TENSORMAP_OVERFLOW

  • 正常情况下不应出现。错误码 10 在实践中不可达(ring 通常先满,会先报出错误码 1 或 3);错误码 11 需要 tensormap 条目池(编译期上限 65536)被海量条目填满,且存在 卡住不回收的生产者。
  • 排查步骤:若确实命中,通常意味着运行时内部不变量被破坏(或属于极端规模的 workload)。请保留设备日志并联系运行时维护者——此类问题无法通过常规调参解决。

4. 调度错误(100+)排查细则

在展开错误码 100 之前,先说明 onboard 环境下的超时竞争——它决定了同一次调度 停滞最终会被观测成 -100(带 sub_class)还是被掩盖成 507018,是理解后续排查 的前提。

关于 onboard 环境下的超时竞争:调度超时(PTO2_SCHEDULER_TIMEOUT_MS)、 STARS op-execute 超时(PTO2_OP_EXECUTE_TIMEOUT_US,默认约 45s,触发 HandleTaskTimeoutaicpu-sd)、host stream-sync 超时 (PTO2_STREAM_SYNC_TIMEOUT_MS)三者之间存在竞争,最终由最先触发的一方决定 呈现结果。最先触发的一方决定了最终观测到的是 -100sub_class,还是被掩盖 的 507018一次 45s 的 op-execute kill 并不等同于死锁,也可能仅仅是 kernel 执行时间过长。若需测量设备上的真实耗时,可调大 PTO2_OP_EXECUTE_TIMEOUT_US

这三个超时的值在 Worker.init() 时即被锁定,之后每次 run() 都不再更新。 在 Worker 直跑通路上,一次 Worker.run(...) 只会在已有的持久流上重新拉起 AICPU / AICore 执行 kernel 并做一次流同步;AICPU / AICore 流以及这三个超时的 配置都是 Worker.init() 阶段的一次性动作:

  • op-execute 超时通过 aclrtSetOpExecuteTimeOutV2 在设备首次 attach(即 init)时设置一次,是 STARS 的设备级设置,后续 run() 不再改动。
  • stream-sync 超时的取值在 init 时从环境变量解析并缓存;此后每次 run() 的流同步都以该缓存值为参数发起,但传入的始终是同一个固定值,并非每次重新配置。

因此,在同一个 Worker 上多次 run() 之间修改 PTO2_OP_EXECUTE_TIMEOUT_US / PTO2_STREAM_SYNC_TIMEOUT_MS 不会生效——这两个 环境变量只在 init() 时读取一次。若需以不同的超时取值做对比(例如借调大 op-execute 超时来区分"真正死锁"与"仅仅是执行缓慢"),必须为每个取值重新创建 Worker(重新 init()),或在独立进程中运行。

100 — SCHEDULER_TIMEOUT(重点在于查看子分类)

  • 触发条件:调度器在约 PTO2_SCHEDULER_TIMEOUT_MS 的时间内检测不到任何前进 进展。这是设备停滞的汇聚性错误码;运行时会进一步将其细分为 S1–S5 并打印 定位信息。
  • 首要动作:读取 host 日志中的 sub_class= 一行,按下表逐项对照:
子分类 含义 主要原因 排查方式
S1 running-stalled 有任务在核上运行但始终无法完成 AICore kernel hang / kernel 执行时间过长 查看 stuck_task_id / stuck_core,定位到具体 kernel,排查死循环或超长计算
S3 ready-but-all-idle 所有核均空闲、存在 fanin 已满足的就绪任务、却未被派发 派发循环或 sync-start 起跑门问题 多属运行时侧;检查是否使用了 sync-start 且配置异常
S4 dependency-deadlock 仅剩 WAIT 状态任务,fanin 永远无法解除 依赖图连线错误或成环 通过公开 API 几乎无法构造(详见下文);多为依赖连线缺陷
S5 orchestrator-starvation 已提交任务均已完成,但编排尚未结束,调度器空转 编排上游被卡住 检查编排线程是否在某处自旋或等待
unknown 前提或记账不变量被破坏 运行时内部缺陷 / 内存损坏 保留设备日志,上报运行时维护者

实践中可复现的主要为 S1 与 S3。 S4、S5、unknown 均为防御性标签:当前公开 API 无法构造出对应的活状态(例如 set_dependencies 只能引用已提交任务,无法表达环; 被卡住的生产者其状态为 RUNNING,归入 S1 而非纯 WAIT)。若确实观测到 S4 / S5 / unknown,几乎可以断定为运行时内部缺陷,请予以上报。

  • S1 的典型排查:host 侧的 sub_class= 一行会给出 stuck_task_idstuck_core。凭此 task id 回到编排代码,找出对应的 kernel,重点排查:死循环、 等待一个永远不会到达的信号,或单纯因计算量过大而触发超时。可临时调大 PTO2_SCHEDULER_TIMEOUT_MS,以区分"真正死锁"与"仅仅是执行缓慢"。

  • 借设备日志查"哪个核在跑哪个 kernel":当 stuck_task_id 还不足以定位时,把 日志级别提到最详细的 V0,设备日志(AICPU log)就会打印停滞时刻的任务快照, 形如:

    [STALL thread=0 idle_iterations=...] TASK ring=1 task_id=42 state=RUNNING \
        fanin_refcount=0/2 kernels=[aic:3 aiv0:7 aiv1:-1] \
        running_on=[owner_thread=0 cores=[core=5(AIC) core=6(AIV0)]]
    

    其中 kernels=[aic aiv0 aiv1] 是该任务三个子核槽位的 kernel id、cores=[...] 是 正在执行它的物理核号与子核类型——由此可直接把"卡住的任务"映射到"具体哪个 kernel、在哪些核上"。设置日志级别的方式:

    • Worker 直跑:在调用 worker.init() 之前将 simpler logger 提级—— logging.getLogger("simpler").setLevel("V0")。级别在 init() 时被快照并下发 设备,因此必须在 init() 之前设置(run() 之间再改不会生效)。
    • pytest / scene test:等价地传 --log-level v0

    V0 是最详细级(V0 最详细、V9 最精简、默认 V5),会一并打开派发路径的其他 诊断行。设备日志默认落在共享目录 ~/ascend/log/debug/device-<id>/,多进程混写、 难以辨认;请先用 ASCEND_PROCESS_LOG_PATH 环境变量把它重定向到本次运行专属的 目录(该目录须预先创建),再去读取——详见第 5 节第 4 步与 .claude/rules/running-onboard.md 的 "Device logs" 一节。

  • 通过 counters 判断completed=c/t(已完成 / 总数)以及 running=r ready=k waiting=w 三个计数,实际上正是子分类的判定依据,其优先级为 running > ready > waiting > 编排未完成。例如,running=1 必然对应 S1; running=0ready>0 对应 S3。

101 — ASYNC_COMPLETION_INVALID

  • 触发条件:异步完成条件非法——例如 COUNTER 类型的 counter_addr 为空、 completion_type 非法,或 SDMA 事件记录无效。其本质是 async 完成机制注册有误
  • 排查步骤:检查 kernel 中 register_completion_condition 及相关 async API 的 参数,尤其是计数器地址与 completion type 是否填写正确。

102 — ASYNC_WAIT_OVERFLOW

  • 触发条件:异步等待列表被填满(在途 async 完成数 ≥ 上限 64,即 MAX_ASYNC_WAITS)。 通常是注册了过多 async 完成,却未及时被 poll 或回收。
  • 排查步骤:减少单任务的在途 async 完成数;确认消费侧确实在轮询并推进这些完成。 另需注意:AICore 侧单任务的 condition 上限为 64(MAX_COMPLETIONS_PER_TASK), 超出该上限时会先报出错误码 102。

103 — ASYNC_REGISTRATION_FAILED

  • 触发条件:从 AICore 收到了非法的 async 完成消息类型(既非 CONDITION,亦非 TASK_NORMAL_DONE)。
  • 正常路径下几乎不可达:正常情况下会被错误码 102(condition 数超上限)提前拦截, 错误码 103 仅在 slab 被破坏或消息内部畸形时才会出现。若确实命中,通常意味着运行时 内部缺陷,请上报并保留设备日志。

5. 标准排查流程(速查)

  1. 获取异常码:查看 RuntimeError: run failed with code <rc>
    • sim 环境:<rc> = -N,直接对照第 2 节的表格。
    • onboard 环境:<rc>507018 时,不应就此止步,需继续向下排查。
  2. 获取自诊断日志行:检索 host 日志 grep -E "orch_error_code=|sched_error_code=|sub_class=" <运行日志>,随后:
    • orch_error_code=N(N∈1..11)→ 参见第 3 节。
    • sched_error_code=100 → 读取 sub_class= 一行,对照第 4 节的 S1–S5 表格。
    • sched_error_code=101/102/103 → 参见第 4 节对应小节。
  3. 区分"编排问题"与"运行时内部问题"
    • 错误码 1、3、4、5、7、8、9 及 100/S1 → 大概率属于用户的编排或 kernel, 按对应小节调整。其中 错误码 1 / 2 / 4 属于资源容量问题,可用 scope_stats DFX 定位瓶颈资源与所在 scope(见第 3 节"用 scope_stats 定位资源瓶颈"),再据实 调整 ring 容量。
    • 错误码 2 → 属于配置(ring 大小)问题,或单任务占用过大。
    • 错误码 10、11、103 及 100/S4、100/S5、100/unknown → 属于运行时内部问题, 请保留设备日志并上报。
  4. 需要更深层的设备真相时:设备日志(AICPU / CCECPU log)才是 ground truth。 在 onboard 环境下,请务必先将设备日志重定向到本次运行专属的目录,避免在共享目录 中依据 pid 或时间戳猜测文件——具体参见 .claude/rules/running-onboard.md 中 "Device logs" 一节(设置 ASCEND_PROCESS_LOG_PATH),以及该文件中关于 507018 的机制分类表。

6. 相关引用

  • 错误码定义:src/{arch}/runtime/tensormap_and_ringbuffer/common/pto_runtime_status.h
  • host 自诊断打印:.../host/runtime_maker.cppvalidate_runtime_impl
  • 子分类逻辑:.../runtime/scheduler/scheduler_cold_path.cppclassify_stall_reason
  • 端到端负向测试(各错误码的实际触发样例):tests/st/runtime_fatal_codes/
  • onboard 507018 机制分类与设备日志定位:.claude/rules/running-onboard.md
  • 本地超时默认值:docs/troubleshooting/local-timeout-defaults.md

Clone this wiki locally