Closed9

llama.cppのマルチモーダルを試す

kun432kun432

たまたま見かけたらserverでマルチモーダル対応していた

https://github.com/ggml-org/llama.cpp/pull/12898

以前もチラホラとマルチモーダルには対応していたようなのだけど、それまではモデルアーキテクチャごとに別コマンドに別れていたのが1つのコマンドというかライブラリに集約されて、それを使ってサーバも・・・ということになったみたい。

https://github.com/ggml-org/llama.cpp/pull/13012

マルチモーダルについてのドキュメントは以下の2つを読めば良さそう

https://github.com/ggml-org/llama.cpp/tree/master/docs/multimodal.md

https://github.com/ggml-org/llama.cpp/tree/master/tools/mtmd/README.md

kun432kun432

https://github.com/ggml-org/llama.cpp/tree/master/docs/multimodal.md

マルチモーダル

llama.cpp は libmtmd を介してマルチモーダル入力をサポートします。現在、この機能をサポートするツールは 2 つあります:

次の 2 つの方法のいずれかで有効化できます:

  • 対応モデルを指定して -hf オプションを使用する(事前量子化モデルの一覧は後述)
    • マルチモーダルを無効にして -hf でモデルを読み込むには --no-mmproj を使用
    • 独自の mmproj ファイルを使用して -hf でモデルを読み込むには --mmproj local_file.gguf を使用
  • -m model.gguf オプションと --mmproj file.gguf を併用して、テキストモデルとマルチモーダルプロジェクタをそれぞれ指定する

既定では、マルチモーダルプロジェクタは GPU にオフロードされます。これを無効にするには --no-mmproj-offload を追加してください。

例:

# CLI での簡単な使用例
llama-mtmd-cli -hf ggml-org/gemma-3-4b-it-GGUF

# サーバでの簡単な使用例
llama-server -hf ggml-org/gemma-3-4b-it-GGUF

# ローカルファイルを使う場合
llama-server -m gemma-3-4b-it-Q4_K_M.gguf --mmproj mmproj-gemma-3-4b-it-Q4_K_M.gguf

# GPU オフロードなし
llama-server -hf ggml-org/gemma-3-4b-it-GGUF --no-mmproj-offload

事前量子化済みモデル

