ローカルPCでLLMを動かす(llama-cpp-python)

プレスリリースで「LLMをオープンソースで公開します!」なんてものが流れてくると、自宅のPCで動かしたみたいと思ったりしませんか?

Amazon SageMakerGoogle Colabがあるから必要だと思わない」「どうせStable DuffusionのようにVRAM不足で落ちるんでしょ?」、ま、まあそういう面は確かにあるのですが、世の中にはCPUだけで動かしてしまう仕組みもあるのです。CPUだったら自宅PCにも必ず乗っている、つまり、ローカルで動くということです。

ここで紹介する「llama-cpp-python」はその一つです。もちろんCPUだけで動きますが、NVIDIA GeForceのカードが刺さったPC(きっとゲーミングPC)であればもっと快適に動かすオプションもあり、有償版のサービスに手を出す前に、LLMを使って遊びたい方には良いプロダクトだと思います。

llama-cpp-pythonとは?

llama-cpp-python公式より。
https://github.com/abetlen/llama-cpp-python

Simple Python bindings for @ggerganov’s llama.cpp library. This package provides:

  • Low-level access to C API via ctypes interface.
  • High-level Python API for text completion
    • OpenAI-like API
    • LangChain compatibility

C/C++で書かれた「llama.cpp」をPythonから使えるようにする(=python bindings)ためのライブラリです。まあ、そうなんだーというやつですね。

では、そのllama.cppはというと、

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

The main goal of llama.cpp is to run the LLaMA model using 4-bit integer quantization on a MacBook

  • Plain C/C++ implementation without dependencies
  • Apple silicon first-class citizen – optimized via ARM NEON, Accelerate and Metal frameworks
  • AVX, AVX2 and AVX512 support for x86 architectures
  • Mixed F16 / F32 precision
  • 2-bit, 3-bit, 4-bit, 5-bit, 6-bit and 8-bit integer quantization support
  • CUDA, Metal and OpenCL GPU backend support

色々と書かれていますが、我々にとって大事なのは、以下2つです。

  • 2~8ビット整数で量子化されたモデル※をAppleシリコン(mac)やIntel系(windows、linuxなど)のCPUを使って実行できる
    • 上のを読んでAppleシリコン重視だったのをいま知った・・・
  • 「CUDA、Metal、OpenCL GPUバックエンドサポート」により高速実行可能

LLaMAモデルはもちろんのこと、GGUF形式に対応(2023/8)してからはLLaMA以外のLLMにも対応(llama.cppの「Supported models」参照)するようになりました。

※大雑把に言うと、モデルの精度をそれほど損なうことなく高速化とメモリ低減を図ったもの

llama-cpp-pythonのインストール(WSL2なし版)

llama.cppの主たる目的が「MacBook上で・・」と書かれていましたが、ここでは空気を読まずにWindows(!?)でのセットアップ方法を記載していきます。

最初に、これを試した環境を記載しておきます。

項目スペック
CPUCorei7-13700F
メモリ128GB
GPUGeForce RTX 4070 Ti(12GB)
OSWindows11(22H2)

システムのpython環境に余計なパッケージを入れることを防ぐため、venvを使ってpython仮想環境を作ります。もちろん、Anacondaで作ってもOKです。
pythonは3.10.6を使っています。

PowerShell実行のための設定

PowerShellスクリプト実行権限の確認

  • スタートメニューを右クリックして「ターミナル(管理者)」を実行
  • PowerShellスクリプトが実行できるかどうか確認
> Get-ExecutionPolicy

スクリプトに実行権限を与える

上記の結果が「Restricted」の場合、現在のユーザにスクリプト実行権限を与える

> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
  • 「RemoteSigned」になっていることを確認
> Get-ExecutionPolicy
  • ターミナルを閉じる

python仮想環境の作成

以下では、作業用ディレクトリを「c:\work\Llama2」に作成したとします。

作業用ディレクトリを右クリックして「ターミナル」を開き、以下のコマンドでpython仮想環境を作成する。

