Qwen3、GLM、GPT-ossなどクソデカ言語モデルを試したところGLM-4.5-Airがお気に入り

Mac Studioを借りたのでいろいろクソデカ言語モデルを試したところ、GLM-4.5-Airがいいなとなってます。

試したモデルこちら。

モデル パラメータ数 アクティブ thinking 画像 時期 URL
GPT-oss 120B 120B 5B o x 2025/8 OpenAI hf link
Qwen3 235B-A22B-2507-thinking 235B 22B o x 2025/8 Alibaba hf link
Qwen3-VL 235B-A22B-thinking 235B 22B o o 2025/9 Alibaba hf link
Qwen3-Next-80B 80B 3B o x 2025/9 alibaba hf link
Qwen3 Coder 480B 480B 35B x x 2025/7 alibaba hf link
Qwen3 Coder 30B 30B 3B x x 2025/7 alibaba hf link
GLM 4.6 355B 32B o x 2025/9 Z.ai hf link
GLM 4.5-Air 106B 12B o x 2025/8 Z.ai hf link
GLM-4.5V 106B 12B o o 2025/8 Z.ai hf link
MiniMax M2 230B 10B o x 2025/10 MiniMax AI hf link
Llama 4 Maverick 400B 17B x o 2025/4 Meta hf link
Llama 4 Scout 109B 17B x o 2025/4 Meta hf link

雑感

Macで実用できるのはGPT-oss 120B、GLM 4.5-Air、Qwen3-Next 80Bというところ。その中ではGLM-4.5-Airがよかった。

GPT-ossは生意気さがあるw。能力がそこまで高くないのにChatGPT(GPT-5)のようなふるまいをするところが。あと文章を書くのが苦手で物語を書かせても箇条書きを多用してしまう。
Qwen3-Nextは、単発のクエリーは強いけどやりとりが続くと弱い印象。
GLM-4.5-Airはバランスいい。コーディング力も高い。

200B以上のモデルはMacでは遅すぎて使い物になりません。出力スピードはいいのだけど、コンテキストがちょっと長くなると入力プロンプト処理の5分とかかかるようになります。
ただ要約したいとか1ショットで収まるならいいです。そうするとGLM 4.6がよかった。 Qwen3 Coder 480もかなり実装力が高いです。さくらで使おう。3000リクエスト/月が無料。

MiniMax M2は、日本語が怪しくてキリル文字が入りまくるのと、Roo Codeでうまくファイルを作ってくれないのとで、よさそうだけど使えない。あと、なんかやりとりが続くとガンコ。エージェントの学習でうまくいったパスしか学習してないとかかな。

Llama 4は、1/4サイズのモデルと勝負する感じ。なので、Maverickは案外いいのだけど、400Bで100Bモデルと競っても・・・というところ。Scoutはアホい。
貴重な画像認識モデルで、画像認識は優秀そうだけど、テキスト性能が悪い。

GLM 4.5Vはチャットテンプレートがおかしいのか、うまく動きませんでした。

なので画像モデルとしてはQwen3-VL 235Bとなるけど、画像エンコーダーはQwen3-VL 8Bなどと同じ気配なので、画像目的なら小さいモデルでよさそう。

DeepSeek-OCRの弱点をつく

DeepSeek-OCRの仕組みが面白いので遊んでしまっている。

最初に試したときは、純粋にOCRさせてますね。きれいな心をしている。
画像でテキストをトークン圧縮するDeepSeek-OCRがいろいろすごい - きしだのHatena

そして前回はランダムな文字列を読ませて誤認識を誘ってみた。
DeepSeek-OCRにはランダム文字列が読めない - きしだのHatena

もう2つ、弱点ぽいところをついてみる。

その前に、DeepSeek-OCRの構造を確認。

https://arxiv.org/abs/2510.18234

ここで、DeepEncoderがSAM->Conv->CLIPってなってるのがキモ。

SAM(Segment Anything Model)は、画像の領域分けをする仕組み。
GitHub - facebookresearch/segment-anything: The repository provides code for running inference with the SegmentAnything Model (SAM), links for downloading the trained model checkpoints, and example notebooks that show how to use the model.

たぶん、任天堂有価証券報告書の場合だとこんな感じで領域分けしてくれるやつ。

Convはたぶん普通のニューラルネットで、SAMの出力を1/16にしてくれる。
SAM->Convがトークナイザとして扱われてる。

CLIPは、ここで試してる、同内容の画像とテキストのベクトル表現を一致させる仕組み。
日本語CLIPを使って画像検索を作ったら素晴らしすぎた - きしだのHatena

