About

ONNXはモデルではなく梱包規格だ — XenovaはBERTを作ったわけではない

誰が学習させたかと、どの箱に詰めて配るかは別の話

ONNXはモデルではない。計算の順番(グラフ)と重みを1つのファイルに入れる梱包規格だ。誰がモデルを学習させたかと、どんな梱包で配るかは別の話になる。これがわかると、Hugging Faceで「このモデルはブラウザで動く?」をリポジトリ名ではなくファイル一覧で判断できる。

きっかけ — 韓国語BERTが入れられなかった

BERT系のモデル6つをブラウザ(transformers.js)で動かす実験アプリを作った。使えたのは Xenova/... のようなONNX変換版だけだった。韓国語BERTの klue/bert-base は、Hugging Face APIで見ると .onnx ファイルが0個で、入れられなかった(2026-09-19確認)。

そこで出た質問が「事前学習とファインチューニングはXenovaがやったの?」だった。混乱するのも当然だ。リポジトリ名に、作った人と梱包した人が混ざっているのだから。

リポジトリ名を1つほどいてみる

Xenova/bert-base-multilingual-uncased-sentiment は4つの段階を経ている。

Who did what in Xenova/bert-base-multilingual-uncased-sentimentGooglepre-trainingWikipedia, 104 languagesNLP Townfine-tuningproduct review starsXenovaONNX exportquantize q8 / fp16transformers.jsrunWASM / WebGPUTraining: the numbers get decided herePackaging and running: same numbers

学習は最初の2段階で終わっている。Googleが104言語のWikipediaで事前学習し、NLP Townが商品レビューの星評価でファインチューニングした。Xenovaは同じ重みをONNXの箱に詰め替え、量子化(q8やfp16のように数値をより小さい形式に縮めること)した版も一緒に上げた。transformers.jsはそれをONNX Runtime(WASMまたはWebGPU)で動かす。

PyTorchのチェックポイントと比べると

PyTorchチェックポイント(.bin・.safetensors) ONNX(.onnx)
中身 重み(数値)だけ 計算グラフ + 重み
動かすには Python + モデルクラスのコード(transformersライブラリ) ONNX Runtimeさえあればいい
どこで動くか 主にPythonのサーバー ブラウザ(WASM・WebGPU)、C#、Java、モバイルなど
実際に当たった制約 — 変換時に指定した出力しか出ない。層ごとのhidden stateやattentionの値は外から見られなかった

最後の行は実際に痛かった。実験アプリで「文がエンコーダの中でどう変わるか」を見せたかったのに、ONNX変換版は最後の層の出力しか出さなかった。梱包するときに扉をいくつ付けるかが決まってしまうわけだ。

ファイル1つ1つも役割が分かれている。

ファイル 役割 どの段階の成果物か
tokenizer.json 語彙の一覧 事前学習データから作った語彙
config.json 層数・次元・ヘッド数 = 設計図の数字 モデルの設計
onnx/model_*.onnx 重み + 計算グラフ 事前学習(+ファインチューニング)の結果

実際に測った数字(2026-09-19、macOS・Chrome・WebGPU shader-f16 利用可)

Xenova/paraphrase-multilingual-MiniLM-L12-v2 のファイルサイズは、model_fp16.onnx が235MB、model_quantized.onnx が118MB、tokenizer.json が17MBだった。同じ重みなのに、梱包の仕方で倍違う。

このMiniLMで文書の断片372個を埋め込んでみた。WebGPU + fp16は初回ダウンロード込みで約21秒。WASM + q8はシングルスレッド(crossOriginIsolated=false)で、1分たっても25%ほどだった。最後までは測っていない。

ほかにも引っかかったところ。

npmパッケージが重い。 @huggingface/transformers 4.3.0をnpmで入れると、onnxruntime-node(node_modulesで289MB)と sharp が付いてくる。ブラウザでしか使わないので、CDN(jsDelivr)からバージョン固定でimportする形に変えた。

ファイルが名前付きでディスクに出てこない。 ブラウザは取得したファイルをCache Storageの transformers-cache に、リクエストURLをキーにして保存する。config.json のようなファイルを探しても見つからない。

だからモデルを選ぶときは

ブラウザで動かすモデルなら、まずリポジトリのFilesタブに onnx/ フォルダがあるかを見る。なければ自分で変換することになる。学習したところと梱包したところが違うことがあるので、ライセンスと学習データは元のリポジトリ(上の例なら nlptown/...)のモデルカードで確かめる。

モデルをブラウザで動かすかサーバーで動かすか、使いたいモデルに変換版がないときどうするかで詰まっているなら、negabaroに相談してもらって構わない。上の実験を実際に回した経験をもとに一緒に見られる。

キーコンセプト

1

リポジトリ名の前半は「梱包した人」かもしれない — 元のモデルはモデルカードで探す

2

ブラウザ用ならFilesタブでまず onnx/ フォルダを確認する

3

同じ重みでもfp16 / q8でサイズが倍違う — 速度・精度と引き換えにする

4

層の内側の値が必要なら変換時に出力を指定する — 既定の変換版には最後の層しかない

ユースケース

ブラウザで埋め込み・感情分析 — transformers.js + ONNX PythonなしでC#・Javaのサーバーでモデルを実行 — ONNX Runtime
← Jev — 文章を書かないLLM、判断だけを速く PyTorch・Hugging Face・Ollamaを同じ棚に並べるな — AIフレームワークは3層だ →