🦙

マルチモーダルモデルのLLaVAをApple Silicon (M1, M2, M3) Mac で 動かす方法4つ

2023/11/29に公開

はじめに

大規模言語モデルの llama を画像も入力できるようにした LLaVA を M1 Mac で動かしてみました。一部動いていないですが。。。
いろんな方法があるので整理してみます。

Llava について詳しく知りたい方は下記サイトを見てみるのが良いと思います。
https://llava-vl.github.io/

下記サイトで実際に試せるので、ローカルで動かしたくなるケースは少ないかもですが。。。
https://llava.hliu.cc/

また、Replicate という Hugging Face に似たサービスで、制限つきですが無償で API が公開されているので、サクッと API で呼び出したい方はこちら。
Python とか Node.js の SDK もあってサクッと呼び出せます。
https://replicate.com/yorickvp/llava-13b

LLaVA

まずは、github 上にあるこれを動かそうとしてみました。でも、動かない・・・。動いた人いれば情報ください。

https://github.com/haotian-liu/LLaVA
https://github.com/haotian-liu/LLaVA/blob/main/docs/macOS.md

やったのはここらです。

pyenv install 3.10
pyenv local
python -m venv .venv
source .venv/bin/activate
python -mpip install --upgrade pip  # enable PEP 660 support
pip install -e .
pip install torch==2.1.0 torchvision==0.16.0
pip uninstall bitsandbytes
python -m llava.serve.controller --host 0.0.0.0 --port 10000
python -m llava.serve.gradio_web_server --controller http://localhost:10000 --model-list-mode reload
python -m llava.serve.model_worker --host 0.0.0.0 --controller http://localhost:10000 --port 40000 --worker http://localhost:40000 --model-path liuhaotian/llava-v1.5-13b --load-4bit --device mps

こんなエラーが出ました。

% python -m llava.serve.model_worker --host 0.0.0.0 --controller http://localhost:10000 --port 40000 --worker http://localhost:40000 --model-path liuhaotian/llava-v1.5-13b --load-4bit --device mps
2023-11-29 22:28:40 | INFO | model_worker | args: Namespace(host='0.0.0.0', port=40000, worker_address='http://localhost:40000', controller_address='http://localhost:10000', model_path='liuhaotian/llava-v1.5-13b', model_base=None, model_name=None, device='mps', multi_modal=False, limit_model_concurrency=5, stream_interval=1, no_register=False, load_8bit=False, load_4bit=True)
2023-11-29 22:28:40 | INFO | model_worker | Loading the model llava-v1.5-13b on worker d9bfa3 ...
2023-11-29 22:28:40 | ERROR | stderr | Traceback (most recent call last):
2023-11-29 22:28:40 | ERROR | stderr |   File "/Users/ito/.pyenv/versions/3.10.13/lib/python3.10/runpy.py", line 196, in _run_module_as_main

PyTorch が Mac に対応してたりするので、動く気はしてるけど、よくわかんないです。。。
諦めて、次。

参考

https://note.com/ngc_shj/n/n2425276cacb6
https://pytorch.org/

llama-cpp

llama を C++で動かすやつ。
MacBook で動かすのメインゴールらしいです。

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

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

Makefileに色々書かれてるので、makeするだけでそれっぽく動きます。すごい。Apple Silicon 版 CUDA の Metal も使うようにしてくれていて、GPU を使って動いてくれてます。すごい。

make

動かす前に、モデルのデータを探してくる必要があります。
トラップがあって、.binのモデル (GGML) だと動かなくて、.gguf (GGUF)のモデルを使う必要がある。2023-08 あたりに、破壊的変更があって、そうなったみたい。
検索して出てくる記事が、古い記事が多くて要注意。

GGUF のモデルは Hugging Face 上に色々あります。
TheBloke, mmnga ってアカウントの方々が色々公開してくれてるので、それを使ったら良さそうです。自分で、コンバートする方法もあるみたいですが、試してないです。巨人の肩に立ったらええやん?
https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML
https://huggingface.co/mmnga/ELYZA-japanese-Llama-2-7b-fast-instruct-gguf

たとえば、上記サイトからELYZA-japanese-Llama-2-7b-fast-instruct-q4_K_M.ggufをダウンロードしてきて下記のようなコマンドで試せます。

./main -m ./models/ELYZA-japanese-Llama-2-7b-fast-instruct-q4_K_M.gguf --temp 0.1 -p "[INST]プログラムを勉強する際、どの言語がいいでしょうか。簡潔に回答してください。日本語でお願いします。[/INST]"

肝心の llava は、まず下記サイトから、ggml-model-q4_k.ggufmmproj-model-f16.ggufをダウンロードします。