CLIPがMoEモデルに投入するエンベディングベクトルを作ってくれる。

たぶん、「あ」の表のなかに「め」やら「ぬ」やら「ね」やら入れておくと、トークナイザ部分を困らせることができる気がする。

そして、日本語っぽいけど実は日本語ではない、みたいな文でCLIPを困らせることができる。
右上の「わしたは」も「わたしは」になってたりする。

仕組み上の弱点かなと思ったところをついてみると実際に弱かったりするの楽しい。

DeepSeek-OCRにはランダム文字列が読めない

DeepSeek-OCRの精度が高くて驚いたところですが、仕組み的にランダムな文字列での認識率がかなり落ちるんではないかと試してみたところ、やっぱりかなり悪かったです。

DeepSeek-OCRについてはこちら。
画像でテキストをトークン圧縮するDeepSeek-OCRがいろいろすごい - きしだのHatena

DeepSeek-OCRは、画像をトークン化したほうがテキストをトークン化するより情報圧縮できるんでは、というアイデアを試すために、トークン化した画像をテキストに戻してみたらOCRとして精度があがった、というものです。

ここで、「画像のほうが情報量が多いのにトークン化したら容量増えるのでは?」ってなりますが、情報量が多いのは画像を画像として復元する場合で、画像についてお話するために必要十分な情報としてであればそこまで多くならないはずです。

テキストのトークン化は、よくある文字の並びを少ないトークンで表せるように学習したトークナイザをつかって、テキスト中の文字を先頭からトークンに割り当てていきます。テキスト全体は見ません。
一方で、画像のトークン化では画像全体を見てトークナイズします。

たとえば、0が16個ならんでいる場合、ChatGPTでは000が1トークンになるので、全部で6トークンになります。

これを人間が覚えるとき、「0が16個」のようにするはずですね。ちなみに「0が16個」のほうがトークンがひとつ少ないです。

また、LLMというのは「続きにくるトークンを予測する」という仕組みで、続きにくるトークンの確率を割り出せます。その続くトークンの確率を利用すれば、テキストの情報圧縮はより効率的になるはずですね。

画像言語モデル用の画像エンコーダーを作るときには、画像からのベクトルと、等価な文章からのベクトルが一致するように学習を進めます。
そのときに、「続きにくるテキスト」の確率も学習してるはずです。そうすると、視覚的な情報と言語学的な情報を利用した情報圧縮ができることになります。
※ 技術的、学術的裏付けがあるわけではなく 仕組み的にそうでは?というきしだの予測です。

で、だとすると、言語学的な情報が利用できないランダムな文字列の認識は苦手ということになりますね。

ということで試したら、かなり間違ってます。特に出だしが全然違う。

YomiToku で同じ画像を認識させると、誤認識はあるけど「まぁその誤認識は仕方ないかな」というレベル。フォントサイズ小さいし。

フォントや画像サイズのせいかとも思ったので、通常文を認識させてみると、こちらはたぶん100%読み取れていました。
文章はここから。
AIが読み書きするコードも読みやすいほうがいい(トランスフォーマの特性の考慮やリーダブルコードについて追記) - きしだのHatena

あと、ランダム文字列は認識速度がかなり遅くなったのもちょっとおもしろい。

通常文字列はこんな感じ。

ランダム文字列は考える時間が長くなってます。

人間も、通常の文章は結構速く読めても、ランダムな文字だと1文字ずつゆっくりになるのと似てますね。

※追記
ケンブリッジ大学コピペ、ところどころ読めてしまっています。

これについてもまとめました。
DeepSeek-OCRの弱点をつく - きしだのHatena

ランダムな文字列を作るコードはこれ。
ひらがなカタカナ数字漢字からなるランダムな文字列の生成 · GitHub

画像でテキストをトークン圧縮するDeepSeek-OCRがいろいろすごい

おとといくらいにDeepSeek-OCRというのが出てました。
https://github.com/deepseek-ai/DeepSeek-OCR

ただのOCRじゃなくて、「テキストを画像にしたほうがトークンサイズを小さくできるのでは?」というのをやっていて、テキストを画像にしてトークン化したものをテキストトークンに戻すというのをやってたらOCRになったという感じですね。
LLMの開発効率化に革新? 中国DeepSeekが「DeepSeek-OCR」発表 “テキストを画像化”でデータ圧縮:Innovative Tech(AI+) - ITmedia AI+

中身的には、3Bでアクティブパラメータが0.6BのMoEモデルに0.4Bの画像エンコーダーを載せた画像言語モデルです。

