WebAssembly(Wasm)适合解决 AI 推理的分发、隔离与跨平台问题,但它不是 CUDA 的浏览器替代品。
如果模型需要本地隐私、离线运行、低延迟交互,或者需要把推理插件安全地嵌入服务端,Wasm 很有价值。
如果目标是集中式大模型吞吐,优先考虑原生 GPU 推理服务,而不是强行套入 Wasm。
先判断:为什么要用 Wasm 跑推理
常见落地场景有三类:
- 浏览器本地推理:文本分类、图像识别、Embedding 和小型生成模型不上传原始数据。
- 边缘设备推理:同一推理模块分发到不同 CPU 架构,降低原生动态库的适配成本。
- 服务端插件推理:通过 Wasmtime 等运行时隔离不可信的预处理、后处理或模型扩展代码。
核心观点一:Wasm 的主要价值是统一执行边界,而不是提供新的 AI 算力。
机制上,Wasm 提供受控线性内存、显式导入导出和运行时验证;真正的张量计算则由 Wasm SIMD、线程、WebGPU,或者宿主机原生推理后端完成。
例如,浏览器可以用 Wasm 处理分词和不规则算子,再把矩阵乘法交给 WebGPU。服务端则可以让 Wasm 组件负责后处理,但让宿主侧 ONNX Runtime 或 CUDA 执行模型。这样的分层通常比"所有计算都塞入 Wasm"更合理。
必须分清的四层概念
学习 Wasm AI 推理前,应先建立执行栈,而不是直接追框架 API。
WebAssembly 核心
WebAssembly Core 定义指令、模块、函数表和线性内存。一个内存页固定为 64 KiB ,SIMD 扩展提供 128 位 v128 向量类型。
它解决的是可移植执行,不提供文件系统、网络和 GPU API。浏览器通过 JavaScript API 暴露宿主能力,服务端通常通过 WebAssembly System Interface(WASI)提供受控系统接口。
WASI 与组件模型
WASI 0.2 采用组件模型和 WebAssembly Interface Types(WIT)描述接口,使模块不必直接依赖某种语言的 ABI。
它适合服务端推理插件,但不能把 WASI 理解成"跨平台 CUDA"。wasi-nn 提案的思路是让 Wasm 调用宿主推理后端,具体支持哪些模型格式和设备,仍取决于运行时构建及后端实现。
推理运行时
ONNX Runtime Web、Transformers.js 3 和基于 llama.cpp 的 wllama 都能在浏览器中使用 Wasm,但抽象层次不同:
- ONNX Runtime Web:执行 ONNX 计算图;
- Transformers.js 3:在模型、Tokenizer 和推理流水线之上提供高层 API;
- wllama:面向 GGUF 和大语言模型,利用
llama.cpp的 Wasm 构建。
WebGPU
WebGPU 不是 Wasm 的一部分。它是浏览器提供的 GPU 计算接口,Wasm 或 JavaScript 负责组织张量、调度计算和管理数据。
核心观点二:浏览器 AI 推理通常是"Wasm 控制路径 + WebGPU 计算路径",不是二选一。
例如 ONNX Runtime Web 可以配置 webgpu 和 wasm 执行提供程序。WebGPU 适合大规模并行算子,Wasm 则兼容更多设备,也适合控制流密集的预处理。需要注意,能否混合执行以及不支持算子的处理方式,要以具体运行时和模型实测为准。
一次推理请求是怎样执行的
典型浏览器流水线如下:
text
模型下载与缓存
↓
Tokenizer / 图像预处理
↓
构造 Tensor
↓
ONNX 图优化与算子分配
↓
Wasm SIMD / 多线程 / WebGPU
↓
后处理与结果展示
下面的配置体现了执行后端选择和线程机制:
javascript
import * as ort from "onnxruntime-web";
// Wasm 多线程依赖 SharedArrayBuffer 和跨源隔离。
// 不要直接使用全部逻辑核心,避免推理阻塞页面其他任务。
ort.env.wasm.numThreads = Math.min(
4,
navigator.hardwareConcurrency ?? 1
);
const session = await ort.InferenceSession.create(
"/models/classifier.onnx",
{
// 优先尝试 WebGPU,Wasm 作为兼容路径。
// 上线前必须验证目标模型的算子支持情况。
executionProviders: ["webgpu", "wasm"]
}
);
const input = new ort.Tensor("float32", inputData, [1, 3, 224, 224]);
const result = await session.run({ pixel_values: input });
核心观点三:性能瓶颈经常不是算子本身,而是模型下载、张量复制和运行时初始化。
原因是 CPU、Wasm 线性内存与 GPU 缓冲区属于不同内存域,数据转换可能产生额外复制。一个只有几十毫秒计算时间的模型,如果需要下载数百 MB 权重,首次可用时间仍然可能不可接受。
以参数存储量估算:一个 1 亿参数模型,FP32 权重理论大小约为 400 MB ,INT8 约为 100 MB,尚未计算图结构和运行时内存。量化、分片下载、持久化缓存和懒加载因此往往比微调某个算子更重要。
Rust 与 Axum 如何进入架构
Rust 1.85 可以同时承担两种角色:编译 Wasm 组件,或者用 Axum 0.8 提供模型、Wasm 文件及控制接口。
浏览器启用 Wasm 多线程时,SharedArrayBuffer 通常要求页面处于跨源隔离状态。Axum 服务需要返回正确的 COOP 和 COEP 响应头:
rust
use axum::Router;
use axum::http::{HeaderName, HeaderValue};
use tower_http::{
services::ServeDir,
set_header::SetResponseHeaderLayer,
};
#[tokio::main]
async fn main() {
let app = Router::new()
.fallback_service(ServeDir::new("dist"))
.layer(SetResponseHeaderLayer::if_not_present(
HeaderName::from_static("cross-origin-opener-policy"),
HeaderValue::from_static("same-origin"),
))
.layer(SetResponseHeaderLayer::if_not_present(
HeaderName::from_static("cross-origin-embedder-policy"),
HeaderValue::from_static("require-corp"),
));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
这段配置使用 Rust 1.85、Axum 0.8 和 tower-http 0.6。启用 require-corp 后,第三方字体、脚本和模型文件也必须满足 CORS 或 Cross-Origin-Resource-Policy 要求,否则资源会被浏览器拦截。
工程选型:不要按框架热度做决定
| 场景 | 推荐方案 | 选择理由 | 不适用条件 |
|---|---|---|---|
| 浏览器分类、检测、Embedding | ONNX Runtime Web | ONNX 生态成熟,可选 Wasm/WebGPU | 模型包含大量不支持算子 |
| 浏览器快速集成预训练模型 | Transformers.js 3 | 包含模型加载、Tokenizer 和 Pipeline | 需要精细控制执行图 |
| 浏览器运行 GGUF 小型语言模型 | wllama | 复用 llama.cpp 量化生态 |
权重过大、设备内存不足 |
| 服务端隔离推理插件 | Wasmtime + WASI 0.2 | 能力隔离、接口明确、跨语言 | 追求极致 GPU 吞吐 |
| 集中式大模型服务 | 原生 CUDA 推理栈 | 批处理、显存管理和算子优化更成熟 | 强隔离插件才是核心诉求 |
核心观点四:模型大小、算子覆盖率和数据位置,比"是否支持 Wasm"更能决定选型。
例如 70 亿参数模型即使采用 4 bit 量化,裸权重理论上也约为 3.5 GB。再加上 KV Cache、运行时工作区和内存复制,很容易触及 wasm32 地址空间及浏览器内存限制。此时应缩小模型、使用远端推理,或者只在本地执行隐私敏感的前后处理。
最常见的三个坑
开发环境正常,多线程部署后失效
触发条件是缺少 COOP/COEP,或者某个跨域资源不满足隔离策略。现象通常是 SharedArrayBuffer 不可用,运行时退化为单线程,甚至初始化失败。
规避方式是检查 crossOriginIsolated,并在真实 CDN、反向代理和模型域名下完成测试。
只测热推理延迟
模型和运行时已缓存后的延迟,无法代表用户首次打开页面的体验。测试应至少拆分为下载时间、会话初始化、首轮推理和稳定推理四项。
默认 WebGPU 一定更快
小模型可能被 GPU 初始化、命令提交和张量复制开销抵消。应在目标设备上同时测试 Wasm 单线程、Wasm 多线程和 WebGPU,而不是只看桌面独立显卡结果。
从原理到落地的学习路径
- 掌握 Wasm 执行模型:理解模块、线性内存、导入导出、SIMD 和线程;手写一个数组计算模块并观察内存复制。
- 学习 WASI 0.2 与 WIT:用 Rust 编译一个组件,通过 Wasmtime 调用,理解宿主能力不是模块默认拥有的。
- 完成小模型推理:选择 MobileNet、文本分类或小型 Embedding 模型,对比 ONNX Runtime Web 的 Wasm 与 WebGPU 后端。
- 补齐工程链路:使用 Axum 托管模型,实现缓存策略、COOP/COEP、进度展示、超时和后端降级。
- 进入性能分析:记录模型体积、峰值内存、首次推理和稳定延迟,再决定量化、图优化或算子替换。
AI 编程工具可以辅助生成 WIT 接口、分析 ONNX 图和编写基准脚本,但不能替代算子兼容性与数值误差验证。尤其经过量化或后端切换后,必须用固定数据集比较输出误差,而不是只验证"能够运行"。
**Wasm AI 推理的学习主线应是执行边界、后端分工和资源约束,而不是框架 API。**先用小模型打通下载、初始化、推理和降级链路,再决定是否引入 WebGPU、组件模型或大语言模型。
你的实际场景中,更难解决的是模型体积、浏览器算子兼容,还是服务端插件隔离?