ローカル LLM で Chat GPT に匹敵する性能を追求 - マルチ GPU 環境の vLLM で Qwen2.5 Coder を動かす

この記事は[NTTコムウェア Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nttcomware) の 24 日目の記事です。

# はじめに
こんにちは、NTTコムウェア コーポレート革新本部 技術革新統括部の木村です。

普段は生成 AI に関する業務に従事しています。

こんなことを思ったことはないでしょうか？
- 生成 AI は便利そうだけども外部と接続するのはポリシー・セキュリティの壁がある(海外技術輸出など)
- 外部と接続するとセキュリティ対策で通信速度が著しく落ちる

そこで、自身はローカル環境で利用できる LLM を調査し、現時点での高性能なモデルを動かす方法を追求しています。 

今回は、LLM のプラットフォームである vLLM を利用し、コーディングに特化した大規模言語モデル Qwen2.5-Coder-32B を OpenAI 互換サーバとして利用する方法をご紹介します。Qwen2.5-Coder-32B は 2024 年 11 月に登場し、コーディングに特化すれば GPT-4o に匹敵するモデルです。

# 概要
本稿では、検証で動かすモデルとして Qwen2.5-Coder を選んだ理由や vLLM での分散推論方法をご紹介します。

もともと、自身は GitHub Copilot に代わる Continue などのコーディング支援ツールを調査しており、ローカルで完結するコーディング支援ツールのアーキテクチャを調査しています。GitHub Copilot は OpenAI の API を利用しているため、ローカルで実現するためには OpenAI に代わる API サーバが必要になります。そこで、ローカル LLM を API サーバとしてプラットフォームできる技術を調査したところ、オープンソースライブラリの vLLM にて、複数 GPU で分散して推論の高速化を図り API サーバとして利用できることがわかりました。

本稿では、推論の高速化を目指し、コーディング性能の高いローカル LLM のモデルである Qwen2.5-Coder を vLLM と分散フレームワークである Ray を組み合わることで分散して推論する方法をご紹介します。また、Chat GPT のような Web UI でローカル LLM を扱える Open WebUI をコンテナ管理ツールの Podman で利用してみた内容もご紹介します。

次のような内容をご紹介します。
- 環境
- Qwen2.5-Coder
- vLLM
- Ray
- Open WebUI
- 分散推論の戦略
- vLLM で 1つの VM でマルチ GPU の利用
- vLLM で複数の VM でマルチ GPU の利用 


