上級 api

Wasm + 機械学習

なぜブラウザでWasmを使ってMLを実行するのか?

ブラウザでの機械学習推論には独自の利点があります:

  従来のMLパイプライン              Wasm MLパイプライン

  ┌────────┐     ┌────────┐        ┌────────────────────┐
  │ クライアント│────>│ サーバー │       │ ブラウザ             │
  │        │     │ (GPU)  │        │                     │
  │ データを│     │ モデルを│        │ ┌────────────────┐  │
  │ ネット  │     │ 実行   │        │ │ Wasm MLランタイム│  │
  │ ワーク  │     │ 結果を │        │ │                │  │
  │ 経由で  │     │ 返す   │        │ │ モデルをロード   │  │
  │ 送信   │<────│        │        │ │ 推論を実行      │  │
  └────────┘     └────────┘        │ │ 結果を返す      │  │
                                   │ └────────────────┘  │
  ● ネットワーク遅延               │                     │
  ● プライバシー: データが端末外へ   │ ● 遅延ゼロ          │
  ● サーバーコスト (GPU $$$)        │ ● データは端末に留まる │
                                   │ ● サーバーコストなし   │
                                   └────────────────────┘
要素 サーバーML Wasm ML(ブラウザ) WebGPU ML
レイテンシ ネットワークRTT 即時(ローカル) 即時(ローカル)
プライバシー データがサーバーに送信 データは端末に留まる データは端末に留まる
ハードウェア サーバーGPU CPU(全スレッド) クライアントGPU
コスト リクエストごと 無料(クライアントCPU) 無料(クライアントGPU)
モデルサイズ制限 無制限 実用的には約100 MB 実用的には約1 GB
精度 フル精度 フル精度 f16を使う場合あり
オフライン対応 不可 可能(Service Worker) 可能
ブラウザサポート N/A(サーバー) ユニバーサル Chrome、Edge、Firefox

Wasm用MLランタイム

tract(RustのONNXランタイム)

tractはWasmにコンパイルできる最も成熟したRust MLランタイムです:

use tract_onnx::prelude::*;

// ONNXモデルをロード
let model = tract_onnx::onnx()
    .model_for_path("model.onnx")?
    .with_input_fact(0, f32::fact([1, 3, 224, 224]).into())?
    .into_optimized()?
    .into_runnable()?;

// 推論を実行
let input: Tensor = tract_ndarray::Array4::from_shape_fn(
    (1, 3, 224, 224),
    |(_, c, y, x)| pixel_value(image, c, y, x)
).into();

let result = model.run(tvec!(input.into()))?;
let output = result[0].to_array_view::<f32>()?;

candle(Hugging Face)

use candle_core::{Tensor, Device};

let device = Device::Cpu;  // Wasmでは常にCPUを使用
let weights = Tensor::from_vec(vec![1.0f32, 2.0, 3.0], (3,), &device)?;
let input = Tensor::from_vec(vec![0.5f32, 0.3, 0.2], (3,), &device)?;
let output = (weights * input)?;

Wasm MLランタイムの比較

  ┌─────────────┬──────────────┬───────────────┬────────────────┐
  │ ランタイム    │ モデル形式    │ Wasmサポート   │ 最適な用途      │
  ├─────────────┼──────────────┼───────────────┼────────────────┤
  │ tract       │ ONNX, TF     │ 優秀          │ 従来のモデル    │
  │ candle      │ safetensors  │ 良好          │ LLM, HFモデル  │
  │ burn        │ burn形式     │ 実験的         │ カスタム訓練    │
  │ ort (onnx)  │ ONNX         │ wasm-pack経由 │ ONNXエコシステム │
  └─────────────┴──────────────┴───────────────┴────────────────┘

テンソル演算の詳細

あらゆるMLランタイムの中核はテンソル計算です。主要な演算を以下に示します:

行列乗算(GEMM)

ニューラルネットワークで最もパフォーマンスが重要な演算です:

  行列A (2x3)       行列B (3x2)       結果 (2x2)

  ┌─────────────┐   ┌─────────┐       ┌───────────┐
  │ 1   2   3   │   │ 7   8   │       │ 58    64  │
  │             │ × │ 9  10   │  =    │           │
  │ 4   5   6   │   │11  12   │       │139   154  │
  └─────────────┘   └─────────┘       └───────────┘

  result[0][0] = 1*7 + 2*9  + 3*11  = 58
  result[0][1] = 1*8 + 2*10 + 3*12  = 64
  result[1][0] = 4*7 + 5*9  + 6*11  = 139
  result[1][1] = 4*8 + 5*10 + 6*12  = 154

活性化関数

  ReLU                    Sigmoid                  Softmax
  f(x) = max(0, x)       f(x) = 1/(1+e^-x)       f(xi) = e^xi / sum(e^xj)

  出力  │     ╱          出力  │    ──────       出力  │   ╱
        │    ╱                │  ╱              (確率) │  ╱
        │   ╱            0.5 │╱                      │╱────
  ───────┼──╱          ───────┼──────           ──────┼──────
        │               ╱   │                       │
        │             ╱     │                       │
  ───────┘                                          │