これらはすぐに使用できるモデルで、大半は既定で Q4_K_M 量子化が施されています。Hugging Face の ggml-org ページ(https://huggingface.co/ggml-org)にあります。

(tool_name) は使用するバイナリ名に置き換えてください。例: llama-mtmd-cli または llama-server

注意: 一部のモデルでは大きなコンテキストウィンドウが必要になる場合があります。例: -c 8192

# Gemma 3
(tool_name) -hf ggml-org/gemma-3-4b-it-GGUF
(tool_name) -hf ggml-org/gemma-3-12b-it-GGUF
(tool_name) -hf ggml-org/gemma-3-27b-it-GGUF

# SmolVLM
(tool_name) -hf ggml-org/SmolVLM-Instruct-GGUF
(tool_name) -hf ggml-org/SmolVLM-256M-Instruct-GGUF
(tool_name) -hf ggml-org/SmolVLM-500M-Instruct-GGUF
(tool_name) -hf ggml-org/SmolVLM2-2.2B-Instruct-GGUF
(tool_name) -hf ggml-org/SmolVLM2-256M-Video-Instruct-GGUF
(tool_name) -hf ggml-org/SmolVLM2-500M-Video-Instruct-GGUF

# Pixtral 12B
(tool_name) -hf ggml-org/pixtral-12b-GGUF

# Qwen 2 VL
(tool_name) -hf ggml-org/Qwen2-VL-2B-Instruct-GGUF
(tool_name) -hf ggml-org/Qwen2-VL-7B-Instruct-GGUF

# Qwen 2.5 VL
(tool_name) -hf ggml-org/Qwen2.5-VL-3B-Instruct-GGUF
(tool_name) -hf ggml-org/Qwen2.5-VL-7B-Instruct-GGUF
(tool_name) -hf ggml-org/Qwen2.5-VL-32B-Instruct-GGUF
(tool_name) -hf ggml-org/Qwen2.5-VL-72B-Instruct-GGUF

# Mistral Small 3.1 24B (IQ2_M 量子化)
(tool_name) -hf ggml-org/Mistral-Small-3.1-24B-Instruct-2503-GGUF

# InternVL 2.5 および 3
(tool_name) -hf ggml-org/InternVL2_5-1B-GGUF
(tool_name) -hf ggml-org/InternVL2_5-4B-GGUF
(tool_name) -hf ggml-org/InternVL3-1B-Instruct-GGUF
(tool_name) -hf ggml-org/InternVL3-2B-Instruct-GGUF
(tool_name) -hf ggml-org/InternVL3-8B-Instruct-GGUF
(tool_name) -hf ggml-org/InternVL3-14B-Instruct-GGUF
kun432kun432

https://github.com/ggml-org/llama.cpp/tree/master/tools/mtmd/README.md

llama.cpp におけるマルチモーダル対応

このディレクトリは llama.cpp にマルチモーダル機能を提供します。当初は LLaVA モデルを実行するためのショーケースとして意図されていましたが、時を経てさまざまな視覚モデルを含むまでに範囲が大きく拡張されました。その結果、LLaVA はもはや唯一の対応マルチモーダルアーキテクチャではありません。

重要

マルチモーダル対応は llama.cpp 内のサブプロジェクトと見なすことができます。開発が非常に活発であり、後方互換性のない変更が入る可能性があります

マルチモーダル対応に関する命名と構造は進化を続けており、混乱を招く場合があります。以下の簡単なタイムラインが経緯を示します:

  • #3436: LLaVA 1.5 の初期サポートが追加され、llava.cppclip.cpp が導入されました。モデルと対話するための llava-cli バイナリが作成されました。
  • #4954: MobileVLM のサポートが追加され、2 番目の視覚モデルとなりました。既存の llava.cppclip.cppllava-cli 基盤を活用しています。
  • 拡張と分裂: その後多くの新しいモデルが追加されました(例: #7599#10361#12344 など)。しかし llava-cli はこれらのモデルが必要とする複雑化するチャットテンプレートをサポートできず、qwen2vl-climinicpmv-cligemma3-cli などモデル固有バイナリが乱立しました。機能的ではあるものの、CLI の増殖はユーザーを混乱させました。
  • #12849: libmtmd が導入され、llava.cpp の置き換えとして登場しました。その目的は、単一で統一されたコマンドラインインターフェースを提供し、ユーザー/開発者体験(UX/DX)を向上させ、音声と画像の両入力に対応することです。
  • #13012: mtmd-cli が追加され、libmtmd を基盤としてモデル固有 CLI を 1 つのツールに統合しました。

事前量子化済みモデル

事前量子化モデルの一覧はこちらをご覧ください。

仕組みと mmproj とは?

llama.cpp のマルチモーダル対応は、画像を別個のモデルコンポーネントで埋め込みにエンコードし、これを言語モデルに入力することで機能します。

このアプローチにより、マルチモーダル要素をコアの libllama から分離できます。分離することで、迅速かつ独立した開発サイクルが可能になります。多くの最新視覚モデルは Vision Transformer(ViT)を基盤としていますが、前処理と投影の手順は大きく異なる場合があります。こうした多様な複雑性を直接 libllama に統合するのは現状では困難です。

そのため、マルチモーダルモデルを実行するには通常 2 つの GGUF ファイルが必要です:

  1. 標準の言語モデルファイル
  2. 画像のエンコードと投影を行う マルチモーダルプロジェクタ(mmproj ファイル

libmtmd とは?

前述の経緯のとおり、libmtmd はマルチモーダル入力を扱うための、元の llava.cpp 実装に代わる最新ライブラリです。

llava.cpp と同様に clip.cpp を基盤として構築され、以下の利点があります:

  • 統一インターフェース: さまざまなマルチモーダルモデルとの対話を一元化することを目指します。
  • UX/DX 向上: Hugging Face の transformers ライブラリの Processor クラスに着想を得た、より直感的な API を提供します。
  • 柔軟性: 多様なチャットテンプレートを尊重しつつ、テキスト・音声・画像など複数の入力タイプをサポートするよう設計されています。

mmproj の入手方法

mmproj(マルチモーダルプロジェクタ)ファイルはモデルアーキテクチャごとに固有です。

以下のモデルについては、convert_hf_to_gguf.py--mmproj フラグ付きで使用して mmproj ファイルを取得できます:

  • Gemma 3(ガイドはこちら)※1B 版はビジョン非対応
  • SmolVLM(HuggingFaceTB より)
  • SmolVLM2(HuggingFaceTB より)
  • Pixtral 12Btransformers 互換チェックポイントのみ対応
  • Qwen 2 VL と Qwen 2.5 VL(Qwen より)
  • Mistral Small 3.1 24B
  • InternVL 2.5 と InternVL 3(OpenGVLab より)
    InternVL3-*-hf モデルの変換は未対応、非 HF 版のみサポート
    InternLM2Model テキスト モデルは未対応

旧モデルについては、該当するガイドを参照して取得または作成してください:

注意: 変換スクリプトは tools/mtmd/legacy-models にあります

kun432kun432

今回はお手軽にやりたいのでローカルのMac(M2 Pro)でやる。

レポジトリクローン

git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp

ビルド

cmake -B build
cmake --build build --config Release -j 8

llama-server と llama-mtmd-cli がビルドされている

ls -lt build/bin/{llama-server,llama-mtmd-cli}
出力
-rwxr-xr-x@ 1 kun432  staff  4448032  5 14 22:31 build/bin/llama-server
-rwxr-xr-x@ 1 kun432  staff  2175280  5 14 22:31 build/bin/llama-mtmd-cli
kun432kun432

ではまず llama-mtmd-cli を使ってみる。モデルは事前量子化されたgemma-3-4b-itを使う。

./build/bin/llama-mtmd-cli -hf ggml-org/gemma-3-4b-it-GGUF

モデルのダウンロードが行われるのでしばらく待つ。以下のように表示されればOK。

(snip)
main: loading model: /Users/kun432/Library/Caches/llama.cpp/ggml-org_gemma-3-4b-it-GGUF_gemma-3-4b-it-Q4_K_M.gguf

 Running in chat mode, available commands:
   /image <path>    load an image
   /clear           clear the chat history
   /quit or /exit   exit the program

>

ということで画像をロード。以下の画像を使う。

> /image ./kobe.jpg
Image ./kobe.jpg loaded

チャットしてみる

> この画像について簡潔に説明して。
出力
encoding image or slice...
image/slice encoded in 15061 ms
decoding image batch 1/1, n_tokens_batch = 256
image decoded (batch 1/1) in 474 ms

この画像は、日本の瀬戸内海の街、岡山港の風景です。

特徴的なのは、赤い塔が印象的な「岡山観覧車」と、背景にそびえる高層ビル群です。穏やかな海面と青空が、この景観をより一層美しくしています。また、港には船が停泊しており、活気のある様子が伝わってきます。

知識は足りないが、画像の内容は理解しているね。

ワンライナーでも

./build/bin/llama-mtmd-cli \
    -hf ggml-org/gemma-3-4b-it-GGUF \
    --image kobe.jpg \
    -p "この画像について説明して"
出力
(snip)

この画像は、日本、瀬戸内海の街、岡山(Okayama)の港風景です。

主な特徴は以下の通りです。

*   **岡山ドーム:** 鮮やかな赤色の巨大なドーム状の建造物で、岡山ドームのシンボルです。
*   **岡山駅ビル:** ドームの左側に位置する高層ビルです。
*   **港:** ドームと駅ビルの間に広がる港には、船や遊覧船が停泊しています。
*   **海:** 港の背景には、青い海が広がっています。
*   **空:** 青空の下、太陽が輝いています。

全体として、岡山港の美しい風景と、岡山ドームの存在感が印象的な写真です。


llama_perf_context_print:        load time =     394.28 ms
llama_perf_context_print: prompt eval time =   15805.24 ms /   273 tokens (   57.89 ms per token,    17.27 tokens per second)
llama_perf_context_print:        eval time =    3688.90 ms /   165 runs   (   22.36 ms per token,    44.73 tokens per second)
llama_perf_context_print:       total time =   20184.48 ms /   438 tokens
kun432kun432

次に llama-server を使ってみる

./build/bin/llama-server -hf ggml-org/gemma-3-4b-it-GGUF

以下のように表示されればOK

出力
(snip)
main: server is listening on http://127.0.0.1:8080 - starting the main loop
srv  update_slots: all slots are idle

サーバへのアクセスは

  • GUI
  • OpenAI互換API

の2種類ある。というか、GUIあるのずっと知らなかったよ・・・

まずGUI。ブラウザで上記のURLにアクセスするとこんな画面になる。

画像を添付して質問するとこんな感じ。

次にAPI。こちらはOpenAI互換APIになっている。

curlでアクセス。

curl http://localhost:8080/v1/chat/completions  \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ggml-org_gemma-3-4b-it-GGUF_gemma-3-4b-it-Q4_K_M.gguf",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "この画像について詳しく説明して"
          },
          {
            "type": "image_url",
            "image_url": {
              "url": "https://storage.googleapis.com/zenn-user-upload/82968d23b6c5-20250228.jpg"
            }
          }
        ]
      }
    ],
    "max_tokens": 1024
  }' | jq -r .
