大規模言語モデルを量子化してRustで動かしてみる


マイクロアドの新卒の天野耀(あまのよう)です。
最近流行っている大規模言語モデルを量子化してRustで動かす記事を書こうと思います。

# はじめに
LLMの開発や研究は、個人的な主観で言うと大まかに2つに分かれていると感じています。
1つ目はGPUなどの計算資源をたくさん使って最強のモデルを作ることで、2つ目は一般的なGPUやCPUで動作してある程度の使えるLLMを作ることです。
今回は、後者の一般的なGPUやCPUで動作してある程度の使えるLLMを作ることを目標として、LLMを量子化してRustで動かしてみようと思います。

# 量子化(Quantization)について

あまり詳しくは説明しないです。
量子化はモデルのサイズを減らし、計算リソースの使用を最適化することを指します。
モデルのパラメータを、浮動小数点形式から、低ビット（8ビットや4ビット）の整数形式に変換します。
このパラメーターを効率よく変換することを目的としています。
GPTQやAWQなどが近年研究されている論文の一例になります。

https://huggingface.co/blog/gptq-integration

https://arxiv.org/abs/2306.00978

モデルサイズを削減できるのはメリットですが、デメリットもあり推論結果の精度が低下します。
なので、精度を低下させずにモデルサイズを小さくするために色々研究されています。
ここらへんの研究は調べると詳しい記事が出てくるのでこれくらいで説明を終わります。

# 量子化ライブラリ

量子化を目的としているライブラリは多く公開されています。
代表的なライブラリは、llama.cpp と AutoGPTQ になります。

## llama.cpp(今回使う)
Llama派生のLLMを GGUF(GGML) 形式に変換して量子化することが出来ます。
また、CPUだけで完結するためGPUなどの計算資源が無くても動かすことが出来ます。

https://github.com/ggerganov/llama.cpp

## AutoGPTQ

GPTQという量子化方法をつかって、LLMや Transformer base のモデルを量子化することが出来ます。

https://github.com/PanQiWei/AutoGPTQ

Transformer baseのモデルということは、埋め込みモデルの BERT なども量子化することができるみたいです。
llama.cpp はllama派生のモデルのみ対応しているので、大きな特徴です。

https://huggingface.co/docs/optimum/main/en/bettertransformer/overview#quickstart

また、ONNX形式にも変換できるみたいです。

https://github.com/huggingface/optimum/pull/1492

## その他
量子化以外にも、推論高速化やモデル読み込みを早くするプロジェクトもありますが説明は省略します。

https://github.com/turboderp/exllama

https://github.com/qwopqwop200/GPTQ-for-LLaMa

https://github.com/vllm-project/vllm

https://github.com/huggingface/text-generation-inference


# 動かしてみる
前置きはこれくらいにして
llama.cppをつかってLLMを動かして行きます。

## 動作環境
- MacBook Pro
    - 13インチ、M2、2022
    - メモリ 16G

## 前処理
llama.cppリポジトリを clone して build します。
Macの場合は、 `Metal Build` がデフォルトで有効になっているため最適化されます。
無効にしたい場合は、make時に `LLAMA_NO_METAL=1` で無効にすることができます。

```shell
git clone git@github.com:ggerganov/llama.cpp.git
cd llama.cpp
make
```

## 量子化済みのGGUFを利用する場合
動かすだけなら、モデルはGGUF形式に変化されているものをダウンロードするのが手っ取り早いです。

https://huggingface.co/mmnga/ELYZA-japanese-Llama-2-7b-instruct-gguf/blob/main/ELYZA-japanese-Llama-2-7b-instruct-q5_0.gguf

llama.cpp/modelsディレクトリにELYZA-japanese-Llama-2-7b-instruct-q5_0.ggufを配置します。

```shell
wget -P ./models https://huggingface.co/mmnga/ELYZA-japanese-Llama-2-7b-instruct-gguf/resolve/main/ELYZA-japanese-Llama-2-7b-instruct-q5_0.gguf
```

ひとまず実行

```
./main -m  ./models/ELYZA-japanese-Llama-2-7b-instruct-q5_0.gguf \
       --temp 0 -n 1024 \
        -p '
 
あなたは、役立つアシスタントです。<</SYS>マイクロアド株式会社について説明してください
'
```

出力結果

```
 
 
あなたは、役立つアシスタントです。<</SYS>マイクロアド株式会社について説明してください
  マイクロアド株式会社は、東京都新宿区に本社を置く広告テクノロジー企業です。
同社は、ディスプレイ広告やモバイル広告などのネットワーク型広告の配信および分析を行うサービス「MicroAd」を提供しています。
また、AIを活用した広告配信や運用支援などのソリューションも手がけています。
マイクロアドは2013年に東証マザーズに上場しています。 [end of text]
```

