【LLMの量子化】phi-3-mini-4kを軽量化してCPUで動かす

# はじめに
こんにちは！ザワッチです！
今回は、巷で話題のローカルLLM「phi-3-mini-4k」をGPUではなく、CPUで動かそうと思います。

<details><summary>phi-3について</summary>
phi-3はMicrosoft社が開発しているローカルLLMで、高品質なデータセットが学習しているがために、少ないパラメータ数でもパフォーマンスを発揮しており、とても注目されています。

最近では、ファミリーモデルとして画像入力にも対応しているphi-3-visionやコンテキストが128kに対応している&より小さいパラメータ数のphi-3-mini-128kなど進化が目覚ましいです。

phi-3-mini-128kモデルはiPhone15での動作も確認されているようで、エンド2エンドでの推論が既に実現されています。


最近になって、phi-3.5-miniも出てきています。

</details>

phi-3の詳細情報は以下のリンクをご確認ください。

https://news.microsoft.com/ja-jp/2024/04/24/240424-the-phi-3-small-language-models-with-big-potential/

とはいえ、ただ動かすだけなら簡単なので、今回は2つの推論効率化手法を用いて、ノーマルなphi-3-mini-4kと推論効率化されたphi-3-mini-4kとで比較し、性能評価することを目的とします。


## 推論効率化手法
# ①phi-3-mini-4kモデルの量子化
そもそもLLMの量子化とは、重みなどのパラメータをより少ないビットで表現し、モデルの軽量化、高速化を図る手法です。

その背景には、世間で広く利用されているLLMは数憶～数百億のパラメータを持っており、動かすだけでも一定水準以上のハードウェア要件が必要になります。

いい感じでまとめると、数値で表現されているパラメータの表現（情報量）を量子化によって、乏しくすることでモデル自体が持つデータ量が小さくなり、結果、モデル自体も軽くなるという仕組みです。

しかしながら、量子化によって失われる情報量を無視することはできず、少ないビット数で表現しすぎると、とんちんかんな回答が返ってくることもあるため、量子化のバランスが大事になります。

量子化モデルは、Microsoft社がHuggingFaceに公開しているモデルを拝借しました。

https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-gguf

:::note info
.GGUFはLLMをllama.cppで動かすためのファイルフォーマットになります。
ちなみにllama.cppは.ggufファイルに変換されたLLMをC,C++を用いて動かすプロジェクトのようです。

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

ノーマルモデルはfp16（16ビット浮動小数点）で表現されており、量子化モデルは、4bitで表現されているようです。

そして、容量は2.2GB！軽い！

# ②KVキャッシュの量子化
こちらでも量子化が出てきていますが、量子化する対象が異なります。

LLMが次に来るであろう単語（トークン）を予測するときに、KeyとValueが必要になるのですが、今までに生成した単語からの情報も必要になり、それらはKeyとValueに該当します。

次の単語を予測するためには、これまでのkeyとValueの行列演算結果と新たにKeyとQueryを用いて、Valueを出す必要があり、いちいち前のKeyとValueの演算を行うことは冗長ということで、キャッシュとして保持し、それらを量子化するという試みです。

詳しくは、以下のサイトをご覧ください。

https://huggingface.co/blog/kv-cache-quantization


llama.cppでValueの量子化を行うためには、flash attentionが必要で、それはまだCPUには対応していないため、Valueの量子化ができませんでした。（2024/09/04現在）

```text
V cache quantization requires flash_attn
```
そのため、Keyのみ量子化を行います。


## ハードウェア仕様
- 機種
　Lenovo ThinkPad T14 Gen 4 (Intel® Core™)
- OS
　Microsoft Windows 11
- CPU
  Intel(R) Core(TM) i5-1335U
- メモリ
　16GB
 
## 実行環境
- 言語
　Python 3.12
- IDE
　VSCode
- その他
　venvで仮想環境を作成し、必要なライブラリをインストールし、実行
 
# 性能評価指標
phi-3-mini-4k-instruct-fp16モデルと2つの推論効率化手法を施したphi-3-mini-4k-instruct-q4モデルで性能評価を行います。

性能評価指標は、以下の５つの項目で設定しました。


## ①生成内容
各プロンプトに対して生成されたテキストを記録します（クオリティ評価用）。

定性評価指標という位置づけになります。

## ②生成速度
各プロンプトの生成にかかった時間を測定します。

Pythonの標準ライブラリであるtimeを使用します。

※トークンの生成速度ではなく、推論が終わるまでの時間とします。

## ③CPU使用率
各プロンプトで生成中に消費したCPUリソースの平均値を計算します。

