浏览器里跑大模型的硬限制

内存天花板、静态托管下没有 SharedArrayBuffer 所以只能单线程 WASM、WebGPU 支持参差不齐,以及 3–8 token/s 对 0.5B 模型意味着什么。

作者 SecureRAG Team · · 10 分钟阅读

浏览器标签页是一个受限的运行环境:内存有硬上限、线程不可靠、GPU 支持参差不齐。这三条决定了哪些大模型任务能在本地做,哪些不能;搞清楚它们比记住任何跑分数值都有用,因为约束是结构性的。

第一堵墙是内存

模型需要的一切——权重、KV 缓存、中间激活值、分词器,再加上页面本身、索引和向量——都装在同一个标签页里。桌面浏览器上,实际能用的额度在个位数 GB 之内就得开始担心;手机和平板上预算小得多,系统还可能在你不注意的时候直接把标签页回收掉。

模型占多少内存在算术上没什么商量余地。float32 全精度权重每个参数约占四个字节,一个 0.5B 模型就要约 2GB。这就是可选生成档使用 4 位量化的原因:Qwen2.5 0.5B(q4)约 400MB,1.5B(q4)约 1.0GB。这是下载体积,也接近常驻占用;超过 30MB 的档位不会在你没点确认的情况下开始下载,代价总是先摆出来再发生。

上下文长度抢的是同一块内存。注意力机制需要一份随上下文长度和已生成 token 数增长的 KV 缓存。0.5B 模型配几千 token 的上下文很舒服;让它把两百页的报告整个记住,就不舒服了。这也是流程只取 6 块、而不是把整个文档库塞进提示词的原因:检索既是为质量服务,也是为内存服务。

没有 COOP 和 COEP,就没有多线程

多线程 WebAssembly 依赖 SharedArrayBuffer,而浏览器只把 SharedArrayBuffer 交给跨源隔离的页面——也就是带 Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp 响应头的页面。

问题出在静态托管上。GitHub Pages、普通的 Netlify 或 Cloudflare Pages 部署,以及多数免费套餐都不允许你设置这些响应头,于是页面不在跨源隔离状态,SharedArrayBuffer 用不了,运行时的多线程构建在初始化阶段就失败,报的还是“没有可用适配器”之类看不出根因的错。出路有两条:换到能自定义响应头的托管,或者固定在单线程。本站选了后者,而且是有意选的:

wasm.numThreads = 1;
wasm.simd = true;

交换条件清楚且不对称。单线程 WASM 开 SIMD 之后什么任务都更慢,但本站的前提就是“只要能打开浏览器就能用”,不依赖构建步骤,也不依赖你控制不了的响应头策略。如果你把这份代码部署在自己能设置 COOP/COEP 的托管上,把线程数打开是单项收益最大的性能改动。

WebGPU 是抛硬币,这没关系

WebGPU 是“每秒几个 token”和“用起来顺”之间的分界,而它的可用性并不均匀:桌面 Chrome 和 Edge 现在都有,Safari 是一步步加上来的,Firefox 是较近的事,移动端取决于浏览器版本和设备。即便 API 存在,某个具体驱动也可能在加载时失败。

所以引擎把 WebGPU 当作优化项,而不是前提条件:浏览器暴露它就申请适配器;拿不到适配器,或者管线初始化失败,就在 CPU/WASM 上加载同一个模型。同样的权重,同样的输出,只是慢。产品里没有任何功能依赖 GPU 这条路径存在——这也是站点里找不到“需要 WebGPU”这种字样的原因。

0.5B 模型能做什么,不能做什么

一个 4 位量化的 0.5B 或 1.5B 指令微调模型,在一个很窄的任务带里确实好用,出了这个带子就很糟。两半都得讲:

任务 本地 0.5B–1.5B 原因
改写或摘要你提供的段落 可以,通常还不错 事实已经在提示词里,任务本质是文本变换
从召回的章节里抽出义务清单 可以,但要核对 短上下文上的模式抽取
回答“哪一条这么规定的”并带引用 可以,关键是能核对引用 这是选择任务,不是推理任务
多步规划、调用工具、修代码的循环 不行 需要在长流程上稳定遵循指令
对表格里的数字做算术 不行 小模型做不对算术,该用代码算
依赖提示词之外的事实 不行 这个规模的权重里没有事实

真正要提防的不是答错,是流畅地答错。你拿提示词之外的东西问一个 0.5B 模型,它会产出语法正确、语气笃定的段落,因为它就是被训练成这样的。应对办法是结构性的,不是靠什么技巧:把答案用到的块列出来,并且在文档库沉默时用相关度门控直接拒答。本站默认的回答环节就是把召回的段落本身整理出来,生成档是可选升级。

每秒 3 到 8 个 token 是什么体感

生成速度决定了功能能不能用。在 CPU/WASM 上,量化后的 0.5B 模型在常见硬件上大约每秒生成 3 到 8 个 token。换算一下:

  • 每分钟 180 到 480 个 token。
  • 中文大约每分钟 200 到 500 字——二十秒出一个小段落,一页密集文字要几分钟。
  • 英文大约每分钟 130 到 360 个词,注意英文每个 token 覆盖的字符更少。

单次生成的回答还有约 320 个新 token 的上限,这是有意设的:一旦超过一两段,模型对召回证据的把握就开始松,输出会飘。一个被限制住、扎在证据上的段落,比一段等四分钟才出现、已经离题的长文有用得多。

落到使用上:逐 token 流式输出让等待变得可以忍受——用来读可以,用来来回交互就难受。你没法像用云端模型那样跟一个 1.5B 模型反复来回打磨,但你可以提一个问题、读一段话、核一下引用,而文档查阅本来就是这件事。

还有一点值得记住:在同一台机器上,WebGPU 相对 WASM 常常是几倍的差距,二十秒的等待会缩短到几秒。但它不会改变这个模型能被要求做什么。速度上限和能力上限是两条独立的边界,GPU 存在时移动的只有前一条。

把活儿分开

有用的做法是在动手之前按任务分类:

  • 交给本地模型: 改写和摘要你给出的段落、在短上下文上做信息抽取、把召回的表格转成叙述、把一段话统一改写另一种语体。
  • 不要交给本地模型: 多步推理、任何需要调用工具的事、算术、整篇文档翻译、长文写作,以及答案根本不在召回块里的问题。
  • 干脆不用生成档: 当你的问题就是查阅类问题时。带引用的检索回答“合同对通知期怎么规定”,比一个 0.5B 模型转述一遍更好,而且瞬间返回、断网也行。

浏览器推理不是一个缩小版的云端模型,它是另一种工具,有一条很窄但确实有用的适用带。知道这条带子的边界在哪里,才不会做出“本地 AI 没用”的结论——真正的问题往往是让它去干一件标签页内存根本装不下的事。