Skip to content
Published on

llama.cpp 走进浏览器 — WebGPU 后端在 16 台设备上测出的天花板

分享
Authors

引言 — 浏览器 LLM 毕业了吗

在浏览器里跑 LLM 这个想法,已经在演示领域停留了好几年。WebLLM 展示了在标签页里跑 Llama,Transformers.js 把 pipeline API 搬到了网页上,每一次都伴随着同一句口号 ——"服务器成本为零,数据不出设备"。这句口号是真的。问题在于下一个问题——那到底有多慢、吃多少资源、在哪里会崩——几乎没有公开数据能回答。大多数基准测试都来自作者的一台笔记本电脑,而浏览器 GPU 性能并不能跨硬件泛化。

所以 2026 年 5 月 20 日发布在 arXiv 上的 Llamas on the Web 才显得有意思。这篇由 UC Santa Cruz 的 Reese Levine、Tyler Sorensen 等人撰写的论文,构建了 llama.cpp 的 WebGPU 后端(论文中称为 LlamaWeb),并在 8 家厂商的 16 块 GPU、10 个模型、4 种权重格式上做了实测。用作者的话说,这是"在单一推理引擎框架内收集到的最大规模跨设备、跨模型评估数据集",也是唯一包含移动设备的 WebGPU 数据集。

本文梳理这些数据揭示的 2026 年浏览器推理现实。先说结论——它变好了,但天花板在哪儿看得清清楚楚。而这个天花板,是有名字的。

LlamaWeb — 给 llama.cpp 装上 WebGPU 后端意味着什么

先把地形梳理清楚。WebGPU 是浏览器架在多个原生 API 之上的一层抽象 — macOS/iOS 上是 Metal,Linux 和 Android 上是 Vulkan,Windows 上是 DirectX。而且每个浏览器的实现都不一样。Chromium 系用的是 Dawn,Firefox 用的是 wgpu,Safari 用的是 WebKit 自己的实现。这里有一个重要事实 — Dawn 是用 C++ 写的,所以在浏览器之外的原生代码里也能用。这个事实在后文会起到关键作用。

现有的浏览器推理框架各自依赖不同的引擎 — Transformers.js 靠 ONNX Runtime,WebLLM 靠 MLC-LLM。而 LlamaWeb 选择的路是直接给 llama.cpp 本体装上 WebGPU 后端。这为什么重要?因为它原封不动地继承了 llama.cpp 的全部资产 — 尤其是 GGUF 量化格式的整个生态。

这项工作的核心部件之一,已经合并进了 llama.cpp 本体。PR #17031("ggml webgpu: faster matrix multiplication/matrix-vector multiplication",reeselevine,于 2025 年 11 月 8 日合并)加入了两套矩阵乘法内核。照搬 PR 描述的原话 — 一个用寄存器分块(register tiling),另一个用 "subgroup matrices"(一个能让你访问张量核心、或经过优化的 subgroup 例程的 WebGPU 特性),并且"目前 subgroup matrices 还是实验性的,在不支持的设备上会回退到寄存器分块方式"。

请记住这句关于回退的话。这篇文章接下来的所有内容,说的都是这句话。

真正的限制不是显存,而是标签页

这正是初次接触浏览器推理的人最容易搞错的地方。限制不是 GPU 内存,而是标签页进程。

论文指出的现有框架第一个问题是内存效率低下。LlamaWeb 在启动时就静态分配好内存,让内存占用与提示词长度、生成长度无关、可预测。甚至连多个内核所需的中间存储空间,也会在模型第一次运行之前就预先占好。

更有意思的是模型加载这一侧。作者们在给 wllama(原本是让 llama.cpp 仅在 CPU 上以 WASM 运行的库)加上 WebGPU 支持时,把加载路径整个重写了一遍。权重存放在 OPFS(Origin Private File System)里,完全不会实体化到 WebAssembly 堆中。原因写在论文脚注里,读完会让人点头。