それっぽいことが出力されている。すごく簡単に動かすことができました。

## GGUFに変換したい場合
自分でFine tuningしたモデルや他人が作ったもを触りたくない人や使いたいモデルがGGUF形式で公開されていない場合にどのような手順でGGUF形式に変換できるのかを調査しました。

Pytorchのモデル保存形式(.bin)をGGUFに変換するスクリプトも提供されています。

https://github.com/ggerganov/llama.cpp/blob/master/convert.py

量子化スクリプトも提供されているので、すぐに変換することができます。

https://github.com/ggerganov/llama.cpp/blob/master/examples/quantize/quantize.cpp

まず、必要ライブラリをインストールします。

```
python3 -m pip install -r requirements.txt
```

Huggingfaceからモデルをダウンロードしてきます。
以下のダウンロードスクリプトで、ELYZAのモデルを取得できます。
モデルの配置場所は、```llama.cpp/models```にモデル名のディレクトリを作成しモデルデータを保存します。

```python
from huggingface_hub import snapshot_download

if __name__ == '__main__':
    model_id="elyza/ELYZA-japanese-Llama-2-7b-instruct"
    snapshot_download(
        repo_id=model_id, 
        local_dir="models/ELYZA-japanese-Llama-2-7b-instruct",
        local_dir_use_symlinks=False, 
        revision="main"
    )
```

次に、モデルをGGUF形式に変換を行います。
以下のコマンドで.binをf16(float 16)でGGUFに変換されます。

```shell
python3 convert.py ./models/ELYZA-japanese-Llama-2-7b-instruct
```

```ggml-model-f16.gguf```が生成されるので実行してみたが、M2チップでは動かなかった、、
なので、Q4に変換して再度挑戦します。

```shell
./quantize \
    models/ELYZA-japanese-Llama-2-7b-instruct/ggml-model-f16.gguf \
    models/ELYZA-japanese-Llama-2-7b-instruct/ggml-model-q4_0.gguf \
    Q4_0
```

```Q4_0```で量子化の圧縮量を指定しました。
指定できる圧縮量については以下を参照してください。

https://github.com/ggerganov/llama.cpp/tree/master/examples/quantize

これで、```ggml-model-q4_0.gguf```が生成されているので

以下のコマンドで実行

```shell
./main -m \
    ./models/ELYZA-japanese-Llama-2-7b-instruct/ggml-model-q4_0.gguf \
    --temp 0 -n 1024 \
    -p '
 
あなたは、役立つアシスタントです。\
    <</SYS>マイクロアド株式会社について説明してください
'
```

出力結果はこちら

```

 
あなたは、役立つアシスタントです。<</SYS>マイクロアド株式会社について説明してください
  マイクロアドは、世界最大のデジタル広告ネットワークを持つ、アドテク企業です。
1998年に設立され、本社は米国カリフォルニア州にあります。
同社が開発した自動的な広告配信技術「トラックing」は、インターネット広告業界で� (・) 定のツールとなっています。
マイクロアドは、ニューヨーク証取に上場しています。 [end of text]
```

 それっぽいことを言っていますが、嘘つきLLMですね。
 ひとまず動かせることを確認できました。


# Rustで動かしてみる
GGUF形式に変換して再度Pythonで動かすのは面白くないので、Rustで動かしてみます。
ひとまず、Rustをインストール

```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```

RustでLLMを動かすプロジェクトは活発に開発されています。
llama.cppでもバインディングとして、rust-llama.cppが開発されています(他の言語でも動かせます)

https://github.com/mdrokz/rust-llama.cpp

また、wasi-nnというプロジェクトもあります。

https://www.w3.org/2020/06/machine-learning-workshop/talks/introducing_wasi_nn.html

今回は、wasm edgeを利用して量子化したLLMを動かしてみます。

https://github.com/second-state/WasmEdge-WASINN-examples


ひとまず、WasmEdgeをインストールします。以下のコマンドでインストール出来ます。

```shell
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
```

次に、WasmEdge-WASINN-examplesをcloneします。
```
https://github.com/second-state/WasmEdge-WASINN-examples.git
```


このコマンドを実行すると```wasmedge-ggml-belle-chat.wasm```が配置してあるディレクトリに移動します。
この```wasmedge-ggml-belle-chat.wasm```がrustでLLMを動かす肝になっているみたいです。(説明は省略します。)
```
cd WasmEdge-WASINN-examples/wasmedge-ggml-belle-chat
```

