系列目录 · 工程与交付 · Read in English
早上一次编译成功,下午改了模型再编译失败。自动化脚本检查输出文件仍然存在,于是把上午的模型送去运行。最后大家围着下午的新图讨论错误输出,调查对象却从一开始就拿错了。
这个场景说明了一个关于产物契约的反例:文件存在,不等于本次操作成功地产生了它。
输出参数首先是一个语义承诺
命令行里的 --output 看起来只是字符串,实际决定了整个工具如何组织状态。如果有时表示目录,有时表示最终文件,有时又只影响中间 IR,调用者就很难写出可靠脚本。
一种清晰的教学接口是:
1 | compile-model --input network.onnx --output result/ |
它承诺最终模型位于 result/ 下;调试选项只决定是否额外保留中间文件,而不改变“最终模型是什么”。
所阅读的演进把原本分散的输出选项收敛到一个必选目录参数,拒绝把模型文件路径误传为目录,同时把被移除的选项保留为可识别的错误入口,给出迁移提示。这种处理比让旧参数静默失效更容易诊断。
调试模式和普通模式应共享什么
普通模式通常希望目录干净,调试模式希望看到分阶段 IR、权重和展开后的模型目录。两者对保留文件的要求不同,对编译语义的要求却应尽可能一致。
可以把产物分成三类:
| 类别 | 例子 | 生命周期 |
|---|---|---|
| 最终交付物 | 可加载模型 | 成功后由用户保留 |
| 排障证据 | 各阶段 IR、映射信息 | 调试模式保留 |
| 临时工作文件 | 转换临时文件 | 由本次调用管理 |
危险在于把第三类散落到当前工作目录,或者清理时把第一类、第二类一起当垃圾。更合理的流程是在明确的工作目录中运行转换,必要时保留证据,并只清理工具自己管理的生成目录。
但一个固定名字的生成目录仍然可能与用户文件冲突。因此输出目录的用途必须说明清楚;并发任务也不能不加隔离地共用同一个目录。代码里有清理函数,不代表清理边界天然正确。
为什么改变工作目录会弄丢输入
引入临时目录以后,一个很常见的回归是相对输入路径失效。原来从项目根目录运行时,data/model.onnx 能找到;切换到临时工作目录以后,它指向另一处。
所以在改变执行目录之前,应把需要保持身份的输入位置解析清楚。模型引用的外部权重也值得单独验证,不能假设主文件路径绝对化就自动解决所有加载器的相对路径规则。
输出路径同样应有明确的解析起点:是相对于用户调用时的目录,还是相对于临时目录?读者应在看见命令时就能预测结果,不必理解内部如何调用多个子进程。
所审阅的修改正是对模型、量化文件和输出目录做了相应路径处理,并把调试产物集中到输出目录。它减少了路径含义的漂移,却不能据此断言所有包含特殊字符的路径和外部权重组合都已测试通过。
成功判据不能只是一句日志
进程退出码、最终文件、文件可解析性和运行精度,是逐层增强的证据。
- 退出码为零:程序认为流程完成。
- 最终文件存在:预期位置出现了文件,但可能是旧文件。
- 文件属于本次任务且可解析:产物身份和结构更可信。
- 实际执行结果正确:进一步验证模型语义。
- 性能符合预期:又是另一组实验。
历史修改检查了预期最终文件是否存在,文档也提醒失败后旧模型可能仍保留。应当忠实保留这个边界,而不能把它写成“已经彻底解决旧产物问题”。
如果进一步设计,可以为每次任务建立独立工作目录,成功验证后再发布结果,并记录任务标识或输入摘要。若采用临时文件后重命名,还要考虑同一文件系统条件、并发覆盖策略以及读者看到结果的时刻。这些是建议,不是原差异已经实现的保证。
为什么保留一个旧入口,却删掉它的大部分代码
同一条编译管线常常有多个历史脚本。复制一份流程来兼容旧命令,短期方便,长期会产生两个略有不同的参数集合、不同的清理逻辑和不同的 bug。
一种更轻的兼容方法是让旧入口只负责提示弃用,然后转交给唯一主入口。这样修复集中在一处,兼容层也不会悄悄维护另一套真实行为。
代价是需要认真定义参数兼容性。入口名字可以继续存在,但旧选项不一定还能保持原义;此时应明确拒绝并给出替代方式,而不是为了“兼容”猜测用户意图。尤其涉及输出位置和自动清理时,猜错比报错危险得多。
一个真正针对输出契约的测试矩阵
下面是建议的验证方法:
| 情况 | 应检查的行为 |
|---|---|
| 空输出目录,普通模式 | 生成本次最终模型,临时产物按约定清理 |
| 空输出目录,调试模式 | 同时保留阶段证据,最终模型仍存在 |
| 旧最终模型已存在,随后编译失败 | 失败状态不会被文件存在性掩盖 |
| 把文件名传给目录参数 | 尽早给出明确诊断 |
| 使用已移除参数 | 提示如何迁移,而非忽略 |
| 相对输入,临时工作目录 | 仍读取预期模型及关联数据 |
| 两任务同名或共用目录 | 有明确隔离或拒绝规则 |
| 子进程在不同阶段失败 | 保留最有用的诊断,避免只剩包装层异常 |
测试不要只检查“有没有中间文件”。还应对比普通与调试模式的语义结果,避免调试模式意外改变优化设置,却被误当成只是保留文件。
日志输出也要照顾自动化。若用户把日志通过管道保存,外层 shell 的退出状态需要正确反映编译器失败;文档中的可复制示例应该包含这类运行契约,而不只是最短的成功命令。
简化接口不是减少参数数量比赛
一个参数如果隐藏了三种不同含义,表面很简洁,实际很难用。清晰接口的目标是降低调用者需要记住的隐含规则。
同样,清理更多文件未必更可靠。对排障来说,失败时保存关键 IR 可能比干净目录更重要;对交付来说,过多中间文件又容易混淆用户。把这两种需求通过显式模式表达,比让调用者猜哪些文件可以删除更合理。
把这个区别写清楚,才不会让一篇讨论可靠性的文章,自己先制造新的可靠性错觉。
If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !