Ubuntu Weekly Recipe

第897回GPUに画像の文字を解析させる⁠⁠⁠あるいはQwen3-VL入門

今回はllama.cppでQwen3-VLを動作させ、文字を解析させる方法を紹介します。

llama.cpp+Qwen3-VL

第880回PyTorchを使用した画像の文字認識について解説しました。

これはこれでいいのですが、第891回などで紹介したllama.cppという、もう少し扱いやすいものでも画像の解析ができたら便利です。

それが現在ではQwen3-VLのリリースとllama.cppの該当コミットによって可能となりました。

しかもQwen3-VLはパラメータ数などによって多数のバリエーションがあります。これはいろいろなパターンを試したくなります。

というわけで、筆者が過去に撮影した写真の中からたくさんの文字が書かれた看板等をピックアップし、解析させることにしました。一体どんな結論になるのでしょうか。

llama.cppのビルドなど

使用したPC、llama.cppのビルド方法に関しては第891回とほぼ同じです。その記事ではGeForce RTX 5060 Ti 16G VENTUS 2X OC PLUSとRD-RX9060XT-E16GB/DFを使用していますが、今回の記事で使用したのは前者のみです。

llama.cppのビルド方法等は第891回を参照してください。

使用したモデル

今回使用したモデルは次のとおりです。

パラメータ数 Instruct Thinking
2B 2B-Instruct 2B-Thinking
4B 4B-Instruct 4B-Thinking
8B 8B-Instruct 8B-Thinking
30B(A3B) 30B-A3B-Instruct 30B-A3B-Thinking

以上の8パターンでモデルは他にもありますが、ディスクリートGPUのVRAM容量(16GB)を鑑みると、このあたりが妥当という結論に達しました。

Instructはその名のとおり入力された指示を素直に解釈し、Thinkingもその名のとおり回答の前に考えます。前者のほうが出力までの時間は短いですが、後者のほうが期待する回答に近くなるというのが一般的な解釈です。

そして2〜8BはDense、30BはMoEモデルです。30Bのアクティブパラメータは3Bです。

直感的には、画像に書かれている文字をシンプルに読み取るだけであればさほどのパラメータ数は必要とせず、ある程度のところで頭打ちになるのではないかと考えてしまいますが、そのあたりもよく確認したいところです。

llama.cppのオプション

チュートリアルによると、llama.cppのオプションはInstructとThinking、DenseとMoEで異なるため、実際に実行したコマンド例を提示することとしました。

$ ./build-cuda/bin/llama-server --model ~/Downloads/model/2bi/Qwen3-VL-2B-Instruct-Q8_0.gguf --mmproj ~/Downloads/model/2bi/mmproj-F16.gguf --n-gpu-layers 99 --jinja --top-p 0.8 --top-k 20 --temp 0.7 --min-p 0.0 --flash-attn on --presence-penalty 1.5 --ctx-size 32768 --host 0.0.0.0 --port 8080
$ ./build-cuda/bin/llama-server --model ~/Downloads/model/2bt/Qwen3-VL-2B-Thinking-Q8_0.gguf --mmproj ~/Downloads/model/2bt/mmproj-F16.gguf --n-gpu-layers 99 --jinja --top-p 0.96 --top-k 20 --temp 1.0 --min-p 0.0 --flash-attn on --presence-penalty 0.0 --ctx-size 32768 --host 0.0.0.0 --port 8080
$ ./build-cuda/bin/llama-server --model ~/Downloads/model/4bi/Qwen3-VL-4B-Instruct-Q8_0.gguf --mmproj ~/Downloads/model/4bi/mmproj-F16.gguf --n-gpu-layers 99 --jinja --top-p 0.8 --top-k 20 --temp 0.7 --min-p 0.0 --flash-attn on --presence-penalty 1.5 --ctx-size 32768 --host 0.0.0.0 --port 8080
$ ./build-cuda/bin/llama-server --model ~/Downloads/model/4bt/Qwen3-VL-4B-Thinking-Q8_0.gguf --mmproj ~/Downloads/model/4bt/mmproj-F16.gguf --n-gpu-layers 99 --jinja --top-p 0.96 --top-k 20 --temp 1.0 --min-p 0.0 --flash-attn on --presence-penalty 0.0 --ctx-size 32768 --host 0.0.0.0 --port 8080
$ ./build-cuda/bin/llama-server --model ~/Downloads/model/8bi/Qwen3-VL-8B-Instruct-Q8_0.gguf --mmproj ~/Downloads/model/8bi/mmproj-F16.gguf --n-gpu-layers 99 --jinja --top-p 0.8 --top-k 20 --temp 0.7 --min-p 0.0 --flash-attn on --presence-penalty 1.5 --ctx-size 32768 --host 0.0.0.0 --port 8080
$ ./build-cuda/bin/llama-server --model ~/Downloads/model/8bt/Qwen3-VL-8B-Thinking-Q8_0.gguf --mmproj ~/Downloads/model/8bt/mmproj-F16.gguf --n-gpu-layers 99 --jinja --top-p 0.96 --top-k 20 --temp 1.0 --min-p 0.0 --flash-attn on --presence-penalty 0.0 --ctx-size 32768 --host 0.0.0.0 --port 8080
$ ./build-cuda/bin/llama-server --model ~/Downloads/model/30bi/Qwen3-VL-30B-A3B-Instruct-Q4_K_M.gguf --mmproj ~/Downloads/model/30bi/mmproj-F16.gguf --n-gpu-layers 32 --jinja --top-p 0.8 --top-k 20 --temp 0.7 --min-p 0.0 --flash-attn on --presence-penalty 1.5 --ctx-size 32768 --host 0.0.0.0 --port 8080
$ ./build-cuda/bin/llama-server --model ~/Downloads/model/30bt/Qwen3-VL-30B-A3B-Thinking-Q4_K_M.gguf --mmproj ~/Downloads/model/30bt/mmproj-F16.gguf --n-gpu-layers 32 --jinja --top-p 0.96 --top-k 20 --temp 1.0 --min-p 0.0 --flash-attn on --presence-penalty 0.0 --ctx-size 32768 --fit on --host 0.0.0.0 --port 8080

