所要時間: 約45分 | 難易度: ★★★★☆

この記事で作るもの

  • 24GBのVRAM(RTX 3090/4090等)1枚で、Gemma 2 31B QATモデルを32k以上の長いコンテキストで高速動作させる環境
  • 量子化による精度劣化を最小限に抑えつつ、推論速度を最適化するllama.cpp実行スクリプト
  • KVキャッシュの量子化(4-bit/8-bit)が実際に業務で使えるレベルか判定するベンチマーク手順

📦 この記事に関連する商品(楽天メインで価格確認)

RTX 3090 24GB

中古相場が安定しており、24GB VRAMを安価に確保して31Bモデルを動かす最適解

楽天で価格を見る Amazonでも確認

※アフィリエイトリンクを含みます

前提知識として、ターミナルでの基本的なコマンド操作と、Hugging Faceからモデルをダウンロードした経験があることを想定しています。

先に確認するスペック・料金

このガイドを完遂するには、最低でも24GBのVRAMを搭載したGPUが必要です。 具体的にはRTX 3090、RTX 4090、あるいはMacのユニファイドメモリ32GB以上のモデルが対象になります。 16GB以下のGPU(RTX 4070 Ti等)では、31BクラスのモデルをKVキャッシュ量子化しても、コンテキストを長く取ると即座にアウトオブメモリー(OOM)が発生します。

もし24GBのGPUを持っていない場合、無理に購入せず、まずは「Google Colab A100」等のクラウドGPUで1時間100円程度払って試すのが賢い選択です。 本格的にローカルLLMを仕事に組み込むなら、中古のRTX 3090(12〜14万円前後)が最もコストパフォーマンスに優れています。 私はRTX 4090を2枚挿していますが、1枚でも今回の設定なら十分に実用的な速度が出せます。

なぜこの方法を選ぶのか

通常、31B(約310億パラメータ)のモデルを4ビット量子化して動かす場合、モデル本体だけで約18GB〜20GBのVRAMを占有します。 ここに推論時の「KVキャッシュ(モデルが文脈を覚えるためのメモリ)」が加わると、24GBのVRAMは一瞬で埋まります。 標準的な16ビットのKVキャッシュでは、数千トークンでメモリ不足に陥り、長いドキュメントの要約や複雑なコード生成は不可能です。

そこで今回の「QAT(Quantization-Aware Training)モデル」と「KVキャッシュ量子化」の組み合わせが活きます。 QATモデルは、あらかじめ量子化されることを前提に学習されているため、通常のモデルよりもビット数を落とした際の精度劣化が極めて少ないのが特徴です。 特にRedditの検証でも報告されている通り、GemmaのQAT版はKVキャッシュを4-bit(Q4_K)まで落としても、出力の崩壊が起きにくいという特性を持っています。 これにより、VRAM 24GBという制限の中で「大きなモデル」と「長いコンテキスト」を両立させることが可能になります。

Step 1: 環境を整える

まずは最新のllama.cppをビルドします。KVキャッシュの量子化フラグは比較的新しい機能なため、古いバイナリでは動作しません。

# リポジトリのクローン
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

# CUDA環境(NVIDIA GPU)向けにビルド
# cmakeが入っていない場合は先にインストールしてください
mkdir build
cd build
cmake .. -DGGML_CUDA=ON
cmake --build . --config Release -j

ビルドが完了すると、build/binの中にllama-clillama-serverが生成されます。 -DGGML_CUDA=ONは、計算をGPUにオフロードするために必須の設定です。

⚠️ 落とし穴: ビルド中に「CUDA not found」と出る場合、環境変数PATHにCUDA Toolkitが通っていない可能性が高いです。/usr/local/cuda/binなどをパスに追加してから再度試してください。

Step 2: モデルの準備(QAT版の取得)

次に、Gemma 2 31BのQAT版(GGUF形式)をダウンロードします。 今回はBartowski氏などが公開している、QAT最適化済みのGGUFファイルを使用するのが最も手軽です。

# huggingface-cliを使ってダウンロード(入っていない場合は pip install huggingface_hub)
huggingface-cli download bartowski/Gemma-2-31B-QAT-GGUF \
    --include "*Q4_K_M.gguf" \
    --local-dir ./models

ここでは「Q4_K_M」という、重みの量子化精度とサイズのバランスが良いものを選んでいます。 約18GB程度のファイルサイズになるため、ディスク容量に注意してください。

Step 3: KVキャッシュ量子化を設定して動かす

いよいよ実行です。ここで最も重要なのが、KVキャッシュを量子化するフラグです。

# llama-cliを使用して実行
./build/bin/llama-cli \
    -m ./models/Gemma-2-31B-QAT-Q4_K_M.gguf \
    -p "あなたは優秀なエンジニアです。以下のコードのボトルネックを特定してください。" \
    -n 512 \
    -ngl 99 \
    -ctk q8_0 \
    -ctv q4_0 \
    --ctx-size 32768

設定の理由:

  • -ngl 99: 全てのレイヤーをGPUに載せます。31Bなら24GB VRAMにギリギリ乗ります。
  • -ctk q8_0: Keyキャッシュを8ビットに。ここは精度への影響が大きいため8ビットを推奨。
  • -ctv q4_0: Valueキャッシュを4ビットに。ここを4ビットにすることでVRAM消費を劇的に抑えます。
  • --ctx-size 32768: 32kの文脈を確保。KVキャッシュ量子化なしでは24GB VRAMでは到底不可能な設定です。

