WebAssembly 不会替代 Ollama 这类本地大模型服务,但它正在成为 AI 推理的"可移植执行层"。
如果你的目标是跑 Qwen 这类大语言模型,优先用 Ollama;如果目标是把小模型、后处理、插件化推理能力安全地放到浏览器、边缘节点或多语言服务里,WebAssembly 才是关键。
学习 WebAssembly 做 AI 推理,不能从"前端能不能跑模型"开始,而要从运行时、沙箱、内存、WASI 和模型执行框架的边界开始。
问题背景:什么时候 AI 推理需要 WebAssembly
中高级开发者通常会在三类场景里遇到 WebAssembly,简称 Wasm:
- 浏览器侧推理:例如语音唤醒、OCR 预处理、Embedding、小型分类模型,不希望每次请求都回传服务端。
- 边缘侧推理:在网关、门店设备、工控机上部署模型,要求跨平台、低运维、强隔离。
- 服务端插件化推理:平台允许用户上传自定义逻辑,例如 prompt 过滤、结果重排、特征处理,但不能让用户代码直接运行在宿主进程里。
以一个具体栈为例:
- 本地大模型基线:Ollama 0.5.x + Qwen2.5:7b-instruct-q4_K_M
- Wasm 运行时:Wasmtime 26.x / WasmEdge 0.14.x
- 浏览器推理框架:ONNX Runtime Web 1.20.x
- 标准接口:WASI Preview 2 + WebAssembly Component Model
核心结论一:Wasm 更适合作为 AI 推理的部署边界,而不是大模型训练或超大模型主执行引擎。
机制上,Wasm 提供沙箱、线性内存、确定的模块接口和跨语言编译目标。它擅长把"推理前后的一段计算"封装成可移植组件,例如 tokenizer、特征归一化、规则过滤、reranker 的轻量版本。
例证是:你可以用 Ollama 负责 Qwen 的主推理,再用 Wasm 插件处理输入清洗、敏感词过滤、JSON 修复和结果重排。这样既保留本地 LLM 的能力,又避免把不可信插件放进主服务进程。
核心概念:先分清 Wasm、WASI 与运行时
学习 WebAssembly 做 AI 推理,最容易混淆三个层次。
WebAssembly 是一种二进制指令格式,常被称为虚拟指令集。它不是 JavaScript 的替代品,也不是容器。它的核心设计是:模块被编译成 .wasm,在宿主运行时中执行,通过显式导入导出函数与外界交互。
WASI,WebAssembly System Interface,是 Wasm 的系统接口规范。普通 Wasm 模块默认不能读文件、开网络、访问时钟。WASI 用能力授权的方式提供文件、环境变量、随机数等接口。WASI Preview 2 进一步引入组件模型和 WIT,适合描述跨语言接口。
Wasm 运行时 是执行 Wasm 的宿主,例如 Wasmtime、WasmEdge、Wasmer,浏览器本身也是运行时。运行时负责 JIT/AOT 编译、内存隔离、系统调用代理和资源限制。
核心结论二:AI 推理里的 Wasm 价值不在"更快",而在"可控、可移植、可嵌入"。
机制上,GPU 原生推理框架可以直接调用 CUDA、Metal、ROCm,而 Wasm 通常要通过宿主暴露能力,或者使用 WebGPU、WASI-NN 这类接口间接调用加速器。这个抽象层带来可移植性,但也可能带来性能损耗。
例证是:同一个轻量 ONNX 模型可以用 ONNX Runtime Web 在 Chrome 中通过 Wasm SIMD 执行,也可以在服务端封装为 Wasm 插件;但如果你要跑 Qwen2.5 7B 的高吞吐推理,Ollama、vLLM、llama.cpp 这类原生路径通常更合适。
关键机制:AI 推理在 Wasm 中怎么跑起来
一个 AI 推理链路通常包括五段:
text
输入文本/图片
-> 预处理 tokenizer / resize / normalize
-> 张量构造
-> 模型执行
-> 后处理 decode / top-k / rerank
-> 输出结构化结果
Wasm 可以覆盖其中一段,也可以覆盖多段。工程上常见三种方式。
方式一:浏览器中用 Wasm 后端跑 ONNX
ONNX Runtime Web 支持 Wasm 后端,并可使用 SIMD 和多线程。示例配置如下:
ts
import * as ort from "onnxruntime-web";
// 指定 Wasm 文件路径,避免生产环境路径解析失败
ort.env.wasm.wasmPaths = "/static/ort/";
// 多线程依赖 SharedArrayBuffer,浏览器需要 COOP/COEP 响应头
ort.env.wasm.numThreads = 4;
// 示例:加载一个小型分类或 embedding ONNX 模型
const session = await ort.InferenceSession.create(
"/models/text-classifier.onnx",
{
executionProviders: ["wasm"],
}
);
// 输入 tensor 需要与模型签名严格一致
const input = new ort.Tensor("float32", new Float32Array(128), [1, 128]);
const output = await session.run({ input_ids: input });
console.log(output);
这里的关键不是 API,而是机制:模型权重在浏览器侧加载,张量在 Wasm 线性内存中参与计算,运行时通过 SIMD 和线程提升性能。
核心结论三:Wasm 推理性能的上限,通常由内存布局、SIMD、线程和宿主能力决定。
机制上,Wasm 使用线性内存,频繁在 JS、Wasm、GPU API 之间复制数据会吞掉推理收益。SIMD 可以提升向量计算效率,多线程可以并行算子,但浏览器必须启用 SharedArrayBuffer。
例证是:同一个 ONNX 模型,如果没有正确配置 COOP/COEP,Chrome 会禁用 SharedArrayBuffer,ONNX Runtime Web 可能退化到单线程执行,延迟明显升高。
方式二:服务端用 Wasm 插件包住推理前后处理
对于 Ollama + Qwen 的本地部署,推荐架构不是把 Qwen 整个塞进 Wasm,而是:
text
Client
-> API Gateway
-> Wasm 输入治理插件
- prompt 规范化
- PII 脱敏
- JSON schema 校验
-> Ollama /api/chat
- qwen2.5:7b-instruct-q4_K_M
-> Wasm 输出治理插件
- 结构化修复
- 安全策略
- 结果重排
-> Client
WASI Preview 2 的组件模型适合做这种插件边界。接口可以用 WIT 描述:
wit
package ai:guardrails;
// 定义一个可跨语言实现的推理治理接口
interface filter {
record request {
user_id: string,
prompt: string,
}
record decision {
allowed: bool,
reason: string,
rewritten_prompt: string,
}
// 宿主调用 Wasm 插件,由插件返回处理决策
check: func(req: request) -> decision;
}
world plugin {
export filter;
}
这类接口的好处是宿主语言可以是 Rust、Go 或 Java,插件可以由 Rust、TinyGo、C/C++ 编译成 Wasm。接口稳定后,插件可以独立发布。
核心结论四:Wasm 最适合承载"可替换、可限制、可审计"的 AI 推理逻辑。
机制上,运行时可以限制内存、CPU 时间、文件访问能力,并通过显式接口控制输入输出。相比动态链接库或脚本插件,Wasm 的攻击面更小,跨语言兼容性更好。
例证是:一个面向企业内部的 Qwen 本地助手,可以允许团队上传 Wasm 策略插件做 prompt 改写,但不允许插件访问任意文件系统,也不允许直接发外网请求。
工程实践与选型:不要把所有模型都塞进 Wasm
| 场景 | 推荐方案 | 适用条件 | 不适用条件 |
|---|---|---|---|
| 本地大语言模型推理 | Ollama + Qwen2.5 / llama.cpp | 单机部署、快速验证、需要中文能力 | 高并发、多 GPU 调度、复杂 serving |
| 浏览器小模型推理 | ONNX Runtime Web + Wasm/WebGPU | 模型较小、重隐私、可接受首次加载 | 模型很大、弱网、移动端内存紧张 |
| 边缘插件推理 | Wasmtime / WasmEdge + WASI | 多架构部署、强隔离、逻辑可插拔 | 需要直接吃满 GPU、依赖复杂原生库 |
| 服务端主推理引擎 | vLLM / TensorRT-LLM / llama.cpp | 高吞吐、GPU 优化、模型服务化 | 插件隔离、跨语言分发不是重点 |
| AI 治理与后处理 | Wasm Component Model | 规则变更频繁、需要审计与隔离 | 逻辑很简单,普通函数即可 |
明确结论:
- 跑 Qwen 主推理:优先 Ollama 或原生推理框架。
- 跑小模型、预处理、后处理、插件逻辑:优先考虑 Wasm。
- 需要浏览器侧隐私推理:评估 ONNX Runtime Web、WebGPU 与模型大小。
- 需要边缘多架构分发:评估 Wasmtime、WasmEdge 和 WASI 能力。
常见误区与踩坑
坑一:把 Wasm 当成容器
触发条件:你希望 Wasm 模块像 Docker 容器一样访问文件、网络和系统命令。
现象:本地能跑,部署到运行时后各种权限错误,文件路径、网络调用全部失败。
规避方式:把 Wasm 当作"受限函数模块",所有外部能力都通过宿主显式注入。文件、网络、模型路径不要写死在模块内部。
坑二:浏览器多线程没有生效
触发条件:ONNX Runtime Web 设置了 numThreads = 4,但服务器没有返回正确响应头。
现象:推理延迟比预期高,CPU 只跑单核,性能分析看不到 worker 并行。
规避方式:为页面配置:
http
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
这是 SharedArrayBuffer 的安全要求,不是 ONNX Runtime Web 自己的限制。
坑三:模型太大导致启动慢和内存爆
触发条件:把数百 MB 甚至 GB 级模型直接放到浏览器或边缘 Wasm 模块旁边加载。
现象:首次加载慢、移动端崩溃、边缘设备 OOM。
规避方式:小模型本地化,大模型服务化。浏览器侧优先考虑量化、分片加载、缓存策略;大模型交给 Ollama 或专用推理服务。
知识地图与学习路径
第一阶段:补齐 Wasm 基础
先理解这些概念:
- Wasm 模块、导入导出函数、线性内存
- JIT 与 AOT 执行
- Wasm sandbox 与宿主能力
- WASI Preview 1 与 Preview 2 的区别
- WIT 与 Component Model
动手任务:
- 用 Rust 编译一个
.wasm模块。 - 用 Wasmtime 运行它。
- 给模块加内存限制和执行超时。
- 尝试用 WIT 定义一个插件接口。
资料选择建议:优先看 Wasmtime、WASI、Bytecode Alliance 的官方文档,不要只看"浏览器 Wasm 教程"。
第二阶段:理解 AI 推理基础
需要掌握:
- tokenizer 与 embedding
- ONNX 模型格式
- 张量 shape、dtype、batch
- 量化:例如 int8、q4_K_M 这类权重量化思路
- CPU、GPU、SIMD、线程对推理性能的影响
动手任务:
- 用 Ollama 拉起 Qwen2.5 7B 模型,作为本地主推理服务。
- 写一个最小 API,把请求转发到 Ollama
/api/chat。 - 在请求前后分别插入本地函数,模拟未来的 Wasm 插件边界。
第三阶段:浏览器与边缘推理
重点学习:
- ONNX Runtime Web 的 Wasm execution provider
- WebGPU 与 Wasm 后端的边界
- 浏览器缓存、模型加载、SharedArrayBuffer
- WasmEdge / Wasmtime 在服务端和边缘的部署差异
动手任务:
- 把一个小型 ONNX 分类模型放到浏览器侧运行。
- 对比单线程与多线程延迟。
- 记录模型大小、首次加载时间、单次推理耗时,这些数据要来自自己的设备。
第四阶段:插件化 AI 推理架构
进阶目标不是"能跑",而是"能治理"。
你需要设计:
- 插件接口:WIT 或明确的 ABI
- 资源限制:内存、执行时间、文件访问
- 版本管理:插件版本与宿主接口版本分离
- 可观测性:记录输入摘要、耗时、错误码
- 安全策略:禁止默认网络访问,敏感信息最小化传入
一个可落地的练习项目:
text
本地 AI 助手网关
- Ollama 负责 Qwen 主推理
- Wasm 插件负责 prompt 脱敏
- Wasm 插件负责输出 JSON 修复
- 网关记录每个插件耗时与拒绝原因
这个项目能覆盖 Wasm、WASI、AI 推理、接口设计和工程治理,比单纯跑一个 hello world 更接近真实生产系统。
总结
WebAssembly 在 AI 推理中的核心位置,不是取代 Ollama、vLLM 或 GPU 推理框架,而是提供一个安全、可移植、可嵌入的执行边界。
学习路线应当从 Wasm 运行机制开始,再进入 ONNX/WASI/组件模型,最后落到本地大模型网关、浏览器小模型和边缘插件化推理。
如果你要把一个 Qwen 本地助手产品化,哪些逻辑应该放在 Ollama 服务里,哪些逻辑应该拆成 Wasm 插件?