浏览器跑LLM:WebGPU达63 tokens/s,WASM仅1.6

浏览器内跑 LLM 实测:WebGPU 达 62.9 tokens/s,WASM 回退仅 1.6,1B 模型反而最划算。
BEINGS 聊天室项目让每个 AI 角色跑在带它进房间的用户浏览器里,从而免去服务器推理账单。实测同一 360M 模型在 WebGPU 后端达 62.9 tokens/s、单条回复 0.54 秒,回退到 WASM 仅 1.6 tokens/s、26.9 秒,后者慢的部分原因是页面未开启跨源隔离只跑了单线程。实现依赖量化 ONNX 模型、Transformers.js 和 WebGPU,模型须放入 Web Worker,且需警惕 Turbopack 把 TS worker 当静态资源输出导致静默失效。反直觉的是 1B 的 Llama 3.2 回复速度不输小模型且更连贯,因为小模型会一路扯到 token 上限。采样参数对观感的影响大于模型大小,规则式垃圾过滤器能拦住坏文本却拦不住无意义文本。
浏览器跑LLM:WebGPU达63 tokens/s,WASM仅1.6
把语言模型塞进网页里运行,这件事已经不只是演示了。一个叫 BEINGS 的聊天室项目做了完整的实测:同一个 360M 参数的小模型,在 WebGPU 后端跑到 62.9 tokens/s,而回退到 CPU 的 WASM 后端只有 1.6 tokens/s——中位回复耗时分别是 0.54 秒和 26.9 秒。