# 環境
環境は、VM 2 台です。各 VM の GPU は NVIDIA L40S を 2 つずつ積んでいます。L40S の RAM は 48GB で、FP32 TFLOPS は 91.6 です。ローカル LLM でモデルを選定するときは、RAM の大きさによって扱えるモデルが決まります。48GB の環境では、autoAWQ 量子化の 30B 程のモデルを扱えます。このサイズの量子化無のモデルを動かす場合は、48GB の GPU を 2 つ利用して分散推論が必要です。L40S の詳細は、[NVIDIA社の公式サイト](https://www.nvidia.com/ja-jp/data-center/l40s/) をご確認ください。

| 台数(台) | OS | GPUの種類 | RAM(GB) | FP32 TFLOPS |
|:----:|:----:|:----:|:----:|:----:|
| 2 | Ubuntu 22.04 |NVIDIA L40S | 48 | 91.6 |

<details><summary>OS 確認 </summary>

```bash
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
```
</details>

<details><summary>GPU 確認 </summary>

```bash
$ nvidia-smi
Thu Dec  5 10:41:36 2024
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 565.57.01              Driver Version: 565.57.01      CUDA Version: 12.7     |
|-----------------------------------------+------------------------+----------------------+
| 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 L40S                    On  |   00000000:00:05.0 Off |                    0 |
| N/A   41C    P0             82W /  350W |   35519MiB /  46068MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA L40S                    On  |   00000000:00:06.0 Off |                    0 |
| N/A   45C    P0             86W /  350W |   35519MiB /  46068MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
```
</details>

## コーディング性能が高い Qwen2.5-Coder
2024 年 11 月中旬ごろ、コーディングに特化したローカル LLM を調査したところ、Alibaba Cloud から登場した Qwen2.5-Coder-32B-Instruct が候補にあがりました。Apache license2.0 が適用されており、商用利用・改変・再配布可能です。

次の図は LLM のコーディングに関するベンチマーク結果です。Qwen2.5 Coder 32B Instruct はローカル LLM の中ではコーディングのベンチマーク結果がトップであり、GPT-4o(2024-08.06)と互角かそれ以上であることがわかります。一方で、Aider のベンチマークを確認する限りは、Claude 3.5 Sonnet(2024-10.22) の方が、コーディング能力が高いとわかります。2024 年 11 月時点では、***Qwen2.5 Coder 32B Instruct は OpenAI のモデルに匹敵するコーディング能力があるローカル LLM*** だといえるでしょう。ローカル LLM も日々進化していますので、日々アップデートは必要です。

![32b-main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/91fedb0f-b8a7-16dc-a74e-bd250ec3347b.png)
出典：Qwen「Qwen2.5-Coder Series: Powerful, Diverse, Practical.」 https\://qwenlm.github.io/blog/qwen2.5-coder-family/  （参照：2024/12/09)

# vLLM で API サーバを構築
## vLLM
今回は OpenAI 互換な API サーバのプラットフォームとして、vLLM を利用します。類似ツールである Ollama も API サーバとして利用できますが、1 つのモデルを複数の GPU にローディングできません。そこで、複数 GPU 環境を活かすために vLLM を利用します。さらに、vLLM は AWQ 量子化モデルも利用でき、リソース効率が最適化されるロジックも導入されています。AWQ 量子化モデルは、重要でない重みに焦点を当て性能の劣化を抑止しています。Ollama と比較して、環境を活かせ、品質も高めることができる vLLM にて検証を行います。

:::note warn
vLLM では量子化モデルの利用は推奨されていません。性能が劣化していないモデルを利用するのが理想でしょう。一方で、推論速度の高速化や RAM の上限は要件や環境によって変わりますので、実態としては量子化モデルが使われることも多いと思います。コード生成では推論速度が求められる節も強く感じます。
:::

## 事前作業
次の作業を全ての VM に対して、事前に実施します。
- vLLM の インストール
- HuggingFace の token 設定
- (プロキシ利用の場合)プロキシ指定、証明書設定

vLLM のインストール手順は [公式サイト](https://docs.vllm.ai/en/v0.6.4.post1/getting_started/installation.html) をご確認ください。

HuggingFace の token は Web 上で取得し、CLI で設定できます。
Web 上での取得は、右上のユーザアイコンをクリックして、[Access Tokens] を選択し、[Create new token] で作成できます。

VM に Hugging Face の token を設定する CLI コマンドは次の通りです。
```bash
huggingface-cli login --token hf_[取得したトークン]
```

また、プロキシ配下の環境で作業している場合は、プロキシや証明書の環境変数の設定をしておきましょう。
- http_proxy
- https_proxy 
- REQUESTS_CA_BUNDLE

## 量子化モデルを動かす
まずは、AWQ 量子化により軽量化されたモデルを動かしてみます。

vLLM のコマンドは次の通りです。
オプションで、[api-key] や [port] は環境に合わせた値を設定して実行します。
```bash
vllm serve "Qwen/Qwen2.5-Coder-32B-Instruct-AWQ" --quantization "awq" --api-key [api-key] --port [port]
```

成功すると、10 秒おきにログが出力されます。
今回は推論速度に着目しており、[Avg generation throughput] の 1 秒あたりの生成トークン数に着目します。

```bash
INFO 12-05 14:38:35 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.0%, CPU KV cache usage: 0.0%.
INFO 12-05 14:38:45 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.0%, CPU KV cache usage: 0.0%.
INFO 12-05 14:38:55 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.0%, CPU KV cache usage: 0.0%.
INFO 12-05 14:39:05 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.0%, CPU KV cache usage: 0.0%.
INFO 12-05 14:39:15 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.0%, CPU KV cache usage: 0.0%.
```

## Open WebUI で使ってみる
Open WebUI で Qwen2.5-Coder を触ってみます。Open WebUI は Chat GPT のような Web UI 機能を持つ OSS です。vLLM は OpenAI 互換であり、OpenAI で Open WebUI を利用するのと同様に vLLM と Open WebUI を連携できます。詳細は [Open WebUIの公式ページ](https://openwebui.com/) をご確認ください。

自身は、Open WebUI をコンテナ管理ツールの Podman にて立てました。
[port] は環境に合わせたポート番号で、[ip address] は環境に合わせた IP アドレスです。

```bash
podman run -d -p [port]:8080   --add-host=host.docker.internal:[ip address]   -v open-webui:/app/backend/data   --name open-web-ui   --restart always   ghcr.io/open-webui/open-webui:main
```

コンテナが立ち上がりましたら、ブラウザにて URL (http://[ip address]:[port]) を入力してアクセスできます。ただし、自身の環境ではローカル IP で立ち上げ、NGINX でリダイレクトしてポートや IP アドレスを変換させて、外部からブラウザで接続しています。

![OpenWebUI-qiita1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/6aae27f6-94e0-47e0-1bee-e29c4b731126.jpeg)
出典：Open WebUI 「Open WebUI」 TOP画面（2024.12.05）

vLLM のモデルを利用するには右上の [ユーザのアイコン(図のK)をクリック→設定→接続] で設定可能です。Manage OpenAI API Connections に、設定した URL と api-key を設定して、有効化してください。URLは、末尾に [/v1] が必要です。
- http://[ip address]:[port]***/v1***
- [api-key]

設定すると、チャット画面の上のところでモデルを選択できるようになります。Qwen/Qwen2.5-Coder-32B-Instruct-AWQ を選択してみましょう。

早速使ってみます。
チャット欄に「〇×ゲームをhtmlで作ってください。デザインもこだわってみてください」と入力した結果です。Claude の Artifacts のように、右側で UI を触れるようになっています。

問題無く動作しています。コーディングの品質はベンチマークで確認するのが定量的で良いと思いますが、実際に触ってみるには良い環境だと思います。また、Open WebUI はもともと Ollama WebUI と呼ばれていた OSS でもあり、Ollama も設定すると有効的に活用できます。簡単に色々なモデルに触れます。Embedding モデルを Ollama で立ち上げておくと、PDF などをインプットにして RAG を使うのに役立てられます。その他、複数 PDF をアップロードして簡易的に RAG を使うことや Functions を定義して呼び出す、Web サーチなど様々なことをローカル LLM で試せます。

![Qiita-qwen2.5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/c5cedaa7-3a68-8cf3-7ed7-1b733ee0b865.jpeg)
出典：Open WebUI 「Open WebUI」 チャット実行時画面（2024.12.05）

次のコードは実際に生成されたコードです。

<details><summary>生成コード - html</summary>

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>〇×ゲーム</title>
</head>
<body>
    <h1>〇×ゲーム</h1>
    <div id="gameBoard">
        <div class="cell" onclick="playGame(0)"></div>
        <div class="cell" onclick="playGame(1)"></div>
        <div class="cell" onclick="playGame(2)"></div>
        <div class="cell" onclick="playGame(3)"></div>
        <div class="cell" onclick="playGame(4)"></div>
        <div class="cell" onclick="playGame(5)"></div>
        <div class="cell" onclick="playGame(6)"></div>
        <div class="cell" onclick="playGame(7)"></div>
        <div class="cell" onclick="playGame(8)"></div>
    </div>
    <div id="result"></div>
    <script src="script.js"></script>
</body>
</html>
```
</details>

<details><summary>生成コード - css</summary>

```css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh;
    background-color: #f0f0f0;
}

h1 {
    margin-bottom: 20px;
    color: #333;
}

#gameBoard {
    display: grid;
    grid-template-columns: repeat(3, 100px);
    grid-template-rows: repeat(3, 100px);
    gap: 5px;
    border: 2px solid #333;
}

.cell {
    width: 100px;
    height: 100px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #fff;
    border: 1px solid #666;
    cursor: pointer;
    font-size: 2em;
    color: #333;
}

#result {
    margin-top: 20px;
    font-size: 1.5em;
    color: #333;
}
```
</details>
<details><summary>生成コード - JavaScript</summary>

```js
let currentPlayer = "X";
let moves = ["", "", "", "", "", "", "", "", ""];
let gameActive = true;
let winningCombinations = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
];

