打开网易新闻 查看精彩图片

有一个问题很少有人认真想过:当我们说一个AI生成的GPU程序"通过测试"了,这句话到底意味着什么?

你可能会觉得这不是废话吗,通过测试当然就是对的。但2025年有一篇论文戳破了这层窗户纸,答案是:不一定。而且不对的概率,比你想象的高得多。

**这篇论文测量了一件此前没人量化过的事情:GPU代码的正确性检查器本身有多不靠谱。**

结果是,官方检查漏掉了六分之一本该被抓到的错误代码,而且这个漏洞不是随机分布的,它精准地漏掉了那些真正危险的错误类型。

先说说这件事为什么突然变得重要。

现在很多AI模型被训练来自动写GPU内核代码,也就是那些让显卡跑得飞快的底层程序。这些模型怎么知道自己写得好不好?靠一个叫KernelBench的评测基准打分。这个打分不仅用来做排行榜,更关键的是,它还被直接用作强化学习的奖励信号,也就是说,AI模型是靠"能不能通过这个检查"来学习怎么写代码的。

如果这个检查本身有漏洞,后果就不是打分不准那么简单了。

想象一下,你在教一个学生做数学题,但你批改作业的方式是只看答案的前两位数字对不对。学生很快就会发现这个规律,然后开始专门凑前两位数字,而不是真的把题做对。AI模型也是一样,如果评测标准有系统性盲区,模型就会学会讨好这个盲区,而不是学会写真正正确的代码。这不是猜测,论文里提到已经有研究观察到这种"奖励黑客"现象,模型确实学会了投机取巧硬编码通过检查的方式。

那么,怎么知道一个检查器到底靠不靠谱?

问题有多大:一个softmax函数的荒诞案例

论文里举了一个特别直观的例子。

有一个处理39万多个数字的softmax函数(一种常见的神经网络运算,作用是把一堆数字转换成概率分布),它的正常输出,每个数字平均只有0.0000025这么大。而官方检查器允许的误差范围是0.01。

误差允许范围比正常输出大了四千倍。

*误差容忍度(tolerance)*:检查程序判断两个结果"足够接近"时设定的容许误差范围,通常包含绝对误差和相对误差两部分。

这意味着什么?一个什么都不算,直接返回全零的假冒程序,能大摇大摆地通过这个检查。而且这不是偶然的运气,这是这道题的输入分布和误差容忍度设置共同导致的必然结果,你换多少次随机种子重新测,结果都一样,因为问题根本不在于抽样运气,而在于容忍带本身就比信号大得多。

论文把这个现象叫做**容忍度空洞化**,意思是随着运算规模变大,这个误差允许带会越撑越大,直到把真实信号完全吞没。

这就好比一个安检口,规定"背包里超过10公斤的违禁品才会被拦下"。如果你背的包本身只有5克重,那这个安检形同虚设,不管里面装了什么都能过关。安检的门槛没有跟着背包本身的重量做调整,门槛本身失去了意义。如果不设置这种随规模自适应的检查标准,那么运算规模越大的程序,反而越容易蒙混过关,这恰恰和我们的直觉相反,规模越大不代表检查越严格,可能是检查越松。

更麻烦的另一面:太严格也会杀错好人

如果说容忍度空洞化是检查器"太宽松",那还有另一个相反的陷阱:检查器可能"太严格",严格到连正确的代码都不放过。

这背后的原理和浮点数运算的一个基本事实有关:两个都完全正确的GPU程序,因为内部计算顺序不同(比如先加哪个数后加哪个数),最终结果也会有微小差异。这不是bug,这是浮点数运算天生的性质。

论文发现,如果测试用的输入数据设计得太极端,比如故意让正负数混杂、互相抵消,那这种微小的计算顺序差异就会被放大,大到足以让一个完全正确的程序被误判为"不通过"。

*验证有效性上限(validity ceiling)*:指测试输入的"攻击性"存在一个客观上限,一旦超过这个界限,连正确实现之间的正常数值差异都会被误判为错误,论文测量出这个界限具体在哪里。

这就像给一个厨师出考题,故意选一道对火候极其敏感的菜,稍微多颠一次勺、多等三秒钟,味道就会有肉眼难辨的细微差别。如果考官把评判标准定得比这种正常波动还严苛,那不管厨师做得多标准,都会被判定不合格。考题的难度设计本身出了问题,惩罚的不是真正的失误,而是正常操作里无法避免的波动。如果不去测量这个上限,只是凭感觉觉得"输入越刁钻越能测出问题",那设计出来的测试很可能会冤枉大量正确的实现,这在论文后面的实验里真实发生了。