> python -m venv ./venv
> .\venv\Scripts\Activate.ps1

# pipコマンドの存在確認
> pip list

# 入っていなければ以下のようなエラーになるので、インストールする
pipp : 用語 'pip' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。名前が正しく記述されている・・・(省略)・・・

> python -m pip install

(ケース1)llama-cpp-pythonのインストール(CPUだけで動かす場合)

GPUを使わずCPUだけで動かす場合のセットアップ手順です。

> pip install llama-cpp-python

非常にシンプルですね。

(ケース2)llama-cpp-pythonのインストール(CPU+GPUで動かす場合)

正確には、NVIDIAのCUDA Toolkitが利用できる環境の場合です。少し煩雑な手順となります。

古いCUDA Toolkit、cuDNNドライバがあればアンインストールする

ここでセットアップするのは、CUDA Tookit 12.2系のライブラリです。古いものがあればアンインストールしておきます。

Build Tools for Visual Studioのインストール

この後でインストールするCUDA ToolKitを使って実行用ファイルをビルドするため、ビルドに必要なツールをダウンロードします。PCにVisual Studio 2017以降が入っていればこの手順はスキップでも問題なさそうです。

ダウンロードサイト:https://visualstudio.microsoft.com/ja/downloads/

  • 上記ページを開き、下にスクロールして「Tools for Visual Stduio」を開く
  • 「Build Tools for Visual Studio 2022」の「ダウンロード」ボタン押下
    • vs_BuildTools.exeがダウンロードされる
  • vs_BuildTools.exeを実行
    • 「c++によるデスクトップ開発」を選択して「インストール」ボタン押下

CUDA ToolKitのインストール

CUDA Toolkitを入手します。
https://developer.nvidia.com/cuda-downloads

2023/9/15現在、GeForceのドライババージョン「566.40」から画面がブラックスクリーンになって回復しないという不具合が報告されており、CUDA Toolkitと共に手に入るGeForceドライバ「537.13」でも治っていないようです。最近はドライバの品質が怪しいため、私は下記のアーカイブサイトにアクセスして、古い版をダウンロードしました。
https://developer.nvidia.com/cuda-12-2-0-download-archive

以下を選択して表示された「Download(3.0GB)」ボタンを押下してダウンロード

選択項目
Operating SystemWindows
Architecturex86_64
Version11
Installer Typeexe(local)

ダウンロードされたexeファイルを実行してインストールする。
インストール後、作業用ディレクトリを右クリックして新たに「ターミナル」を開き、環境変数CUDA_PATHを確認する。

> Get-ChildItem env:
・・・(省略)・・・
CUDA_PATH C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.2

nvccコマンドも使えるようになっているので、ターミナルから実行できることを確認

> nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2023 NVIDIA Corporation
Built on Tue_Jun_13_19:42:34_Pacific_Daylight_Time_2023
Cuda compilation tools, release 12.2, V12.2.91
Build cuda_12.2.r12.2/compiler.32965470_0

cuDNNインストール

配布サイト:https://developer.nvidia.com/rdp/cudnn-download

ダウンロードにはユーザ登録が必要です。

「Download cuDNN v8.9.5 (September 12th, 2023), for CUDA 12.x」→
「Local Installer for Windows (Zip)」をダウンロード。

ダウンロードファイルを展開し、展開されたフォルダ「bin」「include」「lib」をCUDA_PATH配下(私の環境では「C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.2」)に丸ごとコピーする。

同じフォルダは怖い場合には、以下のインストールマニュアルを参照ください。cuDNNを別フォルダに入れる手順が書かれています。
https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html

llama-cpp-pythonインストール

llama-cpp-pythonがCUDA Toolkitを参照するようビルドします。CUDA_PATH変数を確認したターミナル上で以下のコマンドを実行する。

# ターミナルを開きなおしているので、仮想環境を有効化しなおす
> .\venv\Scripts\Activate.ps1

# ビルド
> $env:CMAKE_ARGS = "-DLLAMA_CUBLAS=on"
> $env:FORCE_CMAKE = 1
> pip install llama-cpp-python --force-reinstall --upgrade --no-cache-dir -vv

