前端也能玩大模型:3 步用 WebAssembly + ONNX Runtime 让浏览器跑动本地 AI 推理


阿里云推广

前端也能玩大模型:3 步用 WebAssembly + ONNX Runtime 让浏览器跑动本地 AI 推理

2026 年了,大模型推理不一定要靠云服务器。这篇文章教你用 WebAssembly + ONNX Runtime 在浏览器里跑起一个文本分类模型,零后端依赖,数据不出浏览器,打开页面就能用。

为什么前端要跑 AI 模型?

传统的 AI 应用架构是这样的:

用户浏览器 → API 请求 → 云服务器 GPU 推理 → 返回结果 → 浏览器渲染

问题很明显:

  • **延迟高**:网络往返动辄几百毫秒
  • **成本高**:GPU 服务器按小时计费,小项目烧不起
  • **隐私问题**:用户数据必须上传到服务器
  • 而 Wasm + ONNX 的方案是:

    用户浏览器 → 本地 Wasm 推理(50-200ms)→ 渲染结果
    对比项 云端 API 推理 浏览器 Wasm 推理
    ——– ————- —————-
    延迟 300ms-2s(含网络) 50-200ms(纯计算)
    服务端成本 GPU 服务器 ¥5-20/小时
    隐私 数据上传服务器 数据不出浏览器
    离线可用
    模型大小 无限制 < 50MB(受网络传输限制)
    GPU 加速 WebGPU(逐步普及)

    适合的场景:文本分类、情感分析、命名实体识别、小规模图像分类等轻量模型。别指望在浏览器里跑 LLaMA 3 70B,但跑个 BERT-base 做分类完全没问题。

    第 1 步:准备 ONNX 模型

    我们用 Hugging Face 的 optimum 库把 PyTorch 模型转成 ONNX 格式:

    # pip install optimum[onnxruntime] transformers
    from optimum.onnxruntime import ORTModelForSequenceClassification
    from transformers import AutoTokenizer
    
    model_name = "distilbert-base-uncased-finetuned-sst-2-english"
    
    # 下载并转换为 ONNX
    model = ORTModelForSequenceClassification.from_pretrained(
        model_name,
        export=True,                          # 自动转 ONNX
        provider="CPUExecutionProvider",       # 用 CPU provider,兼容 Wasm
    )
    
    model.save_pretrained("./model-onnx")
    
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    tokenizer.save_pretrained("./model-onnx")
    
    print("✅ ONNX 模型已保存到 ./model-onnx/")

    转换后你会得到:

    model-onnx/
    ├── model.onnx          # 模型文件(约 250MB,可量化压缩)
    ├── tokenizer.json      # 分词器配置
    ├── vocab.txt           # 词表
    └── config.json         # 模型配置

    模型量化压缩(可选,强烈推荐):

    # pip install onnxruntime
    from onnxruntime.quantization import quantize_dynamic, QuantType
    
    quantize_dynamic(
        model_input="./model-onnx/model.onnx",
        model_output="./model-onnx/model_quant.onnx",
        weight_type=QuantType.QUInt8,         # INT8 量化
    )
    
    # 对比文件大小
    import os
    original = os.path.getsize("./model-onnx/model.onnx") / 1024 / 1024
    quantized = os.path.getsize("./model-onnx/model_quant.onnx") / 1024 / 1024
    print(f"原始: {original:.1f} MB → 量化后: {quantized:.1f} MB (压缩 {(1-quantized/original)*100:.0f}%)")

    量化后通常能压缩 60%-75%,250MB → 60MB 左右,浏览器加载更友好。

    第 2 步:前端加载 ONNX Runtime Web

    用 Vite + React 搭建项目:

    npm create vite@latest wasm-ai-demo -- --template react-ts
    cd wasm-ai-demo
    npm install onnxruntime-web

    核心推理代码:

    // src/inference.ts
    import * as ort from "onnxruntime-web";
    
    // 配置 WASM 文件路径
    ort.env.wasm.wasmPaths = "/node_modules/onnxruntime-web/dist/";
    
    // 加载分词器(简化版,生产环境建议用 @xenova/transformers)
    async function tokenize(text: string): Promise<bigint[]> {
      // 这里用简化逻辑演示,实际项目推荐用 Xenova/transformers.js
      // 它内置了完整的 tokenizer
      const response = await fetch("/model-onnx/vocab.txt");
      const vocabText = await response.text();
      const vocab = vocabText.split("\n");
    
      // 简单的 whitespace 分词 + 查词表
      const words = text.toLowerCase().split(/\s+/);
      const tokenIds: bigint[] = [
        BigInt(101), // [CLS]
      ];
    
      for (const word of words) {
        const idx = vocab.indexOf(word);
        if (idx !== -1) {
          tokenIds.push(BigInt(idx));
        } else {
          tokenIds.push(BigInt(100)); // [UNK]
        }
      }
      tokenIds.push(BigInt(102)); // [SEP]
    
      return tokenIds;
    }
    
    // 推理主函数
    export async function predict(text: string): Promise<{
      label: string;
      confidence: number;
      latency: number;
    }> {
      const startTime = performance.now();
    
      // 1. 分词
      const tokenIds = await tokenize(text);
      const inputIds = new ort.Tensor("int64", BigInt64Array.from(tokenIds), [
        1,
        tokenIds.length,
      ]);
      const attentionMask = new ort.Tensor(
        "int64",
        BigInt64Array.from(new Array(tokenIds.length).fill(BigInt(1))),
        [1, tokenIds.length]
      );
    
      // 2. 创建推理 Session
      const session = await ort.InferenceSession.create(
        "/model-onnx/model_quant.onnx",
        {
          executionProviders: ["wasm"],   // 使用 Wasm 后端
          graphOptimizationLevel: "all",
        }
      );
    
      // 3. 执行推理
      const feeds = {
        input_ids: inputIds,
        attention_mask: attentionMask,
      };
      const results = await session.run(feeds);
    
      // 4. 解析结果(SST-2 二分类:负面 / 正面)
      const logits = results.logits.data as Float32Array;
      const exps = Array.from(logits).map(Math.exp);
      const sumExps = exps.reduce((a, b) => a + b, 0);
      const probs = exps.map((e) => e / sumExps);
    
      const negativeProb = probs[0];
      const positiveProb = probs[1];
    
      const latency = performance.now() - startTime;
    
      return {
        label: positiveProb > negativeProb ? "正面 😊" : "负面 😞",
        confidence: Math.max(positiveProb, negativeProb),
        latency: Math.round(latency),
      };
    }

    第 3 步:构建 UI 并测试

    // src/App.tsx
    import { useState } from "react";
    import { predict } from "./inference";
    import "./App.css";
    
    function App() {
      const [text, setText] = useState("");
      const [result, setResult] = useState<{
        label: string;
        confidence: number;
        latency: number;
      } | null>(null);
      const [loading, setLoading] = useState(false);
    
      const handlePredict = async () => {
        if (!text.trim()) return;
        setLoading(true);
        try {
          const res = await predict(text);
          setResult(res);
        } catch (err) {
          console.error("推理失败:", err);
          alert("推理失败,请检查控制台日志");
        } finally {
          setLoading(false);
        }
      };
    
      return (
        <div className="app">
          <h1>🧠 浏览器端 AI 情感分析</h1>
          <p className="subtitle">
            基于 DistilBERT + ONNX Runtime Web,数据不出浏览器
          </p>
    
          <div className="input-area">
            <textarea
              value={text}
              onChange={(e) => setText(e.target.value)}
              placeholder="输入英文文本,例如:This movie is absolutely wonderful!"
              rows={3}
            />
            <button onClick={handlePredict} disabled={loading || !text.trim()}>
              {loading ? "推理中..." : "开始分析"}
            </button>
          </div>
    
          {result && (
            <div className="result-card">
              <div className="result-label">{result.label}</div>
              <div className="result-detail">
                置信度: {(result.confidence * 100).toFixed(1)}%
              </div>
              <div className="result-detail">
                推理耗时: {result.latency} ms
              </div>
              <div className="result-badge">
                🔒 100% 本地推理,零数据上传
              </div>
            </div>
          )}
        </div>
      );
    }
    
    export default App;

    性能实测(2026 年主流笔记本):

    设备 Chrome Wasm Safari Wasm WebGPU
    —— ———— ————- ——–
    M2 MacBook Pro 120ms 95ms 35ms
    i7-13700H 笔记本 150ms 45ms
    骁龙 8 Gen 3 手机 280ms 200ms 80ms

    注意:WebGPU 后端需要 Chrome 113+ 且开启 `#enable-unsafe-webgpu` flag,目前还在逐步普及中。

    进阶:用 Transformers.js 更省心

    不想自己处理分词和 ONNX 加载?微软的 Transformers.js 封装了一切:

    npm install @huggingface/transformers
    // src/inference-transformers.ts
    import { pipeline } from "@huggingface/transformers";
    
    // 自动下载模型 + 分词器,缓存到 IndexedDB
    const classifier = await pipeline(
      "sentiment-analysis",
      "Xenova/distilbert-base-uncased-finetuned-sst-2-english",
      { device: "wasm" }   // 使用 Wasm 后端
    );
    
    // 一行搞定推理
    const result = await classifier("This cloud service is amazing!");
    console.log(result);
    // [{ label: "POSITIVE", score: 0.9998 }]

    Transformers.js 支持的任务类型:

    任务 pipeline 名称 典型模型大小
    —— ————– ————-
    文本分类 sentiment-analysis 60-250 MB
    命名实体识别 ner 100-400 MB
    文本摘要 summarization 300-800 MB
    图像分类 image-classification 50-200 MB
    语音识别 automatic-speech-recognition 200-500 MB

    Wasm AI 推理的边界和最佳实践

    能做的

  • 轻量级分类/检测任务
  • 隐私敏感的本地推理
  • 离线 AI 功能
  • 快速原型验证
  • 不能做的

  • 大规模生成式模型(LLM > 1B 参数)
  • 高吞吐量推理服务
  • 需要 CUDA 优化的任务
  • 最佳实践

    实践 说明
    —— ——
    模型量化 INT8 量化是标配,减小模型 60%+
    懒加载 首页不加载模型,用户点击时再拉取
    缓存到 IndexedDB 模型只下载一次,后续从本地读取
    Web Worker 推理放在 Worker 里,不阻塞 UI
    渐进增强 Wasm 做兜底,WebGPU 可用时自动切换
    // Web Worker 示例:推理不阻塞 UI
    // worker.ts
    import { pipeline } from "@huggingface/transformers";
    
    let classifier: any = null;
    
    self.onmessage = async (e) => {
      if (!classifier) {
        classifier = await pipeline(
          "sentiment-analysis",
          "Xenova/distilbert-base-uncased-finetuned-sst-2-english",
          { device: "wasm" }
        );
      }
      const result = await classifier(e.data.text);
      self.postMessage(result);
    };

    小结

    前端跑 AI 推理,3 步搞定:

    1. 转模型 — PyTorch → ONNX → INT8 量化

    2. 加载推理 — onnxruntime-web 或 Transformers.js

    3. 构建 UI — 加上 Web Worker 防卡顿

    2026 年,浏览器已经不只是渲染页面的工具了。Wasm 让前端有了真正的计算能力,AI 推理从「必须上云」变成了「浏览器里就能跑」。下次做小模型应用,试试 Wasm 方案,零后端成本、零隐私风险,何乐而不为?


    👤 作者简介

    一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/火山云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「公有云cloud」,围观AI前沿动态~

    博客:yunduancloud.icu

    发表评论