这个差距意味着什么?在聊天场景里,27 秒等于对话早就翻篇了,你的 AI 角色还没把话说完。但对于一个不想为推理付一分钱的平台来说,把计算放在用户自己的浏览器里,依然是笔划算的账。
为什么要让用户设备承担推理
BEINGS 的设定是每个人带自己的 AI 角色进聊天室:一个认为披萨是市场信号的金融男、一个把手机当妖术的中世纪骑士。这些角色全天候互相说话、跟人说话,调用量不小。
如果走服务器转发到付费 API,每个热闹的房间都是账单。于是架构选择反过来:每个角色跑在带它进来的那个人的设备上。落地页写得很直白——没有 API key、不用注册、不用付费。云端大脑(OpenAI、Claude、Gemini)是可选升级项,用户粘贴自己的 key,key 存在 localStorage 里直连服务商,不经过平台。
代价也很诚实:用户要下载几百 MB 到 1GB 以上的模型文件,本地 GPU 干活,小模型确实比云端大模型笨得多。对混乱的聊天室来说,笨但免费是合适的交易;对客服机器人来说,大概不是。
核心实现:Worker + Transformers.js + WebGPU
在浏览器里跑 LLM 需要三样东西:一个转成 ONNX 并量化过的模型(4-bit 权重,1B 模型约 1.2GB 而非 5GB)、一个页面内执行它的运行时(Transformers.js,封装了 ONNX Runtime Web)、以及一个 GPU 后端(WebGPU),CPU 上的 WebAssembly 作为回退。模型必须放在 Web Worker 里,否则页面会冻住。
代码量大约 90 行 JavaScript。几个值得留意的细节:
- dtype 跟着设备变:WebGPU 上加载 q4f16(4-bit 权重、16-bit 计算),WASM 上加载 q4。这是 Hugging Face 上两个不同的文件,SmolLM2 的 WebGPU 文件 272.7MB,WASM 文件 387.9MB。
- WebGPU 存在不等于能用:
"gpu" in navigator为 true 不代表驱动配合,所以 WebGPU 加载失败时自动重试 WASM,而不是直接报错。 - 进度要回传页面:1.2GB 的首次下载没有进度条,看起来就像网站坏了。
- Transformers.js 从固定版本 CDN 加载,不走 npm,这是故意的。
一个静默的打包器陷阱
BEINGS 是 Next.js 16 + Turbopack 项目。用“标准”写法(TypeScript worker 文件 + new Worker(new URL(...)))时,Turbopack 把 worker 作为一个原始 .ts 文件吐到静态目录里。浏览器无法执行 TypeScript,worker 永远不启动,本地推理全挂——而且构建过程零报错。
修复方式很朴素:worker 手写成 JavaScript 放在 /public,按原样提供,用普通路径加载,从 CDN 引入 Transformers.js,让打包器完全不碰它。教训是:每次构建后都去部署好的站点上确认 worker 真的启动了,一个悄悄死掉的 worker 看起来跟慢模型一模一样。
一次只跑一个生成
一个用户可能带六个角色进同一个房间。如果六个同时调模型,笔记本要么加载六份副本,要么争抢同一块 GPU。所以页面侧只持有一个 worker,所有生成请求排队串行执行。
关键在于队列衔接的写法:每次调用等待上一次结束,无论成功还是失败,这样一次崩溃的生成不会堵死整条队。90 秒的超时则是因为 WASM 回退下单次回复可能要将近一分钟。
ONNX 与量化:ONNX(Open Neural Network Exchange)是一种跨框架的模型交换格式,PyTorch 等框架训练出的模型可导出为 ONNX 计算图,再由 ONNX Runtime 在多种硬件上执行。浏览器里的 Transformers.js 底层正是 ONNX Runtime Web。量化则是把模型权重从 16-bit 浮点压缩到 4-bit 整数,体积约缩至四分之一,代价是少量精度损失。文中提到的 q4f16 表示权重 4-bit、计算时反量化为 16-bit 浮点,适合 WebGPU 的浮点运算单元;q4 则全程以整数路径执行,更适合 WASM 的 CPU 实现。同一模型因此会有多个体积不同的权重文件,选择哪个取决于目标后端。
WebGPU 与 WebAssembly 的分工:WebGPU 是浏览器暴露底层图形与计算 API 的新标准,可通过 WGSL 着色器执行大规模并行矩阵运算,是当前网页内跑 LLM 的性能路径。WebAssembly(WASM)则是可移植的字节码,在 CPU 上以接近原生的速度执行,但缺少 GPU 的并行吞吐。两者在 Transformers.js 中是可切换的后端:优先 WebGPU,失败则回退 WASM,这也解释了文中两个数量级的性能差距。
实测数据:WebGPU 与 WASM 的鸿沟
测试环境是 Chrome 155、Windows 11、RTX 4070 桌面机、24 逻辑核心。脚本在线上站点开自己的标签页,启动真实的 worker,加载每个模型,用 24 条真实聊天回合提问,每条 70 token 上限。
结果的主线是后端而非模型:
| 模型 | WebGPU | WASM |
|---|---|---|
| SmolLM2 360M | 62.9 tokens/s,0.54 秒/回复 | 1.6 tokens/s,26.9 秒/回复 |
| Llama 3.2 1B | 55–64 tokens/s,0.4–0.5 秒/回复 | 未测 |
| Qwen 2.5 0.5B | 37–40 tokens/s,约 1.8 秒/回复 | 未测 |
WASM 那边的 1.6 tokens/s 可能还是自己挖的坑。ONNX Runtime Web 只有在页面处于跨源隔离(cross-origin isolated)状态时才会用多线程,否则单线程跑。BEINGS 的页面发了 COOP 头但没发 COEP 头,基准测试标签页报告 crossOriginIsolated: false、没有 SharedArrayBuffer——也就是说回退路径在 24 线程的机器上只用了 1 个线程。作者推测开启隔离后回退能快好几倍,但没实测所以不给承诺。麻烦在于 COEP 会拦截未声明许可的跨源嵌入,而 BEINGS 嵌了 YouTube 和维基媒体的内容。
还有个反直觉的发现:最大的模型回复最快。Llama 3.2 1B 的生成速度跟 SmolLM2 接近,但中位回复只花 0.4–0.5 秒,因为它说 24 到 30 个 token 就把话说完了;小模型反而一路扯到 70 token 上限。Qwen 0.5B 是三者里最慢的。
下载才是首玩的真实成本
冷启动(含下载)分别是 7.5 秒(SmolLM2,273MB)、13.4 秒(Qwen,483MB)、26.8 秒(Llama,1238MB)。缓存之后同样的加载只要 1.0、2.2、3.1 秒。Transformers.js 把文件存在浏览器的 Cache Storage 里,第二次访问不用写任何缓存代码就很快。
但用户不都网速快。手机 4G 上下 1.2GB 意味着“回头再来”,这就是为什么应用让用户选更小的大脑,也是为什么进度回传比看起来更重要。
跨源隔离与 SharedArrayBuffer:WASM 多线程依赖 SharedArrayBuffer,而浏览器出于 Spectre 等侧信道攻击的考虑,只有在页面处于“跨源隔离”状态时才允许使用它。开启隔离需要同时发送两个响应头:COOP(Cross-Origin-Opener-Policy: same-origin)和 COEP(Cross-Origin-Embedder-Policy: require-corp)。COEP 要求页面嵌入的所有跨源资源都显式声明许可(通过 CORP 头或 CORS),否则会被拦截。BEINGS 只发了 COOP 而没发 COEP,因此 crossOriginIsolated 为 false,ONNX Runtime Web 退回单线程——这正是 1.6 tokens/s 的部分成因。开启隔离的代价是 YouTube 嵌入、维基媒体图片等第三方内容需要逐一处理,这也是作者没有直接开启的原因。
采样参数比模型大小更影响观感
早期应用用 temperature 1.05、top_p 0.95 追求“更混乱”。结果连 Qwen 都会循环输出 “u.u.u.u.u.” 和 “The The FQ Q M”。最终稳定下来的配置是:temperature 0.75、top_p 0.9、top_k 40、重复惩罚 1.12,加上 no_repeat_ngram_size: 3(禁止任何三 token 序列重复)。
这次只测了温度这一个旋钮,其他保持在安全值:
- Qwen 0.5B:0.75 时 12 条里 2 条被当作垃圾丢弃,1.05 时 12 条里丢了 6 条。高温下的真实样例:“CODESUPONSEGRAPHTEGOINSTRUMENTPRODUSONGRAPH...”
- SmolLM2 360M:中位回复从 15 词涨到 40.5 词,12 条里 7 条撞上 70 token 上限。提示词要求 20 词以内,高温下它不听了。
- Llama 3.2 1B:两个温度下 24 条全部可用,且保持人设。
如果只改一个设置:1B 参数以下保持温度在 0.8 以内,并打开 no_repeat_ngram_size。
采样参数各自的作用:temperature 控制概率分布的平滑程度,值越高输出越随机,接近 0 则趋于贪心解码;top_p(核采样)从累积概率达到 p 的最小词集合中采样,用于截断低概率长尾;top_k 则只保留概率最高的 k 个候选词。重复惩罚对已出现过的 token 降低其再次被选中的概率,而 no_repeat_ngram_size 是更硬的约束:直接禁止任何长度为 n 的 token 序列重复出现,n=3 即禁止三连词复现。小模型因为容量有限,在高温下容易陷入概率分布的局部循环,表现为“u.u.u.u.”这类退化输出;降低温度并加硬性 n-gram 约束,比换更大的模型更能直接改善观感。
垃圾过滤器:沉默好过胡言乱语
即便参数调好,360M 模型偶尔也会产出不该给人看的东西。BEINGS 的规则是“宁可角色沉默,不要角色坏掉”,每条回复在发布前都过一遍确定性的规则过滤——不用第二个模型,不调 API,64 行代码。
过滤器检查的东西包括:markup 或模板碎片、字母占比不足 55% 的符号汤、短片段重复(“u.u.u.u.”)、词汇循环、散落的单双字母碎片、没有元音的生造词。它有自己的测试文件,用 bug 报告里的真实坏行当 fixture,也包含必须放行的好行,比如 “Penar wingle berry” 和 “THIS IS BULLISH ACTUALLY, LOAD UP”。测试结果 14 通过 0 失败。
抓到了什么,漏了什么
拿全部 96 条基准回复跑一遍过滤和指令泄露检查,丢掉了 13 条:11 条判为垃圾,2 条判为泄露指令(有一条 SmolLM2 的回复以 “Speak in character now!” 结尾)。粘在一起的词、游离的 --> 标记、Qwen 的词语沙拉都被拦下了。
但它也放过了人眼会丢掉的东西。Qwen 的 “Toestrafing GRating” 通过了,因为两个词都有元音、都不重复;一条关于 “MULTILATERAL ORTHODOZIC” 的全大写 SmolLM2 回复也通过了。规则过滤器抓的是坏掉的文本,不是无意义的文本。作者的判断是:对混乱聊天室没问题,对任何要给人看的内容远远不够——如果语义重要,你需要更大的模型,而不是更聪明的正则。
踩过的坑与落地建议
作者复盘了几个失败点:打包器把 worker 吐成 .ts 导致推理静默失效;混乱采样参数让小模型打转;小模型复述自己的指令(修法是泄露过滤加提示词改成真实聊天回合格式,因为“小模型会复述元指令,但能处理它们被微调过的聊天格式”);六个角色抢一台笔记本;以及在真实硬件上从未验证过——直到这次实测。
给想在自己项目里做同样事情的人:用 module Web Worker 装 Transformers.js,一个 worker、一个模型、串行生成;选同时提供 q4f16 和 q4 文件的 ONNX 模型;默认 1B 参数左右,而不是能加载的最小模型——实测里 Llama 3.2 1B 每条回复跟 SmolLM2 360M 一样快且连贯得多,小模型留给“慢网速”选项;worker 内部做 WebGPU 到 WASM 的回退,在意回退性能就开跨源隔离,然后测试嵌入内容还能不能用;小模型温度不超过 0.75;发布前过滤;显示下载进度并提前告知体积;每次构建后去真实浏览器里确认 worker 活着。
这个方向的想象空间
作者认为有意思的转变不是小模型变好了——它们还没好。而是“用户设备买单”改变了什么值得做。一个在 API 上每条消息花一分钱的功能,在浏览器里永远免费,于是整类傻气、话痨、常开的功能对独立开发者变得可行。硬件也在移动这条线:今天标签页里的 1B 模型,差不多是不久前需要服务器的量级。对比另一端,一个“开放”的 816B 模型要跑起来需要约 1TB 内存。
至于怎么选:如果应用确实调用托管模型,最便宜的 token 是你不用重复发送的那个;而如果要在浏览器内推理和 API 之间做选择,先把 API 那一侧的成本算清楚。
相关推荐

Cursor AI编程实战:30分钟从零构建学生管理系统
B站UP主实战演示Cursor AI编程:30分钟用自然语言生成学生管理系统。详解Agent模式、Claude模型选择、提示词技巧与使用门槛,帮你判断Cursor是否值得入手。

AI编程工具20强榜单:从夯到拉的评分矩阵
B站UP主发布AI编程工具20强榜单,从夯到拉分为T0-T4五个层级,Claude Code和Codex并列T0。本文详解评分维度(能力、可靠性、耐操性、耐久性、成本、国内可用性)及工具表现,帮助开发者选择适合的AI编程助手。

Vibe Coding系统教程:零基础玩转AI编程实战
B站UP主耗时三个月打造的Vibe Coding系统教程,覆盖Codex、ClaudeCode、Cursor等AI编程工具,从基础逻辑到商业级项目实战三段式讲解,适合零基础学习者系统入门AI编程。