出力
{
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "この画像は、日本の瀬戸内海に面した街並みを捉えた写真です。以下に詳細な説明をします。\n\n**主な要素:**\n\n* **観覧車 (マササゴ観覧車):** 画面の中央に大きく見えます。これは、岡山県岡山市の中心部に位置する、特徴的な赤い観覧車です。そのユニークな形状と、街のランドマークとして知られています。\n* **高層ビル群:** 観覧車を中心に、様々なデザインの高層ビルが並んでいます。これらのビルは、現代的な都市の景観を形成しています。\n* **港と水面:** 画面下部には、穏やかな海面が広がっています。海面には、太陽光を反射した水面の波紋や、船の影が映り込み、奥行きと動きを加えています。\n* **船:** 画面右側に、クルーズ船や小型の船が停泊しています。\n* **背景:** 背景には、緑豊かな山々が連なり、自然との調和を感じさせます。\n\n**場所:**\n\nこの画像は、岡山県の岡山市の中心部にある、岡山港の風景を撮影したものと思われます。観覧車は、岡山港のシンボルとして、観光客に人気のスポットです。\n\n**雰囲気:**\n\n全体的に、晴天で穏やかな雰囲気の画像です。高層ビルと観覧車が織りなす景色は、現代的な都市の魅力を伝えます。\n\n**補足:**\n\n* 撮影角度は、港から陸地に向かって撮影されたものと思われます。\n* 写真の構図は、観覧車を強調し、都市の景観全体を捉えるように工夫されています。\n\nこの説明で、この画像についてより深く理解できることを願っています。"
      }
    }
  ],
  "created": 1747234286,
  "model": "ggml-org_gemma-3-4b-it-GGUF_gemma-3-4b-it-Q4_K_M.gguf",
  "system_fingerprint": "b5381-5e7d95e2",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 384,
    "prompt_tokens": 274,
    "total_tokens": 658
  },
  "id": "chatcmpl-5tOwxMi3h4hGBZ7F6voCfSewFycrExW2",
  "timings": {
    "prompt_n": 1,
    "prompt_ms": 118.884,
    "prompt_per_token_ms": 118.884,
    "prompt_per_second": 8.411560849231183,
    "predicted_n": 384,
    "predicted_ms": 8035.073,
    "predicted_per_token_ms": 20.924669270833334,
    "predicted_per_second": 47.790480559417446
  }
}