Pythonライブラリであるpsutilを使用します。

## ④メモリ使用率
生成時のメモリの使用量の増加分（開始前と終了後の差）の平均値を計算します。

Pythonライブラリであるpsutilを使用します。

## ⑤エネルギー消費量（電力効率）
一回の推論でどれだけの電気を消費しているかという観点も重要だと考え、エネルギー消費量という指標を設けました。

厳密に算出することはできませんが、以下のような式を用いて代替アプローチを取りました。

```
消費電力量（ワット時） = CPU消費率 × 実行時間（秒） × システムのTDP（熱電力設計）
```
- CPU消費率
②で計算したCPU使用率
- 実行時間
①で計算した推論時間
- システムのTDP
55W

# 性能比較
用意したプロンプトで実際に推論を行い、上記で作成した評価指標に基づいて、両者のモデルの性能比較を行います。

## ①生成内容
汎用的な能力を見たいと考え、異なるタスクに対応する5つのプロンプトを作成しました。

生成内容を比較し、総合的にどちらが優れているか評価します。

以下が、各プロンプトを与えたときの量子化モデルとノーマルモデルの生成内容になります。

### プロンプト①：文章生成（クリエイティブタスク）

| プロンプト | 量子化モデル | ノーマルモデル |
|-----------|------------|------------|
| 次の文章の続きを100文字以内で書いてください：『夜空には無数の星が輝いていたが、その中で一際明るい星があった。その星は…』      | その星は、不思議な力を宿していた。隣接する星々の光を操る霊が、孤独な地球に祈りを叫んだ。      | その星は幼い星であり、遠く離れた星雲の中心に位置していた。彼の光は地球に届くまで徐々に落ち着き、人々の心を温かく揺さぶるのであった。|

前提として、私自身クリエイティブタスクを評価する能力があるのかは微妙なのですが、
直感的には、ノーマルモデルの方がいいと感じました。

### プロンプト②：質問応答（事実ベースの質問）

| プロンプト | 量子化モデル | ノーマルモデル |
|-----------|------------|------------|
| イギリスの産業革命が始まったのは何年ですか？また、その影響について簡単に説明してください。     | イギリスの産業革命は、1760年から1830年または1850年にかけて始まったと言われています。この革命は、特に工業化の推進とともに、従来の手作業の農業や手工業から工業製造への移行を担当しました。産業革命は、特に蒸着機、旋椅機、蒸溜機などの発明や工場生産の推進により、効率的な生産方法として生まれました。この時期、イギリスは工業国に発展する基盤を築いたのです。例えば、シャープ専用の火山熱発電装置の開発は、産業の発展を加速させました。また、インドイギリスでの工場や工場船の増加は、資源の活用と国際貿易を促進し、経済成長に大きく寄与しました。産業革命は、経済の近代化を進め、国力を台頭に置くきっかけを作りましたが、同時に都市化や社会的格差の問題も生じました。      |      イギリスの産業革命は通常18世紀後半、特に1760年を含めた1780年代に始まっています。この時期、産業革命の基盤が確立されました。18世紀後半のイギリスでは、鉄道の普及、船舶技術の進歩、産業技術の革新、そして工業化が進みました。これにより、特に工業業務が効率化され、多くの職人の生活が改善されたと言えます18世紀後半には、産業革命の運動家たち、例えばジョン・ジョブズやジェームズ・ワイルドのような人物が活動しています。ジョブズは、19世紀後半にアップデートしたピクニックゲーム「タイムトラベル」の開発によって、電子機器業界をリードしました。ワイルドは、「マーケットベースの経済」の理論を提唱し、イギリスの経済発展に大きな影響を与えました。産業革命はイギリスを世界的な経済大国へと導いたとされ、特に鉄道や産業機械の発展は国内経済と社会構造に大きな変化をもたらしました。結果として、イギリスは産業力を持つ国として他国に強い影響力を持つようになりました。    |

まず、どちらも嘘をついてます。

量子化モデルの方では、「シャープ専用の火山熱発電装置の開発」、
ノーマルモデルの方では、「ジョン・ジョブズやジェームズ・ワイルドのような人物...ピクニックゲーム「タイムトラベル」と意味不明なことを言っていますが、プロンプトチューニング次第で何とかなりそうな気もします。

内容の正誤とまとまり具合からみて、量子化モデルに軍配が上がります。


### プロンプト③：要約
| プロンプト | 量子化モデル | ノーマルモデル |
|-----------|------------|------------|
| 以下のテキストを50文字以内で要約してください：『AI技術の進化により、さまざまな分野で自動化が進んでいます。特に、医療や金融などの分野での活用が期待されており、今後もその発展が注目されています。      | AI技術進化自動化医金融分野で活用予想。      | AI進化自動化医療金融分野発展注目。       |