このとき「No CUDA toolset found.」が発生する(Visual Studioとのintegrationがうまくできていない)ことがあります。その場合には、以下にあるファイル群をコピーする。

コピー元C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.2\extras\visual_studio_integration\MSBuildExtensions\*

コピー先C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Microsoft\VC\v170\BuildCustomizations\

この後で、上記のpip installコマンドを再実行します。

llama-cpp-pythonのインストール(WSL2版)

WindowsのWSL2にllama-cpp-pythonを導入する手順です。
WSL2なし版の方が気楽に使えるのですが、WSL2で動かす方が安定しているような気がします。pythonは3.10.12を使っています。

python仮想環境の作成

以下では、作業用ディレクトリを「~/work/llama2」に作成したとします。
別のディレクトリに入れる場合には適宜読み替えてください。

# 今の環境を見てみる(pipすらないのね)
$ pip list

# pipが無ければ以下の表示になるのでインストール
Command 'pip' not found, but can be installed with:
$ sudo apt install python3-pip

# venvを入れる
$ sudo apt install python3.10-venv

# 作業用ディレクトリを作成
$ mkdir -p ~/work/llama2
$ cd ~/work/llama2
$ python3 -m venv ./venv

# 有効化
$ . ./venv/bin/activate

# 確認)
$ pip list
Package    Version
---------- -------
pip        22.0.2
setuptools 59.6.0

(ケース1)llama-cpp-pythonのインストール(CPUだけで動かす場合)

WSL2なしのWindows版と同じです。

(ケース2)llama-cpp-pythonのインストール(CPU+GPUで動かす場合)

CUDA ToolKitのインストール

CUDA Toolkitを入手します。
https://developer.nvidia.com/cuda-downloads

「いやいや、sudo apt install nvida-cuda-toolkitすればいいでしょ?」なのですが、私の環境ではうまくいきませんでした。CUDAも11.5と古いため、あまり粘らずに諦めました。

アーカイブではなくダウンロードのためのコマンド文字列が手に入ります。それをこんな感じで実行します。

$ wget (ダウンロードサイトに書かれているURL)
$ sudo dpkg -i (ダウンロードサイトに書かれているdebパッケージ).deb
$ sudo apt-get update
$ sudo apt-get -y install cuda

# インストールの確認
$ /usr/local/cuda/bin/nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2023 NVIDIA Corporation
Built on Tue_Aug_15_22:02:13_PDT_2023
Cuda compilation tools, release 12.2, V12.2.140
Build cuda_12.2.r12.2/compiler.33191640_0

同じUbuntuでもWSL版のUbuntuパッケージがあるので注意。
ここでは、CUDA Toolkit 12.2を入手しました。ついでにNVIDIAのGPUがWSL上でちゃんと見えているか確認。

$  nvidia-smi
Thu Sep 14 14:56:40 2023
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.54.04              Driver Version: 536.25       CUDA Version: 12.2     |
|-----------------------------------------+----------------------+----------------------+
| 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 GeForce RTX 4070 Ti     On  | 00000000:01:00.0  On |                  N/A |
|  0%   37C    P8              21W / 285W |   1848MiB / 12282MiB |      0%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
|        ID   ID                                                             Usage      |
|=======================================================================================|
|    0   N/A  N/A        26      G   /Xwayland                                 N/A      |
+---------------------------------------------------------------------------------------+

GeForce RTX 4070 Tiが見えているのでOKでした。

cuDNNインストール

配布サイト:https://developer.nvidia.com/rdp/cudnn-download

ダウンロードにはユーザ登録が必要です。URLはWSLなし版と同じ。

「Download cuDNN v8.9.5 (September 12th, 2023), for CUDA 12.x」→
「Local Installer for Ubuntu22.04 x86_64 (Deb)」をダウンロード。

ダウンロードしたファイルをWSL2にコピー&インストールする。