Pythonで。

sample.py
from openai import OpenAI

client = OpenAI(
  api_key="dummy",
  base_url="http://localhost:8080/v1",
)

response = client.chat.completions.create(
    model="ggml-org_gemma-3-4b-it-GGUF_gemma-3-4b-it-Q4_K_M.gguf",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "この画像について詳しく説明して。"},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://storage.googleapis.com/zenn-user-upload/82968d23b6c5-20250228.jpg",
                    }
                },
            ],
        }
    ],
    max_tokens=1024,
)

print(response.choices[0].message.content)
出力
この画像は、日本の瀬戸内海の街、岡山(Okayama)のハーバーランドの風景です。以下に詳細を説明します。

**主な要素:**

*   **岡山港:** 海面に映る波の模様が特徴的な岡山港が前景に広がっています。
*   **岡山港ターミナル:** 港の左側に、モダンなデザインのターミナルビルが並んでいます。
*   **岡山港タワー:** 画像の中心にそびえ立つのは、岡山港タワーです。特徴的なデザインで、赤と白のラインが印象的です。
*   **岡山駅ビル:** 岡山港タワーの近くには、岡山駅ビルの白い建物が見えます。
*   **船:** 港に停泊しているクルーズ船や客船が、風景の一部として入っています。
*   **背景の山:** 岡山港の背後には、緑豊かな山々が連なっています。
*   **空:** 青空が広がり、晴れた日中の風景を写しています。

