本文是给工程师、研究者、以及想让浏览器"原地开智"的你的一份教学实战指南。我们一边扒原理、一边讲工程;一边谈架构,一边插科打诨。涉及数学时,用文字解释,不塞公式;代码示例全部使用 JavaScript;若你喜欢表情/小图标,路上不时撒点纸片彩带。
目录
- 为什么要在 Web 上跑 AI?🧭
- 小模型 vs 大模型:不止是参数的对决 ⚖️
- 浏览器算力地图:WebGPU、WASM、WebNN 与边缘容器 🗺️
- 轻量化"三板斧":量化、蒸馏、稀疏 🪓
- 序列与缓存:把注意力花在刀刃上 🔪
- 工程化:从首包到首 Token,细节决定体验 🧩
- 任务场景配方:文本/语音/视觉的最小可用路径 🍱
- 性能基线与容量规划:别和物理规律对着干 📏
- 教学实操:用 web-llm 几十行 JS 跑起 1--3B 🧪
- 决策清单与常见坑 🧯
一、为什么要在 Web 上跑 AI?🧭
- 零后端、免安装、即开即用:把模型权重当静态资源发,CDN 已经是你的 DevOps。
- 隐私友好:数据不出浏览器,尤其适合本地知识库、医疗、教育场景的轻量任务。
- 降延迟:输入到输出不经过"地球绕行"的网络路径,干部作风过硬。
- 现实掣肘:GPU 不一定给力,内存上限、线程调度、冷启动都在虎视眈眈。
一句话:WebAI 的美学是"够用就好,聪明编排"。不是把航空母舰塞进浴缸,而是让小舢板开出大智慧。
二、小模型 vs 大模型:不止是参数的对决 ⚖️
-
小模型(约 1--3B 参数)
- 优点:加载快、显存亲民、移动端可用。
- 缺点:推理深度有限,错题率偏高,需要工具与检索做外接大脑。
- 用途:对话助手、嵌入、检索重排、轻量多模态头。
-
中模型(约 3--7B)
- 优点:指令跟随稳定,基本代码/推理可用。
- 缺点:浏览器端压力显著,需要 WebGPU 且谨慎内存。
-
大模型(>7B)
- 优点:能力全面。
- 缺点:Web 端部署属于"挑战物理学";一般退居服务端或边缘原生进程。
关键认知:WebAI 的胜负不在"模型更大",而在"系统更聪明"。把算力留给高价值步骤,其余交给检索、规则、缓存与推测解码。
三、浏览器算力地图 🗺️
-
WebGPU(首选)
- 优势:真·GPU 并行,16 位浮点与混合精度友好,性能可达原生的半壁江山。
- 要点:Chrome/Edge 稳定,Safari 新版跟进;需要做好回退路径。
-
WebAssembly(WASM + SIMD + 多线程)
- 优势:覆盖面广,CPU 路径稳。
- 限制:自回归 LLM 常常慢;适合非自回归、CV 前处理、NMS、VAD 等。
-
WebNN(在路上)
- 未来直通系统 NPU;目前生态尚早期。
-
边缘容器(Tauri/Electron + 原生后端)
- 可以调 Metal/DirectML/CUDA/NNAPI;适合本地 App 化。
一句话图解:
- GPU 跑主干计算
- CPU 管家式调度与小算子兜底
- NPU 是未来的"黑马"
- 缺谁都别缺一个靠谱的回退
四、轻量化"三板斧"🪓
-
量化(最划算的性能/精度比)
- 后量化:把权重量化到 8 位或 4 位;对激活和 KV 缓存也做分组与逐通道处理。
- 感知训练:当压得很狠时,用训练保住精度底线。
- 生态:GGUF/GGML、GPTQ、AWQ、bitsandbytes、onnxruntime-web int4、web-llm int4。
- 心法:先权重后 KV;先逐通道再逐张量;先分组再整饼。
-
蒸馏(让小模型学到"大"的举止)
- 从大模型采样高质量指令和思维轨迹。
- 架构减法:更窄层、更短深度,或使用稀疏的混合专家(MoE)。
-
稀疏与剪枝(刀法要结构化)
- N:M 稀疏(比如 2:4)对内核友好,硬件才能吃到实惠。
- 剪枝优先结构化,避免让 kernel 哭泣。
顺口溜:量化先行,蒸馏增智,稀疏添彩。
五、序列与缓存:把注意力花在刀刃上 🔪
- 上下文不是越长越好。长序列带来的计算增长像滚雪球。
- 可滑动窗口注意力:让模型专注"最近发生的事",对远处的记忆用摘要、检索与缓存。
- KV 缓存是金:分层回收、量化存储、按段分页(Paged KV)。
- Prefill 与 Decoding 分离:把大块预填充和单步解码用不同算子路线优化。
- 推测解码(Speculative):让小模型先"放风筝",大模型做裁判,正确就快进。
比喻版:注意力是你的"视线",不要盯着地平线发呆,而要盯着脚下的台阶。
六、工程化:从首包到首 Token 🧩
-
首包体积
- 权重分片(5--10 MB 一片),HTTP Range + 断点续传,Service Worker 缓存。
- safetensors 或 GGUF,带校验。
-
冷启动
- 首轮先用"小副驾驶"给响应;后台热加载强模型再接管。
- 提前编译/暖身 kernel;把模型图的热路径先跑一遍。
-
内存管理
- 控制最大生成长度;KV 低精度;多 Tab 互斥,必要时单实例队列。
- Worker 隔离推理线程,UI 不要和 GPU 抢队列。
-
监控与降级
- 记录首 Token 延迟、稳定速度、峰值显存、温度。
- 一键回退到 WASM,保证"能用"。
七、任务场景配方 🍱
-
文本生成/对话
- 选 1--3B 指令模型,做 int4;配 RAG(本地嵌入 + 轻量重排)。
- 复杂工具调用走服务端或原生扩展,小模型只做"调度和判断"。
-
代码助手
- 中模型(3--7B)更稳;采用双模型:小模型快速补全,大模型(远端)重构解释。
- 片段上下文 + 语义检索,别喂整个仓库。
-
语音
- ASR:Whisper tiny/base 量化;VAD 前置;流式切块。
- TTS:小型声码器前端,质量追求高则边缘/服务端合成 + 缓存。
-
视觉
- 轻量检测/分类:YOLO-N、RT-DETR、MobileCLIP;动态分辨率。
- 多模态:小视觉编码器 + 轻语言头,图像分块送入,注意内存上限。
八、性能基线与容量规划 📏
-
笔电集显 + WebGPU
- 1--3B int4:大致 5--15 token/s,首 Token 1--3 秒,显存 1--2 GB。
- 7B int4:1--4 token/s,首 Token 4--10 秒,显存 3--6 GB。
-
高端手机
- 1--2B int4 尚可;3B 体验浮动。
-
纯 WASM CPU
- 自回归生成往往低于 1--3 token/s。适合作兜底,而非主力。
经验法则:如果你的产品对"首字快、持续稳"极度敏感,优先压上下文长度和 KV 精度,再考虑模型大小。
九、教学实操:用 web-llm 起一个浏览器内小助理 🧪
说明:
- 示例展示分片加载、WebGPU 首选、WASM 兜底、基本对话循环与 KV 长度限制。
- 你需要将模型文件托管到可 range 请求的静态路径,并提供清单 JSON(web-llm 或 MLC 生态常见)。
- 简化起见,这里使用伪地址与最小 UI。
xml
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>WebAI 轻量小助理</title>
<style>
:root { color-scheme: light dark; }
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin:0; display:flex; flex-direction:column; height:100vh; }
header { padding:12px 16px; border-bottom:1px solid rgba(0,0,0,0.1); display:flex; gap:8px; align-items:center; }
header .logo { font-weight:700; }
main { display:flex; flex-direction:column; gap:12px; padding:12px; overflow:auto; }
.row { display:flex; gap:8px; }
.row input { flex:1; padding:10px; border:1px solid color-mix(in srgb, currentColor 30%, transparent); border-radius:8px; }
.row button { padding:10px 14px; border-radius:8px; border:1px solid transparent; background:#4f46e5; color:white; }
.msg { padding:10px 12px; border-radius:10px; max-width:70ch; }
.user { align-self:flex-end; background:color-mix(in srgb, #4f46e5 12%, transparent); }
.bot { align-self:flex-start; background:color-mix(in srgb, #22c55e 12%, transparent); }
.stat { font-size:12px; opacity:.7; }
footer { padding:8px 12px; border-top:1px solid rgba(0,0,0,0.08); display:flex; justify-content:space-between; }
@media (max-width:600px){ .msg { max-width:100%; } }
</style>
</head>
<body>
<header>
<span class="logo">🤖 WebAI 轻量小助理</span>
<span id="status" class="stat">初始化中...</span>
</header>
<main id="chat">
<div class="msg bot">你好,我是浏览器内运行的小模型助理。试着问我点什么吧!</div>
</main>
<footer>
<div class="row" style="flex:1">
<input id="inp" placeholder="输入你的问题,回车发送..." />
<button id="send">发送</button>
</div>
</footer>
<script type="module">
// 说明:演示以 web-llm 为例。实际使用时请根据 web-llm 文档引入正确的包与模型清单。
// 这里通过动态 import,若 WebGPU 不可用则回退 WASM。
let engine, generate;
const statusEl = document.getElementById('status');
const chatEl = document.getElementById('chat');
const inp = document.getElementById('inp');
const sendBtn = document.getElementById('send');
function appendMsg(text, who='bot') {
const div = document.createElement('div');
div.className = `msg ${who}`;
div.textContent = text;
chatEl.appendChild(div);
chatEl.scrollTop = chatEl.scrollHeight;
}
// 简易计时器
function now(){ return performance.now(); }
// 检测 WebGPU
const hasWebGPU = !!navigator.gpu;
statusEl.textContent = hasWebGPU ? 'WebGPU 可用,加载模型...' : 'WebGPU 不可用,将回退到 WASM...';
// 模型配置:选择 1.5B 左右的指令模型,int4;注意:以下 URL 需要替换为你的静态托管地址
const modelConfig = {
model_id: 'qwen2.5-1_5b-instruct-int4',
// 分片清单和权重路径示例(需按 web-llm 生态提供)
// 此处用示意字段;实际字段以所用库为准
files: {
manifest: '/models/qwen1_5b/manifest.json',
// 分片在 manifest 内部描述
},
kv_config: { precision: 'int4', max_tokens: 1024 }, // 控制 KV 精度与长度
gpu_preference: hasWebGPU ? 'webgpu' : 'wasm'
};
async function init() {
try {
const t0 = now();
// 这里以 web-llm 的全局入口为例;若使用 npm 包,请改为实际导入路径
// 假设存在 window.WebLLM,或用 import('https://.../web-llm.min.js')
const mod = await import('https://esm.sh/web-llm@latest'); // 示例,具体以生态为准
engine = new mod.LLMEngine({
prefillStrategy: 'warmup',
worker: true,
preferGPU: hasWebGPU,
});
await engine.loadModel(modelConfig);
const t1 = now();
statusEl.textContent = `加载完成,耗时 ${(t1 - t0 | 0)} ms;准备就绪。`;
generate = (prompt, onToken) => engine.generate({
prompt,
maxTokens: 256,
temperature: 0.6,
topP: 0.95,
presencePenalty: 0.0,
frequencyPenalty: 0.0,
stream: true
}, onToken);
} catch (e) {
console.error(e);
statusEl.textContent = '初始化失败:' + e.message;
}
}
async function handleSend() {
const text = inp.value.trim();
if (!text || !generate) return;
appendMsg(text, 'user');
inp.value = '';
let acc = '';
const t0 = now();
try {
await generate(text, (evt) => {
if (evt.type === 'token') {
acc += evt.data;
// 渲染策略:最后一条 bot 消息增量更新
const last = chatEl.lastElementChild;
if (!last || !last.className.includes('bot')) {
appendMsg('', 'bot');
}
chatEl.lastElementChild.textContent = acc;
} else if (evt.type === 'end') {
const t1 = now();
const spent = ((t1 - t0) / 1000).toFixed(2);
statusEl.textContent = `完成:${spent}s;平均速率约 ${(acc.length / spent | 0)} char/s`;
}
});
} catch (e) {
console.error(e);
appendMsg('抱歉,生成失败:' + e.message, 'bot');
}
}
sendBtn.addEventListener('click', handleSend);
inp.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleSend(); });
appendMsg('正在加载模型分片与内核,请稍候...', 'bot');
init();
</script>
</body>
</html>
小贴士:
- 把分片放到 CDN,开启 Range 和缓存;Service Worker 可做断点续传与校验。
- 初始化路径上,先跑一个极短的预填充热身,后续首 Token 会更快。
- KV 长度与精度是你最敏感的旋钮:宁可少一点上下文,也要稳住速率与内存。
十、一些"看不见的数学",但必须懂的直觉 🧠
- 自回归计算随序列长度增长得很快:每多一小截上下文,后面每一个 Token 都要多看一遍。
- 注意力是"全连接"的视线:不控窗口,成本就像开无限灯光秀。
- 量化误差像"压缩 JPEG":精度丢了点,但整体轮廓还在;针对关键通道做精细化处理,体感更好。
- 稀疏的好处取决于内核能否"跳过空洞":结构化稀疏才能让硬件真正省力。
十一、决策清单与常见坑 🧯
- 先问清楚产品指标:首 Token 延迟、连续速率、最大内存、目标设备占比。
- 首选小模型 + 系统编排:RAG/工具/规则优先;大模型改走服务端。
- WebGPU 是关键路径;WASM 回退必须"可用即稳"。
- 量化优先、蒸馏其次、剪枝加分;先压 KV,再压权重;遇到精度问题,再引入 QAT。
- 不要把所有文档塞进上下文:滑窗 + 检索 + 摘要,节流才是王道。
- 分片加载与缓存是生命线;断点续传和校验救你于网络抖动。
- 多 Tab 抢 GPU 是常见崩溃源;用 Worker 与单队列控制"交通秩序"。
尾声:给浏览器一顶轻便的"思考帽" 🎩
WebAI 的哲学很朴素:以小博大,以巧胜拙。你不需要把航空母舰塞进浏览器;你需要的是一支训练有素的特种队------一个小模型,几条聪明的路线,若干工程化的"稳准狠"。
如果你愿意,我可以进一步:
- 按你的目标设备矩阵给出具体模型和量化配置清单。
- 结合你的数据与任务,设计蒸馏/RAG/推测解码的编排方案。
- 基于日志做性能火焰图,逐项优化首 Token 与显存占用。
愿每一个在浏览器里跑 AI 的你,都能在风扇低鸣中,看见第一枚 Token 稳稳落地的喜悦。🚀