← レッスン一覧に戻る レッスン 41 / 48
上級 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アクセラレーションを活用しましょう。