做前端的都知道,AI能力基本都在服务端。你的代码补全、智能搜索、聊天机器人,背后全是API调用。但有些场景就是不想把数据发到服务端——隐私敏感数据、离线环境、或者单纯不想付API费用。
WebGPU + WebLLM给了一个不一样的答案:直接在浏览器里跑大模型,零服务端依赖。
我花了两天时间把这套东西跑通了,记录一下真实体验。
WebGPU是什么,为什么现在才行
WebGL搞了十几年,能力一直停留在图形渲染。通用计算(GPGPU)虽然能用WebGL hack出来,但开发体验极差,性能也打折。
WebGPU是W3C搞的新标准,2023年Chrome 113开始支持。和WebGL最大的区别:它原生支持Compute Shader,可以直接在GPU上跑通用计算任务。这意味着矩阵乘法、向量运算这些LLM推理的核心操作,终于能在浏览器里用GPU加速了。
目前的浏览器支持情况:
| 浏览器 | WebGPU支持 | 备注 |
|---|---|---|
| Chrome 113+ | ✅ 默认开启 | 推荐,最稳定 |
| Edge 113+ | ✅ 默认开启 | 和Chrome一样的内核 |
| Firefox | ⚠️ Nightly | 需要手动开flag |
| Safari 18+ | ✅ 默认开启 | macOS 15 / iOS 18 |
WebLLM:把LLM搬进浏览器的工具链
WebLLM是MLC AI团队做的开源项目,核心思路:用TVM编译器把LLM模型编译成WebGPU可以执行的格式,然后在浏览器里加载运行。
API设计完全兼容OpenAI的Chat Completions格式,这一点很聪明。意味着你现有的代码几乎不用改,把API endpoint从OpenAI换成本地的WebLLM engine就行。
安装:
npm install @mlc-ai/web-llm
实战:5分钟跑一个本地AI聊天
我搭了一个最简单的demo来测试实际效果。
import { CreateMLCEngine } from "@mlc-ai/web-llm";
// 初始化引擎,选择模型
const engine = await CreateMLCEngine("Llama-3.1-8B-Instruct-q4f16_1-MLC", {
initProgressCallback: (progress) => {
console.log(`加载进度: ${progress.text}`);
},
});
// 和OpenAI一模一样的调用方式
const response = await engine.chat.completions.create({
messages: [
{ role: "system", content: "你是一个前端技术专家" },
{ role: "user", content: "解释一下React Server Components的工作原理" },
],
temperature: 0.7,
stream: true,
});
// 流式输出
for await (const chunk of response) {
const delta = chunk.choices[0]?.delta?.content || "";
process.stdout.write(delta);
}
代码很简单,但第一次跑的时候有个坑:模型下载。
踩坑记录
坑1:首次加载巨慢
Llama-3.1-8B的q4f16量化版大概4.3GB。首次加载要从HuggingFace下载完整模型到浏览器的IndexedDB里。我50Mbps的网络跑了大概15分钟。
好消息是只需要下载一次,后续刷新页面直接从IndexedDB读取,几秒钟就能加载完成。
如果你的用户在国内,HuggingFace的下载速度大概率会很痛苦。目前没有官方的国内镜像方案,只能自己搭CDN代理。
坑2:显存占用超出预期
8B模型q4f16量化后虽然文件只有4.3GB,但运行时GPU显存占用大概5-6GB。我台式机的RTX 3060(12GB显存)跑起来没问题,但在集显笔记本上直接崩了——共享内存不够。
实测不同模型的显存需求:
| 模型 | 量化 | 文件大小 | 运行显存 | 推理速度 |
|---|---|---|---|---|
| Qwen2-0.5B | q4f16 | 350MB | ~1GB | ~45 tok/s |
| Phi-3.5-mini-3.8B | q4f16 | 2.2GB | ~3GB | ~25 tok/s |
| Llama-3.1-8B | q4f16 | 4.3GB | ~6GB | ~15 tok/s |
| Mistral-7B | q4f16 | 3.9GB | ~5GB | ~18 tok/s |
速度数据是在RTX 3060上测的。集显或者核显的话速度大概只有这个的1/3到1/5。
坑3:Safari的WebGPU实现有差异
虽然Safari 18宣布支持WebGPU,但实际跑起来有些shader编译会报错。MLC团队在持续适配,但目前最稳的还是Chrome。如果你的应用要兼容Safari,建议先用小模型测一遍。
坑4:Web Worker是必须的
千万别在主线程跑推理。模型加载和推理过程会完全阻塞UI,页面直接卡死。必须用Web Worker:
// worker.ts
import { MLCEngineWorkerHandler, MLCEngine } from "@mlc-ai/web-llm";
const engine = new MLCEngine();
const handler = new MLCEngineWorkerHandler(engine);
self.onmessage = (msg) => handler.onmessage(msg);
// main.ts
import { WebWorkerMLCEngine } from "@mlc-ai/web-llm";
const engine = await WebWorkerMLCEngine(
new Worker(new URL("./worker.ts", import.meta.url), { type: "module" }),
"Phi-3.5-mini-instruct-q4f16_1-MLC"
);
WebLLM官方提供了Worker封装,用起来还算方便。但如果你的项目用了Vite或者webpack,Worker的打包配置可能需要折腾一下。
JSON Mode和Function Calling
WebLLM支持JSON Mode结构化输出,这对前端应用很有用——你可以让模型直接输出符合特定schema的JSON,不需要自己解析自然语言。
const response = await engine.chat.completions.create({
messages: [
{
role: "user",
content: "分析这段用户反馈的情感倾向:'这个功能太难用了,每次都要点好几步'"
}
],
response_format: {
type: "json_object",
schema: JSON.stringify({
type: "object",
properties: {
sentiment: { type: "string", enum: ["positive", "negative", "neutral"] },
score: { type: "number", minimum: -1, maximum: 1 },
keywords: { type: "array", items: { type: "string" } },
suggestion: { type: "string" }
},
required: ["sentiment", "score", "keywords"]
})
},
temperature: 0.1,
});
// 输出:
// {
// "sentiment": "negative",
// "score": -0.7,
// "keywords": ["难用", "好几步"],
// "suggestion": "简化操作流程,减少点击步骤"
// }
JSON Mode的实现是在WebAssembly层面做的约束解码,不是靠prompt engineering,所以输出格式是保证正确的。这比调API然后祈祷返回格式正确要靠谱得多。
实际能干什么
说实话,浏览器里跑8B模型的能力和GPT-4比差距还是很大的。但有几个场景确实合适:
1. 表单智能填充
用户输入一段自然语言描述,本地模型解析成结构化数据填入表单。数据不出浏览器,隐私问题直接解决。用Qwen2-0.5B这种小模型就够了,响应速度也快。
2. 离线文档助手
把文档问答能力内嵌到Web应用里。用户断网也能用。适合企业内部工具、敏感文档处理这些场景。
3. 实时文本处理
拼写检查、文本摘要、分类标签。这些不需要太强的模型能力,3B以下的模型就能做得不错,在浏览器里跑延迟比调API还低。
4. AI功能的降级方案
主路径走API,API挂了或者超时就降级到浏览器本地推理。体验虽然差一点,但至少功能还能用。
性能优化建议
如果你真的要在生产环境用,这几点需要注意:
模型预加载:在用户真正需要AI功能之前就开始后台加载模型。可以在Service Worker里做,或者在用户浏览其他页面的时候静默加载。
选对模型大小:不要上来就整8B。大多数文本处理任务0.5B-3B就够了,加载快、推理快、显存占用小。只有需要复杂推理能力的时候才上大模型。
做好降级:检测WebGPU支持,不支持就回退到API调用。代码大概长这样:
async function getAIEngine() {
if (navigator.gpu) {
try {
const adapter = await navigator.gpu.requestAdapter();
if (adapter) {
return await createLocalEngine(); // WebLLM
}
} catch (e) {
console.warn("WebGPU初始化失败,降级到API", e);
}
}
return createAPIEngine(); // 远程API
}
缓存管理:IndexedDB里存的模型文件会一直占用磁盘空间。给用户提供清除缓存的选项,或者设置过期策略。
和其他方案的对比
| 方案 | 运行环境 | 模型大小 | 速度 | 隐私 | 易用性 |
|---|---|---|---|---|---|
| WebLLM (WebGPU) | 浏览器 | 0.5-8B | 15-45 tok/s | 完全本地 | npm装一下就行 |
| Transformers.js (WASM) | 浏览器 | 主要小模型 | 较慢 | 完全本地 | HuggingFace生态 |
| Ollama + API | 本地服务 | 不限 | 取决于硬件 | 本地网络 | 需装桌面软件 |
| OpenAI API | 云端 | 不限 | 很快 | 数据上传 | 最简单 |
WebLLM的核心优势就是零安装 + 完全本地。用户打开网页就能用,不需要装任何东西,数据不出浏览器。但代价是模型能力受限于浏览器环境和用户设备的GPU。
当前的局限
泼一盆冷水:
模型能力上限明显。浏览器能跑的最大就8B量化模型,和70B或者闭源大模型的差距是质的区别。复杂推理、长文本理解这些任务做不了。
用户设备差异巨大。你在RTX 4090上测得飞快,用户可能拿着5年前的集显笔记本,直接OOM。做好设备检测和降级是必须的。
首次加载体验差。下载几个GB的模型文件,对大多数用户来说是不可接受的等待时间。需要很好的进度提示和预加载策略。
调试困难。WebGPU的错误信息通常不太友好,模型推理过程是黑盒,出了问题很难定位。
值不值得现在用
如果你的场景刚好是:隐私敏感 + 不需要太强的模型能力 + 用户设备还行,那WebLLM是个很好的方案。JSON Mode和OpenAI兼容API让集成成本很低。
如果不是上面的场景,调API仍然是更稳妥的选择。不要为了用新技术而用新技术。
但趋势是明确的:WebGPU的浏览器覆盖率在快速提升,模型压缩技术在进步,小模型的能力在变强。浏览器端AI从”能跑”到”能用”的临界点,可能比大多数人预想的要近。