因为 WASM 堆是只增长(grow-only)的,标签页一旦分配过的内存,在该标签页存活期间就无法释放。

也就是说,就算加载过程中只是短暂碰过 2GB、随后就放开了,这 2GB 也要等标签页关闭才能收回。所以 LlamaWeb 用 llama.cpp 的异步加载接口,只靠 4 个 1MB 的缓冲区,就把权重从 OPFS 流式传输到 WebGPU 缓冲区。论文写道,"Safari 的内存使用限制尤其严格,这项改动让我们得以提供更大、更强的模型"。

结果的数字是这样的。以 Chrome 和 Safari 在多种设备组合上测得的峰值内存计算,相比现有浏览器推理框架平均减少 29%~33%。以 f16 版 Llama3.2 1B 为基准、四种测试配置归一化峰值内存的几何平均值来看,比 WebLLM 低 49%,比 Transformers.js 低 41%。按具体配置拆开看差异很大 — 在 Apple M4 Pro 的 Chrome 上,相比 WebLLM 只改善了 13%,相比 Transformers.js 只改善了 16%,但在同一设备的 Safari 上,这个差距扩大到 28% 和 59%。

不过,这个巨大差距的一部分,并不是因为 LlamaWeb 做得好,而是对手在漏内存。论文明确指出,在 Safari 上 Transformers.js 的内存一路涨到 10GB 然后标签页崩溃,看起来像是内存泄漏。在 RTX 5080 的 Windows 配置下,WebLLM 也以同样的方式涨到了接近 10GB。读基准测试的时候,不能把这类话删掉——"少用 61%"里相当一部分,其实是对手的 bug。

数字 — 与现有浏览器框架相比

性能对比是在四块 GPU 上做的:NVIDIA RTX 5080、Apple M4 Pro、AMD RX 7900 XT、Intel Arc B580。关键在于厂商全都不一样。

在解码(生成 token)吞吐量上,LlamaWeb 比 WebLLM 高约 54%,比 Transformers.js 高 69%。用论文摘要的说法,就是"在四块 GPU 上解码吞吐量提升 45%~69%"。

但 prefill(提示词处理)则相反。

但 prefill 性能落后,LlamaWeb 只能达到 WebLLM 吞吐量的 49%、Transformers.js 吞吐量的 79%。

还不到一半。论文指出了两个原因。第一,WebLLM 用了 TVM 精巧的内核融合(kernel fusion),减少了每个小算子反复读取模型权重的开销。第二,Transformers.js 拥有一套用 subgroup 优化过的矩阵乘法实现。

这里就显出了浏览器 LLM 的性格。解码由受内存带宽限制的矩阵-向量乘法主导,prefill 则由受算力限制的矩阵-矩阵乘法主导。LlamaWeb 把前者打磨得不错,但在后者上还缺一件工具。什么工具呢——

天花板的名字 — subgroup matrix

在 WebGPU 里,subgroup 是让工作组(workgroup)内的线程以 SIMT 方式互相交换数值的特性。这项已经落地了 — 在 Chrome 134 上,桌面端和 Android 都默认启用(Chrome Platform Status),此后也在陆续增补。subgroup_id 计划在 Chrome 144,subgroup_uniformity 在 Chrome 145,Subgroup Size Control 计划在 Chrome 152。(截至今天,stable 版本是 Chrome 151,152 的预定 stable 发布日是 2026 年 8 月 25 日。)

但 subgroup matrix 不一样。这是一项能直接打开 GPU 固定大小矩阵乘法单元的特性——用 NVIDIA 的话说就是张量核心——而 LLM 的 prefill 恰恰就想要这个单元。论文的内核库里已经有一个用到它的 sg_mat 内核。问题就在这一句话上。

subgroup matrix 特性目前还不能在 stable 版浏览器中使用,但在部分 WebGPU 实现里可以原生使用。

