编程语言研究书库

实战项目 — 浏览器里的 AI 推理与游戏物理

实战项目 — 浏览器里的 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

  1. 导出的输入是两个张量(input_ids + attention_mask,各长 128);
  2. 过完 ONNX Runtime 的优化器再上线: python -m onnxruntime_tools.optimizer --input bert.onnx --output bert_opt.onnx;
  3. 浏览器里用 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 的渲染循环(书里的完整代码,骨架九行):

const physics = new PhysicsEngine();
// 每帧:detect_collisions() → 撞上的球变黄 → renderer.render()

演示输入:两个半径 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 设计、推理/渲染分模块、为动态链接留路

10

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. 可带走的

  1. 模型进浏览器的标准路径:PyTorch → torch.onnx.export → ONNX.js / onnxruntime-web。
  2. 导出三要点:eval()、假输入形状、opset_version。
  3. 预处理(缩放/归一化/tokenize)归 JS,推理归 Wasm——分工照旧。
  4. tfjs 用 Wasm 后端要显式 setBackend('wasm')。
  5. 物理引擎套路:Rust struct + impl + wasm-bindgen = JS 里的类;wasm-opt -O3 收尾。
  6. Three.js 集成:每帧渲染循环里调 Wasm 函数,结果改场景状态。
  7. 模型压缩三招:量化、剪枝、切块懒加载;缓存进 IndexedDB。
  8. 并发:线程 + Workers + SharedArrayBuffer 是 Wasm 并发的三件套。
  9. 真实工程的坑在书里被简化掉的地方:tokenizer、图像预处理、跨浏览器兼容——照抄 demo 之后这三处等着补。
  10. Wasm 是 AI-in-browser 的「后端之一」不是唯一——选型时把 WebGPU 后端一起评估。

7. 原文地图

主题原书章原文位置
客户端推理的价值Real-World Projects Using WebAssemblytext/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」)

Footnotes

  1. 出处:「Real-World Projects Using WebAssembly」第 13–31 段(text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:19,搜「client-side AI/ML inference」)。 ↩

  2. 出处:「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」)。 ↩

  3. 出处:「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」)。 ↩

  4. 出处:「Real-World Projects Using WebAssembly」第 235–263 段(text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:251,搜「BertForSequenceClassification」)。 ↩

  5. 出处:「Real-World Projects Using WebAssembly」第 285–287 段(text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:287,搜「Replace with actual tokenization」)。 ↩

  6. 出处:「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」)。 ↩

  7. 出处:「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」)。 ↩

  8. 出处:「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。 ↩

  9. 出处:「Real-World Projects Using WebAssembly」第 353–435 段(text/26-fm-ultimate-webassembly-for-high-performance-apps-b.txt:381,搜「requestAnimationFrame」)。逐帧环节按原书代码顺序还原。 ↩

  10. 出处:「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」)。 ↩

On this page