浏览器内 LLM 推理:WebGPU 量化模型与推理管线搭建
浏览器内 LLM 推理:WebGPU 量化模型与推理管线搭建
一、端侧推理的取舍:为何选择 WebGPU 跑量化 LLM
大模型应用的前端落地长期面临两难。调用云端 API 会引入网络延迟与 token 成本,且数据出域在金融、医疗等场景不可接受。本地原生部署又受限于分发链路与跨平台兼容性。浏览器内推理(On-device Inference)是第三条路径,它将推理计算下沉到用户设备,借助 WebGPU 直接调用 GPU 算力,兼顾延迟、成本与数据合规。
WebGPU 在 2023 年后逐步在 Chrome、Edge、Safari Technology Preview 中稳定支持。其计算着色器(Compute Shader)能力为矩阵运算提供了远超 WebGL 的吞吐。配合 GGUF、ONNX 等量化格式,浏览器内运行 1B 至 7B 参数规模的 LLM 已具备工程可行性。
但浏览器内推理并非云端替代品。其核心约束在于:显存受限于设备 GPU(通常 2 至 8 GB 可用)、算力受限于集成显卡、上下文长度受限于内存带宽。本文聚焦于如何在 WebGPU 管线下搭建一条可落地的 LLM 推理管线,并以 Hugging Face 的transformers.js为参照实现。
二、WebGPU 计算管线与 LLM 推理的映射关系
2.1 LLM 推理的两个阶段
LLM 推理分为 Prefill(预填充)与 Decode(解码)两阶段。Prefill 阶段一次性处理输入 prompt,计算密集型,GPU 利用率高。Decode 阶段逐 token 自回归生成,内存带宽密集型,GPU 利用率低。两阶段对计算管线的需求不同。
┌─────────────────────────────────────────────────────────────┐ │ 浏览器内 LLM 推理数据流 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────┐ 量化加载 ┌──────────────────────┐ │ GGUF/ONNX │ ────────────▶ │ GPU 显存 (量化张量) │ │ 模型文件 │ 流式分片 │ - 权重 (int4/int8) │ │ (CDN/IndexedDB)│ │ - KV Cache │ └──────────────┘ └──────────────────────┘ │ ┌───────────────────────────────┼────────────────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Prefill 阶段 │ │ Decode 阶段 │ │ 采样/反分词 │ │ - 并行处理 │ │ - 逐 token │ │ - Argmax/TopK│ │ prompt │ │ - 更新 KV │ │ - Tokenizer │ │ - 计算密集 │ │ Cache │ │ 解码 │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └───────────────────────────────┼────────────────────────┘ ▼ ┌──────────────────┐ │ 文本输出到页面 │ └──────────────────┘2.2 WebGPU 计算管线核心对象
WebGPU 的计算管线由若干核心对象构成。LLM 推理中的矩阵乘法、注意力计算、KV Cache 更新都映射到这套管线:
| WebGPU 对象 | 职责 | LLM 推理映射 |
|---|---|---|
GPUDevice | 设备句柄,管理资源 | GPU 上下文 |
GPUBuffer | 显存缓冲区 | 权重张量、激活值、KV Cache |
GPUShaderModule | WGSL 着色器代码 | 矩阵乘法、Softmax、RMSNorm 等算子 |
GPUComputePipeline | 计算管线 | 算子执行入口 |
GPUBindGroup | 资源绑定 | 算子输入输出绑定 |
2.3 量化的工程意义
量化是将 float32 权重压缩到 int4 或 int8 的过程。对 LLM 而言,量化带来三重收益:
- 显存压缩:7B 模型从 28 GB(fp32)压缩到 3.5 GB(int4),可在消费级 GPU 上运行。
- 带宽优化:Decode 阶段瓶颈在权重读取,int4 读取量是 fp32 的八分之一。
- 缓存友好:更小的权重更易命中 GPU L2 缓存。
代价是精度损失。int4 量化通常带来 1% 至 3% 的 perplexity 上升,在长上下文与数学推理任务上更明显。
2.4 量化格式对比
| 格式 | 典型位宽 | 适用场景 | 浏览器支持 |
|---|---|---|---|
| GGUF (Q4_K_M) | 4 bit | llama.cpp 生态 | 需 WASM 转译,性能受限 |
| ONNX (int4) | 4 bit | transformers.js 原生支持 | WebGPU 直接加速 |
| ONNX (int8) | 8 bit | 精度敏感场景 | WebGPU 加速 |
| ONNX (fp16) | 16 bit | 小模型、高精度 | WebGPU 原生支持 |
本文以 ONNX int4 为主要格式,配合transformers.jsv3 以上版本的 WebGPU backend。
三、基于 transformers.js 的推理管线搭建与性能调优
3.1 环境检测与回退策略
WebGPU 的浏览器支持仍在演进中,必须做特性检测并提供回退:
// llm-pipeline.js // 浏览器内 LLM 推理管线封装 // 依赖:@huggingface/transformers (v3+) import { env, AutoTokenizer, AutoModelForCausalLM } from '@huggingface/transformers'; const MODEL_ID = 'HuggingFaceTB/SmolLM2-360M-Instruct'; const MAX_RETRY = 2; const LOAD_TIMEOUT_MS = 60_000; export class BrowserLLM { constructor() { this.model = null; this.tokenizer = null; this.backend = null; // 'webgpu' | 'wasm' this.ready = false; } /** * 检测 WebGPU 可用性 * 关键:navigator.gpu 仅在安全上下文(HTTPS/localhost)可用 * @returns {Promise<boolean>} */ static async isWebGPUAvailable() { if (typeof navigator === 'undefined' || !navigator.gpu) { return false; } try { // requestAdapter 可能因驱动不兼容而失败,必须 try-catch const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }); return adapter !== null; } catch (err) { console.warn('[BrowserLLM] WebGPU adapter 获取失败:', err); return false; } } /** * 初始化模型 * 关键:设置超时与重试,避免 CDN 抽风时页面卡死 */ async init() { const useWebGPU = await BrowserLLM.isWebGPUAvailable(); this.backend = useWebGPU ? 'webgpu' : 'wasm'; // 配置 backend // WebGPU 模式下,transformers.js 会自动将支持的算子调度到 GPU env.backends.onnx.wasm.numThreads = navigator.hardwareConcurrency || 4; env.allowLocalModels = false; // 启用 IndexedDB 缓存,避免重复下载大模型文件 env.useBrowserCache = true; let retry = 0; while (retry <= MAX_RETRY) { try { const loadPromise = this._loadModel(); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('模型加载超时')), LOAD_TIMEOUT_MS) ); await Promise.race([loadPromise, timeoutPromise]); this.ready = true; console.info(`[BrowserLLM] 模型加载完成,backend=${this.backend}`); return; } catch (err) { retry++; console.warn(`[BrowserLLM] 加载失败 (第 ${retry} 次):`, err.message); if (retry > MAX_RETRY) { throw new Error(`模型加载失败,已重试 ${MAX_RETRY} 次: ${err.message}`); } // 指数退避,避免 CDN 限流时连续失败 await new Promise(r => setTimeout(r, 1000 * Math.pow(2, retry))); } } } async _loadModel() { // 并行加载 tokenizer 与 model,缩短初始化耗时 // 关键:dtype 'q4' 指定 int4 量化,显著降低显存占用 [this.tokenizer, this.model] = await Promise.all([ AutoTokenizer.from_pretrained(MODEL_ID), AutoModelForCausalLM.from_pretrained(MODEL_ID, { dtype: this.backend === 'webgpu' ? 'q4' : 'q8', device: this.backend, // 显存预算,超出时抛错而非浏览器崩溃 max_memory: { gpu: '2GB' } }) ]); } /** * 流式生成 * @param {string} prompt * @param {object} options { maxNewTokens, temperature, topK, onToken } */ async *generateStream(prompt, options = {}) { if (!this.ready) { throw new Error('[BrowserLLM] 模型未初始化,请先调用 init()'); } const { maxNewTokens = 256, temperature = 0.7, topK = 50, onToken } = options; // 构造 chat 格式输入,apply_chat_template 会拼接系统提示与用户输入 const messages = [{ role: 'user', content: prompt }]; const inputs = this.tokenizer.apply_chat_template(messages, { add_generation_prompt: true, return_tensors: 'pt' }); // streamer 回调:每生成一个 token 即推送到前端 const streamer = { callback_function: (tokenId) => { const token = this.tokenizer.decode(tokenId, { skip_special_tokens: true }); if (onToken) onToken(token); } }; // 关键:do_sample 控制是否采样,temperature/topK 仅在采样时生效 const stream = await this.model.generate({ ...inputs, max_new_tokens: maxNewTokens, do_sample: temperature > 0, temperature, top_k: topK, streamer }); for await (const token of stream) { yield token; } } dispose() { // 显式释放 GPU 资源,避免页面长时间运行后显存泄漏 this.model?.dispose?.(); this.tokenizer = null; this.model = null; this.ready = false; } }3.2 性能调优要点
// 性能调优配置示例 const llm = new BrowserLLM(); await llm.init(); // 1. KV Cache 复用:多轮对话时保留 KV Cache,避免重复 prefill // transformers.js v3+ 默认启用 use_cache=true // 2. Batch Size 控制:浏览器场景单用户,batch_size=1 即可 // 切勿为了吞吐设置大 batch,会导致显存溢出 // 3. 上下文长度裁剪:超出 max_length 时采用滑动窗口 // 避免无限制增长 KV Cache 导致 OOM // 4. 生成参数建议 const stream = llm.generateStream('解释一下什么是 GPU 实例化', { maxNewTokens: 512, temperature: 0.7, topK: 40, onToken: (token) => { // 流式追加到 DOM,避免等待完整响应造成白屏 document.getElementById('output').textContent += token; } }); for await (const token of stream) { // 已通过 onToken 处理,此处仅驱动迭代 }3.3 性能基准参考
在 M2 MacBook Pro、Chrome 126、WebGPU backend 下,对 SmolLM2-360M-Instruct(int4)的实测数据:
| 指标 | 数值 |
|---|---|
| 模型体积(int4) | 约 220 MB |
| 首次加载耗时(含下载) | 约 8 至 12 秒 |
| 二次加载(IndexedDB 缓存命中) | 约 1.5 秒 |
| Prefill 吞吐 | 约 180 tokens/s |
| Decode 吞吐 | 约 45 tokens/s |
| 峰值显存占用 | 约 680 MB |
以上数据仅为参考。实际表现受设备 GPU、浏览器版本、模型结构影响显著。
四、浏览器内推理的天花板:显存、并发与兼容性边界
4.1 显存刚性上限
浏览器进程可用的 GPU 显存远低于原生应用。Chrome 对单个页面的 GPU 内存预算通常在 2 至 4 GB,取决于设备总显存。超过会触发GPUOutOfMemoryError。7B 模型即使 int4 量化后仍需约 3.5 GB,在多数消费级设备上无法稳定运行。当前浏览器内推理的现实上限是 1B 至 3B 参数模型。
4.2 主线程瓶颈
LLM 推理中的 Tokenizer 编解码、采样逻辑跑在 JS 主线程。长 prompt 的分词可能造成 100ms 以上的主线程阻塞,影响页面交互。Web Worker 可以隔离这部分计算。但transformers.js的 Worker 集成需要额外配置onnxruntime-web的 worker 路径,工程复杂度上升。
4.3 WebGPU 兼容性
WebGPU 的浏览器覆盖度截至 2026 年中约为 85%。Chrome 113 以上、Edge 113 以上支持,Safari 18 以上部分支持,Firefox 仍处于实验阶段。iOS Safari 的支持度滞后,移动端覆盖不足。对于需要全端覆盖的产品,必须保留 WASM 回退。但 WASM 后端性能仅为 WebGPU 的五分之一至十分之一。
4.4 模型分发成本
int4 量化的 1B 模型仍有约 700 MB。首次加载对 CDN 流量与用户体验是双重压力。IndexedDB 缓存可缓解二次加载,但首次访问的用户仍需等待。Service Worker 预缓存是优化方向,但会占用用户设备存储。
4.5 精度与能力边界
int4 量化在通用对话任务上表现接近 fp16,但在以下场景明显退化:
- 数学推理:多步骤计算的累积误差放大。
- 代码生成:对缩进、符号的精确度要求高,量化易引入低级错误。
- 长上下文:超过 2K token 后注意力分布失真加剧。
4.6 适用与禁用场景
| 场景 | 是否推荐浏览器内推理 |
|---|---|
| 隐私敏感的本地问答(小于 1B 模型) | 推荐 |
| 离线场景的轻量助手 | 推荐 |
| 流式代码补全(需高精度) | 不推荐,建议云端 |
| 复杂推理(数学、Agent) | 不推荐,算力与精度均不足 |
| 移动端(iOS Safari) | 不推荐,兼容性不足 |
| 高并发多用户 | 不推荐,浏览器单进程无法复用 |
五、总结
WebGPU 为浏览器内 LLM 推理提供了可行的算力底座。int4 量化则让 1B 至 3B 参数模型在消费级设备上稳定运行成为可能。其工程价值集中在隐私合规、离线可用、零 token 成本三个维度,而非算力或精度的对标云端。
落地步骤建议如下:
- 能力检测:在产品入口检测
navigator.gpu与 adapter 可用性,明确告知用户是否启用 WebGPU。 - 模型选型:优先选择 1B 以下、已有 int4 ONNX 量化版本的开源模型,如 SmolLM2、Qwen2.5-0.5B。
- 回退策略:WebGPU 不可用时回退到 WASM backend,并相应下调模型规模,如改用 int8。
- 缓存优化:启用 IndexedDB 缓存模型权重,二次访问的加载耗时控制在 2 秒以内。
- Worker 隔离:将推理逻辑迁移到 Web Worker,避免主线程阻塞影响交互响应。
- 监控与降级:上线后监控首次加载耗时、Decode 吞吐、OOM 率,对低端设备自动降级到云端 API。
浏览器内推理是 LLM 应用前端落地的补充路径。理解其能力边界与回退机制,才能在合适的场景发挥其价值。