查一下确实如此。Chrome Platform Status 上与 subgroup 相关的条目就只有 Subgroups(134,默认启用)、subgroup_id(144)、subgroup_uniformity(145)、Subgroup Size Control(计划 152),根本没有 subgroup matrix 这一项。Google 现在处于表示要把 cooperative matrix 提案带到标准化小组的阶段,和当年 subgroups 一样,讨论中相当一部分预计会围绕可移植性(portability)展开。

于是作者们绕了个道。还记得前面提到 Dawn 是个 C++ 库这件事吗——用 Dawn 原生运行同一份后端代码,就能打开 subgroup matrix。这样一测,画面就反过来了。

实际上,如果用 LlamaWeb 原生运行下的 prefill 数据,除了 Apple M4 Pro 上的 WebLLM 之外,LlamaWeb 在所有设备上的 prefill 数据都超过了其他框架,相对 WebLLM 有 88%、相对 Transformers.js 有 205% 的几何平均速度提升。

同样的代码,同样的设备,不同的执行环境。在浏览器里是 WebLLM 的 49%,在浏览器外是 WebLLM 的 188%。如今浏览器 LLM 的 prefill 天花板,既不是算法,也不是内核质量,而是一项尚未标准化的硬件特性

论文的结论也正是这个调子——作者写道,"虽然 subgroup matrix 目前还不在浏览器里,但一旦它进入正式供应,LlamaWeb 就处在能提供有竞争力 prefill 性能的有利位置"。关键词是"一旦",不是"已经"。读的时候不能把这个条件从句抹掉。

与原生的差距 — 从 2.5 倍到 10 倍

那么,离开浏览器和原生后端比呢?论文用 llama.cpp 的 llama-bench,在同一设备上跑原生后端来做比较。

如 Fig. 5(a) 所示,CUDA 和 Vulkan 后端在两种权重格式下,prefill 上仍然领先 WebGPU 最多 10 倍,解码上领先 2.5 倍。这似乎是因为它们更好地利用了张量核心,并能访问异步内存加载等厂商专有的高级特性。

Apple 那边也类似——"Metal 后端始终领先 WebGPU 后端,prefill 上领先 2 倍以上,解码上领先 50%"。

这个差距在前面看到的 PR #17031 的基准测试里也同样出现。这是 PR 作者在自己的 M3 上测出的预备数据(也就是说,不是厂商报告,而是作者一个人、一台设备,llama-bench 是 llama.cpp 的原生 CLI 基准测试工具)。