要約対象文章があまりにも短すぎたので、評価対象外とします。

### プロンプト④：翻訳（多言語対応）
| プロンプト | 量子化モデル | ノーマルモデル |
|-----------|------------|------------|
| この日本語の文章を英語に翻訳してください：『未来の技術がどのように私たちの生活を変えるかは、まだ未知数です。』    | The way future technologies will change our lives is still unknown.     | The way future technology will change our lives is still unknown.       |

どちらもきちんと翻訳できています。

ほぼ変わらないですが、technologiesを複数形にしている量子化モデルに軍配が上がります。

### プロンプト⑤： コード生成（プログラミングタスク）

| プロンプト |
|-----------|
|Pythonでリスト内の奇数をすべて2倍にする関数を作成してください。    | 

#### 量子化モデル
```python
def double_odd_numbers(numbers):
    return [number * 2 for number in numbers if number % 2 != 0]
# 使用例
example_list = [1, 2, 3, 4, 5, 6, 7]
result = double_odd_numbers(example_list)
print(result)  
# 出力: [2, 6, 10, 14]
```

この関数は、与えられたリスト内の全ての奇数をその値に2倍した新しいリストを返します。`filter` 機能を使用する場合も考慮することができますが、リスト内包表記は簡潔に記述できるため、この場合選択されました。


#### ノーマルモデル

```python
def double_odd_numbers(input_list):
    return [number * 2 for number in input_list if number % 2 != 0]
    
# 使用例
example_list = [1, 2, 3, 4, 5, 6, 7]
result = double_odd_numbers(example_list)
print(result)  
# 出力: [2, 6, 10, 14]
```

この関数は、リスト内のすべての奇数を2倍にします。`[number * 2 for number in input_list if number % 2 != 0]`のリスト内包表記を使用して、条件に合う奇数のみを2倍し、結果を新しいリストとして返しています。


どちらとも同じコードを生成していて、しっかりと要件を満たしています。

異なるやり方も提示してかつ、正しい内容を生成している量子化モデルに軍配が上がります。


## ②生成速度
当然、量子化モデルの方は計算量が少なくなっているので、生成速度も速いのは一目瞭然です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/366b2bc5-cdc6-1dfb-c2ea-c08f1f4fda0e.png)

### プロンプトごとの生成速度
量子化モデルはノーマルモデルの約3分の1ほどの速さで生成していることがわかります。

プロンプト④の翻訳タスクに関しては、量子化モデルは6.29秒でかつ正しい内容で生成しています！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/4f9dab9c-79e4-4580-3e70-25bb300f3470.png)

## ③CPU使用率
量子化モデルの方は若干CPU使用率が高くなりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/8294be28-a385-6b88-4699-6d4cb2f81a0a.png)

### プロンプトごとのCPU使用率
プロンプトごとに見てみると、量子化モデルの方がCPU使用率が低くなる場合もありました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/27210df1-3ab6-d94a-cb05-d21365635c8c.png)

要因として、
- 低ビット演算に対するハードウェア的な最適化不足
- 推論時の再計算やデコード処理のオーバーヘッド

などが考えられ、もっと詰めていきたいですね。

## ④メモリ使用率
K(V)キャッシュ・モデルの量子化が効いていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/069abf81-293b-35ae-b3ca-0fb890b44164.png)

### プロンプトごとのメモリ使用率
ノーマルモデルの約3分の1のメモリ使用量であり、安定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/5e3eb1d7-cd02-e22e-167f-8ece862a0e5b.png)


## ⑤エネルギー消費量（電力効率）
量子化モデルのCPU消費率はノーマルモデルの方に比べ、約5%低い代わりに、生成速度が約3分の1であるため、結果エネルギー消費量も3分の1になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/b9acce85-333d-d80d-b8ce-226d4ee34829.png)

### プロンプトごとのエネルギー消費量
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/81482b36-34ee-d447-5632-562f093c1ba7.png)


# さいごに
高度経済成長期には一家に１台、テレビ・冷蔵庫・洗濯機と呼ばれた３種の神器がありましたが、LLMもこれからの世の中の新たな神器になる素質があると実感しています。（もうなっているかもしれませんが）

そして、1PCに1LLMと呼ばれるLLMの民主化の時代が来るのではないかと予感しています。

そのためにも、LLMをより少ないリソースでローカルに動かすための方法を模索していく必要があると改めて感じました。