function playGame(cellIndex) {
    if (moves[cellIndex] !== "" || !gameActive) return;

    moves[cellIndex] = currentPlayer;
    document.getElementsByClassName('cell')[cellIndex].textContent = currentPlayer;

    checkWinner();
    currentPlayer = currentPlayer === "X" ? "O" : "X";
}

function checkWinner() {
    for (let i = 0; i < winningCombinations.length; i++) {
        let [a, b, c] = winningCombinations[i];
        if (moves[a] && moves[a] === moves[b] && moves[a] === moves[c]) {
            gameActive = false;
            document.getElementById('result').textContent = `プレイヤー ${moves[a]} の勝ち！`;
            return;
        }
    }

    if (!moves.includes("")) {
        gameActive = false;
        document.getElementById('result').textContent = "引き分け！";
    }
}
```
</details>

# 分散推論
## 分散推論の戦略
分散推論は大きく 2 つの戦略があります。単一の VM か 複数の VM を扱うかです。
- 1 台のサーバで複数の GPU
- 複数台のサーバで複数の GPU

本稿では、3 パターンの検証を実施します。VM を跨ぐ場合は、OSS である分散フレームワークの Ray を利用します。複数台の GPU をクラスタリングして、分散処理をしてくれる OSS です。***Ray は OpenAI 社でもモデルをトレーニングするために利用されています***。今回は分散して推論するために利用します。Ray の詳細は [Rayの公式サイト](https://www.ray.io/) をご確認ください。必要に応じてインストールしてください。

|パターン| VM の台数(台) | GPUの数 | Ray の利用|
|:-:|:-:|:-:|:-:|
|パターン 1| 1 | 1 |無|
|パターン 2| 1 | 2 |無|
|パターン 3| 2 | 2 * 2 |有|

## Ray の使い方
Ray の利用は、事前に通信の設定を行い、Head Node と Worker Node を起動してクラスタリングを組みます。

Head Node を起動するときは次のコマンドで起動できます。[port] は環境に合わせたポート番号です。
```bash
ray start --head  --port=[port] 
```

Worker Node を起動するときは、Head Node の IP アドレスとポート番号を設定して起動できます。
```bash
ray start --address='[ip address]:[port]'
```

ray status コマンドで状態を確認できます。Resource を抜粋すると次のようになります。0.0/4.0 GPU と表示されていることを確認でき、4 つの GPU を認識しクラスタを組めていることがわかります。

```bash
Resources
---------------------------------------------------------------
Usage:
 0.0/128.0 CPU
 0.0/4.0 GPU
 0B/539.55GiB memory
 0B/235.23GiB object_store_memory

