LLMの量子化とは

本記事は日本オラクルが運営する下記Meetupで発表予定の内容になります。発表までに今後、内容は予告なく変更される可能性があることをあらかじめご了承ください。

https://oracle-code-tokyo-dev.connpass.com/event/325752/

※本記事は下記記事の補足記事となります。

https://qiita.com/ksonoda/items/f3c703fb7b689fd65868

※ミートアップ実施時の動画は↓こちら。

<iframe width="560" height="315" src="https://www.youtube.com/embed/XQCKTrnxO88?si=o7FeZB6h_no65YbO" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# 量子化とは

「量子化」という技術は、深層学習に使われるニューラルネットワークの計算負荷やコンピューティングリソースを軽減する目的で利用されてきました。なので、LLMが流行りだす以前からあった技術になりますが、LLMのように、その他のモデルと比較して超大規模なニューラルネットワークとなると量子化の効果は絶大です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/a2f75366-d710-59ff-d9cf-cf72674a4869.png)

少し端折った説明になりますが、量子化とはニューラルネットワークの中で繰り返し行われる小数点の桁数を丸めて少なくすることにより演算負荷を下げるという処理です。

具体的には、32bitのモデルであれば少数第7位まで計算するところを、16bitに量子化することで少数第3位までの計算で済ませるというイメージです。その程度でそんなに大きな効果がだせるの？と思われるかもしれませんが、ニューラルネットワークの中では天文学的な回数の浮動小数点演算が行われるため桁数を少なくするだけで様々なメリットが生まれます。

| メリット                  | 説明                                                                                      |
|---------------------------|-------------------------------------------------------------------------------------------|
| モデルサイズの削減       | 量子化により、モデルのパラメータが少ないビット数で表現され、モデルのサイズが小さくなる。         |
| 計算速度の向上           | 低精度の計算を使用するため、推論処理が高速化し、リアルタイム応答性が改善される。                 |
| 省電力                   | 低精度の演算はエネルギー消費が少なく、大量のGPUを使った構成においてその電力量を下げることが期待できます。                     |
| コスト削減               | モデルサイズの縮小と計算の高速化により、インフラストラクチャのコスト削減が可能になる。          |
| エッジデバイスでの実行   | リソースが限られたエッジデバイスでもモデルを実行でき、IoTデバイスや組み込みシステムでの利用が現実的になる。|


特にモデルサイズの削減の効果は大きく、これによりメモリ使用量の削減が期待できます。恐らくほとんどのユーザーがこれを目的に量子化を行います。今となっては、GPUのメモリはコンピューティングリソースの中で一番高価なものになりますからそれを節約したいというのは当然の流れです。量子化によって高価なA100やH100にしか乗らなかったモデルが数基のA10にのせられるようになるわけですからこの効果は絶大です。

当然デメリットもあります。ニューラルネットワークの浮動小数点演算の桁数を少なくするということは本来の値とは異なる値を使うということですから最終的なモデルの精度に影響がでます。つまり、量子化のビット数を小さくするほど、メモリの消費量は少なくなり高速化するのですが、小さくするほど精度は落ちるということになります。

以前はLLMの量子化というと8bitまでというのが暗黙の了解だったように感じますが、最近は4bitでテストされる方もちらほら見かけます。また、同じ8bit量子化でも採用するモデルによっても精度は変わってきますので注意が必要です。

ビット数と精度の反比例についてはネットにベンチマーク結果や記事が沢山ありますのでそれらを参考にしつつ、是非ご自身のデータで精度検証を実施してみてください。


