ONNX Runtime Webの遅さを推測で直さない|CPU・WebGPUプロファイル入門

どこが遅い? ONNX Runtime Webの計測で原因を切り分ける ブラウザAI・性能改善
カテゴリー
ブラウザAI・性能改善
公開日
2026.09.09

はじめに

ブラウザでAIモデルを動かしたとき、「WebGPUを使えば速いはず」と設定だけを変えても原因は分かりません。遅い場所は、モデルのダウンロード、セッション作成、最初のシェーダーコンパイル、各演算、CPUとGPUのデータ転送のどこかにあります。

この記事では、ONNX Runtime Webの診断機能を有効にし、どの段階が遅いかを切り分ける最小ページを作ります。対象はJavaScriptの基本が分かり、ONNXモデルを一度動かしたことがある人です。

完成物と測る区間

次の4区間を別々に表示します。

  1. モデル取得
  2. 推論セッション作成
  3. 初回推論
  4. 2回目以降10回の平均

プロファイルは計測の詳細を得るための機能であり、実際の利用者向け画面では通常無効にします。

必要環境

mkdir ort-profile-demo
cd ort-profile-demo
npm init -y
npm install onnxruntime-web@1.27.0
npm install --save-dev vite

package.jsonscripts"dev": "vite"を追加します。今回は公式リポジトリの小さなmul_1.onnxを使います。これは入力値を2乗するテストモデルで、大規模AIの速度を代表するものではありません。

計測コード

main.jsを作ります。

import * as ort from "onnxruntime-web/all";

ort.env.wasm.wasmPaths =
  "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.27.0/dist/";

const MODEL_URL =
  "https://raw.githubusercontent.com/microsoft/onnxruntime/main/onnxruntime/test/testdata/mul_1.onnx";

function input() {
  return new ort.Tensor(
    "float32",
    Float32Array.from([1, 2, 3, 4, 5, 6]),
    [3, 2],
  );
}

async function measure(provider) {
  const fetchStart = performance.now();
  const response = await fetch(MODEL_URL);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const model = new Uint8Array(await response.arrayBuffer());
  const fetchMs = performance.now() - fetchStart;

  const createStart = performance.now();
  const session = await ort.InferenceSession.create(model, {
    executionProviders: [provider],
    enableProfiling: true,
  });
  const createMs = performance.now() - createStart;

  const firstStart = performance.now();
  const first = await session.run({ X: input() });
  const firstMs = performance.now() - firstStart;

  const steadyStart = performance.now();
  for (let i = 0; i < 10; i += 1) await session.run({ X: input() });
  const steadyMs = (performance.now() - steadyStart) / 10;

  session.endProfiling();
  await session.release();

  return {
    provider,
    output: Array.from(first.Y.data),
    fetchMs,
    createMs,
    firstMs,
    steadyMs,
  };
}

measure("wasm").then(console.table).catch(console.error);

index.htmlからmain.jstype="module"で読み、npm run devで開きます。コンソールには区間別の時間とCPUプロファイルが出ます。

WebGPUの演算時間を受け取る

WebGPUではセッション作成前にコールバックを設定します。

ort.env.webgpu.profiling = {
  mode: "default",
  ondata(data) {
    console.table(data);
  },
};

measure("webgpu").then(console.table).catch(console.error);

performance.now()はJavaScript側から見た待ち時間です。WebGPUプロファイルはGPU側の演算を調べる手掛かりになります。同じ種類の数値として単純に足し合わせないでください。

Performanceパネルと結び付ける

ort.env.trace = true;

Traceを有効にするとconsole.timeStamp()を使った印が記録されます。Chrome DevToolsのPerformanceパネルで記録し、UIの長い停止と推論処理が重なるかを確認します。診断時だけort.env.logLevel = "verbose"ort.env.debug = trueを使い、本番では戻します。

結果の読み方

  • 取得だけが遅い:モデル圧縮、CDN、キャッシュ、分割配信を検討
  • セッション作成が遅い:モデル形式、演算子、初期化タイミングを確認
  • 初回だけ遅い:ウォームアップやシェーダーコンパイルの影響を確認
  • 毎回遅い:演算、テンソル形状、EP、CPU・GPU転送を確認
  • UIが固まる:WASMのProxy WorkerやWorker分離を検討

小さなモデルではGPUへ渡す準備の方が重く、WASMが速い場合もあります。EP名だけで性能順位を決めません。

動作確認とエラー確認

正常時は出力が[1,4,9,16,25,36]になり、4区間の有限な時間が表示されます。次も確認します。

  • オフラインではHTTPエラーを表示する
  • WebGPU非対応端末では失敗を握りつぶさない
  • endProfiling()を推論終了後に呼ぶ
  • 診断後にrelease()する
  • 同じモデル、入力、回数、端末で比較する

プロファイリング自体にも負荷があります。記事へ性能値を載せるなら、診断機能を外した測定も別に行い、OS、ブラウザ、CPU、GPU、電源設定を記録します。

トラブル対処

  • 「Got invalid dimensions」:このテストモデルは入力Xへ形状[3, 2]の6値を渡します。
  • WASM取得失敗:wasmPathsとnpmパッケージの版、CSP、配信URLを確認します。
  • WebGPUだけ失敗:WASMで正常系を確かめ、対応演算子とアダプター取得を確認します。

まとめ

性能改善は「WebGPUに変える」ことからではなく、時間を区間に分けることから始めます。ONNX Runtime WebのCPU/WebGPUプロファイルとTraceを組み合わせれば、ネットワーク、初期化、初回、定常処理を混同せずに調査できます。

参考リンク

タイトルとURLをコピーしました