Demands:
 (no resource demands)
```

また、Ray のモニタリング UI で GPU のクラスタリング状況や利用率をブラウザで確認できます。[http://127.0.0.1:8265] へブラウザでアクセスします。4 つの GPU が見えており、クラスタリングが組めていることがわかります。

![ray-cluster.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/6bb49545-6f26-7f54-4bc8-855db1e18187.jpeg)
出典：Ray 「Ray」 Cluster管理画面（2024.12.05）

## 推論速度の検証
GPU の数に応じて、モデルローディングの可否が分かれるように、3 つのモデルにおいて、3 パターンで分散して推論を試みます。次の表には、モデルの大きさが小さい順に記載しています。

|モデル|パターン 1 | パターン 2 | パターン 3 | 
|:-:|:-:|:-:|:-:|
|Qwen2.5-Coder-32B-Instruct-AWQ|〇 |〇| 〇|
|Qwen2.5-Coder-32B-Instruct|読込不可 |〇|〇|
|Qwen2.5-72B-Instruct | 読込不可 |読込不可 |〇|

1 GPU で読み込み可能なサイズ、2 GPU で読み込み可能なサイズ、4 GPU で読み込み可能なサイズとなっています。コーディング性能を求める自身としては、2つ目の Qwen2.5-Coder-32B-Instruct で推論速度がでてほしいです。また、3 つ目の 72B のモデルは、コーディングに特化したモデルではなく汎用的な LLM です。72B は、32B の 2 倍以上の大きさのモデルです。パターン 3 のみが読み込み可能なモデルで検証したかったため、大型モデルである Qwen2.5-72B-Instruct を利用した検証も行います。

各モデル、各パターンにて、長文を出力させている間の [Avg generation throughput] を取得します。単位は tps(tokens per second) です。長時間の推論を行うようにして安定した速度がでているときに、tps 値を抜粋して 5 回平均を測定します。

```bash
INFO 12-05 15:04:26 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 51.5 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.5%, CPU KV cache usage: 0.0%.
INFO 12-05 15:04:31 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 51.4 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.6%, CPU KV cache usage: 0.0%.
INFO 12-05 15:04:36 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 51.3 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.7%, CPU KV cache usage: 0.0%.
INFO 12-05 15:04:41 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 51.2 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 0.8%, CPU KV cache usage: 0.0%.
INFO 12-05 15:04:46 metrics.py:449] Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 51.0 tokens/s, Running: 1 reqs, Swapped: 0 reqs, Pending: 0 reqs, GPU KV cache usage: 1.0%, CPU KV cache usage: 0.0%.
```

### Qwen2.5-Coder-32B-Instruct-AWQ
全てのパターンで読込み可能なモデルです。
次のコマンドを実行した結果です。tensor-parallel にて動作させています。
```bash
vllm serve "Qwen/Qwen2.5-Coder-32B-Instruct-AWQ" --quantization "awq" --api-key [api-key] --port [port]
vllm serve "Qwen/Qwen2.5-Coder-32B-Instruct-AWQ" --quantization "awq" --api-key [api-key] --port [port] --tensor-parallel-size 2 
vllm serve "Qwen/Qwen2.5-Coder-32B-Instruct-AWQ" --quantization "awq" --api-key [api-key] --port [port] --tensor-parallel-size 4 
```

|パターン| tps | 
|:-:|:-:|
|パターン 1| 31 |
|パターン 2| 51 |
|パターン 3| 19 | 

結果、パターン 2 が最も推論の速度が速いです。GPU 1 つのパターン 1 より、***65% 速度が向上***しています。一方で、パターン 3 が最も GPU を多く 4 つも使っていますが、1 GPU の パターン 1 よりも性能が劣っています。この原因は、VM 間通信が TCP/IP になるため、通信のオーバヘッドがボトルネックになっているためだと想定できます。この環境の IF は 10Gbps です。IF の通信速度を速くすることで、分散推論の速度も速くなるかもしれません。

:::note info
- 1 VM で GPU 2 つのとき、GPU 1つの場合と比べて 65% 速度が向上
- 2 VM で GPU 4 つのとき、GPU 1つの場合と比べて 39% 速度が低下
:::

次の図は、推論時のリソースの使用率です。

全てのパターンで、各 GPU の GRAM をしっかり使い切っています。また、GPU もしっかり使い切っています。

***パターン 1***

![ray-cluster-coder-awq-p1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/35d37935-b8bd-a503-8252-dfe2a6a99fcc.jpeg)
出典：Ray 「Ray」 Cluster管理画面一部抜粋（2024.12.05）

***パターン 2***
![ray-cluster-coder-awq-2p.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/ddbc5b7b-812f-4f9a-113b-da84ee47f1d8.jpeg)
出典：Ray 「Ray」 Cluster管理画面一部抜粋（2024.12.05）

***パターン 3***
![ray-cluster-coder-awq-4-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/3751bc30-fb09-b9c1-b7f7-e13d1d98dc3c.jpeg)
出典：Ray 「Ray」 Cluster管理画面一部抜粋（2024.12.05）

### Qwen2.5-Coder-32B-Instruct
パターン 1 は読込不可能で、量子化無のコーディングに特化したモデルです。コーディング支援ツールの LLM としては、今回検証する LLM の中では一番利用したいモデルです。

次のコマンドを実行した結果です。tensor-parallel にて動作させています。
```bash
vllm serve "Qwen/Qwen2.5-Coder-32B-Instruct"  --api-key [api-key] --port [port]
vllm serve "Qwen/Qwen2.5-Coder-32B-Instruct"  --api-key [api-key] --port [port] --tensor-parallel-size 2
vllm serve "Qwen/Qwen2.5-Coder-32B-Instruct"  --api-key [api-key] --port [port] --tensor-parallel-size 4
```

パターン 1 は CUDA out of memory で実行できませんでした。

|パターン| tps | 
|:-:|:-:|
|パターン 1| 不可 |
|パターン 2| 21 |
|パターン 3| 15 | 

結果、パターン 2 が最も速いです。ただし、AWQ 量子化モデルでは 51 tps 出ていましたが、21 tps となり、大幅に速度が低下しています。逆に AWQ 量子化をすると 143 % 速度が向上していることがわかりました。パターン 3 は先ほどよりパターン 2 との速度の乖離はないものの、半分の GPU である パターン 2 に劣っています。1 つの GPU では利用できないモデルも、2 つ以上の GPU の RAM を利用することで動かせました。

:::note info
- 1 VM で GPU 2 つで量子化無のモデルを動作させると、推論速度は 21 tps
- 同条件で、AWQ 量子化モデルは、量子化無のモデルより 143 % 推論速度が速い
:::

次の図は、推論実施時のリソースの使用率です。
パターン 1 の GPU 1 つではモデルをローディングできませんでしたが、パターン 2、3 は複数の GPU の GRAM にモデルをローディングできています。

***パターン 2***
![ray-cluster-Instruct-2p-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/9a0ac728-c5f3-73c9-edcd-9c2abb316752.jpeg)
出典：Ray 「Ray」 Cluster管理画面一部抜粋（2024.12.05）

***パターン 3***
![ray-cluster-coder-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/2908295f-d5bf-304a-4c45-1809cfcab802.jpeg)
出典：Ray 「Ray」 Cluster管理画面一部抜粋（2024.12.05）

### Qwen2.5-72B-Instruct
パターン 1 、パターン 2 は読込不可能なモデルです。

次のコマンドを実行した結果です。tensor-parallel にて動作させています。
```bash
vllm serve "Qwen/Qwen2.5-72B-Instruct"  --api-key [api-key] --port [port] --max_model_len 25328
vllm serve "Qwen/Qwen2.5-72B-Instruct"  --api-key [api-key] --port [port] --tensor-parallel-size 2 --max_model_len 25328
vllm serve "Qwen/Qwen2.5-72B-Instruct"  --api-key [api-key] --port [port] --tensor-parallel-size 4 --max_model_len 25328
```
出典：Ray 「Ray」 Cluster管理画面一部抜粋（2024.12.05）

パターン 1、パターン 2 は CUDA out of memory で実行できませんでした。

|パターン| tps | 
|:-:|:-:|
|パターン 1| 不可 |
|パターン 2| 不可 |
|パターン 3| 9 | 

結果、パターン 3 のみ動かせました。少数の GPU では利用できないモデルも、複数の VM、複数の GPU の RAM を利用することで動かせました。パターン 1、2 の GPU 1 つ、もしくは、GPU 2 つではモデルをローディングできませんでしたが、パターン 3 は 4 つの GPU を利用することでモデルをローディングできています。一方で、推論速度は満足いく速さではなく、実用化は難しいと思います。

:::note info
- Ray で複数の GPU の RAM を利用して大型のモデルもローディング可能
:::


次の図は、推論実施時のリソースの使用率です。
フルにリソースを活用しています。

![ray-cluster-Instruct-p4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3953060/18c5b29c-d267-ea72-782a-39893c3714fe.jpeg)
出典：Ray 「Ray」 Cluster管理画面（2024.12.05）

## 実用に向けて
今回の検証では、Qwen2.5-Coder の AWQ 量子化有のモデルと無のモデルのいずれも、パターン 2 の 1 VM で 2 つの GPU を利用する環境が最も速かったです。この 2 つのモデルにおいて、コーディング支援ツールの LLM としてどちらを利用するか選ぶと、自身の感覚としては***量子化有のモデルしか選択肢はない***と思います。理由は、コーディング支援ツールで使う場合、ストレスなく利用できた方が良く、かつ、複数人で利用することが前提になるためです。複数人で利用すると推論速度は低下します。コーディング支援ツールでの利用を前提とすると、推論速度は速い方がよく、複数人で使ってもストレスなく利用できる環境を整えていく必要があります。

|モデル| 速度(tps) | 品質| 
|:-:|:-:|:-:|
|Qwen2.5-Coder-32B-Instruct-AWQ| 〇 <br>51 | △ <br>AWQ量子化|
|Qwen2.5-Coder-32B-Instruct| △ <br>21 | 〇 <br>量子化無|

また、Qwen2.5-Coder 量子化無のモデルを使う場合は、NVIDIA L40S を 4 つ割り当ててどの程度の速度がでるかも気になるところです。想像としては、恐らく 4 つ割り当てても 30 tps 程になるのではないかと想像してます。検証結果からの想像になりますが、分散して推論する戦略としては、1 GPU にモデルをローディングしきれる状態が、推論速度の追求には良い条件なのかもしれません。

この点を踏まえ、実用面では L40S にて Qwen2.5-Coder を使っていくのはまだ難しいと思います。品質が多少劣化した量子化モデルであればそれなりの推論速度で利用できますが、品質が劣化しますので、現状ではセキュリティや通信速度の課題があったとしても GitHub Copilot などを選択するのが良さそうです。

この環境でローカル LLM の品質・推論速度を追求するには、今後のコーディング特化型の LLM の進化を期待します。

## 今後やっていきたいこと
複数ノードでの推論速度が遅かった点について、文献[^1]からわかったことがあります。文献によると、並列化戦略の選択肢として、Tensor Parallel や Pipeline Parallel などがあります。複数ノードで動作させる場合、Tensor Parallel よりも、Pipeline Parallel のほうが高速になる旨の記載がありました。今回は、vLLM にて Tensor Parallel を指定していたため、遅かった可能性があります。複数ノードの場合、Pipeline Parallel が適しており、複数ノードでの推論速度の向上も追及できれば良いと思います。
[^1]: Hugging Face「Hugging Face - Multiple GPUs and parallelism」 [https://huggingface.co/docs/transformers/main/en/perf_train_gpu_many](https://huggingface.co/docs/transformers/main/en/perf_train_gpu_many)（参照：2024/12/10)

# 終わりに
単一 VM のマルチ GPU 環境にて、vLLM で分散して推論することで推論の速度を向上できるとわかりました。一方で、Ray を利用して複数 VM を跨いで分散して推論をする場合は、推論速度が低下しますが、扱える RAM が増えることによりパラメータ数が多いモデルもローディング可能になるとわかりました。分散推論の戦略としては、同一 VM に多くの GPU を積む戦略が優れているといえるでしょう。

今後は、AI エージェントが流行する時代が来ると個人的に思っています。AI が自律的に考えて社内ドキュメントやコードを扱うことが多くなるとセキュリティを重視したり、高品質な LLM の高騰化、および、ローカル LLM の進化が進む中で何度も LLM API を呼び出す環境下では、ローカル LLM のメリットがでてくると思われます。生成 AI 領域の発展は目まぐるしく、今後も新規モデルや生成 AI の最新技術に注目しつつ追随していければと思います。

記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。