群阅书声
167.36MB · 2025-11-09
本文是给工程师、研究者、以及想让浏览器“原地开智”的你的一份教学实战指南。我们一边扒原理、一边讲工程;一边谈架构,一边插科打诨。涉及数学时,用文字解释,不塞公式;代码示例全部使用 JavaScript;若你喜欢表情/小图标,路上不时撒点纸片彩带。
一句话:WebAI 的美学是“够用就好,聪明编排”。不是把航空母舰塞进浴缸,而是让小舢板开出大智慧。
小模型(约 1–3B 参数)
中模型(约 3–7B)
大模型(>7B)
关键认知:WebAI 的胜负不在“模型更大”,而在“系统更聪明”。把算力留给高价值步骤,其余交给检索、规则、缓存与推测解码。
WebGPU(首选)
WebAssembly(WASM + SIMD + 多线程)
WebNN(在路上)
边缘容器(Tauri/Electron + 原生后端)
一句话图解:
量化(最划算的性能/精度比)
蒸馏(让小模型学到“大”的举止)
稀疏与剪枝(刀法要结构化)
顺口溜:量化先行,蒸馏增智,稀疏添彩。
比喻版:注意力是你的“视线”,不要盯着地平线发呆,而要盯着脚下的台阶。
首包体积
冷启动
内存管理
监控与降级
文本生成/对话
代码助手
语音
视觉
笔电集显 + WebGPU
高端手机
纯 WASM CPU
经验法则:如果你的产品对“首字快、持续稳”极度敏感,优先压上下文长度和 KV 精度,再考虑模型大小。
说明:
<!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**/we*b-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>
小贴士:
WebAI 的哲学很朴素:以小博大,以巧胜拙。你不需要把航空母舰塞进浏览器;你需要的是一支训练有素的特种队——一个小模型,几条聪明的路线,若干工程化的“稳准狠”。
如果你愿意,我可以进一步:
愿每一个在浏览器里跑 AI 的你,都能在风扇低鸣中,看见第一枚 Token 稳稳落地的喜悦。?