Rye を使って llama-cpp-python + ELYZA-japanese-Llama を動かしてみる

# はじめに

昨年、[VMware Private AI Reference Architecture](https://core.vmware.com/resource/deploying-enterprise-ready-generative-ai-vmware-vmware-cloud-foundation#executive-summary) が発表され、関連記事も少し見始めているのですが、どうしても GPU リソースを必要とするものが多く、個人的に、なかなか触りづらさを感じていました。

https://core.vmware.com/resource/deploying-enterprise-ready-generative-ai-vmware-vmware-cloud-foundation#executive-summary

しかし、`llama-cpp-python` を使えば、ノート PC のようなリソースが限られた環境でも、Python から簡単に LLaMA を動かすことができそうなので、今回はこれを試していきます。

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

また、この `llama-cpp-python` を実行する Python 環境は、[Rye](https://rye-up.com/) を使って、構築していきます。
この Rye に関しては、Python でとある OSS を開発していた時にあれば、どんなに幸せだっただろうと思えるくらい、とても便利だったので、どんどん使っていきたいと思っています。

https://rye-up.com/

これだけでも要素が多いですが、LLaMA を動かしていく際に、せっかくなので日本語の学習済みモデル ELYZA-japanese-Llama で、日本語で対話してみたいと思います。

https://huggingface.co/elyza/ELYZA-japanese-Llama-2-7b-instruct


# 前提

今回は Ubuntu OS を利用しており、利用バージョンは、下記の通りです。

```bash
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS"
```

また、長くなってしまうので、Rye は、公式ドキュメントに記載の方法でインストール済みとさせてください。
とはいえ、ガイドにあるコマンドを実行し、少しばかり対話による選択をするのみです。

https://rye-up.com/guide/installation/


# 手順

基本的には、`llama-cpp-python` の GitHub のガイドに沿って、実行していきますが、実行していく上で、いくつか依存パッケージのインストール等が必要だったので、その辺りを補足してあります。

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


## 依存パッケージのインストール

`llama-cpp-python` が、`llama.cpp` という C/C++ のライブラリを Python から使えるようにしたもの (バインディング) である関係上、パッケージのインストール過程で、C/C++ のライブラリのビルドが発生します。
そのため、ビルドのためのパッケージ群を、事前にインストールしておく必要がありました。

```bash
sudo apt install build-essential libpython3-dev
```


## Python プロジェクトの作成

Rye を使って、遊ぶための Python プロジェクトを作成します。

```bash
$ rye init test-llama-cpp-python
success: Initialized project in /home/yusukei/test-llama-cpp-python
  Run `rye sync` to get started
```

:::note warn
**注意**
Rye で Python プロジェクトを作成する際、後でインストールする Python モジュールと同じ名前 (今回の場合は `llama-cpp-python`) とは、異なる名前を指定してください。
はっきりと確認した訳ではないのですが、同じ名前にしてしまうと、Python モジュールをインストールする際 (具体的には `rye sync` 実行時) にエラーしてしまいました。
:::

作成したプロジェクトのディレクトリに移動しておきます。

```bash
cd test-llama-cpp-python/
```

プロジェクトの情報を確認すると、下記のようになるはずです。

```bash
$ rye show
project: test-llama-cpp-python
path: /home/yusukei/test-llama-cpp-python
venv: /home/yusukei/test-llama-cpp-python/.venv
target python: 3.8
venv python: cpython@3.12.1
virtual: false
```


## `llama-cpp-python` のインストール

Rye のパッケージ管理を使って、`llama-cpp-python` をインストールしていきます。
まず、`llama-cpp-python` を依存パッケージに登録します。

```bash
$ rye add llama-cpp-python
Added llama-cpp-python>=0.2.38 as regular dependency
```

登録した依存パッケージの情報を元に、パッケージをインストールします。
前述の通り、C/C++ ライブラリのビルドが走るので、ちょっと時間がかかります。

```bash
$ rye sync
Initializing new virtualenv in /home/yusukei/test-llama-cpp-python/.venv
Python version: cpython@3.12.1
Generating production lockfile: /home/yusukei/test-llama-cpp-python/requirements.lock
Generating dev lockfile: /home/yusukei/test-llama-cpp-python/requirements-dev.lock
...
... (snip) ...
...
Successfully installed diskcache-5.6.3 jinja2-3.1.3 llama-cpp-python-0.2.38 markupsafe-2.1.5 numpy-1.26.3 test-llama-cpp-python-0.1.0 typing-extensions-4.9.0
Done!
```


## 学習済みモデルの用意

冒頭で記載した通り、せっかくなので日本語の学習済みモデル [ELYZA-japanese-Llama](https://huggingface.co/elyza/ELYZA-japanese-Llama-2-7b-instruct) を動かしていきたい思うのですが、`llama-cpp-python` が gguf という形式のモデルを必要とするようなので、Hugging Face のサイトから、変換済みのモデルを利用しています。

https://huggingface.co/mmnga/ELYZA-japanese-Llama-2-7b-fast-instruct-gguf

今回は、お試しなので、一番ファイルサイズの小さい `ELYZA-japanese-Llama-2-7b-fast-instruct-q2_K.gguf` を取得し、Python プロジェクト配下に `models` というディレクトリを作成して、配置しました。

この時点で、プロジェクトの構造は、こんな感じになっているはずです。

```text
.
├── README.md
├── models
│   └── ELYZA-japanese-Llama-2-7b-fast-instruct-q2_K.gguf    <-- 追加
├── pyproject.toml
├── requirements-dev.lock
├── requirements.lock
└── src
    └── test_llama_cpp_python
        └── __init__.py
```


## お試しスクリプトの用意

`llama-cpp-python` の [GitHub](https://github.com/abetlen/llama-cpp-python?tab=readme-ov-file#high-level-api) を見ながら、こんな感じのスクリプトを用意してみました。
プロンプトのテンプレ等が上手く扱えていない辺りは、勉強不足ですので、ご容赦ください...

```python: demo.py
from llama_cpp import Llama

llm = Llama(
        model_path = "./models/ELYZA-japanese-Llama-2-7b-fast-instruct-q2_K.gguf",
        #n_gpu_layers = 1,
        )

output = llm("""
        
 ギャグを言ってみてください。 

        """)

print(output['choices'][0]['text'])
```

実行環境に GPU が無かったので、試していませんが、コメントアウトしてある `n_gpu_layers = 1` の部分をちゃんと指定すると、GPU リソースちゃんと使えるらしいです。

よって、この時点で、プロジェクトの構造は、こんな感じになっているはずです。

```text
.
├── README.md
├── demo.py     <-- 追加
├── models
│   └── ELYZA-japanese-Llama-2-7b-fast-instruct-q2_K.gguf
├── pyproject.toml
├── requirements-dev.lock
├── requirements.lock
└── src
    └── test_llama_cpp_python
        └── __init__.py
```


## スクリプトの実行

毎回ちゃんと `rye run python` と打つのが面倒なので、先に Python venv 環境へ入っておきます。

```bash
$ . .venv/bin/activate
(test-llama-cpp-python) $    # <-- venv 環境のプロンプト
```

:::note info
**参考**
`rye shell` という、いかにも便利そうなコマンドもあるのですが、Rye 0.21.0 時点では、「バグが多いコマンドだから、ちゃんと venv を有効化してね」と警告が出たので、ここでは venv を使っています。
:::

では、いよいよスクリプトを実行して、さぞや面白い冗談を見てみましょう。

```text
$ python demo.py
llama_model_loader: loaded meta data with 21 key-value pairs and 291 tensors from ./models/ELYZA-japanese-Llama-2-7b-fast-instruct-q2_K.gguf (version GGUF V3 (latest))
llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.
llama_model_loader: - kv   0:                       general.architecture str              = llama
llama_model_loader: - kv   1:                               general.name str              = ELYZA-japanese-Llama-2-7b-fast-instruct
llama_model_loader: - kv   2:      general.source.huggingface.repository str              = elyza/ELYZA-japanese-Llama-2-7b-fast-...
llama_model_loader: - kv   3:                   llama.tensor_data_layout str              = Meta AI original pth
...
...(略)...
...
llama_print_timings:        load time =    3867.23 ms
llama_print_timings:      sample time =      11.45 ms /    16 runs   (    0.72 ms per token,  1397.87 tokens per second)
llama_print_timings: prompt eval time =    3867.14 ms /    35 tokens (  110.49 ms per token,     9.05 tokens per second)
llama_print_timings:        eval time =    2171.19 ms /    15 runs   (  144.75 ms per token,     6.91 tokens per second)
llama_print_timings:       total time =    6139.06 ms /    50 tokens
いや、私はAIですので笑うことはできません…。
```

...はい？？？
そういうギャグってことでしょうか...？？ 分からん...


# まとめ

今回は、「とりあえず動かした。以上」レベルで、最後が酷かったですが、まずは色々と遊ぶための入り口に立った感じでしょうか。
もう少し知識をつけて、もう少し知見を増やせたら、また関連記事を書きたいと思います。