欢迎光临
我们一直在努力

WebAssembly AI 插件项目回顾:理想很丰满,浏览器兼容性很骨感的现实

WebAssembly AI 插件项目回顾:理想很丰满,浏览器兼容性很骨感的现实

一、为什么选择 WASM + Rust 做 AI 插件

初始想法很简单:用户在 Figma 或 VS Code Web 里选中一段文字,按快捷键,本地 AI 直接给出改写建议。全程离线,不经过任何服务器,隐私保护拉满。

技术栈选型很自然:Rust 写的推理引擎编译到 WebAssembly,运行在浏览器里。理想中的架构:

第一步很顺利——用 wasm-pack 把一个 200 行的 Rust 示例编译成 .wasm,在浏览器里成功运行了。

// ============================================================
// 第一个 WASM 测试:文本处理功能(编译成功!)
// ============================================================
use wasm_bindgen::prelude::*;

/// 暴露给 JavaScript 的文本预处理函数
/// 在 WASM 中运行,负责清洗和标准化用户输入的文本
#[wasm_bindgen]
pub fn preprocess_text(input: &str) -> String {
// 去掉多余空白字符,统一换行符
input
.lines()
.map(|line| line.trim())
.filter(|line| !line.is_empty())
.collect::<Vec<_>>()
.join("\\n")
}

/// 简单的 token 计数,用于评估文本长度是否超过模型限制
#[wasm_bindgen]
pub fn count_tokens(text: &str) -> usize {
// 简化版:按空格分词(实际应该用 tokenizer)
text.split_whitespace().count()
}

JS 调用端也很简洁:

import init, { preprocess_text, count_tokens } from './pkg/my_wasm.js';
await init();

const result = preprocess_text(" hello world \\n rust ");
console.log(result); // "hello world\\nrust"

到这里,我甚至觉得"WASM 项目真简单,两周搞定"。

二、第一道墙:WASM 内存限制

真正的噩梦从加载模型文件开始。一个最轻量的 ONNX 推理模型(比如 tiny-bert)也要 15MB。在 WASM 里,内存默认限制是 4GB——听起来很大,但浏览器的限制远不止这个:

  • 每个 tab 的内存预算通常在 200~500MB 之间(取决于设备和浏览器)。
  • WASM 的线性内存不能动态扩展超过一定量。
  • 模型推理过程中的临时张量会额外占用大量内存。
  • 实际测试:15MB 的模型加载后占用约 60MB WASM 堆内存,推理一个 256 token 的文本时峰值内存跳到 180MB。在 Chrome 里还能接受,但在移动端 Safari 上直接在 tab 崩溃。

    三、第二道墙:浏览器兼容性地狱

    WASM 本身兼容性还可以,但多线程和 SIMD 是两个巨大的坑:

    特性ChromeFirefoxSafari我们的依赖
    WASM 基础 全部
    WASM 线程 ⚠️ 需要 COOP/COEP wasm-bindgen-rayon
    WASM SIMD ❌ 不支持 推理加速
    SharedArrayBuffer ⚠️ 需要特殊 header 线程间共享模型

    最关键的问题是 SharedArrayBuffer。要在 WASM 中启用多线程,必须用到它,但 Safari 要求服务端设置特定的 HTTP header:

    Cross-Origin-Opener-Policy: same-origin
    Cross-Origin-Embedder-Policy: require-corp

    作为浏览器插件,我们没有能力控制目标网站的 HTTP header。这意味着:多线程 WASM 在 Safari 上直接无法使用,只能退回到单线程模式,推理速度慢了 3~4 倍。

    // ============================================================
    // 条件编译:根据平台决定使用多线程还是单线程
    // ============================================================
    #[cfg(all(target_feature = "atomics", feature = "threads"))]
    mod inference {
    // 多线程推理实现:利用 wasm-bindgen-rayon + SharedArrayBuffer
    pub fn run_parallel(model: &Model, input: &[f32]) -> Vec<f32> {
    use rayon::prelude::*;
    // 并行计算各层的注意力分数
    input.par_chunks(64).map(|chunk| {
    model.forward(chunk)
    }).flatten().collect()
    }
    }

    #[cfg(not(all(target_feature = "atomics", feature = "threads")))]
    mod inference {
    // 单线程回退方案:虽然慢但兼容性更好
    pub fn run_parallel(model: &Model, input: &[f32]) -> Vec<f32> {
    input.chunks(64).map(|chunk| {
    model.forward(chunk)
    }).flatten().collect()
    }
    }

    四、重新思考:WASM AI 的正确打开方式

    经过两周的碰壁,我重新审视了这个项目的可行性。结论是:纯浏览器端的 AI 推理在现阶段还不成熟,但有一些可行的折中方案:

  • WASM 做预处理,推理交给服务端:浏览器端只负责文本清洗、tokenization,通过加密通道把 token 发给服务端做推理。隐私性有折中,但可用性大幅提升。
  • WebGPU 推理:Chrome 113+ 支持 WebGPU,可以用它来加速推理。但 Firefox 和 Safari 的支持还在路上。
  • 等 Rust + WASM 生态再成熟一点:candle/burn 等 Rust 深度学习框架正在积极支持 WASM 目标,可能半年后情况会好很多。
  • 我们最终选择了路径 1——WASM 预处理 + 服务端推理。用户文本在浏览器端完成清洗和 tokenization,只把 token ID 发到服务端,服务端不存原始文本。这是目前技术条件下最务实的方案。

    隔了半年再看,这个折中方案确实是最稳的选择——最近 WebGPU 在 Firefox 上的支持已经 beta 了,可能再等一年就能切到纯浏览器端。

    五、总结

    WASM AI 插件项目是一次"理想照进现实"的典型经历:

  • WASM 不是银弹。它在浏览器里有严重的内存和多线程限制,做轻量功能可以,跑 15MB+ 的模型就吃力了。
  • 浏览器兼容性是绕不过去的坎。Safari 对 SharedArrayBuffer 的限制直接杀死了多线程 WASM。
  • 混合架构是务实选择。WASM 做预处理 + 服务端做重计算,兼顾了隐私和性能。
  • 这次项目的代码我没扔掉——WASM 文本预处理模块被复用到另一个项目中,算是没白忙活。如果你也在考虑用 WASM 做 AI 推理,希望我的经历能让你少走点弯路。

    赞(0)
    未经允许不得转载:171主机测评 » WebAssembly AI 插件项目回顾:理想很丰满,浏览器兼容性很骨感的现实
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址