活性化関数 数式 範囲 用途
ReLU max(0, x) [0, inf) 隠れ層(デフォルト)
Sigmoid 1 / (1 + e^-x) (0, 1) 二値分類
Softmax e^xi / sum(e^xj) (0, 1) 多クラス出力
Tanh (e^x - e^-x)/(e^x + e^-x) (-1, 1) RNN、隠れ層

ブラウザでのモデルロード

  ┌─────────────────────────────────────────────────────┐
  │  モデルロードパイプライン                               │
  │                                                     │
  │  1. fetch("model.onnx")                             │
  │     │                                               │
  │     ▼                                               │
  │  2. ArrayBuffer(生バイト列)                         │
  │     │                                               │
  │     ▼                                               │
  │  3. Wasmに渡す: load_model(&bytes)                  │
  │     │                                               │
  │     ├── モデルグラフを解析(protobuf/flatbuf)        │
  │     ├── Wasmメモリに重みテンソルを確保                 │
  │     ├── グラフを最適化(演算融合、定数畳み込み)        │
  │     └── 不透明ハンドルを返す                          │
  │     │                                               │
  │     ▼                                               │
  │  4. 推論準備完了: predict(handle, input)              │
  └─────────────────────────────────────────────────────┘
// JavaScript側
import init, { WasmModel } from './pkg/ml_wasm';

async function loadAndPredict() {
    await init();

    // モデルバイト列を取得
    const response = await fetch('/models/mobilenet_v2.onnx');
    const modelBytes = new Uint8Array(await response.arrayBuffer());

    // Wasmにロード
    const model = WasmModel.load(modelBytes);

    // 入力を準備(224x224 RGB画像をフラットなFloat32Arrayとして)
    const input = preprocessImage(imageElement);

    // 推論を実行
    const output = model.predict(input);
    const topClass = argmax(output);
    console.log(`予測: ${IMAGENET_CLASSES[topClass]}`);
}

パフォーマンス: Wasm vs TensorFlow.js

一般的なノートPCでのベンチマーク(MobileNet V2、単一画像):

  推論時間 (ms) — 低いほど良い

  TF.js (WebGL)  ████████████  12ms
  Wasm (tract)   ██████████████████  18ms
  TF.js (Wasm)   ████████████████████  20ms
  TF.js (CPU/JS) █████████████████████████████████████████  42ms

  ← 高速                                       低速 →
ランタイム MobileNet V2 ResNet50 BERT (base)
TF.js WebGL 約12ms 約45ms 約120ms
Wasm (tract) 約18ms 約80ms 約300ms
TF.js Wasmバックエンド 約20ms 約90ms 約350ms
TF.js CPU (純JS) 約42ms 約200ms 約800ms
ネイティブ (PyTorch CPU) 約8ms 約30ms 約80ms

主なポイント:

  • Wasmは純JavaScriptより2-3倍高速
  • WebGL/WebGPUは大規模モデルではWasmに勝る(GPU並列処理)
  • Wasmは小規模モデルでGPUセットアップのオーバーヘッドが支配的な場合に優位
  • Wasmはより予測可能(GPUドライバのばらつきなし)

Wasm ML vs WebGPUの使い分け

  判断マトリクス:

                     小規模モデル          大規模モデル
                     (< 1000万パラメータ)   (> 1億パラメータ)
  ┌─────────────────┬────────────────────┬────────────────────┐
  │ オフラインサポート │  Wasm ✓            │  Wasm(収まる場合)   │
  │ が必要?         │                    │  またはWebGPU       │
  ├─────────────────┼────────────────────┼────────────────────┤
  │ 安定した         │  Wasm ✓            │  Wasm              │
  │ パフォーマンス?  │  (GPUばらつきなし)   │ (遅いが安定)       │
  ├─────────────────┼────────────────────┼────────────────────┤
  │ 最大速度?       │  Wasm ✓            │  WebGPU ✓          │
  │                 │  (GPUオーバーヘッドが │  (GPU並列処理が      │
  │                 │   割に合わない)      │   優位)             │
  ├─────────────────┼────────────────────┼────────────────────┤
  │ ブラウザ互換性?  │  Wasm ✓            │  Wasm ✓            │
  │                 │  (ユニバーサル)      │  WebGPUはまだ新しい   │
  └─────────────────┴────────────────────┴────────────────────┘

実用的なユースケース

アプリケーション モデル種類 なぜWasmか?
スパム検出 ロジスティック回帰 微小モデル、即時予測
画像分類 MobileNet あらゆるデバイスで動作、オフライン対応
テキスト自動補完 小規模RNN 低レイテンシ、プライバシー
姿勢推定(カメラ) PoseNet リアルタイム、サーバーとのラウンドトリップなし
ドキュメントOCR CRNN 機密文書がローカルに留まる
音声キーワード検出 小規模CNN 常時オン、低消費電力
異常検知(IoT) オートエンコーダ エッジデバイス、通信不要

まとめ

Wasmはフル精度、オフライン対応、サーバーコストゼロでML推論をブラウザにもたらします。ONNXモデルにはtractを、Hugging Faceモデルにはcandleを使うか、Rustでカスタムテンソル演算を構築しましょう。Wasm MLは純JavaScriptの2-3倍高速で、GPUオーバーヘッドが割に合わない中小規模モデルに最適な選択肢です。大規模モデル(LLM、拡散モデル)の場合は、WasmとWebGPUを組み合わせてGPUアクセラレーションを活用しましょう。

試してみる