论文重建了一个此前发表的模糊测试(fuzzing)方案,结果发现它虽然抓到了86.2%的错误,但同时错误地拒绝了107个完全正确的程序。这不是进步,这是把好学生当成了坏学生开除。

怎么量化"考官不靠谱"这件事:变异测试搬进GPU世界

那论文具体是怎么测量出"漏了六分之一"这个数字的?

这里用到了一个软件工程里五十年前就有的老方法,叫**变异测试**。

*变异测试(mutation analysis)*:通过人为地在正确代码里注入一个个微小错误(称为"变异体"),然后看现有的测试用例能抓到多少个变异体,以此衡量测试用例本身的质量,而不是衡量被测代码的质量。

原理很朴素:如果你想知道一份考卷出得好不好,一个办法是故意在标准答案里改错几个字,看看这份考卷能不能把改错的地方揪出来。如果考卷压根测不出来,说明这份考卷本身有问题,而不是学生的问题。

但这个老方法直接搬到GPU代码上会遇到四个硬骨头,论文一个个解决了。

第一个难题是,GPU程序的判定不是非黑即白的精确匹配,而是"数值上够不够接近"这种模糊判断,前面讲的容忍度空洞化和有效性上限问题,都是这个模糊性带来的副作用。

第二个难题是,不是所有注入的错误都算数。有些改动其实和原来的程序行为完全等价,比如把两个独立线程的执行顺序换了一下,结果根本没变;还有些改动虽然理论上算错误,但错误发生在内存里一个谁也不会用到的角落,任何数值检查都不可能发现它。论文的做法是,只有找到一个具体的、能验证的输入,让这个变异体真的产生了错误结果,才把它算进"应该被抓到"的分母里,其余的一律隔离,不计入任何检查器的成绩。

第三个难题纯粹是工程量的问题。想给一万多个变异版本的代码逐个编译测试,用常规的编译流程要200秒一个,一万个就是接近两年的GPU计算时间。论文改用一种叫NVRTC的运行时编译技术,把单次编译压到84毫秒,提速了五百倍,这才让大规模测量变得可行。

论文团队最终搭建的这套测量流水线,一共处理了188个经过验证的CUDA程序,用124条确定性规则生成了10303个变异体,过滤掉编译失败和重复的之后剩下8253个,其中7384个有明确的"能被抓到"的证据,构成了最终的评分基准。

六分之一的漏网之鱼,到底漏在哪里

结果出来了:官方检查(每道题用固定的几个随机种子测试)总共抓到了6136个变异体,漏掉了1248个,漏检率16.9%,大约每六个本该被抓到的错误就有一个溜走。

这个数字不是抽样误差。论文用自助法(bootstrap)反复抽样验证,发现随着样本量增加,这个16.9%的比例会越来越稳定,说明这是整个基准的固有属性,不是运气问题。

但更值得关注的是,这些漏网之鱼分布得极不均匀。

论文把错误分成六大类:算术错误(比如把加号写成减号)、索引错误(数组下标搞混)、语义错误(逻辑判断出错)、边界错误(数组越界、循环少跑一轮)、同步错误(多线程协作出问题)、精度错误(用了低精度数据类型或省略了数值稳定性处理)。

算术类错误的漏检率只有8.7%,几乎都能被抓到。原因很简单,官方测试用的是密集的随机数输入,乘法变除法这种低级错误在满是随机数的场景里根本藏不住。

但精度类错误的漏检率高达78.6%,接近八成漏网。同步类错误漏检率27.8%,边界类错误漏检率22.9%。

这个规律其实很直白:越是"教科书式"的低级错误越容易被抓,越是真实世界里工程师容易犯的、隐蔽的、精细的错误,反而越容易蒙混过关。这就好比一场考试,如果只考九九乘法表,那考砸的人一定是真的不会算术;但如果题目全是需要极端边界条件才会暴露的隐患,普通的抽查式考试根本测不出来,因为触发这些隐患需要恰好踩中那个特殊情况。**一个检查器如果只在"容易犯的错误"上表现优秀,恰恰说明它在"难抓的错误"上可能形同虚设**。

