3、日志文件定位:编译产物目录结构、日志文件命名规则、如何快速找到目标日志

说实话,很多刚接触NPU开发的同事,最头疼的不是写算子,而是找日志。

编译报错了,日志在哪?哪个文件才是关键信息?我见过有人盯着屏幕翻了几百行,结果发现看的是中间产物,根本不是报错日志。

这一章,我就带你把这个痛点彻底解决掉。

3.1 编译产物目录结构——你得知道东西放哪了

不同芯片厂商的NPU工具链,目录结构大同小异。我以最通用的模型说,你理解了这套逻辑,换平台也能快速上手。

一次完整的算子编译,产物目录大概长这样:

build/
├── ir/                 # 中间表示层
│   ├── xxx_ir.txt      # 原始IR
│   └── xxx_opt_ir.txt  # 优化后的IR
├── schedule/           # 调度方案
│   ├── default.sched   # 默认调度
│   └── tuned.sched     # 调优后调度
├── codegen/            # 代码生成
│   ├── kernel.s        # 汇编代码
│   └── kernel.c        # C代码(部分平台)
├── log/                # 日志目录(重点)
│   ├── compile.log     # 主编译日志
│   ├── pass.log        # 各pass执行日志
│   └── error.log       # 错误汇总
└── output/             # 最终产物
    ├── model.npu       # NPU可执行文件
    └── model.json      # 模型描述文件

我个人习惯,先看 log/ 目录。为什么?因为这里记录了编译的全过程,从IR解析到代码生成,每一步都有痕迹。

核心原则:先找 log 目录,再看 error.log,最后翻 compile.log。这个顺序能帮你节省至少一半时间。

3.2 日志文件命名规则——名字里藏着信息

日志文件不是随便命名的。每个名字都包含了关键信息:时间戳、编译阶段、算子名称、错误级别。

举个例子:

20250321_143205_conv2d_fp16_ERROR.log
│         │         │       │     └── 日志级别
│         │         │       └──────── 数据类型
│         │         └──────────────── 算子名称
│         └────────────────────────── 时间戳(时分秒)
└──────────────────────────────────── 日期

我在项目中遇到过一种情况:同事找了半天没找到某个算子的日志,结果发现是因为算子名称写错了。命名规则里用的是 conv2d,他搜的是 conv2D,大小写不匹配。

嗯,这里要注意:算子名称严格区分大小写,而且通常使用小写+下划线的格式。

日志文件名模式 含义 典型场景
*_INFO.log 普通信息日志 编译进度、pass执行成功
*_WARN.log 警告日志 性能可能下降、精度可能损失
*_ERROR.log 错误日志 编译失败、算子不支持
*_FATAL.log 致命错误日志 工具链崩溃、内存溢出
*_DEBUG.log 调试日志 需要详细跟踪时开启

小技巧:如果你不确定日志级别,直接搜 ERROR 或 FATAL 关键字。我一般用 grep -rn "ERROR" log/ 一把梭,比一个个翻文件快得多。

3.3 如何快速找到目标日志——实战方法论

你想想看,一个项目编译下来,日志文件可能有几十个。怎么快速定位到你要的那个?

我总结了三个步骤,你照着做,基本不会跑偏。

3.3.1 第一步:按算子名过滤

大多数NPU工具链支持按算子名生成独立日志。编译命令里加个参数就行:

# 假设你的工具链叫 npucc
npucc --target-op=conv2d --log-dir=./logs model.onnx

这样只会生成 conv2d 相关的日志,其他算子的日志不生成。省事吧?

我曾经在一个大模型项目里,编译一次生成200多个日志文件。用了这个参数后,直接降到3个。找问题快多了。

3.3.2 第二步:按时间戳排序

如果日志文件很多,按时间戳排序是最靠谱的。因为编译过程是顺序执行的,越晚生成的文件,越接近出错点。

ls -lt log/   # 按修改时间倒序排列

或者用 find 命令:

find log/ -name "*ERROR*" -exec ls -lt {} \;

这样你一眼就能看到最新的错误日志是哪个。

3.3.3 第三步:看关键行号

日志里通常会标注出错的行号。比如:

[ERROR] In file kernel.s, line 128: operand type mismatch

这个信息太重要了。你直接去 codegen/kernel.s 的第128行看,问题基本就锁定了。

注意:行号可能因为优化pass而偏移。比如IR优化后,代码行号会变。我建议你同时看优化前后的两份日志,对比着找。

3.4 知识体系总览

下面这张图,我把日志定位的核心逻辑画出来了。你一看就明白。

日志文件定位核心流程 1. 目录结构 先找 log/ 目录 2. 命名规则 算子名+时间戳+级别 3. 快速定位 三步法实战 log/ → error.log → compile.log 大小写敏感,下划线分隔 过滤→排序→看行号 核心原则:先看 error.log,再翻 compile.log 按算子名过滤 → 按时间排序 → 定位行号 实战中,80%的问题通过 error.log 就能定位

3.5 避坑指南

最后,分享几个我踩过的坑:

  • 日志被覆盖:多次编译默认会覆盖同名日志。我建议每次编译前加个时间戳目录,比如 --log-dir=./logs_$(date +%Y%m%d_%H%M%S)。
  • 日志级别不对:默认只输出 INFO 及以上级别。如果你需要 DEBUG 日志,记得加 --log-level=DEBUG。
  • 跨平台路径问题:Windows 和 Linux 的路径分隔符不同。我建议统一用正斜杠 /,避免转义问题。

我的习惯:每次编译完,第一件事就是 ls -lt log/*ERROR*。如果有 ERROR 文件,直接打开看。没有的话,再去看 compile.log。这个习惯帮我省了无数时间。

说白了,日志定位就是个熟练活。你按我这套方法练几次,以后看到编译报错,心里就有底了。

想要更多资料,联系可可