GPUを搭載していないPCでも、量子化モデルと llama.cpp を使えばローカルLLMを実用的に動かせます。ただし、モデルを起動するだけではCPU性能を十分に引き出せません。
私がCPUのみの環境で試した結果、効果が大きかったのは次の4点でした。
- CPU向けにReleaseビルドする
- GGUFの量子化レベルをメモリ容量に合わせる
- 生成用スレッドとプロンプト処理用スレッドを分ける
- コンテキスト長、バッチサイズ、メモリマップを調整する
この記事では、llama-cli を使った基本的な検証手順と、設定を変更するときの考え方をまとめます。
1. CPU向けにllama.cppをビルドする
まずソースコードを取得し、CPUビルドを作成します。公式ドキュメントでも、CPU版はCMakeによるビルドが基本になっています。
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release --parallel
実行ファイルは通常、次の場所に生成されます。
./build/bin/llama-cli --version
macOSでMetal対応のビルドを過去に作っていた場合は、CPUだけで動かすためにMetalを無効化する方法もあります。
cmake -S . -B build-cpu \
-DCMAKE_BUILD_TYPE=Release \
-DGGML_METAL=OFF
cmake --build build-cpu --config Release --parallel
実行時にデバイスを明示できるバージョンでは、--device none も使えます。
./build/bin/llama-cli \
--device none \
-m ./models/model.Q4_K_M.gguf \
-p "llama.cppの特徴を3つ説明してください。" \
-n 128
ビルド時はDebugではなくReleaseを選びます。Debugビルドは調査には便利ですが、推論速度の比較には向きません。
2. GGUFの量子化レベルを選ぶ
CPU推論では、モデルのサイズだけでなくメモリ帯域も速度に大きく影響します。そのため、FP16モデルを無理に動かすより、量子化されたGGUFを使うほうが現実的です。
私の場合、最初は次のように選びます。
| 量子化 | 特徴 |
|---|---|
| Q4_K_M | メモリ使用量と品質のバランスがよい |
| Q5_K_M | Q4より品質を優先したい場合 |
| Q8_0 | 品質を保ちやすいが、CPUメモリを多く使う |
モデルによって必要なメモリは異なるため、ファイルサイズだけで判断しないことが重要です。コンテキスト用のKVキャッシュや作業用バッファも必要になります。
まずモデルを読み込み、メモリ使用量とエラーの有無を確認します。
MODEL=./models/model.Q4_K_M.gguf
./build/bin/llama-cli \
-m "$MODEL" \
-p "日本語で自己紹介してください。" \
-n 128 \
--perf
--perf を付けると、プロンプト処理と生成処理に関する内部の計測結果を確認できます。比較するときは、同じモデル、同じプロンプト、同じ出力トークン数で測定します。
3. スレッド数を調整する
CPU推論で最初に試すべきなのが -t と -tb です。
-
-t:生成時に使うCPUスレッド数 -
-tb:プロンプト処理やバッチ処理に使うスレッド数
例えば、8コア程度のCPUなら次の設定から始めます。
./build/bin/llama-cli \
-m "$MODEL" \
-p "CPUで動作するローカルLLMの利点を説明してください。" \
-n 128 \
-t 8 \
-tb 8 \
--perf
プロンプトが長い場合は、プロンプト処理のほうが大量の行列演算になります。そのため、次のように生成時より多いスレッドを指定すると改善することがあります。
./build/bin/llama-cli \
-m "$MODEL" \
-p "以下の文章を要約してください。..." \
-n 128 \
-t 6 \
-tb 12 \
--perf
ただし、スレッド数を増やせば必ず速くなるわけではありません。論理コアをすべて使うと、他の処理との競合やメモリ帯域の飽和によって、かえって1トークンあたりの時間が悪化することがあります。
実際には、物理コア数、物理コア数の半分、論理コア数の3パターン程度を比較するのが簡単です。
4. コンテキスト長とバッチサイズを調整する
コンテキスト長は -c、論理バッチサイズは -b、物理バッチサイズは -ub で指定できます。
./build/bin/llama-cli \
-m "$MODEL" \
-c 4096 \
-b 512 \
-ub 128 \
-t 8 \
-tb 8 \
-p "llama.cppでCPU推論を最適化する方法を説明してください。" \
-n 128 \
--perf
コンテキスト長を大きくすると、会話履歴や長文を扱いやすくなります。一方で、KVキャッシュの使用量が増えるため、メモリ不足になりやすくなります。
最初から -c 32768 のような大きな値を指定するより、実際に必要な長さから始めるのがおすすめです。メモリ不足が発生した場合は、まず -c と -b を下げます。
バッチサイズは、長い入力をまとめて処理する場合に効きやすい設定です。対話的な短い入力では、値を大きくしすぎても生成速度が変わらないことがあります。CPU環境では、-b 256、-b 512、-b 1024 のように段階的に比較します。
5. mmapとメモリ常駐を使い分ける
llama.cpp はモデル読み込み時にメモリマップを利用できます。現行のCLIでは、ロードモードを -lm で指定できます。
./build/bin/llama-cli \
-m "$MODEL" \
-lm mmap \
-t 8 \
-tb 8 \
-p "メモリマップを使う理由を説明してください。" \
-n 128
mmap はモデルファイルをメモリに直接対応付ける方式です。起動時にすべてを一括コピーする必要がなく、複数のモデルを切り替える用途でも扱いやすい設定です。
一方、モデルをできるだけRAMに常駐させたい場合は、mlock を試せます。
./build/bin/llama-cli \
-m "$MODEL" \
-lm mmap+mlock \
-t 8 \
-tb 8 \
-p "短いテストです。" \
-n 64
ただし、RAMに余裕がない状態で mlock を使うと、他のアプリケーションがメモリ不足になる可能性があります。モデルサイズだけでなく、OSや他のプロセスが使うメモリも残しておく必要があります。
まとめ
CPUのみでllama.cppを最適化する流れを図にすると次の通りです。
GPUなしで llama.cpp を高速化する場合、特別な設定を一つ入れるより、次の順番で確認するのが効果的でした。
- Releaseビルドを作る
- メモリに収まる量子化モデルを選ぶ
-
-tと-tbを複数パターンで比較する -
-c、-b、-ubを必要最小限にする -
mmapとmlockをメモリ容量に応じて選ぶ
特に重要なのは、生成速度とプロンプト処理速度を分けて見ることです。短い質問への応答を速くしたいのか、長文を読み込ませたいのかで、適した設定は変わります。
最終的には、公式の CPUビルド手順 と CLIオプション一覧 を確認しながら、自分のCPU、メモリ容量、用途に合わせて測定するのが一番確実です。