導入や使い方は、モデルのページに書いてあります。
何も考えずに最新のTransformers 4.57.1を入れたら動かなくて、指定の4.46.3にしたら動きました。
requirement.txtどおりにインストールしましょう。
https://huggingface.co/deepseek-ai/DeepSeek-OCR

※ 追記
なんで画像トークンだとテキストトークンより少なく済むんだろうって考えてたら、ランダム文字列は認識できないんでは?って思ったので、試してみたらやっぱりそうだった。
DeepSeek-OCRにはランダム文字列が読めない - きしだのHatena

※ さらに追記
他にも弱そうなところ試してみた
DeepSeek-OCRの弱点をつく - きしだのHatena

OCRを試す

有価証券報告書を読ませたら完璧

ってことで、任天堂有価証券報告書の表のページを画像キャプチャして読ませてみました。
そしたら、小数点とカンマの違いまで含め完璧。

免許証も結構読める

あと、警察庁のサイトにある運転免許証見本を読み込ませてみました。
運転免許の更新等運転免許に関する諸手続について|警察庁Webサイト

そうするとこんな感じに。大型、中型などの縦書きもなんとなく読み取ってますね。とはいえ、さすがにここは難しそう。
写真もちゃんと別で切り取ってくれました。

他のモデルと比べる

※ 追記 Qwen3-VLとInternVLを比較してますが、論文でもそれぞれのアーキテクチャとその欠点が書かれていました。

Qwen3-VL 8B

ちょうど1週間前にQwen3-VL-8Bと4Bがリリースされて、いい感じのサイズでOCR能力も高い画像言語モデルが出たなぁと思ってたところだったのに。
Qwen/Qwen3-VL-8B-Instruct · Hugging Face

同じ画像でQwen3-VL 8Bを試したところ、なんとなくあってるけど列数がそもそも違うし項目名が足りなくて内容と食い違ってるし、認識ミスもあるし、と、このサイズの画像言語モデルとしてはがんばってるという感じ。

いや、8Bでここまで読めるのはすごいと思うんですよ。
DeepSeek-OCRチャットモデルではなく認識専門なので、チャットができる画像言語モデルとしてQwen3-VLは使いどころがかなりあると思います。動画も読めるっぽい。

InternVL 3.5 8B

Qwen3-VLも、いい感じのサイズの画像言語モデルないかなとInternVL 3.5を試してる最中にリリースされてました。
InternVL 3.5はQwen3とGPT-ossに画像エンコーダーを載せたモデルです。
InternVL3.5

けど、8Bで試したけど、これはちょっと他と比較するとダメですね。いや、これでも8月時点ではかなり読んでくれるほうだったのだけど。

DeepSeek-OCRの位置検出を試す

OCRの場合のプロンプトはこうなってました。

<image>\n<|grounding|>Convert the document to markdown.

こんな感じにすると、位置を検出してくれます。

<image>\nLocate <|ref|>the gocart<|/ref|> in the image. 

ちゃんとカートの位置を検出してくれた。

こんな感じで座標も返してくれます。数値は全体を1000とした場合の相対位置っぽい。

<|ref|>the gocart<|/ref|><|det|>[[504, 480, 712, 645]]<|/det|>

文字の位置も見つけてくれます。

<image>\nLocate <|ref|>ホキ美術館<|/ref|> in the image. 

いい感じ。

<|ref|>ホキ美術館<|/ref|><|det|>[[420, 633, 740, 857]]<|/det|>

追記
元々、AI Insideが「AI-OCRは完成した」と言ってたのを見て、「でもプロプライエタリでしょ」って思ってたところにQwen3-VLが出て、「おーすごい、けどやっぱ商用モデルのようには いかんか」と思ってたら、DeepSeek-OCRでなんか精度でててびっくりした、という経緯。
ASCII.jp:AI-OCRは完成した ― AI insideがデータ化精度“99.999%”を達成 (1/2)

追記2
YomiTokuという日本語AI-OCRの開発者の方の反応があったので、試してみました。モデルのダウンロードサイズが650MBくらいとDeepSeek-OCRの1/10で、かなり精度が高そうです。
GitHub - kotaro-kinoshita/yomitoku: Yomitoku is an AI-powered document image analysis package designed specifically for the Japanese language.

こちらも数字の読み取りは完璧でした。

「人」が「A」になってしまってましたが、このくらいだとDeepSeek-OCRも たまたま全部あってただけと言えるかも。読み込ませた画像の解像度が867x1047なので、もう少し解像度高ければ読み取れるんじゃないかと思います。(短辺1280以上推奨とのこと)