### 実装の概説
[こちらの記事](https://qiita.com/ksonoda/items/febf91746bfc6494fea6)でご紹介したOllama、HuggingFacePipeline(LangChain)ともに量子化モデルが扱えます。

- Ollamaの量子化 : Ollama自体では量子化処理はできませんので、量子化済のモデルを[こちらの記事](https://qiita.com/ksonoda/items/febf91746bfc6494fea6)でご紹介した方法でデプロイする方式になります。つまりOllamaの場合は量子化特有のデプロイ方式というものはありません。例えば、32bitのElyzaを16bitで使いたい場合は16bitに量子化済のElyzaが存在しない場合は量子化モデルを利用することができません。
- HuggingFacePipeline(LangChain)の量子化 : モデルをデプロイする際に引数でビット数を指定します。Ollamaとは異なり、32bitのモデルをデプロイする際に、16bitにしたいのか、8bitにしたいのかをコードの中で指定できます。

従って、以下は HuggingFacePipeline(LangChain)で量子化する方法をご紹介します。
具体的な実装コードとしては「量子化」という難解そうなキーワードからは想像できないほどシンプルなものになります。

実装のキーになるライブラリとしてBitsAndBytesが挙げられます。ディープラーニングのトレーニングや推論において、効率的なメモリ管理と計算の高速化を目的としたライブラリです。これがHuggingFacePipelineに統合されたことで実装コードがかなりシンプルになりました。

まずはライブラリのインストールです。


```python
!pip install -Uq langchain_huggingface transformers torch accelerate bitsandbytes
```

必要なクラスのimportです。

```python
import torch
from langchain_huggingface import HuggingFacePipeline
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
```

まずはモデルとトークナイザの定義をします。ここまではローカルLLMを実装する際のお決まりのコードです。

```python
model_name = "elyza/ELYZA-japanese-Llama-2-7b-instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
```

次に量子化のためBitsAndBytesの定義を行います。
今回は8bitを指定してみます。

```python
from transformers import BitsAndBytesConfig

quantization_config = BitsAndBytesConfig(
    load_in_8bit=True  # 8ビット量子化を指定
    #load_in_4bit=True
)
```

モデルをダウンロードし、デプロイします。ビット数は上記BitsAndBytesの定義で指定した8bitです。

```python
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto",      # 自動で適切なデータ型を選択
    quantization_config=quantization_config,  # 量子化設定を適用
    device_map="auto"        # 自動的に最適なデバイスに配置
)
```

量子化モデルのデプロイは以上、完了です。
メモリの使用率を確認すると以下の通り、約7.3GB(3271MiB + 4055MiB)となりました。
量子化しなかった場合は約13.3GBだった([こちらの記事](https://qiita.com/ksonoda/items/febf91746bfc6494fea6))ので、メモリの使用量が半分近くになっていることが分かります。


```bash
$ nvidia-smi
Thu Aug 22 17:26:02 2024       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.07              Driver Version: 550.90.07      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA A10                     Off |   00000000:00:04.0 Off |                    0 |
|  0%   33C    P0             57W /  150W |    3271MiB /  23028MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA A10                     Off |   00000000:00:05.0 Off |                    0 |
|  0%   32C    P0             55W /  150W |    4055MiB /  23028MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
+-----------------------------------------------------------------------------------------+
```

一応、推論までを実施しておきましょう。モデルのパラメータを指定します。こちらは量子化特有のパラメータはありません。

```python
# transformersのパイプラインを作成
pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    max_length=500,
    temperature=0.7,
    top_p=0.9,
    repetition_penalty=1.2,
    do_sample=True,
    truncation=True,
)
```

HuggingFacePipelineでここまでの定義をまとめます。

```python
llm = HuggingFacePipeline(pipeline=pipe)
```

プロンプトを定義します。

```python
prompt = "昔々、あるところに"
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True)
```

プロンプトに対するテキスト生成を実行します。

```python
output = llm.invoke(prompt)
print(output)
```

以下が出力結果です。(少し残念な出力結果ですがプロンプトを工夫すればもっとちゃんとしたテキストが出力されるはずです。)

```python
昔々、あるところに住んでいたおじいさま。
孫の顔が大好きな爺さまは、小学生の娘を連れて遠くへ出かけようと思っていました。その日、おじいさまは家の片付けを始めたものの・・・！
『ぬりえ』シリーズ第３作品目!
「やわらかアニメ」シリーズ第23弾としてDVD化された本作は、初回限定版に加え通常版も同時発売されている。特典としてキャラクター原画集、ブックレットが同梱されているほか、初回限定版にはピンナップ等のイラストを使用したオリジナルカードが1枚つ属する。また、Blu-ray Disc版も4月7日に発売される。
物語は現代、茅場町駅近くのビルで活動するファミリー向け施設「サザエ坂公園」の話。同館長を務める安西マモル（CV:野沢雅子）は新入社員の藤井一平 (CV:山崎和佳奈) に接客指名制度の導入方法を伝授する。
その後、市民会館の屋上に立地する水族館内に設置されたゴミ箱から不審者の存在を知ったマモルは警備員を配置し、館内放送で注意を促す。だが不審者は館外へ逃走してしまった…。
以下、関東地区の�
```

因みに推論時のメモリ消費量は以下の通り、8.6GB程度ということでデプロイ直後より1GBほど増えた様子です。

```bash
$ nvidia-smi
Thu Aug 22 17:29:51 2024       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.07              Driver Version: 550.90.07      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA A10                     Off |   00000000:00:04.0 Off |                    0 |
|  0%   48C    P0             61W /  150W |    3909MiB /  23028MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA A10                     Off |   00000000:00:05.0 Off |                    0 |
|  0%   49C    P0             60W /  150W |    4705MiB /  23028MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
+-----------------------------------------------------------------------------------------+
```


以上、ローカルLLMの実装について、OllamaとLangChainのHuggingFacePipelineの2パターンとモデルの量子化についてのご紹介でした。