博客算法工具链报错后怎样清理并继续推理?一次 J6 UCP 会话恢复验证

报错后怎样清理并继续推理?一次 J6 UCP 会话恢复验证

Sam Hui2026-10-26
14
0

分类程序跑出第一张图片时,很容易觉得工作已经完成。但把模型留在内存里连续处理以后,还有另一个问题:任务有没有逐帧释放?发生错误时,已经创建的模型、缓冲和任务怎样收尾?下一张正常图片还能不能继续运行?

前面的性能实验主要比较模型常驻与重新加载。这次我沿用同一个会话实现,增加了一份资源与恢复探针,在同一个进程内循环正常输入、制造分阶段失败、执行清理,再重新加载和运行正常输入。

首轮执行一万次正常推理和八十次故障后恢复,随后用三个新进程各复验五万次推理和八十次恢复。合计 16 万次正常推理的原始输出均与参考一致,320 次指定故障之后都能清理资源、重新打开模型并完成正常推理。

这些推理循环使用三十份固定输入,并非识别了 16 万张不同照片。我要检查的是:同一个进程连续工作时,任务有没有收尾;中途出错后,是否必须把整个程序关掉重来。

一、为什么要在同一个进程里测试?

如果每次错误后退出程序,再启动一个新进程,操作系统会替程序回收很多资源。这样只能说明重新启动可以运行,未必检查到会话实现是否正确释放了自己的资源。

首轮与三轮复验各启动一个独立的 recovery_probe 进程;每轮内部的故障、清理和恢复均在同一进程中完成,不通过退出程序回收资源。

正常执行仍使用固定 ResNet18 HBM、三十份正常 INT8 输入和已经核对过的原始输出参考。每次推理结束都比较 1000 个 INT32 元素对应的 4000 字节有效输出,并在释放任务后检查任务句柄是否已经清空。

环境为J6E MatrixB、OE 3.9.1、UCP 3.15.8 和 HBRT 4.11.11,程序由官方 GNU 12.2 ARM 工具链构建。实验使用前一轮检查过的进程内运行库日志过滤。本环境默认会输出 allocator ERROR;本轮采用日志过滤设置,相关报错的原因仍未查清,因此下面报告的是输出一致性和指定失败路径的恢复结果。

本轮程序处理的是已加载到内存的固定 tensor,不重复 JPEG 解码和图片绘制,检查范围集中在 UCP 会话与失败清理。

二、先把正常连续运行检查清楚

程序一次性读取三十份正常输入和参考输出,打开模型和输入输出缓冲,先预热一百帧,然后循环执行一万帧。

正常帧仍然执行输入缓存同步(clean)、创建任务、提交、等待、输出缓存失效处理(invalidate)、raw 比较,以及释放任务。会话保持加载,任务按帧释放。

每一千帧记录一次 RSS 和文件描述符数量;逐帧时间记录先保存在内存,结束循环后再写 CSV。全部一万帧的 raw 与对应参考一致,帧结束时任务句柄也均已清空。

连续运行观察

首轮记录

计量帧数

10000,另外预热 100

正常输入

30 份循环使用

有效 raw 比较

10000/10000 一致

帧结束任务句柄

10000/10000 已清空

十个 RSS 采样点

均为 24320 kB

十个 FD 采样点

均为 18

RSS 是进程驻留内存,FD 是进程打开的文件描述符数量。本轮日志和结果文件也会占用 FD,计时记录也占用内存,所以这里关注连续采样是否增长,不把这些数值直接解释成模型内存大小。

三、四种错误,分别发生在哪个阶段?

四种失败分别发生在加载模型、核对接口、读取输入和检查结果时,覆盖了不同的资源状态。

故障

发生阶段

清理前已经有什么

模型文件不存在

模型初始化

初始化尚未成功

把 YOLO HBM 交给固定分类会话

查询输入输出数量

packed model 已加载,但接口不匹配

输入文件只有一个字节

模型与缓冲准备后读文件

模型、输入输出缓冲已准备

给完成的任务故意提供错误 raw 参考

等待完成、读取输出后核对

模型、缓冲和已完成任务仍在

最后一种很重要。正常路径是在 raw 比较之后释放任务;如果比较失败直接抛异常,任务释放语句就不会走到。异常清理必须接住这个已经完成、但还未释放的任务。

故意提供错误参考,只改参考字节,不改模型输入,也不让硬件任务失败。它检查的是“任务已经成功,后面的应用检查失败”时怎样清理。

每次探针还核对异常原因,不能拿任意一个报错都算故障测试成功。例如接口错误必须来自输入输出数量检查,参考错误必须来自 raw 比较。

alt text