あと、数値のカンマのあとにスペースが入ったり入ってなかったりしてましたが、元画像どおりの読み取りではあります。
DeepSeek-OCRは、LLM側でテキストを再現するときに文脈による補完があるのでツジツマがあった読み取りになりやすいのだと思うけど、逆にいえばハルシネーションで全く違う文字を出す可能性もあるということになりますね。
ガチ利用の場合には、OCR専用AIのほうが制御しやすいんじゃないかと思います。

ライセンスはCC BY-NC-SA 4.0で、非商用の個人利用や研究用途は自由、商用はライセンスを要問合せ、とのこと。
MLism株式会社 | Yomitoku

計算量を具体的に見てみる 2025年版

2009年に「計算量を具体的に見てみる」という、処理から計算量を視覚化するブログを書いてた。
計算量を具体的に見てみる - きしだのHatena

これJava 6時代なので、Java 25で書き直してみた。
より詳しい解説は元ブログのほうを見てください。

O(1)

まず基本のO(1)です。処理量が入力量によらない場合ですね。 この例でいえば、処理がnを使わない場合。

あと、動的配列の拡張で、配列のサイズを2倍にしていくとそれ自体の計算量はO(n)になるのだけど、拡張が起きるのはlog n回になるので、平均するとO(1)というものもありますね。

表示コードを最後に書いてましたが、importが1行、クラスが不要、ラムダ式が使えるってことでだいぶコンパクトになったのでそのまま書いてます。

import module java.desktop;

void main() {
    show("O(1)", 1, 
        (n, proc) -> proc.run());
}

void show(String title, double range, BiConsumer<Integer, Runnable> exec) {
    var f = new JFrame(title);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(408, 333);
    
    int ORX = 30; int ORY = 280;
    var img = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);
    var g = img.createGraphics();
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                       RenderingHints.VALUE_ANTIALIAS_ON);
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, 400, 300);
    g.setColor(Color.BLACK);
    g.drawLine(ORX, 30, ORX, ORY);
    g.drawLine(ORX, ORY, 380, ORY);
    
    var path = new GeneralPath();
    for (int i = 1; i < 350; ++i) {
        int[] count = {0};
        exec.accept(i, () -> count[0]++);
        var c = count[0] * 30 * 250 / 350 * range;
        var y = Math.max(0, ORY - c);
        if (i == 1) {
            path.moveTo(i + ORX, y);
        } else {
            path.lineTo(i + ORX, y);
        }
        if (y <= 0) break;
    }
    g.setStroke(new BasicStroke(2));
    g.setColor(Color.RED);
    g.draw(path);
    
    g.dispose();
    var label = new JLabel(new ImageIcon(img));
    f.add(label);
    f.setVisible(true);
}

O(log n)

処理を行うたびに入力を何割か減らせるアルゴリズムの計算量ですね。二分探索など。四則演算も桁数だけ時間がかかるので数値nに対する計算量がO(log n)になります。

void compute(int n, Runnable proc) {
    proc.run(); // なにか処理
    if (n == 1) return;
    compute(n / 2, proc);
}

void main() {
    show("O(log n)", 1, this::compute);
}

O(n)

O(n)は入力の量に比例して時間がかかるアルゴリズムですね。ふつうにn回繰り返すやつ。

void compute(int n, Runnable proc) {
    for (int i = 0; i < n; ++i) {
        proc.run(); // なにか処理
    }
}

void main() {
    show("O(n)", 1 / 30., this::compute);
}

O(n log n)

O(n log n)はO(n)の処理をlog n回繰り返す処理です。というと難しそうだけど、ソートですね。そして、ソートを前処理に使うアルゴリズムがO(n log n)以上の計算量になります。

void compute(int n, Runnable proc) {
    for (int i = 0; i < n; ++i) {
        proc.run(); // なにか処理
    }
    if (n <= 1) return;
    compute(n / 2, proc);
    compute(n - n / 2, proc);
    
}

void main() {
    show("O(n log n)", 1 / 200., this::compute);
}

O(n ^ 2)

O(n ^ 2)になるアルゴリズムは、要素のすべての組み合わせを処理するようなアルゴリズムですね。

二重ループがあります。

void compute(int n, Runnable proc) {
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            proc.run(); // なにか処理
        }
    }
}
void main() {
    show("O(n ^ 2", 1 / 3000., this::compute);
}

O(2 ^ n)

すべての組み合わせについて処理するような場合です。計算がむずかしくなりがち。

