CAT-Translate-1.4bをONNXへ変換してWindows MLで使えるモデルを作る

初めに

日本語と英語の翻訳モデルである CAT-Translate-1.4b を、Windows上のC#アプリケーションから動かしたくなりました。

最終的には.NET 10とWindows MLを使う予定ですが、Windows MLから直接Safetensorsのモデルを読むことはできません。そこで、まずONNX Runtime GenAIで扱える形式へ変換します。

この記事では、Pythonをモデル変換と検証に限定して使い、uvで環境を管理します。

今回確認した結果は次のとおりです。

  • CAT-Translate-1.4bをONNX Runtime GenAI形式へ変換できた
  • FP32 CPU、INT4 CPU、FP16 DirectMLの3種類を作成できた
  • 変換後のモデルをPythonから検証できた
  • tokenizerは元モデルと変換後モデルで形式が異なり、追加対応が必要だった

開発環境

項目 バージョン / 構成
OS Windows
Python 3.13
Python環境 uv
PyTorch 2.13.0
Transformers 5.15.0
ONNX Runtime GenAI 0.15.2
Tokenizers 0.22.2
ONNX IR 1.0.0

依存関係はプロジェクトのpyproject.tomlで管理しています。

[project]
name = "cat-translate-cs-tools"
version = "0.1.0"
requires-python = ">=3.13,<3.14"
dependencies = [
    "huggingface-hub>=0.30",
    "onnx-ir>=1.0.0",
    "onnxruntime-genai>=0.11,<0.16",
    "tokenizers>=0.22.2",
    "torch>=2.13.0",
    "transformers>=5.15.0",
]

[tool.uv]
package = false

環境の作成と依存パッケージのインストールはuv syncで行います。

uv sync

ONNXへ変換する

Model Builderを使う

今回は、ONNX Runtime GenAIに含まれているModel Builderを使いました。

通常のONNXモデルでは、入力テンソルを渡して出力テンソルを受け取るだけで済む場合があります。しかし、CAT-Translate-1.4bのような生成モデルでは、KV cacheや生成ループなども必要です。

Model Builderを使うと、ONNXモデルだけではなく、ONNX Runtime GenAIが利用するgenai_config.jsonやtokenizer関連ファイルを含むモデルフォルダーを作成できます。

INT4 CPUモデルを作成する

まず、CPU向けのINT4モデルを作成します。

uv run python -m onnxruntime_genai.models.builder `
  -m cyberagent/CAT-Translate-1.4b `
  -o artifacts/cat-translate-1.4b-int4-cpu `
  -e cpu `
  -p int4 `
  -c artifacts/hf-cache

-mはHugging Faceのモデル名、-oは出力先、-eはExecution Provider、-pは重みの形式です。-cにはHugging Faceのキャッシュディレクトリを指定しています。

FP32 CPUモデルを作成する

品質確認用として、FP32のCPUモデルも作成しました。

uv run python -m onnxruntime_genai.models.builder `
  -m cyberagent/CAT-Translate-1.4b `
  -o artifacts/cat-translate-1.4b-fp32-cpu `
  -e cpu `
  -p fp32 `
  -c artifacts/hf-cache

FP16 DirectMLモデルを作成する

DirectMLでの実行候補として、FP16モデルも作成しました。

uv run python -m onnxruntime_genai.models.builder `
  -m cyberagent/CAT-Translate-1.4b `
  -o artifacts/cat-translate-1.4b-fp16-dml `
  -e dml `
  -p fp16 `
  -c artifacts/hf-cache

今回の変換では、以下の3種類のモデルフォルダーを作成できました。

出力先 実行対象 形式
artifacts/cat-translate-1.4b-fp32-cpu CPU FP32
artifacts/cat-translate-1.4b-int4-cpu CPU INT4
artifacts/cat-translate-1.4b-fp16-dml DirectML FP16

出力フォルダーには、主に次のようなファイルが含まれます。

cat-translate-1.4b-fp32-cpu/
├── genai_config.json
├── model.onnx
├── model.onnx.data
├── tokenizer.json
├── tokenizer.model
├── tokenizer_config.json
└── chat_template.jinja

実際のファイル分割は変換形式やModel Builderのバージョンによって異なります。C#から読み込むときは、少なくともgenai_config.json、ONNXモデル本体、tokenizer.modelが必要になります。

変換したモデルを検証する

まず、Pythonから変換後のモデルを読み込みます。

今回の検証スクリプトでは、生成されたtokenizer.jsonをtokenizers.Tokenizerで読み込み、入力IDを作ってからONNX Runtime GenAIへ渡しています。

uv run python scripts/validate_manual_tokenizer.py `
  --model artifacts/cat-translate-1.4b-int4-cpu `
  --source-language Japanese `
  --target-language English `
  --text "🐈はとてもかわいいの。おててがまるくてふわふわなの。"

プロンプトは次の形にしています。

<|user|>Translate the following Japanese text into English.

🐈はとてもかわいいの。おててがまるくてふわふわなの。</s><|assistant|>

この入力に対して、Python側では次のような出力を確認できました。

🐈 is very cute. Its hands are round and fluffy.

この段階では、ONNXへの変換と生成モデルとしてのロードは成功しています。

Transformersの非推奨警告に対応する

変換前の検証コードでは、次のような警告が表示されました。

[transformers] `torch_dtype` is deprecated! Use `dtype` instead!

現在のTransformersでは、pipelineなどへモデルのデータ型を渡す場合、torch_dtypeではなくdtypeを使います。

from transformers import pipeline
import torch

translator = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    dtype=torch.bfloat16,
)

古い引数を残したままにせず、最新のAPIに合わせてdtypeへ変更しました。

tokenizerで詰まったこと

元モデルと変換後モデルで形式が違う

CAT-Translate-1.4bの元モデルに含まれるtokenizerはSentencePiece Unigramです。

一方、Model Builderが出力したtokenizer.jsonはBPE形式になっていました。

tokenizer モデル形式
元モデルのtokenizer.json Unigram
Model Builder出力のtokenizer.json BPE

そのため、元モデルのtokenizer.jsonを変換後のフォルダーへコピーして、ONNX Runtime GenAIのTokenizerで読み込もうとするとエラーになります。

type must be object, but is array

これは、Unigramの語彙が配列形式で保存されているのに対し、ORT側がBPE形式のオブジェクトを期待しているためです。

C#では元のSentencePiece tokenizerを使う

この問題への対応は、記事2で詳しく説明します。方針としては、C#側で元のtokenizer.modelをMicrosoft.ML.Tokenizers.SentencePieceTokenizerに読み込ませます。

その後、C#側で次の処理を行います。

  1. CAT-Translateのプロンプトを組み立てる
  2. 元のSentencePiece tokenizerで入力IDへ変換する
  3. Generator.AppendTokensでONNX Runtime GenAIへ渡す
  4. SentencePiece tokenizerで生成結果をデコードする

つまり、ONNXモデルの生成部分はONNX Runtime GenAIで動かし、tokenizerだけは元モデルのものを使う構成です。

まとめ

CAT-Translate-1.4bをONNX Runtime GenAIのモデル形式へ変換できました。

今回作成したモデルは次の3種類です。

  • FP32 CPU
  • INT4 CPU
  • FP16 DirectML

ONNXモデルの変換自体は問題なく完了しました。一方で、tokenizerは元モデルがSentencePiece Unigram、変換後がBPEとなるため、C#側では元のtokenizer.modelを使う必要があります。

次の記事では、今回作成したモデルを.NET 10とWindows MLを使ってC#から実行します。

参考