图1|故障参考使程序在 raw 核对处抛异常;此时任务已经完成,异常路径仍需释放任务,再释放缓冲与模型。

四、清理以后,立即恢复一次正常推理

每个故障之后,程序调用同一会话的清理函数,再检查应用保存的资源句柄是否清空。随后重新打开正确分类模型,执行一个正常输入,核对 raw,关闭会话。这个过程不更换 PID。

上面展示的是实验结构。实际 C++ 实现还记录每次异常信息、清理前是否仍持有任务,以及恢复后的 raw 核对结果。

下表是首轮四类故障各二十次的记录;后续三个进程分别重复相同的八十次故障与恢复,合计三百二十次。

故障

次数

符合预期的失败

正常推理恢复

清理前仍持有完成任务

缺失模型

20

20

20

0

模型接口不匹配

20

20

20

0

输入截短

20

20

20

0

完成任务后参考核对失败

20

20

20

20

合计

80

80

80

20

那二十个任务并没有靠进程退出释放:探针先检查到句柄仍在,清理成功后继续在同一进程执行正常推理。这是本轮比“报错后重新启动成功”多检查的一步。

五、内存没回到最初值,应该怎样解释?

首轮八十次恢复后的 RSS,从首次 25280 kB 变为最后 25408 kB,增加了 128 kB。最后二十个恢复采样点均为 25408 kB;对应 FD 数始终为 15。程序结束后,BPU 用户列表为空。
alt text

图2|第一段按每1000帧采样;第二段在故障清理并完成正常恢复后采样。RSS、FD包含探针自己的数据结构与结果文件。

这 128 kB 值得保留,但仅凭 RSS 曲线还无法判断来自哪里:运行库缓存、分配器、动态库和探针自身都可能影响它。后段平台说明本轮没有持续逐次上涨,进一步定位泄漏仍需更细的分配记录和更长时间观察。

为避免只有一次观察,我随后用三个新的进程各补跑五万帧和八十次失败恢复。三轮共十五万帧,raw 逐帧一致;共二百四十次恢复也全部完成。

复验轮次

常驻帧数

故障后成功恢复

常驻阶段 RSS 范围,kB

恢复后 RSS 范围,kB

1

50000

80/80

24320—24320

26240—26368

2

50000

80/80

24320—24320

26240—26368

3

50000

80/80

24320—24384

26304—26432

三轮常驻阶段 FD 均为 18,恢复后均为 15;各轮最后二十个恢复采样点的 RSS 保持平台。第三轮常驻 RSS 增加了 64 kB,这个变化也保留在表中。扩大帧数还会增加探针自己的计时记录,不能直接用不同配置的 RSS 总量比较模型内存。

alt text
图3|三轮分别使用新的 PID;同一轮内故障与恢复保持同一 PID。曲线包含探针自身,不作为运行库内部泄漏归因。

合并首轮与三个独立进程的复验,本次计量了 16 万正常帧和 320 次故障后恢复,不含预热与小规模试跑。正常 raw 均与各自固定参考一致;四条指定失败路径能够清理并恢复,采样的 FD 没有逐次增长,RSS 在各轮观察末段保持平台。这些结果适合作为工程回归的一部分。

六、这份探针怎样复用?

recovery_probe.cpp 复用已有分类会话,把错误注入和恢复逻辑放在独立程序里。工程中保留源码、ARM 构建命令、逐帧 CSV、资源采样、异常记录和运行汇总。下面是本轮完整板端工程的调用方式:

这条命令依赖本轮完整工程、三十份固定输入与参考,以及对应运行库,不是下载单个源码即可执行的独立入口。第三个模型参数用来触发“分类会话拿到检测接口”的错误。

迁移到自己的程序时,可以先照着四个阶段列出资源状态:模型尚未打开、模型已打开、缓冲已分配、任务已完成。每种失败后都执行一次正常推理,并同时检查结果和句柄。相比直接照抄次数,这份阶段表更能帮助找到遗漏的释放路径。本轮覆盖的是四类应用侧失败及任务完成后的清理,尚未测试驱动故障、执行中超时或强制取消。

这轮工作让我把“跑通”再向实际工程推进一步:不仅看正常结果,还看失败发生时已经持有什么、怎样释放,以及释放后能不能继续工作。以后修改任务顺序、缓存操作或资源管理,这份探针可以和图片回归工具一起保留。

参考资料

  • OE 3.9.1 模型推理应用开发指导,以及对应 UCP 任务、缓存内存与模型接口。
  • 本轮 resident_session.hpp、recovery_probe.cpp、首轮一万帧及八十次恢复,以及三个新进程各五万帧的复验记录。
算法工具链
社区征文征程6
评论0
0/600