# Ubuntuにパッケージをコピー
$ cp /mnt/c/(WindowsPCにおいたdebパッケージのパス、パスはスラッシュ区切り) .

# インストール
$ sudo apt install ./cudnn-local-repo-ubuntu2204-8.9.5.29_1.0-1_amd64.deb

WSL2のシャットダウン&起動

どこかのサイトにCUDA Toolkitを入れた後はシャットダウンしなさい、と書かれていたので、一度落としてあげなおしました。

WSL2のターミナルを終了したのち、「ターミナル(管理者)」をWindowsから起動します。
そこで以下のコマンドを実行

> wsl --shutdown

そのあとで再びUbuntuターミナルを起動する。

llama-cpp-pythonインストール

llama-cpp-pythonがCUDA Toolkitを参照するようビルドします。

# python仮想環境の有効化(Ubuntuを再起動したので)
$ cd ~/work/llama2
$ . ./venv/bin/activate

# ビルドに備えて環境変数を設定
$ export CUDA_PATH=/usr/local/cuda/bin
$ export PATH=/usr/local/cuda/bin:$PATH

# ビルド
$ export LLAMA_CUBLAS=1
$ FORCE_CMAKE=1 CMAKE_ARGS="-DLLAMA_CUBLAS=on" pip install llama-cpp-python --force-reinstall --upgrade --no-cache-dir -vv

CUDA_PATHはいらないかもしれません。色々試行しすぎて判らなくなりました・・・
PATHは必須です。llama-cpp-pythonのビルドが成功するまではunsetしないように。

大量にデバッグメッセージ出すようにしていますので、CUDA対応のllama-cpp-pythonになるのを諦めたというメッセージが出たら試行錯誤することになります。大量メッセージの真ん中くらいにでます。

動作確認

モデルダウンロード

GGUF形式に変換済みのモデルをHugging Faceからダウンロードする。https://huggingface.co/TheBloke/Llama-2-13B-chat-GGUF/tree/main
llama-2-13b-chat.Q5_K_M.gguf

13B(130億パラメータ)+5bit量子化版のモデルを使用しました。9GBくらいあります。

ダウンロードしたファイルは、作業ディレクトリの下に「models」ディレクトリを作成して入れる

サンプルコードを作って動かす

以下のようなサンプルコードを作成し、作業ディレクトリ直下に「sample.py」として保存。

from llama_cpp import Llama
# プロンプトを記入
prompt = """[INST] <<SYS>>
You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe.Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content.Please ensure that your responses are socially unbiased and positive in nature.If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct.If you don't know the answer to a question, please don't share false information.
<</SYS>>
Write a story about llamas.Please answer in Japanese.[/INST]"""
# ダウンロードしたModelをセット.
llm = Llama(model_path="./models/llama-2-13b-chat.Q5_K_M.gguf", n_gpu_layers=20)
# 生成実行
output = llm(
    prompt,max_tokens=500,stop=["System:", "User:", "Assistant:"],echo=True,
)
print(output)

さあ、大事なのはここから。CUDA対応になっているかどうかは、ここで判明します。これを実行します。

python sample.py

すると、以下のようなメッセージが最後の付近に出てきます。

AVX = 1 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 |

このとき、「BLAS=1」になっていればCUDA対応になっています!

メモリ、速度の比較

上記実行すると、以下のように表示されたと思います。

llm_load_tensors: offloading 20 repeating layers to GPU

GPUにオフロードできるレイヤー数をパラメータ「n_gpu_layers」で調整できます。
上記では「n_gpu_layers=20」としましたが、このモデルでは「0」から「40」まで指定できるそうです。これによるメモリ(メイン、VRAM)、実行時間を比較してみました。

n_gpu_layers=0

llm_load_tensors: using CUDA for GPU acceleration
llm_load_tensors: mem required  = 8801.75 MB (+  400.00 MB per state)
llm_load_tensors: offloading 0 repeating layers to GPU
llm_load_tensors: offloaded 0/43 layers to GPU
llm_load_tensors: VRAM used: 0 MB
...................................................................................................
llama_new_context_with_model: kv self size  =  400.00 MB
llama_new_context_with_model: compute buffer total size =   75.47 MB
llama_new_context_with_model: VRAM scratch buffer: 74.00 MB
AVX = 1 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 0 | VSX = 0 |

