はじめに
手元の環境でOllama + Gemma 4を使い、画像から文字を抜き出す処理を試していた。
短いテキストなら問題なく読めるのに、込み入った画像や細かい文字が多い画像になると、出力が途中で欠けたり、文字の一部を取りこぼしたりする。最初は量子化のせいかと思ってモデルのバリアントを変えてみたが、改善しない。
調べていくと、これはモデルの精度というよりOllama側の実装に起因する、わりとはっきりした原因があった。同じことで悩む人がいると思うので、経緯をまとめておく。
症状
具体的には、こういう挙動になる。
- 文字数の少ない画像はおおむね正しく読める
- 文字が密な画像、または高い解像度が要求される画像で、末尾や一部が欠ける
- モデルのパラメータ数を増やしても(たとえば27Bにしても)改善しない
最後の点が重要で、「大きいモデルにすれば直る」たぐいの問題ではなかった。これがモデルの表現力の限界ではなく、入力側のどこかでボトルネックがあることを示唆していた。
原因: vision encoderのトークン予算が固定されている
GitHubのIssueを探したところ、ずばり該当するものが見つかった。ollama/ollamaのIssue #15626だ。
要点はこうだ。Gemma 4のvision encoderは という、画像をどれだけのトークン数で表現するかの予算(token budget)を持っている。本来この値は可変解像度に対応しており、解像度の高いOCRタスクではこの予算を増やすことが推奨されている。これはGoogle公式のGemma 4ドキュメントにも明記されている。max_soft_tokens
ところがOllamaの実装では、この値が 280 にハードコードされている。場所は model/models/gemma4/process_image.go の25〜31行目あたり。そしてAPIからも ollama-python ライブラリからも、実行時にこの値を上書きする手段が用意されていない。
つまり、画像をどれだけ精細に渡したくても、Ollama経由では一律280トークンに圧縮された状態でモデルに入る。細かい文字が潰れて読めなくなるのは、ここが効いている。
どのくらい違うのか
Issueには、ナンバープレート認識を使った分かりやすい比較が載っていた。
| 構成 | 結果 | 期待される値 |
|---|---|---|
| Ollamaのデフォルト(280トークン) | YRSGNB | YRSGNBY |
HuggingFace Transformers() | YRSGNBY | YRSGNBY |
最後の1文字が欠けている。まさに「全部は読めない」という、私が遭遇したのと同じ種類の症状だ。そしてトークン予算を560に上げると正しく読めている。
なお、この劣化は量子化していないモデルでも再現するとのことで、量子化が原因ではない点もはっきりしている。
回避策
現時点で、Ollamaの公式APIに max_soft_tokens を渡す口はない。Issueのスレッドでは、ソースを直接いじってビルドする方法が報告されている。
スレッド内の報告によると、process_image.go の min/max トークンの値を引き上げたカスタムビルドを作ったところ、OCRタスクでの差は大きかったという。具体的には min を 560、max を 1120 にした例が挙げられている。一方で、値の設定によっては一部の画像でクラッシュしたり、レースコンディションらしき不安定さが出たという報告もある。また、20〜50枚といったまとまった枚数を一度に処理するとクラッシュしやすいという話も出ていた。
要するに、ソースを書き換えれば精度は明確に上がるが、安定性とのトレードオフが残っている、という状況だ。手軽に試せる回避策とは言いがたい。
なお、ここで挙げた具体的な数値(min 560 / max 1120 など)はIssueスレッドにおける個人の報告に基づくものであり、環境によっては同じ結果にならない可能性がある。試す場合はその点に留意してほしい。
実用上の選択肢を整理すると、おおむね次のようになる。
- 精度が最優先で、ローカル実行にこだわるなら、HuggingFace Transformersで直接動かして
を指定する。Issueの比較でも、こちらは期待通りの結果になっている。max_soft_tokens - どうしてもOllamaで動かしたいなら、
process_image.goを書き換えてカスタムビルドする。ただし安定性の問題は自己責任で。 - 急がないなら、ランタイムパラメータ化を求めるこのIssueの進展を待つ。
まとめ
OllamaのGemma 4で画像OCRが途中までしか読めなかったのは、モデルの精度ではなく、vision encoderのトークン予算がOllama側で280に固定されていたことが原因だった。Gemma 4自体は可変解像度に対応しているのに、その能力を実行時に引き出す手段がOllama経由では塞がれている、という構図だ。
ローカルLLMは「とりあえず動く」までは早いが、こうした実装上の制約が精度にそのまま響くことがある。期待した結果が出ないとき、モデルそのものを疑う前に、入力がどう前処理されているかを確認する価値はある、という教訓だった。
参考
- ollama/ollama Issue #15626: Expose
max_soft_tokens(image token budget) as a runtime parameter for Gemma 4 models - 関連: ollama/ollama-python Issue #651
- Google Gemma 4 ドキュメント(vision / variable resolution token budget の項)


コメント