ポイントとしては、モデル毎にmmprojオプションで指定するファイルも必要であるということです。ファイル名は同じですが、モデル毎に異なったものがリリースされており、そちらもダウンロードを忘れないでください。

画像の解析

前述のとおり主として筆者が撮影した写真の文字を解析させますが、スクリーンショットを解析させたものもあります。また誤字は手でカウントしたものを使用しているので、正確でない可能性が高いです。数%の誤差はあらかじめご了承ください。LLMに解析させようかと思って相当頑張りましたが、正確な値からは遠かったのです。話題の某モデルなんてマイナスの値が出てきてひっくり返りました。最適化が足りていないようです。

ローカルLLM冬の時代へ

まずは第891回の一節のスクリーンショットを撮影し、文字を解析させました。

図1 第891回の一節

結果は次のとおりです。

モデル 正答率
2B-Instruct 94.3%
2B-Thinking 85.6%
4B-Instruct 98.9%
4B-Thinking 91.8%
8B-Instruct 99.6%
8B-Thinking 98.8%
30B-A3B-Instruct 98%
30B-A3B-Thinking 100%

パラメータ数が少ない場合、ThinkingよりもInstructのほうが結果が良くなります。⁠下手の考え休むに似たり」という状態になっているのでしょう。

全体的に正答率は高く、30B-A3B-Thinkingは全く誤りがありませんでした。これは筆者の文章が理解しやすいということを示している、のだったらいいのですが。

愛国駅と国鉄広尾線の歩み

次からは写真です。広尾線旧愛国駅(現交通公園)にある看板です。

図2 愛国駅の看板

白地に黒文字で、光量も充分であり、文字はかなり読みやすいです。結果は次のとおりです。

モデル 正答率
2B-Instruct 97.6%
2B-Thinking 98.9%
4B-Instruct 99.7%
4B-Thinking 98.9%
8B-Instruct 99.7%
8B-Thinking 95.7%
30B-A3B-Instruct 100%
30B-A3B-Thinking 100%

軒並み正答率は高いですが、8B-Thinkingが一番正答率が低いという意外な結果になりました。文脈から勝手に付け足したり変更したりしていて元の文意が損なわれたわけではないものの、生成LLMの一筋縄ではいかないところです。

昭和館について

次は九段にある昭和館の案内です。手前の「昭和館」は文字数にはカウントしていません。

図3 昭和館について

こちらは黒字に白ですが読みやすいです。結果は次のとおりです。

モデル 正答率
2B-Instruct 97.8%
2B-Thinking 94.5%
4B-Instruct 99.5%
4B-Thinking 98.1%
8B-Instruct 100%
8B-Thinking 98.6%
30B-A3B-Instruct 99.5%
30B-A3B-Thinking 100%

