在错误的时间学习

系统里的机器学习确实进了生产环境——但都是先被编译、蒸馏或改写成普通代码。这个模式,就是全部的故事

先把这个领域应得的赞誉给足,因为只有夸得真,批评才作数。

过去十年,机器学习之于系统,是计算机系统里智识上最令人满足的工作线之一——这话我以参与者而非旁观者的身份说。我们的验证神经网络连续五年是 VNN-Comp 的标准测试集之一;我们造出了第一个为系统训练完全验证神经网络的系统;我们训练的一个模型,在 Azure 做过实验性部署。

奠基的一步很漂亮:数据库索引说到底就是一个从键到位置的函数,那就把这个函数学出来。一旦看见这一点,模式便处处泛化,因为系统里满是我们手写的预测器。缓存淘汰策略是对未来复用的预测。调度器是对作业时长的预测。拥塞控制是对网络的预测。预取器、分支预测器、查询优化器、自动扩缩容——全是猜测,全靠人几十年手工调校,也全都在原则上可以从数据里学出来。

顺嘴就想接一句:这些一个都没上线。我自己也信过某个版本的这句话,而它是错的。上线的不少。有意思的,是上线之物的形状

看看真正在跑的是什么

Azure 的虚拟机调度器咨询一个学习出的负载行为模型——随机森林加梯度提升树,几百 KB,离线训练,由链接进客户端的库来服务,遇到不认识的情形就返回显式的「不预测」。Google 的闪存缓存准入策略,人工调了多年,如今交给一个优化器:把流量分门别类,解一个背包问题,省下 6.5% 的运营成本。Microsoft 的 SCOPE 查询优化器由一个模型引导,模型出自作者们所称的庞大离线流水线,在生产环境默认开启。

连最有意思的两个案例,也守着这个模式。Google 的虚拟机调度器确实在生产环境咨询一个学习出的生命周期模型——但模型被编译进 Borg 二进制本身,像任何代码一样金丝雀发布、随版本上线,九微秒出答案,所在的决策点每秒触发几十次,不是几十亿次。而给 Google 机群打包、上线一年多、拿回其全球算力约 0.7% 的那条启发式,是两个比值之上的四行算术,出自离线搜索。

把这些摆到论文旁边看。打败 B 树的学习索引,深度强化学习的调度器,神经网络的拥塞控制。而走进生产环境的,是兜底后面的一片随机森林,一个背包求解器,四行算术,外加一个必须焊进调度器二进制才被放行的运行时模型。学习是真的,落地也是真的。没能活下来的,是热路径上的神经网络。

所以问题不是机器学习在系统里为何失败,而是所有成功者为何都穿着同一件伪装抵达。

五道障碍

以下五道障碍,我想是公认的、生产环境不发布模型本身的原因。

推理太贵。我们最想改进的决策,恰是做得最频繁的;做得最频繁的,预算以纳秒计。许多系统的在线决策,连一轮 CPU–GPU 通信都负担不起,更不必说通信那头还候着一次前向传播。

它静默退化。用上季度负载训练的学习组件,负载一挪并不报错,只是悄悄变差。我们在 Cartur——我们把 Azure 虚拟机安置到不同 NUMA 节点的项目——里亲眼见过:天长日久,客户的模式就从模型脚下挪走了,而没有任何东西通报这次挪动。

尾部不对。平均更好的学习策略,最坏情形可能差得多,而生产系统按最坏情形打分。这里的理论不讲情面:经典布隆过滤器的假阳率对任何查询集都一样;学习版的假阳率,却取决于你恰好问了什么——不需要对手来攻破,只要查询与存储的键相像,经验错误率就能被挪动五倍。

难以维护。代价来自两头。发布一个模型,等于发布训练数据采集、特征提取、重训、版本管理、漂移监控和一整套回滚方案——讲这件事的经典论文里,学习代码是一大张基础设施图中央漂着的一个小方块。你没有添加一个组件,你领养了一个受抚养人。而当这个受抚养人行为失常,你读不了它。启发式出了错,指给你一个分支;模型出了错,什么也不指给你,因为行为住在权重里,而权重不解释自己。

专家太贵。做好这件事,要求同时精通机器学习与系统。这个交集很薄,而一门需要稀有人才来操作的技术,是一门无法规模化的技术。

