🔖

Mac M2 チップでマルチモーダルLLM

に公開

目的

MacのM2チップ(24GB unified memory)で VLM (Vision Language Model) を動かしたくなりました。調べてみると 数ヶ月前(2025年3月くらい?)に Google から発表された Gemma 3が良さそうでした。しかも、現在では画像入力もllama.cppで扱えるようになっているようです。この記事では、Gemma 3を使って、llama.cppでVQA(Visual Question Answering)を行う方法を紹介します。

Gemma 3

Introducing Gemma 3: The Developer Guide を gpt-4o で要約すると、gemma 3 は以下のような特徴があるみたいです。

  • マルチモーダル対応: テキストと画像の入力をサポートし、視覚と言語の統合処理が可能です。
  • 長いコンテキストウィンドウ: 最大128Kトークンのコンテキストを扱えるため、長文の理解や生成が得意です。
  • 多言語対応: 140以上の言語をサポートし、グローバルな利用が可能です。
  • 高性能な推論能力: 数学的推論、コーディング、構造化出力、関数呼び出しなど、複雑なタスクにも対応します。
  • サイズバリエーション: 1B、4B、12B、27Bの4つのモデルサイズが提供されています。
  • 新しいトークナイザー: Geminiと同じトークナイザーを採用し、多言語対応が強化されています。
  • SigLIPビジョンエンコーダー: 4B、12B、27Bモデルに統合され、画像や動画の解析、質問応答、比較、物体認識、画像内のテキスト理解などが可能です。

llama-cpp-gemma-cli

若干くどいようですが、私の環境は MacのM2チップ(24GB unified memory)なので、今回は、4B の量子化されたモデルを使用します。公式(?)から 配布されている google/gemma-3-4b-it-qat-q4_0-gguf を使用することにます。
Model card をみると、llama-gemma3-cli を用いるようです。

./llama-gemma3-cli -hf google/gemma-3-4b-it-qat-q4_0-gguf \
    -p "Describe this image." \
    --image ~/Downloads/surprise.png

これは、github から llamacpp のリポジトリをクローンしてコンパイルして用意します。

git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build
cmake --build build/ --config Release
./build/bin/llama-gemma3-cli \
    -m /path/model/gemma-3-4b-it-qat-q4_0-gguf/gemma-3-4b-it-q4_0.gguf \
	-p "describe this image." \
	--image /path/image/sample.png \
	--mmproj /path/model/gemma-3-4b-it-qat-q4_0-gguf/mmproj-model-f16-4B.gguf

ローカルに保存したモデルを読み取る場合は、-hf ではなく -m で gguf ファイルを指定するようです。また、--mmproj もつけてファイルもつけるようです。./build/bin/llama-gemma3-cli --helpで確認すると以下のように書かれていました。

--mmproj FILE path to a multimodal projector file. see tools/llava/README.md

ということで、llama.cpp/tools/llav/README.mdを確認します。
以下和訳です。

llama.cpp におけるマルチモーダル対応は、画像を別のモデルコンポーネントで埋め込み(embedding)に変換し、その埋め込みを言語モデルに入力することで実現されています。
このアプローチでは、マルチモーダル処理部分を libllama のコアライブラリから切り離しています。これにより、マルチモーダル機能をより高速かつ独立して開発・改良できるようになります。多くの最新ビジョンモデルは Vision Transformer(ViT)に基づいていますが、それぞれに固有の前処理や射影ステップがあるため、これらを直接 libllama に統合するのは現在のところ困難です。
そのため、マルチモーダルモデルを実行するには、通常2つの GGUF ファイルが必要になります:
1. 通常の言語モデルファイル
2. 画像のエンコーディングと射影を行う マルチモーダルプロジェクタ(mmproj) ファイル

llama.cpp では、言語モデルと Vision encoding 部分を分けているようです。ということで、上記のコマンドを実行します。すると、

WARNING: The binary 'llama-gemma3-cli' is deprecated.
Please use 'llama-mtmd-cli' instead.

となります。どうやら、llava-climinicpmv-cligemma3-cliqwen2vl-clilibllava は、llama-mtmd-cliに統合されたようです。ということで、現時点での適切な実行コマンドは、

./build/bin/llama-gemma3-cli \
    -m /path/model/gemma-3-4b-it-qat-q4_0-gguf/gemma-3-4b-it-q4_0.gguf \
	-p "describe this image." \
	--image /path/image/sample.png \
	--mmproj /path/model/gemma-3-4b-it-qat-q4_0-gguf/mmproj-model-f16-4B.gguf

でした。

Mac M2 チップで動かしてみる

入力画像

ということで、上記コマンドで実行してみました。入力画像にはアマゾンのページを入力しました。
Amazon のページです。プロンプトはシンプルに、describe this imageとしました。

ログ

llama_model_load_from_file_impl: using device Metal (Apple M2)
...
print_info: file format = GGUF V3 (latest)
print_info: file type   = Q4_0
print_info: file size   = 2.93 GiB (6.49 BPW) 
...
ggml_metal_init: found device: Apple M2
ggml_metal_init: picking default device: Apple M2
...
load_tensors: offloading 34 repeating layers to GPU
load_tensors: offloading output layer to GPU
load_tensors: offloaded 35/35 layers to GPU
...
llama_context:      Metal compute buffer size =   517.00 MiB
llama_kv_cache_unified:      Metal KV buffer size =   544.00 MiB