https://huggingface.co/mys/ggml_llava-v1.5-7b/tree/main

その後、modelsフォルダにおいて、下記コマンドを実行します。tokyo.jpeg は適当に拾った画像を同じフォルダにおいて試しました。

./llava-cli -m models/ggml-model-q4_k.gguf --mmproj models/mmproj-model-f16.gguf --image tokyo.jpeg -p 'これは何?日本語で答えて'

いい感じに動いて感動。先人たちに感謝。

参考

https://zenn.dev/michy/articles/c3116fa7bc2e0b
https://zenn.dev/michy/articles/d13d24e5f19c56
https://qiita.com/rairaii/items/1c5c322f66b1423a74a0
https://note.com/bakushu/n/nbe8d2813c76b
https://nowokay.hatenablog.com/entry/2023/10/17/153657

llama-cpp-python

次は、llama.cpp を python 向けにラップしたライブラリ。こちらもすんなり動きました。素晴らしい。
https://github.com/abetlen/llama-cpp-python

準備はこれくらい。

pip install llama-cpp-python

まずは、llama を動かしてみます。モデルを、llama-cpp の時と同じように、事前に Hugging Face からダウンロードして使います。

main.cpp
from llama_cpp import Llama
llm = Llama(model_path="../llama.cpp/models/llama-2-7b-chat.Q4_K_M.gguf")
output = llm(
  "Q: Name the planets in the solar system? A: ", # Prompt
  max_tokens=32, # Generate up to 32 tokens
  stop=["Q:", "\n"], # Stop generating just before the model would generate a new question
  echo=True # Echo the prompt back in the output
)
print(output)

実行はこんな感じ。

python main.cpp

llava はこんな感じ。URL 指定するパターンと、ローカル画像を使うパターン。

from llama_cpp import Llama
from llama_cpp.llama_chat_format import Llava15ChatHandler
### Test
clip_model_path = "../llama.cpp/models/mmproj-model-f16.gguf"
model_path = "../llama.cpp/models/ggml-model-q4_k.gguf"

chat_handler = Llava15ChatHandler(clip_model_path=clip_model_path)
llm = Llama(
    model_path = model_path,
    chat_format = "llava-1-5",
    chat_handler = chat_handler,
    n_ctx = 2048, # n_ctx should be increased to accomodate the image embedding
    n_gpu_layers = 1,
    logits_all = True,
    verbose = False
)

output = llm.create_chat_completion(
    messages=[
        {"role": "system", "content": "あなたは完璧に画像を日本語で説明するアシスタントです"},
        {
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {"url": "https://zounokuni.com/wp/wp-content/themes/animal/images/animals/animals_list_15@2x.png"}},
                {"type": "text", "text": "この画像を日本語で詳細に説明してください。"}
            ]
        }
    ]
)
print(output)
from llama_cpp import Llama
from llama_cpp.llama_chat_format import Llava15ChatHandler
import base64

def image_to_base64_data_uri(file_path):
    with open(file_path, "rb") as img_file:
        base64_data = base64.b64encode(img_file.read()).decode('utf-8')
        return f"data:image/png;base64,{base64_data}"


file_path = '../llama.cpp/tokyo.jpeg'
data_uri = image_to_base64_data_uri(file_path)

### Test
clip_model_path = "../llama.cpp/models/mmproj-model-f16.gguf"
model_path = "../llama.cpp/models/ggml-model-q4_k.gguf"

chat_handler = Llava15ChatHandler(clip_model_path=clip_model_path)
llm = Llama(
    model_path = model_path,
    chat_format = "llava-1-5",
    chat_handler = chat_handler,
    n_ctx = 2048, # n_ctx should be increased to accomodate the image embedding
    n_gpu_layers = 1,
    logits_all = True,
    verbose = False
)

output = llm.create_chat_completion(
    messages=[
        {"role": "system", "content": "あなたは完璧に画像を日本語で説明するアシスタントです"},
        {
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {"url": data_uri}},
                {"type": "text", "text": "この画像を日本語で詳細に説明してください。"}
            ]
        }
    ]
)
print(output)

どっちもいい感じです。

Ollama

https://ollama.ai/
ダウンロードしたらすぐ llama を使えるやつです。ロゴが可愛い。
まだ、llava 使えないけど、イシューも PR も出てるのでそのうち使えそう。素晴らしい。
https://github.com/jmorganca/ollama/issues/746
https://github.com/jmorganca/ollama/pull/1216

おわりに

ざっと、試したり調べたことを整理してみました。
MacBook で動かすなら、llama.cpp と llama-cpp-python でいいかという気持ちになってます。Apple Silicon の GPU サポートがいつの間にか充実してるんだなぁと思いました。

GitHubで編集を提案

Discussion