五分之四,是同一个

再看一遍这张单子,它其实不是五个问题。

推理开销存在,因为模型跑在运行时。静默退化存在,因为模型跑在运行时,训练与生产之间没有任何环节会重新审视它。尾部问题存在,因为模型跑在运行时——你无法事先定界你无法事先检视的东西。维护负担存在,因为模型跑在运行时,维持模型正确的那套机器就得永远在线——而你必须在生产环境里调试的那个产物,恰恰是唯一读不了的产物。

五道障碍里的四道,不是独立的问题,而是同一个架构选择的四种症状:学习发生在运行时。

第五道是那个例外,值得说清为什么,而不是硬圆进模式里。专家稀缺关乎人,不关乎架构。但恰恰是它,告诉你诊断是对的。系统代码库里的其他一切,一个好的系统工程师都维护得了;学习组件不行。这不是人手问题。这是代码库里的异物。

奠基论文早就知道

有一个细节让我确信这不是事后诸葛,而它自 2018 年起,就躺在这个领域被引最多的论文里。

学习索引的作者们测过他们的第一次尝试——一个两层、三十二个神经元的网络——在 TensorFlow 里每次预测约 80,000 纳秒;它要取代的那次 B 树遍历,约 300 纳秒。他们没有把模型缩小了事。他们造了一个在推理时压根不运行模型的框架:取出训练好的网络,抽出权重,生成 C++。模型执行降到 40–50 纳秒——整整四个数量级——而最终交付的是一个数据结构,不是一个模型。

这就是本文的全部论点,在开创这个领域的那篇论文的方法一节里被执行了一遍,然后没有被命名为原则。

它一再重演。做缓存替换的研究者离线训练了一个 LSTM,研究它学到了什么,然后交付了一个准确率相同、开销低几个数量级的支持向量机。一个网络团队把深度策略转成决策树:决策延迟低 27 倍,性能损失不到 2%。为了满足网卡上的微秒预算,另一个团队把神经拥塞控制器蒸馏成决策树,快 500 倍。Google 的 Borg 启发式出自演化式代码搜索,作者们直言为何选它而不选深度强化学习:代码方案「在可解释性、可调试性、可预测性与部署便利上具有明显优势——对关键任务系统而言是必备品质」。

五个团队,五个子领域,同一个动作:离线学习,然后把模型扔掉,只留下它教给你的东西,装进机器执行起来便宜、人也读得懂的形式里。没人给它起名。它一直被当作通往测试数字路上的实现细节,随手一报。

把学习挪早

最近变了的是:「把模型扔掉」这一步,不再需要研究者手工来做。

会写代码的智能体可以看你的 trace、你的负载、你的性能画像,然后产出一条普通的启发式:分支、阈值、几个调好的常量,附一条解释思路的注释。Google 的 Borg 启发式证明了:这个过程的产物可以只有四行算术,却仍值全球机群的千分之几。

现在回头重读那五道障碍。推理开销:没了——交付的产物是算术。静默退化:变质了——产物是静态的,按代码的方式失败。最坏情形:可分析了——因为你读得懂。维护:它是你仓库里的代码,走你的评审流程,一条 git 命令就能回滚,按调代码的方式调试。专家:你的系统工程师就能维护,因为这正是他们本来就在维护的东西。

还应当说明,我并不是一个人站在这里,近来已有文章先行触及其中的部分。Berkeley 的一组人论证说,系统研究格外适合 AI 驱动的发现,恰因我们的验证器便宜——检查归结为拿产物对着负载跑一遍——并预测人类将退守到问题构述。而一组 Google 的老兵——正是这条工作线上的人——主张 AI for systems 是「AGI 完全」的:一般形态的调度与代码优化,需要的正是前沿实验室在追逐的那种推理。我认为两者对机制的判断都对。我想更进一步的,是它们都未曾言明的那部分:哪些决策在挪动,它们何时被冻结,以及这场挪动受什么纪律约束。

所以我的主张不是「永远别在运行时学习」,而是:运行时应当承载尽可能少的学习,而我们花了十年,假设它应当承载全部。

这就引出那个显而易见的问题——学习既然不该住在运行时,究竟该住在哪里?用什么词汇把这件事说清楚?那是下一篇。

参考文献