【LlamaIndex 0.12.x対応】GGUF形式の量子化モデルをGPUに載せて爆速推論するまで【Qwen2.5-1M】

# はじめに

ChatGPTやClaudeなどを使えば簡単にコードを書いてくれる時代ではありますが、モジュールのアップデートのサイクルが早すぎてLlamaIndex最新バージョン（v0.12系統）には対応できていませんでした。

LlamaIndex＋llama.cppのPythonバインディングモジュール「llama-cpp-python」を使ってGGUF形式の量子化モデルをGPUに載せるまでの備忘録です。

使用GPU：NVIDIA GeForce RTX 3090
CUDA：12.4

CUDA 12.4を利用する前提で説明します。

# モジュールのインストール

以下のバージョンで動作を確認しました。

```
llama_index==0.12.8
llama_cpp_python==0.3.4
llama-index-embeddings-huggingface==0.5.3
llama-index-llms-llama-cpp==0.4.0
```

特に`llama_cpp_python`は公式GitHubリポジトリから、ビルド済みのwhlファイルを入手できます。本来はソースコードからローカルのコンパイラでビルドする必要がありますが、コンパイラ環境が無くてもそのままインストールできました。

https://github.com/abetlen/llama-cpp-python

ビルド済みのモジュールをインストールする場合、説明に以下のコマンドでインストールできるとありましたが、なぜかソースコードを取ってきてビルドしようとしてしまったため、whlファイルを直接指定してインストールしました。

```powershell
pip install llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu124
```
- whlファイルを直接ダウンロードしておく
  - https://abetlen.github.io/llama-cpp-python/whl/cu124/llama-cpp-python/

```powershell
pip install .\llama_cpp_python-0.3.4-cp310-cp310-win_amd64.whl
```

# 実装

最低限以下のプログラムを実行することで、ストリーミング応答の動作確認ができました。
設定をconfig.pyとして外だししています。

また、たまに続きの会話を生成しようとして出力が終わらないことがまだあるため、stop_wordsとして生成を止める文字列のリストも定義しました。

```python:config.py
MODEL_CONFIG = {
    "model_path": "Z:\models\GGUF\Qwen2.5-14B-Instruct-1M.Q4_K_M.gguf",
    "temperature": 0,
    "max_new_tokens": 1024 * 5,
    "context_window": 1024 * 10,
    "model_kwargs": {"n_gpu_layers": -1},
    "verbose": False,
    "stop_words": [
        "user:",
        "assistant:",
        "user\n",
        "assistant\n"
    ],
}
```


```python
from llama_index.llms.llama_cpp import LlamaCPP
from llama_index.core.llms import ChatMessage

from config import MODEL_CONFIG

llm = LlamaCPP(
    model_path=MODEL_CONFIG["model_path"],
    temperature=MODEL_CONFIG["temperature"],
    max_new_tokens=MODEL_CONFIG["max_new_tokens"],
    context_window=MODEL_CONFIG["context_window"],
    generate_kwargs={},
    model_kwargs=MODEL_CONFIG["model_kwargs"],
    verbose=MODEL_CONFIG["verbose"],
)

messages = [
    ChatMessage(role="system", content="システムプロンプト"),
    ChatMessage(role="user", content="質問"),
]

# 推論

response = llm.stream_chat(messages)
response_text = ""

for token in response:
    if any([stop_word in response_text + token.delta for stop_word in MODEL_CONFIG["stop_words"]]):
        print("[STOP WORD DETECTED]")
        break
    print(token.delta, end="", flush=True)
    response_text += token.delta
```


# モデルの応答比較

2025年1月末に公開された比較的新しい「Qwen2.5-14B-Instruct-1M」と、GPT-4oと応答を比較してみました。

ローカルのQwen2.5の動作確認には、オリジナルのWebアプリを作成してみました。



## プロンプト

漫画「SPYxFAMILY」のWikipediaにあるフォージャー家のキャラクター紹介文を基に、「水族館に遊びに行ったとき」のシーンを想定して会話を考えさせてみます。

全体で4000文字弱の説明とはいえ、作品を知らない場合具体的にどのような言葉のやり取りがあるかすぐに読み解けないため、この説明からいかにキャラクター同士の関係性を理解してセリフを考えられるかが肝になります。

https://ja.wikipedia.org/wiki/SPY%C3%97FAMILY

```text:ユーザプロンプト
以下はある漫画作品のキャラクター紹介です。
「水族館に遊びに行ったとき」のシーンを想定して、３人の会話がどのようなものであるか想像してください。

◆”黄昏”／ロイド・フォージャー（Loid Forger）
    声 - 江口拓也[57]
    本作の主人公[57]で、物語の核となる仮初めの『家族』を構成する、父親役を演じる男性。
    ・・・中略・・・

◆“被検体007”／アーニャ・フォージャー（Anya[注 7] Forger）
    声 - 種﨑敦美[58]
    物語の核となる仮初めの『家族』を構成する、子供役を演じる少女。
    ・・・中略・・・

◆“いばら姫”／ヨル・フォージャー（Yor Forger）／ヨル・ブライア（Yor Briar）〈旧姓〉
    声 - 早見沙織[58]
    物語の核となる仮初めの『家族』を構成する、母親役を演じる女性。
    ・・・中略・・・

（合計4000文字程度のプロンプト）
```

モデル名にもある通りコンテキストウィンドウは最大1Mなので、モデルにとっては楽々な文章量です。
（1M対応とはいえ、家庭用GPUではVRAMが全く足りません）

## Qwen2.5-14B-Instruct-1M.Q4_K_M.gguf（ローカル）