**場所:**

この写真は、岡山港のハーバーランド地区を撮影したものです。ハーバーランドは、岡山港を背景にした、ショッピングやレジャーが楽しめるエリアです。

**全体的な印象:**

全体的に、岡山港の活気と、近未来的な建築物との調和が感じられる写真です。晴れた日中の明るい空と、海面を照らす光が、この風景をより魅力的にしています。

もし、この写真について何か特定の質問があれば、遠慮なく聞いてください。

ローカルのファイルをBASE64に変換して送信することもできる。

sample2.py
from openai import OpenAI
import base64

def encode_image(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode("utf-8")

image_path = "kobe.jpg"
base64_image = encode_image(image_path)

client = OpenAI(
  api_key="dummy",
  base_url="http://localhost:8080/v1",
)

response = client.chat.completions.create(
    model="ggml-org_gemma-3-4b-it-GGUF_gemma-3-4b-it-Q4_K_M.gguf",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "この画像について詳しく説明して。"},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{base64_image}",
                    }
                },
            ],
        }
    ],
    max_tokens=1024,
)

print(response.choices[0].message.content)
kun432kun432

でここまではggml-org公式の事前量子化済+mmproj作成済のモデルを使用していた。自分でこれを作るにはどうすればいいか?

以下のモデルについては、convert_hf_to_gguf.py を --mmproj フラグ付きで使用して mmproj ファイルを取得できます:

ということでllama.cpp側でも対応済みである必要があるのだが、mmprojファイル付きのGGUF化を試してみる。例としてgemma-3-4b-itを使う。

https://huggingface.co/google/gemma-3-4b-it

まあこれはドキュメントが用意されているのだけどね。一応写経ということで。

https://github.com/ggml-org/llama.cpp/tree/master/docs/multimodal/gemma3.md

llama.cppのクローンしたレポジトリの直下で作業を続ける。

まずHuggingFaceからモデルをダウンロード

git lfs install
git clone https://huggingface.co/google/gemma-3-4b-it

GGUF変換を行うスクリプトはPython環境が必要なので、仮想環境を用意して、パッケージをインストールする。

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

convert_hf_to_gguf.pyを使って、モデルをGGUFに変換する。この時--mmprojを指定する