Llama-3.2-1B-Instruct, Apple M3, llama-bench(PR #17031 作者测量,预备数据)

                      pp512 (t/s)         tg128 (t/s)
  F16   WebGPU      1014.17 ± 9.38       28.71 ± 0.19
  F16   Metal       1368.47 ± 0.95       35.99 ± 0.78
  Q4_0  WebGPU       960.52 ± 6.05       41.76 ± 0.62
  Q4_0  Metal       1346.68 ± 1.21      103.92 ± 0.37

看 Q4_0 的解码——41.76 对 103.92,正好是 2.5 倍。和前面论文的数字,独立地重现出了完全相同数量级的差距。

但方向并不是单一的。这是论文里最令人意外的部分——确实存在 WebGPU 战胜厂商专用后端的情况。

  • 在 Apple 上对比 Vulkan:相对于经由 MoltenVK 的 Vulkan 后端,WebGPU 在 prefill 上最多快 59%,在解码上快 45%。
  • 在 AMD 上:原生 HIP 后端始终领先 WebGPU,但 Vulkan 后端在 prefill 上落后 WebGPU 3 倍。
  • 在 Intel 上:关闭安全检查的 WebGPU 后端在 f16 模型的 prefill 上最快,领先 SYCL 后端 23%,在同一模型的解码上也领先原生 SYCL 10%。

论文将这一点解释为跨设备调优的效果——即便在 Linux 上 WebGPU 底层运行时正是 Vulkan,仍然存在 LlamaWeb 调优过的矩阵乘法参数击败 Vulkan 专用后端的设备。"原生总是更快"这种直觉是错的。更准确地说,调得好的可移植实现,能赢过没调好的原生实现

安全不是免费的 — bounds check 的账单

在浏览器里,WebGPU 编译器会往着色器里插入安全检查,用来防止缓冲区越界访问和除零。这不是可以商量的事——在任意网页都能往你的 GPU 上扔着色器这种模型下,没法把它去掉。

论文测量了这笔账到底是多少。

比较开启和关闭安全检查的性能,prefill 上 q4_k_m 模型平均慢 14%,f16 模型平均慢 23%,解码上则分别慢 5% 和 1%。这种减速在 NVIDIA RTX 5080 的 prefill 上最多达到 42%。

也就是说,安全检查在 prefill 上吃掉两位数的性能,在解码上则几乎免费。这也是一致的图景——因为在算力密集的矩阵乘法循环里,每个索引都要挂上一次检查。再回头看看前面 Intel 的例子,WebGPU 战胜 SYCL 的前提正是"关闭了安全检查"这个条件。在浏览器里,这个条件用不了。

论文的后续工作清单里,原封不动地列着这些——用更好的静态分析去掉不必要的 bounds check,在语言层面更稳健地处理浮点数差异,从而保证低精度执行下的模型稳定性,以及给 WebGPU 添加 u16 之类的新数据类型,让更高效的内存加载和原生硬件加速成为可能。

在这里,量化不是提速技巧

这部分越是玩过本地 LLM 的人,越会觉得意外。

就目前而言,WebGPU 上的 llama.cpp 量化,是一种降低运行给定模型所需内存量的技术,而不是一种性能优化。

在 CPU 或 CUDA 上用 q4,理由固然有内存,但也是为了省带宽从而换取速度。在 WebGPU 上,这个等式并不成立。论文给出了两个原因。第一,通用权重格式的反量化(dequantization)例程复杂且计算量大——不像 WebGPU 不支持的 nvfp4 这类硬件原生格式。第二,当前的内存布局,在 WebGPU 所面向的各种各样的 GPU 上,可能并未针对性能做过优化。

前面 M3 的表格印证了这一点——Q4_0 的 prefill(960)反而比 F16 的 prefill(1014)更慢。哪怕文件大小已经从 2.30GiB 缩到了 729.75MiB,只剩三分之一。在解码上,量化以 41.76 对 28.71 取胜(这里带宽占主导),但在 prefill 上,反量化的成本把收益吃掉了。

实务上的含义很简单。在浏览器里,量化的作用是让本来跑不动的模型跑起来,而不是让本来就能跑的模型跑得更快。而且正如上一节看到的,既然浏览器真正的限制是标签页内存,光凭这一点,它就已经值回票价了。

浏览器差异 — Firefox 干脆被排除在外

在一篇讲可移植性的文章里,把这一段拿掉是不诚实的。

Firefox 虽然支持 WebGPU,但我们观察到性能明显偏低。举例来说,在 Apple M3 上,使用 q4_k_m 权重的 llama 模型在 Firefox 上大约以每秒 1 个 token 的速度运行,在 Chrome 上则约为每秒 52 个 token,因此我们在评估中排除了 Firefox 的结果。

整整 50 倍。论文写道,Chrome 及其底层的 Dawn "在很多平台上都一贯提供了最高、最稳定的 WebGPU 性能",所以除非另有说明,所有实验都在 Chrome 上运行。在 iOS 上,因为用不了其他浏览器引擎,所以用 Safari。

「WebGPU 三大浏览器都支持」这句话是事实,但在生产环境里靠这句话吃饭会吃亏。功能支持(feature support)和性能对等(performance parity)是两个不同的维度,截至 2026 年 7 月,后者完全没有到位。

浮点数那边也有地雷。看论文的一条脚注——开发过程中,如果在寄存器分块内核里用 f16 累加(accumulation),会导致 Qwen2.5 之类的部分模型在 Apple M 系列 GPU 上输出胡言乱语,所以目前这个内核改用了 f32 累加。用作者的话说,"浮点精度差异是一个众所周知的问题,在 WebGPU 及其应用中处理这个问题,并不是一个已解决的问题"。这类 bug 讨厌的地方在于,它不会表现为崩溃,而是悄无声息的质量下降。

给实践者的总结 — 特性检测与回退

在代码层面,这一切归结成一件事:不要假设有某个特性,去查询它。

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) throw new Error('WebGPU 어댑터 없음');