给已有补丁做"体检":谁真的有效,谁在自欺欺人

既然有了这套测量工具,论文顺手给业内几个已经发布的"改良版检查器"做了体检。

其中一个叫KernelBench-Verified的改进方案,给每道题额外加了四种输入分布的变化,还把误差容忍度调紧了十倍。论文发现,它相比官方检查多抓到了8.5个百分点的错误,但这8.5个百分点里,只有4.0个百分点来自那四种新增的输入分布,剩下的4.5个百分点全部来自调紧误差容忍度这一个改动。也就是说,单单收紧一个数值上限,效果就超过了四种精心设计的输入变化加起来。

有意思的是,这个改进方案自己的作者在论文里坦承,他们没办法计算出这两部分各自贡献了多少,而这篇测量论文用变异测试的方法,直接把这个说不清的问题算清楚了。

论文还发现,这个改进方案有个天生的死角:它的四种输入变化全部都是"把数值放大或缩小",从来没有改变过输入的形状或结构。这意味着那些藏在数组边界、索引计算里的错误,不管加多少种数值缩放的变体都抓不到,因为这些错误压根不是数值大小的问题,而是结构性问题。这就好比想检验一把锁的安全性,却只测试用不同粗细的钥匙去开,永远不会发现锁芯本身有没有被撬开的可能。

至于前面提到那个模糊测试方案,论文的体检结果更直接:抓到86.2%的错误,但代价是107次错杀无辜。

从测量到行动:两个输入就能顶过去官方五个

光是测量出问题还不够,论文接下来做了一件更有实际价值的事:既然已经知道每个测试输入分别能抓到哪些变异体,那就可以用经典的集合覆盖算法,反过来设计出效率最高的测试组合。

结果相当惊艳。用这种方法优化出来的测试套件,平均每道题只需要两个精心挑选的输入,就能达到98.0%的检测率,而官方用五个随机输入只能做到83.1%。

论文还专门做了一次交叉验证,把每道题的错误样本一分为二,一半用来挑选测试输入,另一半留着完全不用来验证效果,结果在没见过的数据上依然能达到94.8%的检测率。这说明优化出来的测试输入抓住的是错误的"类型"和"规律",而不是死记硬背了具体哪几个错误样本,这个区别很重要,前者才是真正有泛化能力的检测手段。

论文还做了一个特别巧妙的实验,叫"知识阶梯"。他们让一个AI模型去生成能抓住漏网错误的测试输入,分四个等级递进:什么都不告诉它,让它随机生成;告诉它"测试可能漏掉细微的错误";再给它这篇论文测量出来的错误分类体系和数值边界规律;最后干脆把具体哪里被改错了直接告诉它。

结果排出来的名次很反直觉:什么都不知道的随机生成只能抓到21.6%,而且还错杀了107次;给它错误分类体系这一档,直接跳到61.0%;但最后那档,直接把作弊答案(具体哪行代码被改了)喂给它,效果反而降到57.3%,不进反退。

原因也不难理解:知道具体答案的AI会针对那几个具体案例去过拟合,写出来的测试输入只能抓住那几个特定的错误,换一批新的错误就抓瞎了。而理解了错误的"类型规律"的AI,写出来的测试输入具备泛化能力,能触类旁通抓住同一类里没见过的新错误。这就好比教一个新手医生看病,直接告诉他"这个病人是阑尾炎"只能让他认识这一个病人,但教会他阑尾炎的典型症状和判断逻辑,他才能在下一个陌生病人身上做出正确判断。**测量出来的抽象规律,比具体的错误案例本身更有教学价值**。

当代码从"一个函数"膨胀到"一整个模型"

前面讲的都是针对单个GPU运算函数的测量,论文还往前走了一步,把目光投向了完整的神经网络架构,也就是由几十上百个GPU函数拼接起来的整个模型。

这一步的动机很自然:现实里AI生成代码的终极目标不是写一个孤立的小函数,而是写出能跑完整模型的代码。那么问题来了,当错误被藏在一个很深的网络的前几层,它还能不能被最终的输出检测出来?

论文测量了48个完整架构,涵盖12361个变异体,发现问题只会更严重不会更轻。整体漏检率从单函数级别的16.9%上升到至少17.3%,而且这还是保守估计的下限。

