Metaの大規模言語モデル「Llama 2」をMacBook(M2)にダウンロードして会話をしてみるまでの記録

[Supership](https://supership.jp/)の名畑です。[サイコミ](https://cycomi.com/)で連載されていた「[リプライズ　2周目のピアニスト](https://cycomi.com/title/173)」が完結。毎週楽しみに読んでいました。楽しみが一つ減ってしまったのは少し残念。

## はじめに

[Meta](https://about.meta.com/ja/)がリリースした大規模言語モデル[Llama 2(ラマ2)](https://ai.meta.com/llama/)が話題です。

詳しくはここでは触れませんので興味のある方は

- [Meta、新たな大規模言語モデル「Llama 2」　商用利用可でGPT-3.5に匹敵 - Impress Watch](https://www.watch.impress.co.jp/docs/news/1517245.html)
- [Metaが商用可能な大規模言語モデル「Llama 2」を無料公開、MicrosoftやQualcommと協力してスマホやPCへの最適化も - GIGAZINE](https://gigazine.net/news/20230719-meta-ai-llama-2/)

このあたりを読んでいただくとして、今回はこの**Llama 2**を**Appleシリコン**の**MacBook**でダウンロードして簡単な会話をするまでを試したので、その手順を記録に残します。

## 私の環境

[MacBook Pro(Apple M2 Proチップ)](https://support.apple.com/kb/SP889?locale=ja_JP)です。OSは[macOS 13 Ventura](https://www.apple.com/jp/macos/ventura/)です。

Pythonのバージョンは3.10.12です。

```console
$ python --version
Python 3.10.12
```

また、Llamaはモデルをダウンロードする際のチェックで**md5sum**が呼ばれるのですが、MacOSはデフォルトだと**md5**しかないと思います。
ですので**md5sum**をインストールしておきました。

```console
$ brew install md5sha1sum
Running `brew update --auto-update`...

以下略
```

一応バージョンを確認しておきます。

```console
$ md5sum --version
Microbrew md5sum/sha1sum/ripemd160sum 0.9.5 (Wed Dec  6 12:48:56 EST 2006)
  Compiled Nov 19 2022 at 04:26:05
Written by Bulent Yilmaz

Copyright (C) 2004,2006 Microbrew Software
```

## Llama 2のモデルのダウンロード

まずはLlama 2のモデルをダウンロードします。

そのためにはユーザー登録が必要です。
[Request access to the next version of Llama](https://ai.meta.com/resources/models-and-libraries/llama-downloads/)を訪れて氏名などの情報を入力して規約をよく読んで同意すると、メールが届きます。

届いたメールの本文に下記のように**ダウンロード用のURL**が記載されています。これを後ほど使います。このURLは24時間のみ有効らしいので、気をつけてください。

>    Visit the Llama repository in GitHub and follow the instructions in the README to run the download.sh script.
>    When asked for your unique custom URL, please insert the 
> following:
> 【ここにダウンロード用のURLが記載されています】
>     Select which model weights to download

好きな場所に[Llamaのリポジトリ](https://github.com/facebookresearch/llama)をcloneします。

```console
$ git clone https://github.com/facebookresearch/llama.git
```

落とし終わったならその中の**download.sh**を叩きます。

```console
$ cd llama
$ chmod 755 download.sh
$ ./download.sh
```

すると

```console
Enter the URL from email:
```

と表示されるので先ほどのメールのURLをコピペしてEnterを押します。

次に

```console
Enter the list of models to download without spaces (7B,13B,70B,7B-chat,13B-chat,70B-chat), or press Enter for all:
```

ダウンロードするモデルを聞かれます。**7**、**13**、**70**はそれぞれパラメータ数が違います。**-chat**がついているものは対話形式に最適化されているようです。

今回はまずは試してみるためということで、オーソドックスに**7B**と打ってEnterを押します。それでも容量が**13.5G**近くありますので、時間がかかります。

```console
略

./llama-2-7b/checklist.chk        100%[=============================================================>]     100  --.-KB/s 時間 0s       

2023-07-21 01:28:41 (10.6 MB/s) - `./llama-2-7b/checklist.chk' へ保存完了 [100/100]

Checking checksums
consolidated.00.pth: OK
params.json: OK
```

ダウンロードが完了すると**llama-2-7b**というフォルダの中にモデルが格納されています。

## llama.cppのビルド

LlamaをMacBookなどで使えるようにする[llama.cpp](https://github.com/ggerganov/llama.cpp)というプロジェクトがあります。

まずは好きな場所に**llama.cpp**のリポジトリを**clone**します。

```console
$ git clone https://github.com/ggerganov/llama.cpp
```

次に、**llama.cpp**のフォルダに、先ほどダウンロードしたモデル類を移動します。元の場所のままでもできるのですが、移動した方がpathの指定がわかりやすいので移動させました。

具体的な手順は下記です。

- **llama**直下の**tokenizer_checklist.chk**と**tokenizer.model**を**llama.cpp/models**に移動する。
- **llama.cpp/models**に**7B**というフォルダを作り、その配下に**llama/llama-2-7b**配下のファイルをすべて移動する。

用意するフォルダ名は**7B**でなくてもいいのですが、**llama.cpp**の公式サンプルコードに合わせました。

結果として下記のファイル構成になりました。

```console
$ cd llama.cpp
$ ls ./models
7B   ggml-vocab.bin   tokenizer.model   tokenizer_checklist.chk
$ ls ./models/7B
checklist.chk   consolidated.00.pth   params.json
```

次に**llama.cpp**直下で**make**を叩いてbuildします。

```console
$ make
I llama.cpp build info: 
I UNAME_S:  Darwin
I UNAME_P:  arm

略

c++ --shared -I. -I./examples -O3 -std=c++11 -fPIC -DNDEBUG -Wall -Wextra -Wpedantic -Wcast-qual -Wno-unused-function -Wno-multichar -pthread -DGGML_USE_K_QUANTS examples/embd-input/embd-input-lib.cpp ggml.o llama.o common.o k_quants.o -o libembdinput.so  -framework Accelerate
c++ -I. -I./examples -O3 -std=c++11 -fPIC -DNDEBUG -Wall -Wextra -Wpedantic -Wcast-qual -Wno-unused-function -Wno-multichar -pthread -DGGML_USE_K_QUANTS examples/embd-input/embd-input-test.cpp ggml.o llama.o common.o k_quants.o -o embd-input-test  -framework Accelerate -L. -lembdinput
```

## モデルの変換

次に[Prepare Data & Run](https://github.com/ggerganov/llama.cpp#prepare-data--run)に従ってモデルの変換を行なっていきます。変換済みのモデルもネット上にはあるのですが、公式か不明なので使用を避けました。

まずPythonのライブラリ類をインストールします。

```console
$ python -m pip install -r requirements.txt
Collecting numpy==1.24
  Downloading numpy-1.24.0-cp310-cp310-macosx_11_0_arm64.whl (13.8 MB)

以下略
```

次にモデルを**RWKV**(pth)から**GGML**(bin)に変換します。

ファイル形式についてはここでは触れないので、初耳で興味のある方は下記あたりをご参照ください。

- [従来の大規模言語モデルの制約だった「入力量の限界」を取り払った「RWKV」は一体どんな言語モデルなのか？ - GIGAZINE](https://gigazine.net/news/20230709-rwkv-language-model/)
- [ggml.ai、GPUなしでチャットAIを動作させるライブラリ「GGML」開発中　Raspberry Pi上で音声認識モデルを実行可能：開発プロセスはオープンで、誰でも参加できる - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2306/26/news051.html)
- [ggerganov/ggml: Tensor library for machine learning](https://github.com/ggerganov/ggml/)

変換には**convert.py**というファイルを使います。引数はモデルファイルまでのpathです。

```console
$ python convert.py ./models/7B/consolidated.00.pth 
Loading model file models/7B/consolidated.00.pth
Loading vocab file models/tokenizer.model
params: n_vocab:32000 n_embd:4096 n_mult:256 n_head:32 n_layer:32

略

UnquantizedDataType(name='F32')
Wrote models/7B/ggml-model-f32.bin
```

**ggml-model-f32.bin**という**bin形式**のファイルが生成されました。

次に、生成された**ggml-model-f32.bin**を**4-bits**に変換します。**quantize**を呼びます。

```console
$ ./quantize ./models/7B/ggml-model-f32.bin ./models/7B/ggml-model-q4_0.bin q4_0
main: build = xxx (xxxxxxx)
main: quantizing './models/7B/ggml-model-f32.bin' to './models/7B/ggml-model-q4_0.bin' as Q4_0
llama.cpp: loading model from ./models/7B/ggml-model-f32.bin
llama.cpp: saving model to ./models/7B/ggml-model-q4_0.bin

略

main: quantize time = 11449.52 ms
main:    total time = 11449.52 ms
```
**ggml-model-q4_0.bin**というファイルが生成されました。

これで環境の準備は完了です。

## サンプルの実行

では実際に**Llama 2**を**llama.cpp**経由で呼び出してみましょう。

**llama.cpp/examples**の中にサンプルコードがいくつかあるので、今回はこの中の**chat.sh**を使うこととします。

参考までに**chat.sh**のコードは下記です。

```c++
#!/bin/bash

#
# Temporary script - will be removed in the future
#

cd `dirname $0`
cd ..

# Important:
#
#   "--keep 48" is based on the contents of prompts/chat-with-bob.txt
#
./main -m ./models/7B/ggml-model-q4_0.bin -c 512 -b 1024 -n 256 --keep 48 \
    --repeat_penalty 1.0 --color -i \
    -r "User:" -f prompts/chat-with-bob.txt
```

非常に短いコードです。
mainを呼び出し、その際にモデルなどの各種パラメータが渡されます。パラメータについて詳しくは[mainのREADME](https://github.com/ggerganov/llama.cpp/blob/master/examples/main/README.md)をご覧ください。

**chat.sh**に書かれたモデルのpathは必要に応じて各自の環境に合わせてください。

プロンプトとして**prompts/chat-with-bob.txt**が渡されています。
**prompts/chat-with-bob.txt**の内容は下記です。

```text
Transcript of a dialog, where the User interacts with an Assistant named Bob. Bob is helpful, kind, honest, good at writing, and never fails to answer the User's requests immediately and with precision.

User: Hello, Bob.
Bob: Hello. How may I help you today?
User: Please tell me the largest city in Europe.
Bob: Sure. The largest city in Europe is Moscow, the capital of Russia.
User:
```

つまりは**chat.sh**を実行するとBobとの会話が始まるようです。

**chat.sh**を実際に呼んでみます。

```console
$ ./examples/chat.sh                             

略

== Running in interactive mode. ==
 - Press Ctrl+C to interject at any time.
 - Press Return to return control to LLaMa.
 - To return control without starting a new line, end your input with '/'.
 - If you want to submit another line, end your input with '\'.

 Transcript of a dialog, where the User interacts with an Assistant named Bob. Bob is helpful, kind, honest, good at writing, and never fails to answer the User's requests immediately and with precision.

User: Hello, Bob.
Bob: Hello. How may I help you today?
User: Please tell me the largest city in Europe.
Bob: Sure. The largest city in Europe is Moscow, the capital of Russia.
User:
```

ここまではプロンプト通りですね。

会話を続けてみます。各行において**User**の後ろが私の入力した内容です。

```console
User:Where are you from now?
Bob: I'm from Toronto, Canada.
User:Thank you
Bob: You're welcome.
```

無事に動いていそうです。
ちなみに一つずつのレスポンスは私の環境ではだいたい即座に返ってきました。数秒かかる時もたまにはあります。

**Ctrl+C**で終了します。

## 日本語でも会話をしてみた

**prompts/chat-with-bob.txt**の内容を下記のように変えてみました。「**good at Japanese writing**」としています。

```text
Transcript of a dialog, where the User interacts with an Assistant named Bob. Bob is helpful, kind, honest, good at Japanese writing, and never fails to answer the User's requests immediately and with precision.

User: こんにちは、ボブ
Bob: こんにちは。今日はいかがいたしましたか？
User:
```

その上で**chat.sh**を呼び出し、会話してみた結果は下記です。いい感じですね。新しい名前を記憶していることから、会話の履歴が活かされていることがわかります。

```console
User: こんにちは、ボブ
Bob: こんにちは。今日はいかがいたしましたか？
User:日本の首都はどこですか？
Bob: 東京です。
User:日本で最北端の都道府県はどこですか？
Bob: 北海道です。
User:あなたの名前は実はボブではなくトムです 
Bob: すいません。名前はトムです
User:あなたの名前は何ですか？
Bob: 名前はトムです。
```

## 最後に

できることがかなり幅広そうです。

次回はPythonからの呼び出しを実装してみる予定です。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。