实战项目 — 浏览器里的 AI 推理与游戏物理¶
1. 这一章讲什么¶
全书的动手收官章,两个领域各走一遍完整项目:AI/ML(模型怎么从 PyTorch 搬到浏览器里实时跑)和游戏(物理计算怎么从 JS 挪进 Rust/Wasm)。 共同骨架一句话:重型计算进 Wasm,编排和渲染留 JS,中间靠前面章节练过的 互操作机制。1
2. 顶层全景¶
AI/ML 线:
PyTorch 训练 → 导出 ONNX(通用模型交换格式)→ 浏览器里 ONNX.js / onnxruntime-web 推理
→ 配套:量化/剪枝压模型、IndexedDB 缓存、懒加载
│
游戏线:
Rust 写物理引擎(碰撞检测)→ wasm-bindgen + wasm-opt → Three.js 渲染循环里逐帧调用
→ 配套:SIMD 优化、少跨边界、模块化复用
│
▼
七组最佳实践:模型优化 / 懒加载 / 安全 / 内存 / 并发 / 模块化(+ wasm-opt 与剖析)
图说:两条线,同一套「算进 Wasm、界面留 JS」的分工。
3. 核心原理¶
3.1 AI 线第一步:模型怎么进浏览器¶
浏览器不认识 PyTorch 的模型文件,中间要过一座桥:ONNX (Open Neural Network Exchange,各框架互通的模型格式)。 书里的第一步永远是 Python 侧导出:
model = models.resnet18(pretrained=True)
model.eval()
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "resnet18.onnx", opset_version=11)
三个要点:模型切到评估模式(eval());喂一个形状正确样的「假输入」
(这里 1×3×224×224,一张 224 像素方的 RGB 图);opset_version 指定
算子集版本,影响兼容性。2
浏览器侧,ONNX.js 两行起session:
const session = new onnx.InferenceSession();
await session.loadModel('resnet18.onnx');
const tensor = new onnx.Tensor(imageData, 'float32', [1, 3, 224, 224]);
const outputMap = await session.run([tensor]);
取最大值的索引就是预测类别。预处理(把任意图片缩放到 224×224、归一化) 留在 JS/Canvas 做——又是一次「界面归 JS」的分工。3
3.2 AI 线第二步:NLP 模型的额外工序¶
图像模型直出即可,BERT 这类语言模型还要多两步(书里的情感分析——判断一句话是夸是骂——项目)。 先把这六个名字认全:NLP(自然语言处理,让机器读文字)、token(模型切分文本的最小单位)、 张量(装数字的数组,模型输入输出都是它)、优化器(导出后压缩模型的工具)、 logits(模型输出的原始打分,哪个大就偏向哪个)。4
- 导出的输入是两个张量(
input_ids+attention_mask,各长 128); - 过完 ONNX Runtime 的优化器再上线:
python -m onnxruntime_tools.optimizer --input bert.onnx --output bert_opt.onnx; - 浏览器里用 onnxruntime-web 起 session,跑完比两个 logits 的大小判正负情感。
这个例子的潜台词值得点破:tokenizer(把文字变成模型认得的数字序列)在
书里的演示中被简化成「128 个 0」,真实项目里这是一块不小的前端工程——
书里照实写了 // Replace with actual tokenization。5
3.3 AI 线第三步:实时姿态估计¶
TensorFlow.js 路线,关键一行 await tf.setBackend('wasm')——
Wasm 后端不是默认,要显式切;之后 PoseNet 每帧检测、置信度 >0.5 的关键点
画红圈,requestAnimationFrame 驱动循环。用途:健身、游戏。6
3.4 游戏线:Rust 物理引擎 + Three.js¶
书里的物理模块,一个 Wasm 化的球体碰撞引擎(Rust):
pub fn detect_collisions(&self) -> Vec<bool> {
// 双重循环扫所有球对
let distance = ((s1.x - s2.x).powi(2) + (s1.y - s2.y).powi(2)
+ (s1.z - s2.z).powi(2)).sqrt();
if distance < (s1.radius + s2.radius) {
collisions[i] = true; collisions[j] = true;
}
}
距离小于半径之和 = 碰撞。构建三链:cargo build --target wasm32-unknown-unknown --release
→ wasm-bindgen --out-dir dist → wasm-opt -O3 收尾。7
接入 Three.js 的渲染循环(书里的完整代码,骨架九行):
演示输入:两个半径 1 的球放在 (0,0,0) 和 (0.5,0,0)——距离 0.5 < 半径和 2,
输出 [true, true],正确。8
3.5 主线例子:一帧的旅程(把两章机制串起来)¶
以姿态估计为例走一帧(库名出自原书;具体耗时数字未在书里给出,不编):
摄像头帧 → JS 从 <video> 抓到图像 → 预处理(JS)→ net.estimatePoses()
进 Wasm 后端算关键点 → 结果数组回 JS → 画到 <canvas>。
每个环节都是前文学过的:类型化数组传图、异步 API、渲染归 JS、计算归 Wasm。9
3.6 七组最佳实践¶
| 组 | 要点 |
|---|---|
| 模型优化 | 量化(int8/float16)、剪枝(把模型里贡献小的权重剪掉)、wasm-opt 压二进制 |
| 懒加载 | 异步加载、IndexedDB/Service Worker 缓存、大模型切块渐进加载 |
| 安全 | WASI 能力制、沙箱、npm audit 扫依赖 |
| 内存 | 少分配、为线性内存优化数据结构、DevTools/Wasmtime 盯泄漏 |
| 并发 | 线程提案做并行推理、Web Workers 保 UI 响应、SharedArrayBuffer 共享 |
| 模块化 | Component Model 设计、推理/渲染分模块、为动态链接留路 |
4. 作者的判断与证据¶
- 三个 AI 项目的代码骨架:与 ONNX.js / onnxruntime-web / tfjs 的真实 API 形态一致, 可照抄起步;但都是「最小可演示」,预处理(尤其是 tokenizer)被刻意简化,书里用注释标了;
- 物理引擎的碰撞数学:可复算(0.5 < 2),正确;
- 「客户端推理省服务器钱 + 隐私」:真实收益,无争议;但本书没有任何一个项目 给出端到端延迟数字,全部停留在架构描述。
判断(我们的,不是书里的): 这一章的 AI 路线在 2026 年要加一个前提检查: 浏览器里跑 ONNX 模型,今天的第一选择往往已经不是「ONNX.js + 自写胶水」, 而是各家 runtime 的成熟 Web 端(onnxruntime-web 自己也比书里演示的完善得多), 以及 WebGPU 后端——Wasm 后端常在无 GPU 时当兜底。 书里把 Wasm 写成 AI-in-browser 的主角,实际是「三后端之一」。 如果错,会错在: 如果 WebGPU 的普及慢于预期(苹果系跟进节奏历来保守), Wasm/SIMD 后端会在更长时间里保持主力地位,本章的写法就又算准确了。
5. 边界与局限¶
- 零实测数字:没有任何一个项目报端到端性能(首帧延迟、稳态帧率、内存峰值);
- tokenizer 被跳过:NLP 项目最关键的预处理环节只有一行注释;
- Babylon.js 只在标题里:正文没有任何 Babylon 的代码,只有 Three.js;
- 训练完全不涉及:全书只讲推理侧,「Wasm 训模型」一个字没有——这是定位使然,但要心里有数。
6. 可带走的¶
- 模型进浏览器的标准路径:PyTorch →
torch.onnx.export→ ONNX.js / onnxruntime-web。 - 导出三要点:
eval()、假输入形状、opset_version。 - 预处理(缩放/归一化/tokenize)归 JS,推理归 Wasm——分工照旧。
- tfjs 用 Wasm 后端要显式
setBackend('wasm')。 - 物理引擎套路:Rust struct + impl + wasm-bindgen = JS 里的类;
wasm-opt -O3收尾。 - Three.js 集成:每帧渲染循环里调 Wasm 函数,结果改场景状态。
- 模型压缩三招:量化、剪枝、切块懒加载;缓存进 IndexedDB。
- 并发:线程 + Workers + SharedArrayBuffer 是 Wasm 并发的三件套。
- 真实工程的坑在书里被简化掉的地方:tokenizer、图像预处理、跨浏览器兼容——照抄 demo 之后这三处等着补。
- Wasm 是 AI-in-browser 的「后端之一」不是唯一——选型时把 WebGPU 后端一起评估。
7. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 客户端推理的价值 | Real-World Projects Using WebAssembly | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:53(搜「reduces server dependencies」) |
| ResNet18 导出 ONNX | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:95(搜「resnet18」) · text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:107(搜「opset_version=11」) |
| ONNX.js 推理与 HTML 集成 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:127(搜「InferenceSession」) · text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:155(搜「indexOf(Math.max」) |
| 模型优化实践 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:205(搜「Quantize models」) |
| BERT 导出与优化 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:251(搜「BertForSequenceClassification」) · text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:271(搜「onnxruntime_tools.optimizer」) |
| onnxruntime-web 推理 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:279(搜「onnxruntime-web」) |
| PoseNet + Wasm 后端 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:365(搜「setBackend('wasm')」) |
| Rust 碰撞引擎 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:477(搜「PhysicsEngine」) · text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:519(搜「distance」) |
| 构建三链与 JS 接入 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:545(搜「--target wasm32-unknown-unknown」) · text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:567(搜「Collisions:」) |
| Three.js 集成 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:615(搜「PerspectiveCamera」) · text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:649(搜「Highlight on collision」) |
| 七组最佳实践 | 同上 | text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:679(搜「Best Practices for Wasm Applications」) |
-
出处:「Real-World Projects Using WebAssembly」第 13–31 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:19,搜「client-side AI/ML inference」)。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 87–107 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:95,搜「resnet18」)与text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:107(搜「opset_version=11」)。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 119–157 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:127,搜「InferenceSession」)与text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:157(搜「Predicted class」)。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 235–263 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:251,搜「BertForSequenceClassification」)。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 285–287 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:287,搜「Replace with actual tokenization」)。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 339–397 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:365,搜「setBackend('wasm')」)与text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:417(搜「score > 0.5」)。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 467–537 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:521,搜「s1.radius + s2.radius」)与text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:549(搜「wasm-opt -O3」)。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 551–567 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:567,搜「[true, true]」)。碰撞判定的数值验证:两球心距 0.5,半径和 2,0.5 < 2,故均为 true。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 353–435 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:381,搜「requestAnimationFrame」)。逐帧环节按原书代码顺序还原。 ↩ -
出处:「Real-World Projects Using WebAssembly」第 679–847 段(
text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:689,搜「int8」)与text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:717(搜「IndexedDB」)、text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:807(搜「std::thread::scope」)。 ↩