更有意思的是架构之间的巨大差异。像VGG-19、SqueezeNet这类结构很深且高度同质化(一层接一层长得都差不多)的网络,漏检率高达90%,几乎是个黑箱。而像ResNet-18、SwinMLP这类带有大量归一化处理和分支结构的网络,即便层数很深,漏检率也只有11%到14%。

这个反差揭示的规律是:让错误逃脱检测的关键因素不是网络深不深,而是网络里有没有足够多的"归一化"和"分支"环节来重新校准数值。深层同质化的网络就像一条很长的传送带,一开始出的小问题会原封不动地被一路传递到终点,中间没有任何环节会去重新检查或修正它;而带有归一化层的网络,相当于传送带上每隔一段就设一个质检站,前面出的偏差会被拉回正常范围,反而更容易在早期被发现。

论文更进一步发现,在同一个网络内部,错误被注入的位置越靠近输出端,越容易被检测到,注入在网络前半段的错误存活率能到44%到49%,而注入在最后四分之一的错误存活率只有39%。这个梯度虽然不算剧烈,但方向很一致,离终点越近,错误越难藏身。

最让人意外的一个发现藏在这一部分的尾巴上:论文找到了两个"根本没法被裁判"的问题。一个叫Mamba2ReturnY,它的正确参考实现本身,用32位浮点数计算出来的结果,和用64位浮点数(更精确的版本)计算出来的结果相比,竟然超出了官方设定的容忍度,在352个位置上违反了自己的标准。另一个叫UNetSoftmax,同样的问题更严重,偏差达到9379处。

也就是说,这两道题的"标准答案"本身就没资格当标准答案,因为它连自己更精确版本的自己都对不上。而在这篇论文之前,没有任何一个已发布的改良检查方案发现过这件事。

写在后面

读完这篇论文,最让我意外的其实不是那个16.9%的漏检率,而是那个"标准答案自己打自己脸"的发现。

我们通常默认,评测基准里的"正确答案"是不需要质疑的地基,所有的验证工作都建立在这个地基之上。但这篇论文提醒我们,连地基本身都可能是有裂缝的,而且这个裂缝存在了很久,却从来没有人用系统性的方法去测量过它。

这背后其实是一个更普遍的问题:任何一个自动化的判断系统,不管是代码检查器、考试评分系统还是内容审核算法,一旦它变成了某个学习过程的反馈信号,它自身的缺陷就会被放大成整个系统的缺陷,而且是以一种悄无声息的方式。模型不会告诉你它在钻空子,它只会安安静静地在那个空子上把分数刷高。

论文里那个"知识阶梯"实验也让我重新想了想教学这件事。给出具体答案反而效果更差,这个结果初看很反直觉,但仔细想想,这其实是我们在教育里也常犯的错误,直接给学生答案,他只会记住这一道题;讲清楚背后的原理和规律,他才能应付没见过的新题目。这个道理放在人类学生身上是常识,但看到它在AI模型身上被这么干净利落地验证出来,还是让人愣了一下。

这篇论文没有解决的问题是,它的124条变异规则终究是人为设计的,没法覆盖所有可能的真实错误模式,比如更复杂的多线程交互错误,或者依赖特定硬件特性的隐患。检查器和错误之间的这场猫鼠游戏,恐怕不会有真正终结的一天。

Q&A

Q1:KernelBench-M是什么?

A:KernelBench-M是论文作者发布的一套完整工具集,包含10303个人工注入错误的GPU代码变异体、验证过的错误检测证据、优化后的测试套件和整个测量流水线,目的是让后续研究者能够用可量化的方式检验GPU代码检查器的质量。

Q2:官方GPU代码检查器漏检率这么高,是不是意味着现在的AI写的GPU代码都不可信?

A:不能这么简单下结论。论文测量的是检查器的漏洞,而不是说所有通过检查的代码都是错的。但确实说明,通过了官方检查不能完全等同于代码正确,尤其是涉及精度处理、边界条件和多线程同步这几类错误时,通过检查的可信度会明显打折扣。

Q3:为什么测试输入设计得越极端越能测出错误,这个思路不对吗?

A:不完全对。论文发现测试输入存在一个有效性上限,一旦输入设计得过于极端(比如让正负数大量抵消),连完全正确的程序都会因为正常的浮点数计算顺序差异被误判为错误。论文实测中一个模糊测试方案就因此错杀了107个正确程序。