系列目录 · 工程与交付 · Read in English
“生成的汇编跟预期一样,所以代码生成器没问题。”
如果二进制编码器和汇编打印器是两条不同路径,这个结论少了一半。打印器可能正确显示某个枚举名字,而编码器把对应字段写错位置。反过来,机器码可能正确,只是调试汇编误导了排障者。
测试代码生成器,必须先问清楚自己正在观察哪一个对象。
三个输出层,各自会撒什么谎
一个典型发射器可能提供可读的伪指令、展开后的真实汇编,以及最终二进制。
1 | 算子请求 |
只看伪指令,会错过展开错误;只看真实汇编,会错过位域编码错误;只看二进制长度,又会错过“同样长但完全不同”的错误内容。
所阅读的测试从向量算子的配置加载和指令前缀检查,扩展到了张量计算、搬运与调试输出的伪汇编、真实汇编、二进制逐项对照。这种演进增加了被观察的维度,而不是简单多放一些字符串快照。
尤其需要准确说明:早期一些用例检查的是关键前缀与二进制词数,并非所有用例从一开始就逐位校验机器码。博客不能因为文件名字里有 golden,就自动赋予它不存在的证明能力。
选用例时,不要只挑最顺手的地址
配置块靠得很近时,可以用一个基址加短偏移访问。把参数块放远以后,立即数范围不足,发射器可能需要重新加载基址。
这给测试提供了一个自然边界:近距离路径与远距离路径都要覆盖。若只用方便手算的小地址,所有测试都走同一条短路径,另一半实现永远没有被审问。
教学上可设一个带符号偏移范围为 [-B, B-1] 的虚构指令。测试点不应只是 0 和 1,还应包括:
1 | -B-1, -B, -1, 0, B-1, B |
范围内检查是否使用预期相对寻址;范围外检查是否改用合法的基址加载策略。这比任意增加几十个随机地址更容易解释失败原因。
真实 ISA 的边界与编码格式应由其规范决定,不能直接使用本文的符号示例。
名字相同,字段是否同样有效
二输入算子还有向量—向量、向量—标量、标量—向量等路径。对减法而言,交换两边会改变语义;对某些配置格式而言,即使算子可交换,标量值位于哪个字段仍然重要。
有符号与无符号数据也应分开观察。一个打印出来相同的整数立即数,可能在编码与运行时被作不同解释。
因此测试矩阵应该来自语义分支:
| 维度 | 典型区分 |
|---|---|
| 操作数角色 | 向量、左标量、右标量 |
| 数据解释 | 有符号、无符号、不同位宽 |
| 配置距离 | 近偏移、远地址 |
| 输出形式 | 伪指令、真实指令、机器码 |
| 操作族 | 计算、搬运、调试、依赖控制 |
| 合法性 | 正常边界、刚好非法 |
测试数量不是目标;每个用例为什么与另一个不同,才是可维护性的来源。
负向测试到底是在测试谁
“传错参数会失败”听起来很朴素,但如果错误被拖到硬件端才发生,就很难再告诉用户哪个 shape 或 dtype 不合规。
阅读到的负向用例覆盖了输入输出形状不匹配、索引类型不符、索引长度与输出约定不一致、切片范围、reshape 元素数、矩阵乘内维和卷积通道等问题。
这些用例检查的是接口边界,不是证明每种合法输入都数值正确。二者各有用途:正向测试回答“允许的事情做对了吗”,负向测试回答“不允许的事情是否被及时、明确地拒绝”。
诊断也要检查到足以定位的问题类型。如果只接受任意异常,一次空指针或 unrelated failure 也可能让测试误通过。另一方面,把完整错误句子逐字锁死,又可能让措辞改进造成大量噪声。选择稳定的错误类别和关键上下文,通常比两个极端更实用。
为什么 golden 可能把错误永久保存
最危险的更新流程是:实现变了,测试失败,于是自动接受新的输出。
如果 expected 和 actual 都由同一套编码逻辑生成,两个结果相等只是证明函数对自己忠诚。测试并没有引入独立判断。
因此 golden 应有独立依据:人工解释过的小例子、独立解码器、明确的规范字段推导,或经过确认的参考执行。更新基线时,应说明哪些字段为什么改变,而不是只提交一块新十六进制。
可以用一个虚构的编码式演示检查思路:
1 | word = opcode | (destination << p) | (source << q) | flag |
若修改 source 只应影响某一位段,测试就可以额外检查其他位段保持不变。这类变形性质与固定基线互补,能够减少整体输出虽然变化但没人知道为什么的情况。
优化以后,逐字节相等还是正确要求吗
合法优化可能减少指令数,改变寄存器编号或调整等价顺序。此时旧 golden 失败,不一定意味着语义错误。
解决方法不是取消基线,而是分层设定契约:编码器单条指令的字段可严格比较;内核关键依赖顺序需要结构检查;允许自由调度的区域可以由数据依赖和参考结果约束。
例如测试“先准备参数,再执行计算”是稳定的不变量;测试“恰好使用某个临时寄存器”是否必要,则取决于寄存器选择是不是公开接口的一部分。
这也解释了为什么同时保存伪汇编、真实汇编和机器码有帮助:失败时可以判断变化发生在哪一层,而不是只面对一个不同的最终哈希。
不要把代码生成回归写成性能报告
某条路径从多一次地址加载变成相对偏移,确实可能减少指令。但整体耗时还受引擎执行、访存、调度与同步影响。测试里期待更少词数,不能自动变成“模型更快”的证据。
若要测量,应另建性能实验,明确输入、缓存状态、设备与统计方法。功能测试首先要稳定证明预期结构;性能分析则需要回答结构变化是否处在瓶颈路径上。
本篇能支持的结论是:三种输出和负向用例使代码生成的约束更可观察。测试不是因为名字叫 golden 就珍贵,而是因为它有能力与实现不同意,并能说清楚为什么不同意。
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 !