ChatGPTとの会話の速度とあまり変わらずサクサク回答してくれました。

![bandicam 2025-04-14 23-20-53-529.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1373557/5cc332fd-7eb2-4693-aac5-33a42b650558.gif)

```text:回答
では、水族館でのシーンを想像して、黄昏（ロイド）、アーニャ、ヨルの会話を考えます。

場面：水族館のペンギンの展示コーナー

黄昏（ロイド）
「アーニャ、このペンギンたちを見ていると、なんだか彼らの生活がとてもシンプルで羨ましい気がするな。毎日餌をもらい、泳いで、休むだけの生活だ。」

アーニャ
「うぃ、ちち。ペンギンはお勉強しなくてもいいんだもん。でも、ペンギンはお腹すいたときだけ食べるんだよ。だから、お腹すいたときだけ食べるって、お勉強と同じなんだよ。」

黄昏（ロイド）
（内心苦笑）「そうか、アーニャの理屈だな。でも、確かにその通りかもしれない。」

ヨル
「あの、ペンギンって本当にかわいいですね。でも、ちょっと寒そうに見えます。」

黄昏（ロイド）
「ヨル、寒そうに見えるのは、彼らが寒さに強いからだよ。ペンギンは寒い環境に適応しているんだ。」

アーニャ
「うぃ、はは。ペンギンは寒さが大好きなんだよ。寒さが大好きだから、寒い場所で泳いでいるんだよ。」

ヨル
「あ、そうなんだ。そういえば、アーニャ、お勉強はどう？今日も頑張った？」

アーニャ
「だいじょうぶます。今日はペンギンのお勉強をしたから、お勉強はお休みです。」

黄昏（ロイド）
（内心苦笑）「アーニャ、ペンギンの生態を勉強したのはいいが、自分の宿題も忘れないようにね。」

ヨル
「そうそう、アーニャ。宿題もちゃんとやってね。」

黄昏（ロイド）
「ヨル、アーニャの宿題を心配するのは結構だが、あまり過保護にならないように。」

ヨル
「あ、ごめん。つい気になっちゃうんだ。」

黄昏（ロイド）
「ふふ、ヨルもアーニャも、家族としての心遣いは嬉しいよ。」

この会話では、黄昏の冷静な指摘、アーニャの独特な発言、そしてヨルのやや天然な性格がうまく反映されていると思います。また、家族としての絆や、それぞれのキャラクターの個性が自然に表現されているように思います。もし、もっと具体的なシーンやキャラクターの関係性を強調したい部分があれば、ぜひ教えてください！
```


## GPT-4o

```text:回答
もちろん！以下は「水族館に遊びに行ったとき」のシーンを想定した、ロイド、アーニャ、ヨルの３人の会話です。キャラクターの性格や関係性を反映した会話になっています。

🐠 水族館にて — フォージャー家の休日
アーニャ（目を輝かせながら）
「ちちー！あそこにでっかいさかないます！すっごいです！あれ、たぶん“すいりゅうビーム”はけます！」

ロイド（微笑を浮かべつつ、内心は情報収集モード）
「うん、あれはジンベエザメだ。大人しい性格で人間に危害を加えることはない。…（心の声）この施設の警備体制は意外と甘いな。潜入経路を確保するなら裏手の排水口か…いや、今日は任務じゃない。」

ヨル（ガラスにぺったり顔を寄せて）
「うわぁ……お魚さんたち、とってもキレイです……。でもこの中に毒を持った魚もいますよね？うっかり触ったら……即死、なんてことも……！」

アーニャ（心を読んで青ざめる）
「はは、こわいこと考えすぎです！おさかなさん、なかよくしたいだけです！」

ヨル（はっとして）
「ご、ごめんなさいアーニャちゃん、楽しい場所なのに、つい……（ニコッ）ほら、イルカショーの時間みたいですよ。行きましょうか？」

アーニャ（ジャンプしながら）
「うぃ！イルカさん、ぴょんぴょんしますか！？あとボンドにおみやげ、さかなクッキー買いたいです！」

ロイド（腕時計で時間を確認しながら）
「了解だ。イルカショーはあと10分で始まる。席を確保しておこう。…（心の声）アーニャの笑顔、任務以上に価値があるかもしれないな。」

ヨル（小さな声で）
「…ロイドさんが少し笑ってる。……ふふっ」

このシーンでは、それぞれの「秘密」と「仮初めの家族」が織り交ざりながらも、穏やかでどこかぎこちなくも微笑ましい日常が描かれています。アーニャのテレパシーによる突拍子もない反応や、ヨルの天然ぶり、そしてロイドの完璧を装いつつ少しずつ緩む表情が、まさに『SPY×FAMILY』らしい空気ですね。
```

## 比較結果

どちらも少し変な言い回しがありますが、限定的な説明文からここまでくみ取って会話例を考えることができており、`Qwen2.5-14B-Instruct-1M`も負けていません。
さすがに、GPT-4oはSPYxFAMILYの事前知識があるのか、作品特有の雰囲気まで表現しようとしてくれました。

# さいごに

MCP等でAIエージェントの開発が盛り上がっている中、2025年4月になって今更感満載ですが試してみました。

最近のローカルモデルは10B前半のサイズにもかかわらず、体感GPT-4レベル以上の理解性能があるように感じて驚いています。（実際、以下のスコアボードでもQwen2.5-14B-Instruct-1MはGPT-4に匹敵するレベルのようです）

LLM勉強会が作成している日本語応答のスコアボードが以下に随時反映されていますので、モデルを選択するのに参考になるかと思います。

https://huggingface.co/spaces/llm-jp/open-japanese-llm-leaderboard