// subgroups 在 Chrome 134+ 中默认启用,
// 但仍需确认适配器是否真的声明支持,并显式请求它。
const wanted = ['subgroups', 'shader-f16'].filter((f) => adapter.features.has(f));

const device = await adapter.requestDevice({ requiredFeatures: wanted });

// 截至 2026 年 7 月,subgroup matrix 在任何 stable 浏览器中都不存在。
// 不要写出假装它存在的代码,把回退路径当作默认路径来对待。
const hasSubgroups = device.features.has('subgroups');

LlamaWeb 的设计正是这个形状——sg_mat 内核是有的,但不是默认路径,在不支持的设备上会回退到寄存器分块。照搬 PR #17031 的话再说一遍,"在不支持的设备上,代码会回退到寄存器分块方式"。在浏览器里,现在这个回退就是所有设备的路径。

还有一点。内核编译会在模型第一次前向传播(forward pass)期间产生一次性开销(约 1~5 秒),此后就用缓存好的流水线来执行。设计首 token 延迟时,不要忘了这几秒钟。

那么,你应该在浏览器里跑 LLM 吗

把数据摆在面前,判断标准就相当清晰了。

值回成本的情况

  • 数据不能离开设备。这几乎是唯一压倒性的理由——因为这是唯一值得为之忍受 2.5 倍变慢的那种需求。
  • 模型很小。论文实测的模型体积在 0.23GB 到 3.11GB 之间。
  • 工作负载以解码为主——短提示词、长生成。这是浏览器推理相对输得最少的区间。
  • 部署必须只靠一个 URL。不用安装、不用运行时、不用服务器——这仍然是实实在在的价值。

还不到时候的情况

  • prefill 主导——像 RAG 那样把整篇长文档一次性塞进去的模式。这正是差距最多拉到 10 倍的区间,而能修好它的特性还没进标准。
  • 必须支持 Firefox。50 倍不是性能问题,是功能问题。
  • 对精度敏感。论文里已经有 f16 累加悄悄毁掉输出的案例。
  • 能用服务器 GPU、也没有隐私限制。那就直接放在服务器上跑。浏览器推理是隐私与部署技术,不是省成本的技术。

最后一项最重要。"服务器成本为零"是事实,但作为代价,你要承担用户的电量、2.5~10 倍的性能差距,以及 8 家厂商的硬件差异。那不是节省,而是转移(transfer)。

结语

把 2026 年浏览器 LLM 推理的现状浓缩成一段话,是这样的。工程层面已经成熟——静态内存规划和 OPFS 流式加载把内存降低了 29%~33%,调优过的可移植内核在部分设备上能赢过厂商专用的原生后端。可是在 prefill 上,依然要输给原生最多 10 倍,而这背后很大一块原因,是缺少一个能打开张量核心的标准特性。证据就是,把同一份代码搬到浏览器外、配上这项特性运行,名次就会反过来。

所以这个故事的教训并不是"浏览器还是慢"。恰恰相反——瓶颈现在已经不在软件层面,而在标准化的时间表上。想想 subgroups 从最初的提案到在 Chrome 134 里默认启用,经历了大约一年的 origin trial,就能明白 cooperative matrix 目前还处在提案阶段这件事意味着什么。而那场讨论的核心,会是可移植性而不是性能——用一套安全的 API 去覆盖 8 家厂商的矩阵单元,本来就很难。

在那之前,决定在浏览器里跑 LLM,是一个隐私决定,不是一个性能决定。用这个框架去看,这项技术在今天就已经足够值回票价。用基准测试的框架去看,你会失望的。

参考资料