cd gemma-3-4b-it
python ../convert_hf_to_gguf.py --outfile gemma-3-4b-it-f16.gguf --outtype f16 --mmproj .
出力
INFO:hf-to-gguf:Model successfully exported to mmproj-gemma-3-4b-it-f16.gguf

mmprojファイルが生成されている

ls -lt  *.gguf
出力
-rw-r--r--@ 1 kun432  staff  851251552  5 15 08:29 mmproj-gemma-3-4b-it-f16.gguf

ただしモデルの本体はまだGGUF化されていない。一緒にやってくれるのかなと思ったんだけどね。とりあえずこちらもGGUF作成する。--mmprojを外して再度実行。

python ../convert_hf_to_gguf.py --outfile gemma-3-4b-it-f16.gguf --outtype f16 .

これでモデル本体のGGUFファイルと、mmprojファイルができた。

ls -lt  *.gguf
出力
-rw-r--r--@ 1 kun432  staff  7767474208  5 15 08:31 gemma-3-4b-it-f16.gguf
-rw-r--r--@ 1 kun432  staff   851251552  5 15 08:29 mmproj-gemma-3-4b-it-f16.gguf

これがあれば推論できるはずなんだけど、一旦量子化する。

../build/bin/llama-quantize gemma-3-4b-it-f16.gguf gemma-3-4b-it-Q4_K_M.gguf Q4_K_M

では推論

../build/bin/llama-mtmd-cli \
    -m gemma-3-4b-it-Q4_K_M.gguf \
    --mmproj mmproj-gemma-3-4b-it-f16.gguf \
    --image ../kobe.jpg \
    -p "この画像について説明して。"

エラー・・・

出力
(snip)
main: loading model: gemma-3-4b-it-Q4_K_M.gguf
encoding image or slice...
ggml_metal_graph_compute: command buffer 1 failed with status 5
error: Internal Error (0000000e:Internal Error)
clip_image_batch_encode: ggml_backend_sched_graph_compute failed with error -1
failed to encode image
failed to eval chunk 1
Unable to eval prompt

うーん、ちょっとよくわからない・・・ちなみに量子化しないままでも試してみたんだけど、同じエラーになった。まあまだEXPERIMENTALだし一旦寝かしておくか。

追記20250515

Linux(Ubuntu-22.04)上で、上と全く同じ手順でやってみたら上手く行った。んー、Macだと上手くいかないのかな?

出力
(snip)
main: loading model: gemma-3-4b-it-Q4_K_M.gguf
encoding image or slice...
image/slice encoded in 411 ms
decoding image batch 1/1, n_tokens_batch = 256
image decoded (batch 1/1) in 442 ms

この画像は、日本の瀬戸内海の街、岡山(Okayama)のハーバーランドの風景です。

主な特徴は以下の通りです。

*   **岡山ドーム:** 画面中央にそびえ立つ、特徴的な赤いドーム型の建造物です。
*   **ハーバーランド:** 岡山ドームの周辺には、商業施設やホテルなど、現代的な建物が並んでいます。
*   **海と船:** 水面に映る太陽の反射が美しく、背景には海と停泊している船が見えます。
*   **晴天:** 青空の下、晴れた日差しが印象的です。

このハーバーランドは、岡山市を活性化させるプロジェクトの一環として開発された複合施設で、観光や商業の中心地となっています。


llama_perf_context_print:        load time =     490.27 ms
llama_perf_context_print: prompt eval time =    1113.12 ms /   274 tokens (    4.06 ms per token,   246.15 tokens per second)
llama_perf_context_print:        eval time =    8169.85 ms /   168 runs   (   48.63 ms per token,    20.56 tokens per second)
llama_perf_context_print:       total time =    9589.03 ms /   442 tokens
kun432kun432

とりあえずマルチモーダルもお手軽に使えるようになってきたということで、今後に期待。

個人的にはOvis2が使えるようになると嬉しいなぁ・・・・

このスクラップは4ヶ月前にクローズされました