void compute(int n, Runnable proc) {
    if (n <= 0) return;
    proc.run(); // なにか処理
    compute(n - 1, proc); // 要素を採用する場合
    compute(n - 1, proc); // 要素を採用しない場合
    
}
void main() {
    show("O(2 ^ n)", 1 / 3000., this::compute);
}

nビットの数だけ処理を走らせるような場合ともいえるので、次のようにも書けます。

void compute(int n, Runnable proc) {
    long count = 2 << n;
    for (long i = 0; i < count; ++i) {
        proc.run();
    }
}

O(n!)

すべての順序について処理する場合です。最短経路を求める場合とか。基本的に計算がおわりません。

void compute(int n, Runnable proc) {
    if (n <= 0) return;
    proc.run();
    for (int i = 0; i < n; ++i) {
        compute(n - 1, proc);
    }
}
void main() {
    show("O(n!)", 1 / 5000., this::compute);
}

計算量の話なら、乱択ガール

もっと物語的であれば、「最短経路の本」

ちょっとガチめにやるなら「計算理論の基礎」
3版が出てるのだな。


計算理論の基礎 [原著第3版] 1.オートマトンと言語
計算理論の基礎 [原著第3版] 2.計算可能性の理論

Javaでプラットフォームスレッドだと終了を待ってくれるのに仮想スレッドだと途中でプロセスが終わる

Javaで、プラットフォームスレッドだとmainメソッドが終わってもスレッド終了を待ってくれるのに、仮想スレッドだとmainスレッドが終わると仮想スレッドの処理が途中でもプロセスが終わるの何でだろうな、と思った話。

こういうコードを動かします。

void main() {
    Thread.ofPlatform().start(() -> {
        IO.println("start");
        sleep(2);
        IO.println("end");
    });
    IO.println("hello");
    IO.println("fin");
}
void sleep(int sec) {
    try {
        Thread.sleep(Duration.ofSeconds(sec));
    } catch (InterruptedException ex) {
    }
}

そうすると、スレッドがendまで出力するのを待ってからプロセスが終わります。

hello
fin
start
end

これを仮想スレッドにしてみます。

void main() {
    Thread.ofVirtual().start(() -> {
        IO.println("start");
        sleep(2);
        IO.println("end");
    });
    IO.println("hello");
    IO.println("fin");
}

endにくる前にプロセスが終わってしまいました。

hello
fin
start

どういう仕様に基づくんだろうな、と思ってたら、仮想スレッドは常にデーモンスレッドだということを教えてもらいました。

Virtual threads are always daemon threads. The Thread.setDaemon(boolean) method cannot change a virtual thread to be a non-daemon thread.

JEP 444: Virtual Threads

デーモンではないスレッドがひとつでも動いてるときはプロセスは終わらず、動いてるスレッドがデーモンスレッドだけになるとプロセスは終わります。

ということで、プラットフォームスレッドでも、daemon()を加えてデーモンスレッドにすると仮想スレッドの場合と同様に処理の途中でプロセスが終わるようになりました。

void main() {
    Thread.ofPlatform().daemon().start(() -> {
        IO.println("start");
        sleep(2);
        IO.println("end");
    });
    IO.println("hello");
    IO.println("fin");
}
hello
fin
start

ところで、例外を気にしなくていいIO.sleepが欲しい。

ComfyUIでQwen ImageやQwen Image Editを動かしてJavaから呼び出す

Javaのコードから画像生成したいな、ローカルで」と思って、どうやらComfyUIだとAPI呼び出しができるようなので、やってみた。

ついでに、Qwen Imageを試したかったので、ここを参考にインストール。
徹底解説:Comfy UI + GGUF Qwen Image / Edit 2509 をローカルGPU / Macで動かす完全マニュアル #comfyui - Qiita

ビールとカレーを持った写真が、コーラとピザにきれいにおきかわった。

APIはここを参考に。
ComfyUIをAPIサーバーとして使ってみる

というか、PythonコードをGPT-5にJavaに変換してもらったものをベースに汎用化した。
WebSocketの非同期接続やイベントハンドラなども、きれいにJavaらしく実装してくれていた。

ただ、他アプリから呼び出せるようにする過程で、基本構造はつくりかえた。
https://gist.github.com/kishida/0d4bd9d3a937a1383e7c2295fea88ef3

ワークフローのJSONファイルは自分の環境にあわせたものを作る必要がある。

そしたら、なんかキャバ嬢プロフィールを自動生成してそれっぽい写真を生成するプログラムができてしまった。