我的地下室有一台服务器,不适合运行现代语言模型。它是一个经过改造的 HP StoreVirtual 存储盒,大约有 13 年历史,有两个 Ivy Bridge Xeon,没有 GPU。它是用来存放磁盘的,而不是用来做数学的。截至本周,它运行着 Google 的 Gemma 4,这是一个 260 亿参数的开放权重混合专家模型,速度约为每秒 5 个代币。阅读速度。
任何人都可以租用 GPU。采用现代教育部模型和死气沉沉的企业盒子并让它们在中间相遇是更困难的,而这种差距就是我写这篇文章的全部原因。 “擅长人工智能”已经悄悄地意味着“付费订阅”。我认为真正的技能是不同的:足够了解一个模型,能够将它指出一个没有人为你打包的问题,并判断它返回的答案是否实际上是正确的。因此,与其声称我们擅长于此,这里有一个在没有业务合作的硬件上的有效示例。
几周前,一篇名为“你需要的就是 10 年前的 Xeon”的文章在 Hacker News 上流传。作者在单个 2016 Xeon 上运行 Gemma 4,没有 GPU 和 128 GB 慢速 DDR3,使用 ik_llama.cpp 和大约 25 个精心选择的标志。这是一本很棒的书,它依赖于现代推理手册中的每一个技巧:推测性解码、CPU 感知的专家混合路由、移植到 CPU 的闪存注意力、运行时权重重新打包。真正的工程。
“我也有 Xeon,”我想。事实上,有几个。所以我尝试了一下。它没有运行。
构建在启动时就终止了。我把失败的事情交给了克劳德,并询问出了什么问题。答案很快就回来了,而且很具体。作者2016年的芯片是Broadwell的部分。我的是 Ivy Bridge,英特尔称之为“v2”的一代。该分支中的快速内核采用 AVX2 和 FMA3,这些指令集直到 2014 年 Haswell(“v3”一代)才发布。我的 CPU 比编写代码所依据的指令更旧。优化的路径无法执行。
所以我问了显而易见的后续问题:我们能让它运行吗?我已经用一个免费模型进行了第一次挥杆,虽然很接近但无法落地。克劳德采用了半成品的方法,认为这是正确的方法,并完成了它,重新设计了热路径,以便它们干净地回到 AVX2 之前的芯片上,而不是去寻找不存在的指令。
这是我关心的部分。这并不是通过输入“修复它”一次并获得可用补丁来实现的。有人必须阅读另一个人的性能关键型 C++,弄清楚为什么内核在这个特定的微架构上无效,并在不放弃使分叉值得使用的优化的情况下绕过它。克劳德做了这项工作。我的工作范围更窄:进行正确的实验并识别输出何时最终正确。我离开时留下了深刻的印象。
Gemma 4 的 26B 专家混合模型现在可以在硬件上以读取速度生成文本,而该硬件在模型架构存在之前就已退役。最初的文章从未发布过每秒令牌数的数字,只是“读取速度”,所以这是具体的:在十三年的硅片上每秒大约有五个令牌,没有边界。
运行证明:Gemma 4 26B 在地下室盒子上应答,仅 CPU。
如果您想要确切的差异,则该补丁已发布为 ikawrakow/ik_llama.cpp#2138 — 在我撰写本文时仍然打开并等待维护者审查,因此现在从分支运行它。希望任何坐在古老企业铁上的其他人都可以保留本地模型:当付费 API 关闭时的后备方案,或者当按代币付费没有意义时,一种廉价的方法来处理缓慢的批处理作业。
在我进一步讨论之前,请先全面披露。我不是 C++ 程序员。我可以读取堆栈跟踪,并且了解构建系统的方式,但我没有为量化 matmul 引擎手写内核回退,而且我不会假装我这样做了。我所做的就是开车。我进行了实验,阅读了输出,提出了下一个问题,并知道“正确”应该是什么样子。诊断和补丁来自在服务器本身上运行的 Claude 实例。我要求它写下修复的内容,本节的其余部分是经过轻微编辑的摘要。如果您是从《黑客新闻》来到这里进行真正的拆解的,那么这一部分就是为您准备的。
我们需要的引擎是 ik_llama.cpp ,ikawrakow 的 llama.cpp 分支,它添加了 Gemma 4 的 MoE 推理所依赖的优化。它假定 AVX2 作为其底层。此盒中的 Xeon E5-2690 v2 有 AVX1,但没有 AVX2。在构建时关闭 GGML_USE_IQK_MULMAT ,大多数代码库都会尊重它:快速路径编译出来,并且模型回退到普通标量/SSE 数学。对于普通的 Q8_0 矩阵乘法来说这很好。
两个图形操作是例外。 Gemma 4 MoE 前馈网络发出 MOE_FUSED_UP_GATE(与 SwiGLU 融合的每专家门 + 向上 matmul)和 FUSED_UP_GATE(其密集模拟)。两者都在计算调度程序内的 GGML_USE_IQK_MULMAT 上 #if 门控,但图形生成器仍然无条件发出它们。在这个构建中,调度程序的交换机没有这些开放枚举的情况,因此它们变成了默认值,并且每个专家 FFN 的目标张量永远不会被默默地计算出来。 Gemma 4 26B 有 30 层,每个令牌有 8 个活跃专家,因此每次前向传递消耗了内存缓冲区中已经存在的大约 240 个张量。
症状是看起来很流利的多语言胡言乱语。令牌 ID 均匀分布在 262K 词汇表中,该模型同样乐意发出泰语脚本、韩语、<未使用>标记或英语片段。温度 0 时具有确定性,单线程和多线程运行之间的字节相同,任何地方都没有 NaN。只是一个隐藏状态在每一层都被一个大常数推挤,直到最终的 softmax 变得平坦。
正是这种决定论破解了它。 Claude 在采样前对原始 logits 进行了检测,打印前 5 个标记以及范围、平均值和 NaN 计数。数字暴露了这一点:第一个预测标记的平均 Logit 为 +16,此时它应该接近于零,并且大约 80% 的词汇量处于正 Logits。随机腐败看起来并非如此。仅当隐藏状态的一大块是未初始化的内存且恰好包含较小的正浮点数时,才会发生清除偏差。
在分叉的 main 之上进行了三个提交。
回退会花费一些成本,两个独立的 matmul-ID 而不是一个融合内核,但无论如何,这个 CPU 都是内存带宽限制的,并且融合内核仅支持 AVX2,所以我们不会放弃任何东西。端到端我们在 26B-A4B MoE 上得到大约 5.2 tok/s 的解码速度和大约 16 tok/s 的提示评估速度。
还有一个问题。 --run-time-repack 在启动时将量化权重重新排序为仅 AVX2 的交错布局 ( Q8_0_R8 ),这会以相同的方式在 AVX1 上混淆输出。这是一个单独的错误,补丁不会尝试修复它。运行脚本只是删除标志。
指令集不匹配很容易被发现。沉默的失败却并非如此。阅读代码不断清除明显的嫌疑:RMSNorm 帮助程序看起来是正确的,ggml_vec_dot_q8_0_q8_0 中的 AVX1 回退看起来是正确的,并且位相同的单线程运行排除了线程。只有在对 logits 进行检测并看到平均值固定在 +16 且每个长尾标记大致绑定之后,搜索范围才缩小到“残余流的一大块未初始化”。大约一分钟后,在调度程序中查找 #if GGML_USE_IQK_MULMAT 发现了两个丢失的案例。
如果您有 AVX2 之前的盒子并想尝试以下操作:
这就是整个配方:大约 5 tok/s 解码,仅 CPU,盒子中任何地方都没有 GPU。
订阅是最简单的部分。剩下的就是愿意打开引擎盖,阅读陌生人的代码,并不断询问,直到十三岁的 CPU 做了一些它从未打算做的事情。这与一个已有 15 年历史的 Rails 应用程序需要的工作相同,或者是一个团队中没有人仍然理解的数据库:有人会挖掘直到找到杠杆所在,以及工具本身不会告诉你的东西。
如果您的 AVX2 之前的铁器积满了灰尘并尝试了该分支,我很想听听它在您的硅片上的作用 - 这会在 CPU 代中下降到什么程度? PR 线程是错误报告的正确位置。如果您正在维护的是一个已有 15 年历史的 Rails 应用程序,而不是一个已有 13 年历史的 Xeon,那么这就是我们的谋生之道。
为好奇的人提供一些建议。服务器本身成本不到 300 美元;下面是关于为什么地下室盒子比每月 1,500 美元的云服务更便宜的数学计算。首先,让一台尖叫的企业设备安静且可启动是它自己的项目,在这里为喜欢这类事情的人们编写。