Running Gemma 4 26B at 5 tokens/sec on a 13-year-old Xeon with no GPU
TL;DR · AI 摘要
在无GPU的13年老旧Xeon服务器上,通过优化实现Gemma 4 26B模型每秒5个token的推理速度。
核心要点
- 使用ik_llama.cpp和25个特定编译标志可在老旧Xeon上运行Gemma 4模型
- AVX1指令集限制导致需回退到预AVX2兼容代码路径
- Claude协助优化了CPU兼容性问题,实现模型在Ivy Bridge架构上的运行
结构提纲
按章节快速跳转。
- §引言
展示在无GPU的老旧服务器上运行现代大模型的可行性
- ·硬件配置
使用双Xeon E5-2690 v2(Ivy Bridge)和DDR3内存的13年旧服务器
Gemma 4 26B-A4B模型实现5.2 tokens/sec的推理速度
- ›优化方法
采用ik_llama.cpp和25个特定编译标志进行性能调优
- ·技术挑战
AVX1指令集限制导致需重构代码路径以兼容Ivy Bridge架构
- ›解决方案
通过Claude协助实现预AVX2兼容代码的回退机制
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 老旧Xeon运行Gemma 4
- 硬件限制
- Ivy Bridge架构
- AVX1指令集
- 优化方法
- ik_llama.cpp
- 25个编译标志
- 技术挑战
- 代码路径兼容性
- Claude辅助优化
金句 / Highlights
值得收藏与分享的关键句。
使用ik_llama.cpp和25个特定编译标志可在老旧Xeon上运行Gemma 4模型
AVX1指令集限制导致需回退到预AVX2兼容代码路径
Claude协助优化了CPU兼容性问题,实现模型在Ivy Bridge架构上的运行
在无GPU的13年老旧Xeon上以每秒5个标记的速度运行Gemma 4 26B | Neomind
2026年6月8日 · Ryan Findley
在无GPU的13年老旧Xeon上以每秒5个标记的速度运行Gemma 4 26B
我地下室里有一台服务器,按理说根本不可能运行现代语言模型。这是一台被改造成存储设备的HP StoreVirtual设备,大约有13年历史,搭载两颗Ivy Bridge架构的Xeon E5-2690 v2处理器,没有GPU。它原本的设计目的是存储硬盘而非执行计算。但截至目前,它能够以每秒约5个标记的速度运行Google的Gemma 4模型,这是一套包含260亿参数的开放权重专家混合模型。
硬件配置
- 重构后的HP StoreVirtual:双Xeon E5-2690 v2(Ivy Bridge,2013年),DDR3内存,无GPU
指令集
- 仅支持AVX1 — 不支持AVX2和FMA3
模型
- Gemma 4 26B-A4B(专家混合模型),Q8_0量化
解码速度
- 约5.2个标记/秒
提示评估速度
- 约16个标记/秒
设备成本
- 低于300美元
任何人都可以租用GPU。但要将现代专家混合模型与一台报废的企业级设备结合运行却要困难得多,这种技术鸿沟正是我撰写本文的原因。"擅长AI"这个词如今悄悄变成了"支付订阅费用"。我认为真正的技能在于:充分理解模型特性,能够将其应用于无人预先封装的特定问题,并判断模型返回的答案是否真正正确。因此我们不声称自己擅长这些,而是通过一个实际案例来展示,这个案例使用了一台本不该协同工作的硬件设备。
Gemma 4 的 26B 专家混合模型现在可以在架构诞生前就已退役的硬件上以阅读速度生成文本。原始文章从未公布过每秒生成的 token 数量,只提到“阅读速度”,因此这里给出具体数据:在十三年前的芯片上,每秒生成约 5 个 token,几乎可以说是免费的。
运行证明:Gemma 4 26B 在地下室的电脑上仅使用 CPU 进行回答。
如果你需要确切的代码差异,补丁已上传至 ikawrakow/ik_llama.cpp#2138。目前该补丁仍处于开放状态,等待维护者审核,因此建议你暂时从该分支运行。希望这个补丁能让其他使用老旧企业级硬件的人也能本地运行模型:当付费 API 停服时作为备用方案,或在按 token 计费不划算时以低成本处理缓慢的批量任务。
给想要了解实际错误的人
在继续之前,我需要完全公开说明。我不是 C++ 程序员。我能够阅读堆栈跟踪,也熟悉构建系统,但我没有手动编写量化矩阵乘法引擎的内核回退代码,也不会假装自己做过。我所做的只是推动实验:运行实验、阅读输出、提出下一个问题,并知道“正确”应该是什么样子。诊断和补丁来自服务器上运行的 Claude 实例。我让它写出自己修复了什么,本节其余内容就是这个总结的轻微编辑。如果你是从 Hacker News 来查看真实分析的,这部分就是为你准备的。
实际上损坏的部分
我们需要的引擎是 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 融合)和 FUSED_UP_GATE(其密集版本)。这两个操作在计算调度器内部通过 GGML_USE_IQK_MULMAT 的 #if 条件进行控制,但图构建器仍然无条件生成它们。在这个构建版本中,调度器的 switch 语句没有为这些操作符的枚举值设置 case,导致它们直接进入默认分支,每个专家的前馈网络的目标张量从未被计算。Gemma 4 26B 有 30 层,每 token 激活 8 个专家,因此每次前向传播都会消耗大约 240 个张量,这些张量的内容是内存缓冲区中原本就存在的数据。
症状表现为看似流畅的多语言胡言乱语。token ID 在 262K 词汇表中均匀分布,模型同样乐意生成泰语、韩语、<unused> 哨兵或英文片段。在温度为 0 时表现确定性,单线程和多线程运行结果字节完全相同,没有任何 NaN 值。只是隐藏状态在每一层都被一个大常数推移,直到最终的 softmax 变得平坦。
这种确定性是破局的关键。Claude 在采样前对原始 logit 进行了仪器测量,打印出前 5 个 token 加上范围、均值和 NaN 数量。这些数字暴露了问题:第一个预测 token 的平均 logit 值为 +16(应该接近零),约 80% 的词汇表具有正 logit 值。随机损坏不会呈现这种模式。如此干净的偏置只会在隐藏状态中有一大块未初始化内存恰好存储着小正浮点数时才会发生。
修复方案
在分支的主分支上新增了三个提交。
- 编译修复。iqk_quantize.cpp 中 quantize_row_q8_0_x4 和 quantize_row_q8_1_x4_T 的标量 #else 分支实际上并非标量实现,仍引用了 hsum_i32_8 和其他 AVX2 辅助函数。这些内容被重写为可移植的标量循环,并在 ggml.c 和 ggml-quants.c 中若干 IQK 调用周围添加了 #if GGML_USE_IQK_MULMAT 保护,同时修复了 iqk_cpu_ops.cpp 编译时缺失的包含文件。缺少这些修改后,该分支在非 AVX2 硬件上根本无法构建。
- 运行时错误。修复方案未修改分发器,而是让图构建器生成在当前构建中确实包含计算路径的操作。在 ggml_moe_up_gate 中,当 GGML_USE_IQK_MULMAT 关闭时:如果权重是合并后的 up_gate_exps 张量(形状 [n_embd, 2*n_ff, n_experts],前半部分是 gate,后半部分是 up),将其拆分为两个 ggml_view_3d 切片,分别执行两次 ggml_mul_mat_id 调用,最后通过 ggml_fused_mul_unary(gate, up, SILU) 合并。如果 gate 和 up 已经是独立权重,则跳过拆分,直接执行两次 mul-mat-id 加上融合的 mul-unary。ggml_fused_up_gate(非 MoE 层使用的密集版本)也采用相同处理方式。所有涉及的操作已有非 IQK 实现(mul_mat_id 是原生 ggml,fused_mul_unary 通过单次计算完成 SILU 和乘法)。整个修改被 #if !GGML_USE_IQK_MULMAT 包裹,因此 AVX2 构建与之前完全一致。
- CI 存根。iqk 源文件的 #else 存根部分与 iqk_mul_mat.h 不同步,导致 ci/run.sh 无法在非 AVX2 硬件上构建:缺少 <cstdint> 包含文件,存根函数签名错误(此处多了一个参数,彼处缺少 sinks 参数),以及部分函数完全没有存根,导致链接时出现未定义引用。虽然工作枯燥,但缺少这些修改就无法在该硬件上运行测试套件。
该回退方案有一定代价,需要执行两次独立的 matmul-id 而非一次融合内核,但该 CPU 本身受内存带宽限制,且融合内核仅限 AVX2,因此并未损失性能。端到端测试显示,26B-A4B MoE 模型在该硬件上可实现约 5.2 tok/s 的解码速度和 ~16 tok/s 的提示评估速度。
另一个需要注意的陷阱是:--run-time-repack 参数会在启动时将量化权重重新排列为仅限 AVX2 的交错布局(Q8_0_R8),这会导致 AVX1 上输出同样被破坏。这是一个独立的 bug,补丁未尝试修复。运行脚本只需忽略该标志即可。
指令集不匹配问题很容易发现,但静默通过的错误则难以察觉。代码审查时反复排除了明显嫌疑点:RMSNorm 辅助函数看起来正确,ggml_vec_dot_q8_0_q8_0 中的 AVX1 回退也正确,单线程运行与原版完全一致,排除了线程问题。直到对 logits 进行仪器化分析,发现均值始终固定在 +16,且每个长尾 token 的分布都紧密关联,才锁定问题根源:“残差流中的一大块数据未初始化”。在分发器中搜索 #if GGML_USE_IQK_MULMAT 后,约一分钟内发现了两个缺失的 case。
复现方法
如果你拥有预 AVX2 硬件并想尝试此方案:
- 硬件:双 Xeon E5-2690 v2(Ivy Bridge,AVX1,无 AVX2),DDR3,无 GPU。
- 构建:从上述分支编译 ik_llama.cpp,关闭 GGML_USE_IQK_MULMAT。编译修复正是让其能在非 AVX2 硬件上构建的关键。
- 模型:Gemma 4 26B-A4B,Q8_0。
- 运行:使用常规的 ik_llama.cpp CPU 参数,但移除 --run-time-repack(该参数会将权重重新排列为仅限 AVX2 的布局,并在 AVX1 上重新打乱输出)。
这就是全部方案:约 5 个 token/秒的解码速度,纯 CPU 运行,整个系统中没有任何 GPU。
为什么这会出现在公司博客中
订阅服务只是容易的部分。剩下的则是需要愿意打开引擎盖、阅读陌生人的代码,并不断追问直到让一个十三年前的 CPU 做一些它从未设计过的事情。这与维护一个十五年前的 Rails 应用或一个团队中无人理解的数据库是一样的工作:需要有人不断挖掘,直到找到杠杆点,以及工具本身不会主动告诉你的信息。
如果你有一些预 AVX2 时代的老旧 CPU 正在积灰,尝试这个分支的话,我很想听听它在你的硬件上表现如何——这个方案能支持到多老的 CPU 世代?PR 讨论线程是提交 bug 报告的正确位置。而如果你正在维护的不是一个十三年前的 Xeon,而是一个十五年前的 Rails 应用,那这就是我们日常工作的核心内容。
给好奇者的一些提示。这台服务器本身成本低于 300 美元;这里展示了为什么地下室的机器能击败每月 1500 美元的云服务费用。而让一台高性能企业设备安静下来并首次成功启动本身就是一个独立的项目,这里详细记录了相关内容,适合对此类事情感兴趣的人阅读。
← 返回博客 /