Phi-3.5-mini-instruct のモデルを ONNX 形式に変換して動かしてみる

# 前提
## やりたかったこと
Phi-3.5-mini-instruct を ONNX で動かしてみたい！（あわよくば ONNX Runtime GPU とか ONNX Runtime DirectML でハードウェアアクセラレーションがかかったらどうなるか見てみたい）

## 現状
Hugging Face の Phi-3.5-mini-instruct の公式リポジトリには、今のところ safetensors 形式しかない…。→ (2024/09/18 追記) Microsoft から AWQ 量子化済みの公式な ONNX のモデルがでました！

:::note info
待ちに待った公式モデルがでました！こちらからダウンロードしましょう！
https://huggingface.co/microsoft/Phi-3.5-mini-instruct-onnx
:::

## 解決策
ONNX Runtime の Model Builder で、任意の Hugging Face のリポジトリにある safetensors 形式のモデルを ONNX 形式に変換して、ローカルで動作させることが出来ました！

https://onnxruntime.ai/docs/genai/howto/build-model.html

# 手順
## 前提条件
### Python のインストール
まずは Python を入れましょう！自分の手元の環境はいつ入れたか定かでない Python 3.12.5 でした。

https://www.python.org/downloads/

インストールが終わったらコマンドプロンプトで Python がちゃんと動くことを確認。

```cmd
python --version
```

### Hugging Face CLI のインストール
Model Builder 内部で Hugging Face CLI を使って、変換元のモデルをダウンロードしてきますので、インストールとログインが必要です。下記 URL の手順に従って、インストールとログイン作業を済ませましょう。また、Hugging Face のアカウントを持っていない人はこの作業を始める前に作りましょう。

https://huggingface.co/docs/huggingface_hub/guides/cli

以下のコマンドを CLI インストール後に入力すると、Hugging Face のポータルで生成できるトークンを入力せよ、と言われるので、ここも指示通りにやります。

```cmd
huggingface-cli login
```

`Add token as git credential? (Y/n)` という選択肢が出てくるはずなので、そこは `y` で進めましょう。

`Login successful` の出力がでたら、Hugging Face CLI の準備は完了です。

## 作業用ディレクトリと venv を作る。ついでに必要なパッケージも入れる。
わかりやすいところどこでもいいので、ディレクトリを一つ掘って、その下で作業を進めます。例えば `D:\modelBuild` とかでもいい気がします。

```cmd
python -m venv .venv
.venv\Scripts\Activate
pip install torch transformers onnx onnxruntime
pip install --pre onnxruntime-genai
```

## Model Builder の動作確認
以下のコマンドを打って、ヘルプが読めることを確認しましょう。
Usage がでてきたら、とりあえずインストールは終わっているはずです。

```cmd
python -m onnxruntime_genai.models.builder --help
usage: builder.py [-h] [-m MODEL_NAME] [-i INPUT] -o OUTPUT -p {int4,fp16,fp32} -e {cpu,cuda,dml,web} [-c CACHE_DIR]
                  [--extra_options KEY=VALUE [KEY=VALUE ...]]

options:
  -h, --help            show this help message and exit
  <以下略>
  ```

## いよいよお目当てのモデルを変換！
以下のコマンドで、Phi-3.5-mini-instruct のモデルを CPU だけで動作する ONNX のモデルに変換可能です。
ついでなので int 4bit で量子化もかけておきます。（この辺はお好み）

```cmd
 python -m onnxruntime_genai.models.builder -m "microsoft/Phi-3.5-mini-instruct" -o Phi-3.5-mini-Instruct-onnx-cpu-int4 -p int4 -e cpu -c cachedir
```
例えば CUDA 対応のモデルを作るならこんな感じです。オプションの多少の違いでしかないので、あとは Help を読んでお好みの設定にしましょう。

```cmd
python -m onnxruntime_genai.models.builder -m "microsoft/Phi-3.5-mini-instruct" -o Phi-3.5-mini-Instruct-onnx-cuda-int4 -p int4 -e cuda -c cachedir
```

DirectML 対応のモデルにするならこんな感じ。

```cmd
python -m onnxruntime_genai.models.builder -m "microsoft/Phi-3.5-mini-instruct" -o Phi-3.5-mini-Instruct-onnx-dml-int4 -p int4 -e dml -c cachedir
```

いずれのコマンドでも、完走までにしばらくかかりますので待ちましょう！

## ONNX モデルとご対面
ここまで済ませると、作業用ディレクトリの下に、先ほどのコマンドの `-o` オプションで指定したディレクトリ内に ONNX ファイルが生成されているはずです。これを、利用したいアクセラレーション方式に対応する ONNX Runtime を組み込んだアプリにロードすれば OK です！

## SLM で Completion を試す簡単なサンプル（日本語解説付き）
ONNX 形式の Phi-3 モデルをロードして Completion を試す簡単なサンプルは、同僚の物江さんが作ったものに日本語解説までついているので、こちらをご参照ください！サンプルの解説では Phi-3 の話をしていますが、Phi-3.5 でも同様の手順でモデルだけ差し替えれば動きました。

https://github.com/osamum/phi3_TinyChatApp-C-

このサンプルコードを使って、GeForce RTX 4080 Mobile な環境で動かしてみたときの出力例はこんな感じです。

https://www.youtube.com/watch?v=0iIPFejpjBM

# まとめ
ONNX 形式のモデルが提供されてなくたって、変換できそうだってことがわかったのはかなり嬉しい。

# 補足
## 今回変換したモデルについて
int 4bit 量子化はかけているものの、それ以外はツールのなすまま特に最適化などかけずに変換しているのみとなります。したがって今回作ったモデルでベストパフォーマンスとなるかどうかは保証の限りではありませんので、その点について了承の上、参考にしてください。

:::note info
同僚より Microsoft が提供している Phi3 の ONNX モデルについては、AWQ を用いて量子化されているとの情報を得ました。もし Phi3 Microsoft 公式モデルと同等の最適化をご要望であれば、量子化を別途 AWQ で行ってから ONNX に変換することが出来れば良いかもしれません。
https://arxiv.org/abs/2306.00978
:::

## 今回うまくいかなかったこと
モデル変換コマンドで DirectML のモデルに変換するコマンドを紹介しなかったところでお気づきの方もいらっしゃるかもしれませんが、今回 DirectML 対応の形で変換したモデルをサンプルアプリに読み込ませても、推論時にエラーになってしまいました。CUDA 版が動いたのでそれ以上深追いはしていない状況なのですが、今後の課題とします。

:::note info
2024/08/31 追記
その後、別の DirectML を使うアプリにモデルを食わせたら動きました。当初使っていたサンプルの実装の問題だったようです。なので、変換コマンドの例として DirectML 用の ONNX に変換するものも追記しました。
:::

## Model Builder を動作させるときにちょっと引っ掛かったエラーについて
Model Builder を走らせるときに Numpy のバージョンが新しすぎて対応していないようでエラーとなりました。（ちょっとエラーをメモし忘れてしまったのですが、Numpy 1.x 系デコンパイルされたモジュールがあるから、Ver.2 以上のものはダメよ、って感じだったと記憶しています。）

んで、以下のコマンドで無理やり Numpy をダウングレードして対応しました。

```cmd
pip install numpy=1.26.4
```