こちらも軒並み正答率は高いですが、⁠労苦」が引っかかっていることが多かったのが印象的でした。⁠苦労」はよく使いますが、⁠労苦」はあまり使わないので、別の漢字に置き換えているという感じです。もちろんパラメータ数が多くなるとそのようなこともなくなるのですが。

JR新橋駅の動輪と鉄道唱歌の碑

これはJR新橋駅にある動輪と鉄道唱歌の碑の説明です。筆者は対象物とその説明を連続して撮影し、後から見てそれが何か思い出せるようにする、ということをよくやります。

図4 動輪と鉄道唱歌の碑の説明

経年劣化で読みにくくなっており、かつ光の当たり方が違うところがあります。正答率に影響を与えそうです。

モデル 正答率
2B-Instruct 97.2%
2B-Thinking 86.3%
4B-Instruct 97.2%
4B-Thinking 90.1%
8B-Instruct 98.2%
8B-Thinking 90.8%
30B-A3B-Instruct 97%
30B-A3B-Thinking 99.2%

100%の正解がなくなりましたが、傾向としてはこれまでと同様です。⁠1,115両」の解釈の誤りが多かったように見受けられました。

今治電信発祥の地

愛媛県今治市の港付近をふらふらと散歩していて見つけたのが、今治電信発祥の地です。

図5 今治電信発祥の地

ご覧のとおり文字数は少ないもののかなり読みにくいので、正解を掲示します。

今治電信発祥の地

明治11年9月25日、ここに今治電信分局
が設置され、愛媛県で始めてモールス通信によ
る電報の取扱いが開始された。
日本電信電話公社発足20周年を記念
してこれを刻む。
昭和47年10月23日
日本電信電話公社

結果は以下のとおりです。

モデル 正答率
2B-Instruct 95.3%
2B-Thinking 87.9%
4B-Instruct 99%
4B-Thinking 86.9%
8B-Instruct 99%
8B-Thinking 100%
30B-A3B-Instruct 99%
30B-A3B-Thinking 98.1%

正答率は低くないですが、正解は8B-Thinkingだけだったというのが興味深いです。

JR四国管内の運行について

これは2025年3月23日にJR松山駅で撮影した運行案内です。特に影響を受けたわけではなく(前日に松山入りして鯛めしを食べていたため⁠⁠、たまたま撮影していました。

図6 JR四国管内の運行について
モデル 正答率
2B-Instruct 84%
2B-Thinking -
4B-Instruct 89.8%
4B-Thinking 84.4%
8B-Instruct 88.6%
8B-Thinking 87.6%
30B-A3B-Instruct 86.6%
30B-A3B-Thinking 99%

固有名詞が多いからか、パラメータ数の多寡ではっきりと結果が分かれました。2B-Thinkingはあまりも間違いが多すぎて数えるのを断念しました。0%だと思っていただいて結構です。

淡島の概要

これはあわしまマリンパークで見つけた淡島の案内です。名前が似ているので親近感があります。

図7 淡島の概要

これは見るからに難しそうです。文字数が多いのもそうですが、固有名詞も多く、また「珍らしい」「㍍」などあまり使わない表現が見受けられ、LLMと相性が悪そうです。

モデル 正答率
2B-Instruct 93.2%
2B-Thinking 87.9%
4B-Instruct 94.9%
4B-Thinking 91.1%
8B-Instruct 97.4%
8B-Thinking 91.4%
30B-A3B-Instruct 96.7%
30B-A3B-Thinking 97.7%

懸念していたよりも悪くはなかったです。結果を見ると「㍍」「m」に置き換えていたりします。これは誤りとしていますが、意味合いとしては正解で、文意としては損なっていません。

かかった時間

7つの画像を一括で処理し、かかった時間は次のとおりです。

モデル 時間(秒)
2B-Instruct 17.91
2B-Thinking 32.22
4B-Instruct 33.99
4B-Thinking 86.46
8B-Instruct 57.84
8B-Thinking 131.90
30B-A3B-Instruct 158.31
30B-A3B-Thinking 353.11

まとめ

今回の例では30B-A3B-Thinkingが高性能を発揮しましたが、同時にダントツで時間がかかっています。性能と時間のバランスを考えたら、8B-Instructが妥当に思われました。VRAMが8GBしかないGPUであっても動作しそうなところも魅力的です。

また2B-Instructであっても精度はかなりのものなので、このクラスのモデルがNPUで動作するようになったら、また違う世界が見えてきそうです。ハードウェア的にもソフトウェア的にも、まだまだ伸びしろがありそうでLLMは本当に面白い技術です。

おすすめ記事

記事・ニュース一覧