llama_print_timings:        load time =  5707.38 ms
llama_print_timings:      sample time =    56.53 ms /   367 runs   (    0.15 ms per token,  6491.90 tokens per second)
llama_print_timings: prompt eval time =  5707.34 ms /   145 tokens (   39.36 ms per token,    25.41 tokens per second)
llama_print_timings:        eval time = 114633.20 ms /   366 runs   (  313.21 ms per token,     3.19 tokens per second)
llama_print_timings:       total time = 121021.23 ms

メインメモリ:8801.75MB、VRAM:0MB、トータル時間:約121秒

n_gpu_layers=20

llm_load_tensors: using CUDA for GPU acceleration
llm_load_tensors: mem required  = 4518.74 MB (+  400.00 MB per state)
llm_load_tensors: offloading 20 repeating layers to GPU
llm_load_tensors: offloaded 20/43 layers to GPU
llm_load_tensors: VRAM used: 4284 MB
...................................................................................................
llama_new_context_with_model: kv self size  =  400.00 MB
llama_new_context_with_model: compute buffer total size =   75.47 MB
llama_new_context_with_model: VRAM scratch buffer: 74.00 MB
AVX = 1 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 0 | VSX = 0 |

llama_print_timings:        load time =  3198.77 ms
llama_print_timings:      sample time =    53.04 ms /   367 runs   (    0.14 ms per token,  6919.05 tokens per second)
llama_print_timings: prompt eval time =  3198.73 ms /   145 tokens (   22.06 ms per token,    45.33 tokens per second)
llama_print_timings:        eval time = 75402.79 ms /   366 runs   (  206.02 ms per token,     4.85 tokens per second)
llama_print_timings:       total time = 79243.01 ms

メインメモリ:4518.74MB、VRAM:4284MB、トータル時間:約79秒

n_gpu_layers=40

llm_load_tensors: using CUDA for GPU acceleration
llm_load_tensors: mem required  =  235.73 MB (+  400.00 MB per state)
llm_load_tensors: offloading 40 repeating layers to GPU
llm_load_tensors: offloaded 40/43 layers to GPU
llm_load_tensors: VRAM used: 8567 MB
...................................................................................................
llama_new_context_with_model: kv self size  =  400.00 MB
llama_new_context_with_model: compute buffer total size =   75.47 MB
llama_new_context_with_model: VRAM scratch buffer: 74.00 MB
AVX = 1 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 0 | VSX = 0 |

llama_print_timings:        load time =   648.24 ms
llama_print_timings:      sample time =    47.89 ms /   367 runs   (    0.13 ms per token,  7662.92 tokens per second)
llama_print_timings: prompt eval time =   648.20 ms /   145 tokens (    4.47 ms per token,   223.70 tokens per second)
llama_print_timings:        eval time = 18498.05 ms /   366 runs   (   50.54 ms per token,    19.79 tokens per second)
llama_print_timings:       total time = 19727.28 ms

メインメモリ:235.73MB、VRAM:8567MB、トータル時間:約19秒

n_gpu_layersを増やしていくと

  • メインメモリの消費量が減る(8801→4518→235MB)
  • VRAMの消費量が増える(0→4284→8567MB)
  • 処理時間が減る(121→79→19秒)

でした。

おおよそVRAMが増えればその割合分だけ処理時間が減っているようです。VRAMが多いほど良いという理由がよくわかります。2分近く待つのはきついので、CUDA対応は必須ですね。

まとめ

以上、llama-cpp-pythonの紹介(ほぼセットアップ手順)でした。

最初、CUDA Toolkit、cuDNNが全く分からず、適当にインストールして大ハマりしてしまったので、同じ目にあっている方の参考になれば幸いです。

最後まで読んでいただきありがとうございました。