LLM量子化してみた

# 背景と概要
13Bモデルを家で動かしてみたがキツかったので軽量化したかった。
いいスペックのPCだけど数分たってハングしちゃったので13Bはきついみたい。（ものによってはギリギリ戦えるという言説も見る。）
今回は量子化によって必要なメモリ数と計算時間を短縮しようという試み。

理論よりHow toを書くやってみた系記事です。
そもそも理論詳しくないので。

【参考】PCスペック 
CPU: i9 13900K
GPU: RTX4090
Mem: DDR5 128GB


# 具体的な話
## 量子化の概要
ニューラルネットワークで扱われる情報の重要度を表す要素として重みがある。
この重みが16bitとかの精度で表現されてるのでそれを4bitに荒くすれば情報量と計算量を減らすことができる。
というアイデア。

## 量子化すると何がいいか
* モデルのデータ量が小さくなる
* → モデルロードに必要なメモリも小さくて済む
* メモリバス消費が小さくなる
* 計算量が少なくて済む 
    * → 計算時間短縮
    * → プロセッサのアーキテクチャによってビット数の向き不向きはあるかも

## 具体的なやり方
ライブラリや手法はいくつかありますが、今回やった方法を書きます。

### 使ったもの
* モデル Elyza (https://huggingface.co/elyza/ELYZA-japanese-Llama-2-13b-fast-instruct/tree/main)
* transformers (Huggingfaceが公開しているML統合ライブラリ)
* Google Colab (GPU: A100)
    * 100円ちょっと（課金最低単位が1000円ちょっと。そのうち消費分）

### 手順
#### 1. Google Colabに課金
腹立たしいことだが、RTX4090ではProgressを見る限り数日かかりそうなのでGoogleに課金する。
コンピューティングユニットというものを購入して、それを消費することで強いGPUが使えるという仕組み。
最低が100ユニットで1000円ぐらいで買える。

#### 2. Google ColabでA100をアタッチ
A100をアタッチしようとしても「無理だったのでT4にしますね～ｗ」って言われる。
（通称Google Colab GPUガチャ）
めげずにA100を連打。
間違えてしょぼいGPUで処理を開始してしまうと、気づいたタイミングで処理を中断して最初からやり直しになるのでコンピューティングユニットを無駄に消費してしまうので注意。

#### 3. 量子化実行
大体10コンピューティングユニットを消費した。
つまり100円ほどでできた計算。
```py
from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig
model_id = "elyza/ELYZA-japanese-Llama-2-13b-fast-instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id)
gptq_config = GPTQConfig(bits=4, dataset = "c4", tokenizer=tokenizer, group_size=64)
model = AutoModelForCausalLM.from_pretrained(model_id, device_map="cuda", quantization_config=gptq_config)

tokenizer.save_pretrained("./quantized")
model.save_pretrained("./quantized")
```
足りないライブラリがあれば適宜インスコ。

#### 4. GPUでタッチ
A100はコンピューティングユニットを大量に消費するので忘れずデタッチする。

#### 5. 完成したモデルをDLして動かしてみる
```py
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

B_INST, E_INST = "
", "
"
B_SYS, E_SYS = "
\n", "\n
\n\n"
DEFAULT_SYSTEM_PROMPT = "あなたは誠実で優秀な日本人のアシスタントです。"
text = "仕事の熱意を取り戻すためのアイデアを5つ挙げてください。"

model_name = "./"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    use_cache=True,
    device_map="auto"
)
model.eval()

prompt = "{bos_token}{b_inst} {system}{prompt} {e_inst} ".format(
    bos_token=tokenizer.bos_token,
    b_inst=B_INST,
    system=f"{B_SYS}{DEFAULT_SYSTEM_PROMPT}{E_SYS}",
    prompt=text,
    e_inst=E_INST,
)
token_ids = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt")

with torch.no_grad():
    output_ids = model.generate(
        token_ids.to(model.device),
        max_new_tokens=256,
        pad_token_id=tokenizer.pad_token_id,
        eos_token_id=tokenizer.eos_token_id,
    )
output = tokenizer.decode(output_ids.tolist()[0][token_ids.size(1) :], skip_special_tokens=True)
print(output)
```

出力
```
仕事の熱意を取り戻すためのアイデアを5つご紹介します。

1. 目的と目標を見直す: 現在の仕事に目的や目標が共有されていない可能性があります。目標や目的が明確になれば、仕事に対する熱意が再燃する可能性があります。
2. 環境を変える: 環境を変えることで、仕事に対する熱意を取り戻す可能性があります。環境を変えることで、新たな刺激を受けることができる可能性があります。
3. 目標を分割する: 大きな目標を分割して、短期的な目標を設定します。短期的な目標を達成することで、仕事に対する熱意を取り戻す可能性があります。
4. 休憩する: 仕事に熱中しすぎて、疲れてしまっている可能性があります。休憩することで、リフレッシュし、仕事に熱意を注ぐことができる可能性があります。
5. 目的とするものを見直す: 現在の仕事に納得がいっていない可能性があります。目的とするものを見直すことで、仕事に対する熱意を取り戻す可能性があります。

これらのアイデアを試しても、仕事に熱意が戻らない場合は、転職を考えるのも良いでしょう。
```

#### 6. 評価
モデルロード: 2秒
出力: 5秒
→ 時間面ではかなり実用的になった。

上記の例だと劣化してないっぽいが、Elyza tasks100だとアホになってるのがわかる。
Elyza tasks100
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3776243/d049cf00-18a3-f74b-7e77-8c575eac5eee.png)


### 今後やりたいこと
計算量で見ればRTX4090はA100にメタクソに負けてるという訳でもないのでコンピューティングリソースを効率よく使えればRTX4090でも寝てる間に処理しきれるんじゃないか説を突き詰めたい。
A100使えば1時間で処理できるんだからRTX4090使って5時間ぐらいで処理できてほしい。

RTX4090: TF32 82.6/165.2 TFLOPS
A100: TF32 156/312 TFLOPS
ref: 
https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/a100/pdf/nvidia-a100-datasheet-us-nvidia-1758950-r4-web.pdf
https://images.nvidia.com/aem-dam/Solutions/Data-Center/l4/nvidia-ada-gpu-architecture-whitepaper-v2.1.pdf

英語データセット使ったけど、これで精度下がりそうな気がする。
日本語データセットを使った方が精度高い説を検証したい。