これはログを抜粋したものになります。n_gpuを設定していませんが、load_tensors: offloaded 35/35 layers to GPUとあり、すべて GPU の載っていました。Active monitor を確認すると推論時には、メモリ使用率が13.52 GBが 16.92 GB に増えており、print_info: file size = 2.93 GiB (6.49 BPW) と大体一致します。また、GPUもしっかり使われていることがわかります。
Active monitor

出力

以下が出力になります。

**Overall Impression:**

The image is a screenshot of the Amazon Japan website, heavily focused on promotions and deals, particularly around the "100 Point Up!" campaign. It’s designed to be visually engaging and encourage users to shop.

**Key Elements & Layout:**

* **Top Navigation:** The standard Amazon navigation bar is visible at the top, including links to various categories and services (Prime Video, Books, etc.). The "プライムから500点领取" (Prime Rewards - Get 500 Points) banner is prominently displayed.
* **Main Promotional Banner:** A large, colorful banner dominates the center of the screen. It features a stylized image of a gift box with a large number "100" inside, suggesting a reward or discount. Text reads "音楽を聴くだけで100万ポイントUP!" (Listen to music and get 100 million points!).
* **Smaller Promotional Boxes:** Around the main banner, there are several smaller boxes highlighting different promotions:
    * **Amazon Music:** Promotes Amazon Music with a graphic of the app.
    * **Home & Kitchen:** Shows a picture of a kitchen scene with a discount offer.
    * **Kindle Unlimited:** Features a Kindle device and a discount offer.
    * **Prime Reading:** Displays a Kindle device and a discount offer.
    * **Amazon App Store:** Shows a graphic of the app.
    * **Deals:** A box with a festive Christmas-themed graphic and a discount offer.
* **Product Sections:** Below the promotional boxes, there are sections showcasing specific products:
    * **Books:** A grid of book covers.
    * **Gaming:** A prominent display of Nintendo Switch consoles and accessories.
    * **Audio:** Displays of headphones and earbuds.
* **Right-Hand Side:** The right side of the screen contains login buttons, promotional banners, and a "Scan QR Code" button.

**Color Palette & Style:**

* **Bright and Festive:** The image uses a vibrant color palette, with reds, yellows, and greens, creating a celebratory and enticing atmosphere.
* **Modern and Clean:** The overall design is clean and modern, typical of Amazon's website.

**Overall, the image is a classic example of an e-commerce website using visual marketing to drive sales and highlight current promotions.**

Do you want me to zoom in on a specific part of the image or describe something in more detail?

...。正直凄すぎるんですけど。この質の回答が 3 GB 程度のメモリで実現できるようになったことにとても驚いています。しかも、トークンの生成速度はたった 25.21 tokens/second でした。

llama_perf_context_print:        load time =     844.19 ms
llama_perf_context_print: prompt eval time =   43012.83 ms /   273 tokens (  157.56 ms per token,     6.35 tokens per second)
llama_perf_context_print:        eval time =   21342.13 ms /   538 runs   (   39.67 ms per token,    25.21 tokens per second)
llama_perf_context_print:       total time =   65194.51 ms /   811 tokens

プロンプトを この画像を解説してくださいと日本語の出力も確認しました。

この画像は、Amazonのウェブサイトのホーム画面の一部です。主な内容は、以下の通りです。

**1. メインのバナー:**

*   **「音楽を聴くだけで100万ポイントUP!」**という大きな文字で、Amazon Musicのキャンペーンを強調しています。
*   背景には、音楽を聴いている人々のイラストや、音楽記号などが配置され、音楽を聴く楽しさを表現しています。
*   キャンペーンの詳細ページへのリンクがいくつかあります。

**2. 関連コンテンツ:**

*   **Amazon Music:**  Amazon Musicのアプリ画面がいくつか表示されています。
*   **ポイント獲得:**  Amazonのポイントプログラムに関する情報がいくつか表示されています。
*   **Amazon Home & キッチン:**  Amazon Home & キッチン製品の広告が表示されています。
*   **Kindle Unlimited:** Kindle Unlimitedの広告が表示されています。
*   **Prime Reading:** Prime Readingの広告が表示されています。
*   **Amazon Echo:** Amazon Echoの広告が表示されています。

**3. 右側のナビゲーションバー:**

*   Amazonの様々なカテゴリへのリンクが並んでいます。
*   「プライムから5分読み放題」というリンクも目立ちます。

**4. 広告:**

*   様々な商品(タブレット、書籍、ヘッドホンなど)が広告として表示されています。

**全体的な印象:**

この画像は、Amazonの様々なサービスやキャンペーンをアピールする目的で作成されたものです。特に、Amazon Musicのポイントアップキャンペーンを強調し、ユーザーにAmazonのサービスを利用してポイントを獲得してもらうことを促しています。

もし、この画像についてさらに詳しい情報が必要な場合は、お気軽にお尋ねください。

日本語対応もバッチリでした。

まとめ

  • llama.cpp で VQA 対応も始まっていたようです。
  • Gemma 3、4B パラメータだけどめちゃくちゃ優秀でした。日本語も問題ないし、3 GB で 20-25 tokens/sec、しかもマルチモーダル対応で、正直感動しました。
  • このサイズで、これだけの性能があれば、家庭用PCでローカルLLMのみを用いたツール利用などが現実味を帯びてきた気がします。

Discussion