能否把 Maven 编译进神经网络?
我们尝试了,而且它确实能构建。 我们将一个 Maven 风格构建工具的决策部分编译成了一个固定权重矩阵,并用它来构建一个真实的 Java 库。输出与 Apache Maven 逐字节完全一致。带测试的干净构建耗时是相同逻辑用 Python 编写的 1.49 倍(95% 置信区间 1.4 倍至 1.54 倍,30 次配对运行)。
网络的权重表,每一行对应一个条形。条形高度表示该行在一次 ZXing 构建中运行的频率;亮点逐步回放该构建的真实片段。
每个 Java 开发者都见过 Maven 思考的样子。它解析依赖、编译、测试和打包。大部分时间属于 javac 和 JUnit,但贯穿其中的是一小段琐碎而挑剔的逻辑,决定接下来发生什么。当两个模块意见不一致时,哪个库版本胜出。哪些文件进入编译器,以及使用什么标志。一个失败的测试是否会停止构建。
我们想知道那部分逻辑能否存在于神经网络内部。不是训练一个模型来猜测 Maven 通常接下来会做什么:这种模型大多数时候是对的,而一个大多数时候正确的构建工具是有问题的。我们想要的是决策逻辑本身,被编译成权重,这样网络要么给出完全正确的答案,要么拒绝回答。
本文顶部的视频就是那个网络。它的 26,626 个条形中的每一个都是其权重表的一行(模型 nmvn-exact-v2,在降低成本的章节中有解释),条形高度表示该行在一次 ZXing 构建中运行的频率,亮点逐步回放该构建的真实片段。决定构建如何进行的全部逻辑都在那里。
从程序到权重表
这个技巧来自 Handwired:你可以编写一个程序,然后设置网络的权重,使网络就是那个程序。我们用 Python 的一个受限子集编写了一个小型 Maven 兼容的构建控制器,并将其编译成一个 29,625 × 7 的数字表,外加 7,382 个常量单元(这是第一版 v1;后面的章节会将表缩减到你在上面看到的 26,626 行)。没有训练,没有梯度下降:零优化器步数。
运行它就是一个循环。每一步,网络通过两次矩阵乘法查找其表中的一行,在小型寄存器文件上执行该行编码的算术运算,并将结果写回内存。网络不能打开文件或启动 javac。当它需要时,它会将请求写入内存中的一个固定槽位,一个轻量桥接层将请求带出,并将原始答案写回。桥接层从不做任何决定。每一个选择都来自权重。
pom.xml 字节+ 目前结果 固定权重表:每步一行,两次矩阵乘法来获取,无训练 一个请求:“读取此文件”“运行 javac 并...” 桥接:磁盘、javac、JUnit、bnd、zip 原始结果写回网络的内存,循环继续
测试对象是 ZXing 3.5.3,这个条形码库:它的父项目以及 core 和 javase 模块,使用 clean package 构建并开启测试。
它真的能构建吗?
是的。它生成的 JAR 与 Apache Maven 逐字节完全一致,构建在一个沙箱中进行,其中 Maven 无法运行,网络关闭,并且既不能读取逻辑的 Python 版本,也不能读取 Maven 自己的输出。所有 549 个上游测试运行并通过,新构建的 JAR 在 27 项检查中 27 次正确编码和解码了 QR 码。
519 个手写构建用例通过,每个都对照真实 Maven 检查 10,000 个生成的项目用例通过 33 个其他小型项目使用相同权重正确构建 4,796,191 步网络操作完成一次完整的 ZXing 构建
两项检查让我们确信工作确实发生在权重中。网络的 CPU 和 GPU 版本在一次完整构建中做出了完全相同的 1,731 个请求,顺序也相同。而当我们删除或损坏权重文件时,构建完全不会发生。
[LOADING...] 一个由新构建的 ZXing JAR 写入并由它们读回的 QR 码。
观看它构建
左:作为普通 Python 的控制器。右:作为神经网络的同一控制器。两者都在同一台 Mac 上先后真实构建 ZXing,以真实速度回放。 每侧一次录制的构建,实时回放。时间线上的灰色是花在 javac、JUnit、bnd 和 zip 上的时间。
账单
在我们测量的每个场景中它都更慢。Python 直接运行控制器的逻辑;网络在每条微小指令上花费一整轮矩阵算术,而一次 ZXing 构建需要 4,796,191 轮。
中位实际时间(越低越好)。最后一列:神经网络 CPU 时间除以 Python 时间。带测试的干净构建,中位秒数 条形 = 中位数;须线 = p25 至 p75。经典 Python 15.52 s (n=30) 神经网络 CPU 23.12 s (n=30) 神经网络 GPU (Metal) 70.11 s (n=30) Apache Maven 3.9.11 15.29 s (n=30) 带测试的干净构建,中位秒数。条形 = 中位数;须线 = p25 至 p75。
仅就决策而言,网络远远落后。在整个构建中,差距缩小到约一倍半,因为大部分时间花在 javac 和 JUnit 上,而两个版本使用相同的它们。在 GPU 上运行网络使情况变得更糟,而不是更好。一个逐条执行指令的程序给 GPU 没有什么可并行做的。
所以这不是一个更快的构建工具。这是一个你可以作为张量持有的构建工具。
在不触及网络的情况下降低开销
时间大致是步数 × 每步成本,所以我们统计了步数去向。我们在指令级模拟器中回放了一次完整的 ZXing 构建,并统计了它执行的每条指令。在 v1 的 4,796,191 步中,652,773 步(14%)只是将一个值从一个寄存器复制到另一个寄存器,另有 553,333 步(12%)加载常量或跳回循环顶部。那是编译器胶水,不是构建逻辑。
所以我们不动网络,而是修复编译器。结果现在直接落在需要它们的寄存器中,常量搭乘在使用它们的指令内部,条件直接编译成分支,循环在底部测试而不是跳回顶部。机器、其步骤图和 GPU 内核保持不变。只有数字表不同,而且更小。
23% 更少的网络步数完成一次完整 ZXing 构建(4,796,191 → 3,676,728) 10% 更少的权重表行数(29,625 → 26,626) 相同 的决策:输入 v1 记录的输入,v2 在 CPU 和 GPU 上以相同顺序做出相同的 1,731 个请求 2/2 正确性关卡被 v2 重新通过(相同的测试套件、沙箱构建、字节完全相同的 JAR)
按指令类别划分的网络步数,v1 vs v2
对于相同的完整 ZXing 构建:v1 运行了 4,796,191 步,v2 运行了 3,676,728 步。
每次完整 ZXing 构建的每一步,按指令类别划分(指令集模拟器回放录制的构建;两个模型的动作序列完全相同)。
神经网络 CPU,v1 vs v2(中位实际时间)。比率是同一会话中配对运行的神经网络时间 / Python 时间;v1 来自 nmvn-bench-v0,v2 来自后续协议。
GPU 获得的收益更多,因为它几乎纯粹按步付费:仅决策在 v1 中耗时 27.97 秒,在 v2 中耗时 21.61 秒(减少 23%),接近步数的削减。它仍然是最慢的引擎。
我们在新权重上重新运行了整个正确性链:每个测试套件、必须产生字节完全相同 JAR 的沙箱构建、阴性对照以及 CPU/GPU 回放。削减是真实的,但在关键之处是适度的。Java 工具仍然占据完整构建的大部分,因此缩减网络的份额对端到端数字的影响小于步数所暗示的。
不仅仅是 Maven
为了检查编译器没有秘密地针对一个程序进行调优,我们给了它一些不相关的东西:一个迷宫求解器(广度优先搜索,programs/maze/maze_nml.py)。它编译成 279 行权重。一个小应用同时启动经典版本和神经网络版本,作为独立进程,并在程序报告时立即绘制每个单元格。
应用窗口的录制,它在运行时保存自己的帧;神经网络侧在 CPU 上。 GPU 版本 耗时更长。
在 3 个不同的 171×121 迷宫上,纯 Python、Python 调试垫片、神经网络 CPU 运行和神经网络 GPU 运行产生了相同的事件序列:每个访问的单元格,顺序相同,距离相同。第一个迷宫花费了 2,572,288 步网络操作。神经网络在 CPU 上耗时是纯 Python 的 161 倍至 168 倍,在 GPU 上是 791 倍至 812 倍(每个程序自己的计时器,每个迷宫运行一次,所以将这些视为粗略值)。
所以它更慢。它甚至有用吗?
合理的问题。我们也问了。如果你想要更快的构建,不:继续使用 Maven。而更大的 GPU 也救不了它,因为网络一次运行一条指令,构建控制器中没有任何东西可以分布到数千个核心上。
不过,速度从来不是有趣的部分。我们最终得到的是一个每个权重都有已知含义的神经网络,这很罕见。以下是我们认为它有价值的地方,从最有信心的开始。
给拆解网络的人一个答案键
可解释性研究试图仅从权重弄清楚网络做什么。困难的部分是给答案评分,因为对于训练过的模型,没有人知道真相。这个网络自带真相。除了一个七行的启动存根外,它的 26,626 行中的每一行都属于控制器 249 个已知函数之一,我们知道每个函数做什么。所以我们可以指出 do_surefire 的行,它是运行测试并决定失败是否停止构建的部分。如果一个工具声称找到了“依赖解析电路”,你可以检查它。手工构建的测试网络往往是小玩具。这个网络在真实代码库上做真实的工作,我们认为这是它最好的用途。
猜测模型内部的精确部件
一个 99% 的时间选择正确库版本的模型,每百次构建会错一次。编译的权重是一种将精确子程序以与学习模型相同的形式放置的方法:相同的张量、相同的运行时、相同的硬件。明确地说,我们还没有将一个连接到训练过的模型。那是下一个实验,不是结果。尽管如此,这是我们发现最令人兴奋的方向。
你可以哈希的决策逻辑
整个策略就是一个数字文件。将其从 CPU 移到 GPU,它会以相同顺序做出相同的 1,731 个决策;向它提供录制的构建,它会精确回放。这就是我们在降低成本章节中逐决策比较 v1 和 v2 的方式。你可以对其进行 diff、签名,并展示给定构建来自它。公平地说,带有锁文件的普通代码可以让你完成大部分工作。张量版本增加的是,运行策略的东西对于你编译到其中的任何程序都是相同的固定查找和算术循环,而更小的运行时是更小的可信任之物。
还有一点对它有利:当决策是重工具之上的薄层时,成本会缩小。在完整的 ZXing 构建中,javac 和 JUnit 完成大部分工作,这就是为什么网络在那里花费约 Python 时间的 1.5 倍而不是二十多倍。围绕昂贵步骤的胶水逻辑支付同样适度的价格。
不适合用于
-
更快的构建,或任何毫秒级重要的场景
-
向问题投入更多 GPU
-
替换 Maven
值得一看的地方 -
用已知答案的网络测试可解释性工具
-
与学习模型相邻的精确子程序
-
可以哈希、回放和比较的决策逻辑
我们没有声称什么
- 这不是 Maven 的全部。它处理一个真实项目的一次构建(
clean package),跳过了 Enforcer、Checkstyle、Javadoc 和源 JAR。签名、部署以及一切只在verify中运行的内容都不在范围内。插件不是神经的:javac、JUnit、bnd 和 zip 作为普通程序运行。 - 这不是机器学习。权重是从程序编译而来,不是学习得到的。我们没有尝试训练版本,并且我们预计一个训练版本会在它没见过的项目上失败。
检查我们的工作
权重、运行时和完整结果在 Hugging Face 上的 shirman/neural-maven:方法、所有十一个场景、内存使用、环境和证据哈希,以及原始测量、两个协议、分析代码和校验和。这足以重新计算本文中的每个数字。