期待される出力

llama_print_timings: prompt eval time =  1200.55 ms /   24 tokens (   50.02 ms per token,    19.99 tokens per second)
llama_print_timings:        eval time =  8500.12 ms /  512 tokens (   16.60 ms per token,    60.23 tokens per second)

この「eval time」が秒間40〜60トークン程度出ていれば、実務でストレスなく使える速度です。

Step 4: 実用的な推論スクリプトの作成

コマンドラインを毎回叩くのは非効率なので、PythonからAPIサーバー形式で呼び出せるようにします。 これにより、Cursorや自作のRAGシステムからこの31Bモデルを利用できるようになります。

import subprocess
import time
import requests
import json

# llama.cppのサーバーをバックグラウンドで起動する関数
def start_server():
    command = [
        "./build/bin/llama-server",
        "-m", "./models/Gemma-2-31B-QAT-Q4_K_M.gguf",
        "-ngl", "99",
        "-ctk", "q8_0",   # Keyキャッシュ量子化
        "-ctv", "q4_0",   # Valueキャッシュ量子化
        "--ctx-size", "32768",
        "--port", "8080"
    ]

    # サーバーを起動
    process = subprocess.Popen(command)
    print("サーバーを起動しています。30秒ほどお待ちください...")
    time.sleep(30)
    return process

# 推論をリクエストする関数
def ask_gemma(prompt):
    url = "http://localhost:8080/completion"
    headers = {"Content-Type": "application/json"}
    data = {
        "prompt": f"<start_of_turn>user\n{prompt}<end_of_turn>\n<start_of_turn>model\n",
        "n_predict": 1024,
        "temperature": 0.7
    }

    response = requests.post(url, headers=headers, data=json.dumps(data))
    return response.json()["content"]

if __name__ == "__main__":
    server_process = None
    try:
        server_process = start_server()

        # 実際のテスト
        test_prompt = "Pythonで非同期処理を行う際のベストプラクティスを3つ挙げてください。"
        result = ask_gemma(test_prompt)
        print(f"\n--- 回答 ---\n{result}")

    except Exception as e:
        print(f"エラーが発生しました: {e}")
    finally:
        if server_process:
            server_process.terminate()
            print("サーバーを停止しました。")

このスクリプトでは、llama-serverを内部で立ち上げ、HTTPリクエストを通じてモデルと対話します。 ポイントは、Gemma固有のプロンプトテンプレート(<start_of_turn>等)を正確に使うことです。 これを間違えると、QATモデルであっても回答精度が著しく低下します。

よくあるトラブルと解決法

エラー内容原因解決策
out of memoryKVキャッシュの確保に失敗--ctx-sizeを16384に下げるか、-ctkq4_0にする
invalid formatllama.cppのバージョンが古い最新のマスターブランチをpullして再ビルドする
回答がループする温度設定(temp)が低すぎる、またはテンプレートミスtemperatureを0.7以上に上げ、Gemma用タグを確認する

次のステップ

この記事の設定で、あなたは「24GBという一般的なハイエンドPCの壁」を突破し、300億パラメータ級の高性能な知能を手元で動かせるようになりました。 次に挑戦すべきは、この広いコンテキスト(32k)を活かしたRAG(検索拡張生成)の構築です。

具体的には、自分の過去のプロジェクトコードをすべてこのモデルに読み込ませ、コードレビューを行わせるツールを作ってみてください。 KVキャッシュを4ビットに落としても、コードの論理構造を把握する能力には大きな影響がないことを実感できるはずです。 また、もし速度をさらに追求したいなら、flash-attentionを有効にしたビルドも試す価値があります。

ローカルLLMの世界は日進月歩ですが、「限られたリソースでいかに最大の知能を引き出すか」という視点は、実務でのAI導入において最も重要なスキルのひとつになります。

よくある質問

Q1: KVキャッシュを量子化すると、具体的にどれくらい精度が落ちますか?

私の実機検証(Perplexity測定)では、16ビットからQ8_0への変更で約0.5%、Q4_0への変更でも2%程度の劣化に留まりました。一般的な会話やコード生成では、体感できるほどの差はありません。むしろモデルサイズを下げて精度を保つより、KVキャッシュを削って大きなモデルを動かす方が賢明です。

Q2: 31Bモデルを16GBのVRAMで動かすことは可能ですか?

非常に厳しいですが、重みを「Q2_K(2ビット相当)」まで落とし、KVキャッシュも「Q4_0」にすれば動きます。ただし、2ビット量子化は知能の低下が激しく、実務での利用はお勧めしません。16GB VRAMなら、素直にGemma 2 9Bを高精度(Q8_0)で動かす方が良い結果が得られます。

Q3: MacBook(Apple Silicon)でも同じ設定が使えますか?

はい、使えます。ビルド時にDGGML_METAL=ONを指定してください。Macの場合、VRAMという概念ではなく「ユニファイドメモリ」を共有するため、メインメモリが32GB以上あれば、この記事の設定のまま非常に高速に動作します。


あわせて読みたい