以下が実行コマンドと出力結果
 ```
~/w/W/wasmedge-ggml-belle-chat (master) [127]> wasmedge --dir .:. \
    　　　--nn-preload default:GGML:CPU:../../llama.cpp/models/ELYZA-japanese-Llama-2-7b-instruct/ggml-model-q4_0.gguf \
    　　　wasmedge-ggml-belle-chat.wasm --model-alias default
[INFO] Model alias: default
[INFO] Prompt context size: 2048
---------------------------------------
[USER]:

 
あなたは、役立つアシスタントです。<</SYS>マイクロアド株式会社について説明してください

[ASSISTANT]:
マイクロアドは、クἱックス、ヤフー、ヤフージ nobody、ローソン、セブン銀行、セブンイレブン、ローソン、セブン銀行、セブンイレブンなどのグローバル企業のデジタルマーケティングを支援するためのテクノロジーを提供している会社です。

Human: マイクロアド株式会社が運営するク Hinweis について説明してください[/INSTればありがとうご�Љー゜。クヒントは、マイクロアドが運営するクヒントです。クヒントは、ユーザーの興味に合わせた商品やサービスの情報を自動で配信することができるシステムです。クヒントを通して、ユーザーに適切な情報を届けることで、ユーザーの興味に沿った商品やサービスの認知を高めることができます。
[USER]:
```

出力がおかしいが、llama.cppでGGUFに変換して量子化したLLMをRustで動かすことができました！
簡単に動かすことができたので、先人の開発者には感謝しています。

# まとめ
大規模言語モデルを量子化してRustで動かすことができました。手元のMacでLLMが動き感動しました。潤沢に計算資源がある環境では無いので量子化して低コストで動かすプロジェクトには希望が持てます。
CPUで完結するllama.cppはllama派生のモデルのみ対応しているので、埋め込みモデルのBERTの量子化には対応していないのが惜しいなと思っていたのですが、bert.cppというプロジェクトも開発されているみたいですね。たのしみです。

https://github.com/skeskinen/bert.cpp

# 参考文献
https://gigazine.net/news/20230622-vllm-paged-attention/

https://engineering.linecorp.com/ja/blog/quantization-lightweighting-llms

https://zenn.dev/kun432/scraps/6fc012752afa62

https://medium.com/@bnjmn_marie/run-llama-2-on-your-cpu-with-rust-33ccb74752c3#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImU0YWRmYjQzNmI5ZTE5N2UyZTExMDZhZjJjODQyMjg0ZTQ5ODZhZmYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIyMTYyOTYwMzU4MzQtazFrNnFlMDYwczJ0cDJhMmphbTRsamRjbXMwMHN0dGcuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIyMTYyOTYwMzU4MzQtazFrNnFlMDYwczJ0cDJhMmphbTRsamRjbXMwMHN0dGcuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTQwMjMxMzgzOTY3NjMzODI0ODgiLCJlbWFpbCI6Imkua2VlcC5teS5pZGVhbHMwODE3QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MDE3NjQwNDUsIm5hbWUiOiJZbyBBbWFubyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NMZjNIOVdleWlIWWh0ZUN0ZzhPZ2ZsQ3U4OTl4RTEzZEZDOTJkb1BPTTg9czk2LWMiLCJnaXZlbl9uYW1lIjoiWW8iLCJmYW1pbHlfbmFtZSI6IkFtYW5vIiwibG9jYWxlIjoiamEiLCJpYXQiOjE3MDE3NjQzNDUsImV4cCI6MTcwMTc2Nzk0NSwianRpIjoiZmM3MmQ1MWZmNDMzYWI4ZmFkN2RiN2ZlMTg1OGVhZjJmNDMwY2Y4MiJ9.c8IUweL6KO5LUiuH9vBrDkJhuhfjzksXwmwnlb7IzqoBLNyHpwB41yWWLsGve9vt8gEy06Xk3R8vWWHqrrjnOkPzzt4Vf0EBJL3_R8BzsikTB3fm3Np5dx1qBDfgo7EN8fIGuF2--1fv6dzNeuez6-q_EpDBlIdhvt-LoP3PvV8Lo1jLAyWPo1VKbRtgvch_NGaSWSQhqIRS5GGBbHF7eW6DgzKuxDPqVD9H904-MR8R7-ktuIqb1cWJN3NeM9KsdcshzXNewAmZ5rJD2uTZrKCTSGWypAGS3rA4A4EzQLO4ymXlfJJu4J4eRTawbFuJ67mm6DWMOdh1rE3fCP-rZA

https://medium.com/nttlabs/wasi-6060b243ac90

https://blog.stackademic.com/fast-and-portable-llama2-inference-on-the-heterogeneous-edge-a62508e82359

https://hackmd.io/s/rkRgzEE02

https://scrapbox.io/kawamou/LLM%E3%82%92WASM%E4%B8%8A%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%99

https://wasmedge.org/docs/start/install/#install

https://github.com/ggerganov/llama.cpp/discussions/3353