0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

128GBのMacでQwen3.8-Flash-Nextを動かす――expert削減版の落とし穴と、n-gram表のmmapで240Kトークンまで

0
Last updated at Posted at 2026-09-24

こんにちは、皆さん。

手元のMacで一番賢いモデルを動かしたい、と思ったことはありますか?わたしはあります。ただ、賢いモデルほど大きく、128GBのメモリでも「ぎりぎり入らない」ことがよくあります。そういうとき、つい小さく削った版に手が伸びます。

さて、今日はQwen3.8-Flash-Nextを、Mac Studio M4 Max 128GBで実用的に動かすまでの話です。expertを削って小さくした版(REAP-288)は、日本語と一般知識が崩れていました。4bitのフル版はメモリ不足で動きませんでした。最後に、モデルが持つ32GBのn-gram表をディスクから必要な分だけ読むようにしたところ、品質を落とさずに240Kトークンまで動き、同じ入力を読み込むのにQwen3.8-27Bの35分に対して7.4分で済みました。そのまま自宅の推論サーバーkiapiへ組み込み、mlx-vlmへの修正も上流へ出しています。

128GBのM4 Maxで長いプロンプトを読み込むまでの時間。Qwen3.8-27Bは240Kトークンで35.2分かかるが、n-gram表をmmapにしたQwen3.8-Flash-Nextは7.4分、REAP-288は7.1分

Qwen3.8-Flash-Nextとは

Qwen3.8-Flash-Nextは、Alibabaのqwenチームが2026年8月に重みを公開したモデルです。125BパラメータのMoE(Mixture of Experts。多数の小さな専門家ネットワークから、トークンごとに一部だけを選んで計算する構造)に、51Bパラメータのn-gram埋め込み表を加えた構成で、1トークンあたりに動くのは約6Bです。入力はテキスト・画像・動画で、文脈長は262Kトークン(1Mまで拡張可能)です。

Qwenの公式比較では、密なモデルのQwen3.8-27Bをほぼ全項目で上回っています。差が大きいのはエージェント系の評価で、DeepSWE 1.1は58.7対42.2です。これはQwenの公表値で、本記事で追試した値ではありません。

この記事で扱うモデルは3つです。

モデル 構成 ディスク 役割
mlx-community/Qwen3.8-Flash-Next-4bit Flash-Nextの4bit量子化(フル版) 111.5 GB 本命
sh0wie/Qwen3.8-Flash-Next-REAP-288-MLX-4bit 各層のexpertを512から288へ削った4bit版 73.5 GB 小さい候補
mlx-community/Qwen3.8-27B-4bit 27Bの密なモデルの4bit版 16.1 GB これまでの基準

REAPは、校正データを流して各expertの効き具合を測り、効きの小さいものから削る手法です。REAP-288は校正にエージェント型コーディングの通信記録(約686Kトークン)だけを使っていて、モデルカードの公開評価はHumanEval(93.9→91.5)だけでした。

ライセンス

対象 ライセンス
Qwen3.8-Flash-Next(REAP-288を含む派生) Qwen Community License 1.0
Qwen3.8-27B Apache-2.0
mlx-vlm MIT

Qwen Community License 1.0は、外部に出さない内部利用なら自由に使えます。一方で、商用のModel-as-a-ServiceやAIワークアシスタントの事業にはQwenからの別ライセンスが必要です。

今回検証する内容

  1. expertを削ったREAP-288は、日本語・長い文脈・エージェント作業でどれだけ劣化するか
  2. 4bitのフル版は、128GBのMacで動くのか
  3. 動かないなら、品質を保ったまま収める方法はあるか
  4. 実際にサーバーへ組み込むと、何が起きるか

評価は2つのlabに分けています。

検証環境の再現

128GBのApple Silicon Mac、mise、uv、Gitが必要です。重みは3モデルで約200GBをHugging Faceのキャッシュへ取得します。

git clone --depth 1 --filter=blob:none --sparse \
  https://github.com/kiarina/labs.git
cd labs
git sparse-checkout set .mise/tasks \
  2026/09/24/qwen38-flash-next-reap-eval \
  2026/09/24/qwen38-flash-next-ple-mmap
mise trust . \
  && mise trust 2026/09/24/qwen38-flash-next-reap-eval \
  && mise trust 2026/09/24/qwen38-flash-next-ple-mmap
mise -C 2026/09/24/qwen38-flash-next-reap-eval run

既定のrunはモデルを使わない単体テストだけです。評価は個別に呼びます。他にGPUを使う処理がない状態で実行してください。

コマンド 内容
mise -C 2026/09/24/qwen38-flash-next-reap-eval run eval dense27b Qwen3.8-27Bの全評価
mise -C 2026/09/24/qwen38-flash-next-reap-eval run eval reap288 REAP-288の全評価
mise -C 2026/09/24/qwen38-flash-next-ple-mmap run prepare フル版にmmap用の目次を付けた構成を作る(重みはハードリンクで複製しない)
mise -C 2026/09/24/qwen38-flash-next-ple-mmap run eval full4bit-plemmap needle,agent,ja mmap版フル版の全評価

prepareはハードリンクを使うので、Hugging Faceのキャッシュと同じディスク上にcheckoutしてください。

評価の中身

評価 内容
日本語32問 知識16・指示への従順さ8・読解6・敬語2。部分一致・正規表現・JSONなどで自動採点
長い文脈からの検索 倉庫の記録を模した日本語の文書に、3つの合言葉(10/50/90%の位置)と3つの紛らわしいダミーを埋め、3つとも答えさせる。32K / 128K / 240Kトークン
エージェント作業 ファイル一覧・読み込み・書き込み・テスト実行の4つのツールで、小さなPythonリポジトリのバグを直す4題。成功は隠しテストまで通ることと、テストを書き換えていないこと

条件はすべて、temperature 0、thinking無効、prefillの刻み2048トークンです。

【1】REAP-288は、知識と日本語が崩れた

日本語の知識16問はフル版16、27Bが15、REAP-288が1。日本語32問はそれぞれ32、30、12。リポジトリ修正は4、4、3

モデル 日本語32問 知識 指示 読解 敬語 エージェント
Flash-Next フル版 32 16/16 8/8 6/6 2/2 4/4
Qwen3.8-27B 30 15/16 7/8 6/6 2/2 4/4
REAP-288 12 1/16 6/8 5/6 0/2 3/4

REAP-288の知識問題は16問中1問しか合いませんでした(「予算の先議権を持つ院」への「下院」は、採点では不正解ですが衆議院のことなので、実質的には2問です)。崩れた答えの例です。

質問 REAP-288の答え
三重県の県庁所在地は? 不存在
兼六園がある都市は? 日本
「春暁」の作者は? (中国語で)そんな漢詩は存在しない

日本語だけの問題でもありません。英語で「日本で一番長い川」を聞くと「Jangwan River」という架空の川を答え、「日本の首都は?」には中国語で「日本には首都がない」と返しました。一方で、Pythonの関数を書かせると普通に書けます。同じ実行環境でフル版は全問正しく答えたので、原因は実行環境ではなく、expertを削ったことです。コードの通信記録だけで「重要でない」と判断されたexpertに、知識と日本語が入っていたのだと考えています(推測です)。

自動採点の12点も実態より甘めです。箇条書きの問題は形式を満たしていても中身が中国語で、富士山の説明は「世界最長寿のLinuxディストro」、である調の問題は「灰の天空の光の反射のののの」でした。

崩れはコードにも及びました。エージェント作業で落とした1題は、「1時間30分を秒に直す関数を実装する」という課題です。REAP-288は単位の「時間」を簡体字の「时间」に書き換え、しかもdocstringの仕様の例まで「1时间30分」に直しました。テストは落ち続け、上限の24ステップを使い切っても、この1文字の違いに気付けませんでした。

**一方で、長い文脈からの検索は落ちていません。**32K / 128K / 240Kのすべてで、3つの合言葉を正しく答え、ダミーも無視しました。REAPが削るのはexpertだけで、アテンションや長い文脈を扱う部分はそのまま残るので、構造から見ても自然な結果です。

【2】4bitのフル版は、メモリ不足で動かなかった

フル版は品質では一番でしたが、読み込んだ時点でMLXのピークメモリが111.5GBあります。

  • 32Kトークンの検索を既定の設定で始めると、Metalのメモリ不足(kIOGPUCommandBufferCallbackErrorOutOfMemory)で止まる
  • prefillの刻みを256まで細かくすると32Kは通るが、128Kでまた止まる
  • エージェント作業も、刻み256・キャッシュ1GBまで絞ってようやく動く(ターン間のキャッシュは効かなくなる)

macOSのGPUメモリ上限(iogpu.wired_limit_mb)を上げる手もありますが、システム設定の変更になるので試していません。

【3】n-gram表をmmapにすると、全部入った

Flash-Nextの51Bパラメータのn-gram埋め込み表(PLE)は、1トークンで数行しか引きません。mlx-vlmのqwen4_exp実装には、この表をMLXのパラメータとして常駐させず、safetensorsファイルをmmapして必要な行だけ取り出す仕組み(qwen4_exp/ple_storage.py)がすでに入っています。コードのコメントには、普通に読み込むと約30GiBの「冷たい」参照データが常駐する、と書かれています。

では、なぜフル版は111.5GBだったのか。mlx-community版の変換には、この仕組みを有効にする目次ファイルple-store.jsonが付いていなかったからです。REAP-288が41.5GBと小さかったのも、実は目次が付いていたからでした。

目次を作る関数(prepare_external_ple_model)もmlx-vlmにあります。これは次のことをします。

  • ple-store.jsonに、元のsafetensors内のPLEのバイト範囲を書く
  • config.jsonにtext_config.ple_storageを足す
  • PLE以外の重みファイルをハードリンクする

重みはコピーも再量子化もしないので、中身は元と完全に同じです。ディスクの使用量も増えません。

MLXのピークメモリ。PLEを常駐させたフル版は読み込み時111.5GBで32Kトークンからメモリ不足。mmapにすると読み込み時79.5GB、240Kトークンで96.0GB。REAP-288は41.5GBと57.9GB、Qwen3.8-27Bは16.1GBと61.7GB

PLEを常駐(前回) PLEをmmap
読み込み後のピーク 111.5 GB 79.5 GB
32K / 128K / 240Kの検索 メモリ不足 3/3・3/3・3/3(ピーク83.6 / 89.9 / 96.0 GB)
エージェント作業(既定の設定) 動かない 4/4(ターン間キャッシュも効く)
日本語32問 32 32
生成速度(日本語32問の中央値) 47.1 tok/s 40.2 tok/s

減ったのは32.0GBで、PLEのバイト数と一致します。代わりに生成は約15%遅くなりましたが、それでも27B(34.3 tok/s)より速いです。実行中にスワップは増えませんでした。

【4】長い文脈では、27Bより5倍近く速い

冒頭の図のとおり、Flash-Nextのprefillは長さによらず530〜580 tok/sでほぼ一定でした。27Bは217→152→114 tok/sと、長くなるほど遅くなります。

長さ Flash-Next(mmap) Qwen3.8-27B
32K 65.5秒 153.0秒
128K 228.7秒 865.5秒
240K 447.0秒(7.5分) 2,114.9秒(35.2分)

時間はどれも、出力(約50トークン)を書き終えるまでの合計です。ほとんどが入力の読み込みでした。

理由は2つあると考えています(推測です)。1つは、1トークンで動くパラメータが27Bは全部の27B、Flash-Nextは約6Bと4.5倍違うこと。もう1つは、Flash-NextのQwen Sparse Attentionが過去のトークンのうち関連の高いものだけを見るので、文脈が長くなっても1トークンあたりの計算が増えにくいことです。逆に、1トークンずつ書き出す生成速度の差は1.2倍程度でした。Flash-Nextが特に強いのは、長い入力を読ませる場面です。

kiapiへ組み込む

kiapiは、Apple SiliconのMacをLLMエージェント向けのAPIサーバーにする、わたしのOSSです。ここにqwen3.8-flash-nextとして組み込みました。

  • 生成処理は27Bのものを流用。チャットテンプレートもツール呼び出しの形式(Hermes/XML)も同じだったため
  • 読み込み時に、上のmmap用の構成を自動で作る。重みは元のsnapshotへのシンボリックリンクで、コピーはしない
  • 開けるファイル数の上限を引き上げる。mmapしたPLEは128個のshardを開いたままにするので、macOSの既定の256ではOSError: [Errno 24] Too many open filesで読み込めない

合わせて、chatではモデル指定を必須にしました。Flash-Nextは載せると約74GiB常駐し、音声・動画は受け付けません。音声・動画対応のOmniとは入力もメモリも大きく違うので、暗黙の既定モデルがあると、他のモデルが予想外に降ろされたり、メディアを拒否されたりします。代わりに短い別名vlmをFlash-Nextに付けています。

curl http://HOST:8500/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"vlm","messages":[{"role":"user","content":"日本の首都は?一言で。"}]}'

組み込んでから気付いた癖が1つあります。ツールを渡すと、「こんにちは」と送っただけでも天気のツールを呼ぶ傾向がありました。temperature 0でも起きたので、ツールを使ってほしくない場面では、システムメッセージでそう伝えるのがよさそうです。

画像キャッシュの修正を上流へ

kiapiは会話の前半をキャッシュして使い回します。27Bは、会話の途中で画像を足しても変わっていない前半を使い回せるよう、わたしがmlx-vlmへ出しているPR(#2309)で対応済みでした。一方でFlash-Nextは、画像を1枚足すと最初から全部読み直していました(本番で使い回し0/1333トークン)。

Flash-Nextのモデル実装を読むと、画像の処理も位置の計算も27Bの実装を継承していて、対象外になっていた理由は「モデル種別がqwen3_5のときだけ」という1行の条件だけでした。そこで対象をqwen4_expへ広げ、同じPRに同梱しました。Flash-Next対応は上流にとっても優先度が高いはずで、一緒に出したほうがマージされやすいと考えたからです。

操作(Flash-Next) キャッシュなし 使い回し
画像の会話に画像を1枚追加 4.77秒 0.50秒
画像の会話に画像を2枚追加 4.88秒 0.71秒

時間は最初のトークンまでで、どちらも前半の2,897トークンを使い回し、新しい画像だけを処理しました。本番のkiapiでも、画像を足したときの使い回しは0/1333から1047/1333トークンになりました。

ここで1つ、予想外のことがありました。キャッシュが正しいかは、キャッシュなしの結果と最初のトークンの確率分布を比べて(KLダイバージェンス)確かめます。27Bでは0.001未満でしたが、Flash-Nextでは0.104と、27B向けに決めていた基準0.05を超えました。

原因を切り分けるため、キャッシュを使わずにprefillの刻み幅だけを変えて比べました。

比較(キャッシュなし同士) 対称KL
刻み2048と512 0.016
刻み2048と384 0.064
刻み512と384 0.096
刻み512と、画像の使い回し 0.129
刻み256と、画像の使い回し 0.007

Flash-Nextは、キャッシュを使わなくても、計算の刻み方だけで最初のトークンの分布がこれだけ動きます。最も確率の高いトークンと生成される文は、どの場合も同じでした。画像を使い回した結果は、キャッシュなしの結果が散らばる範囲の内側に入っています。そこでPRの検証スクリプトは、「キャッシュなしを刻み2048 / 512 / 256で回し、そのいずれかとKL 0.05未満」を条件にしました。27Bはどの刻みとも0.001未満なので、実質的な基準は変わりません。

何に使えて、何に使えないか

**長い資料やエージェント作業には、Flash-Nextのフル版が向いています。**240Kトークンを7.5分で読み、ツールを使ったリポジトリ修正も既定の設定で4題とも解けました。27Bに比べ、長い入力ほど待ち時間の差が開きます。

**REAP-288は、日本語や一般知識を扱う用途には使えません。**コード生成と長い文脈の検索は保たれていたので、英語中心のコーディング補助に限れば選択肢になるかもしれません。ただ、コード中の日本語まで中国語に書き換えるので、日本語のコメントや文字列を扱うリポジトリでは危ういです。

**メモリの面では、Flash-Nextは「1台を占有する」モデルです。**240Kトークンでピーク96GBに達するので、128GBのMacでは他の大きいモデルとは同時に載りません。kiapiでは、必要なときに載せ替える運用にしています(読み込みはOSのキャッシュが温まっていれば約12秒)。

制約と未確認事項

  • 1台(Mac Studio M4 Max 128GB)、各条件1回、temperature 0、thinking無効での結果である
  • エージェント作業は4題で、失敗の型を示すものであって成功率ではない
  • 日本語の採点は自動で、形式に敏感である。1問(敬語で「欠席」を想定していなかった)は計測後に基準を広げ、全モデルを保存済みの出力から採点し直した
  • 長い文脈の検索は合成データで、長い文脈での推論能力は測っていない
  • mmapしたPLEの読み込みがOSのキャッシュから来たのかSSDから来たのかは分けていない。起動直後などキャッシュが冷えた状態では、生成がもっと遅い可能性がある
  • REAPは288 expert・1つの校正データの版だけを試した
  • 画像入力は、キャッシュの検証以外では評価していない
  • expertをSSDから必要な分だけ読む仕組み(mlx_vlm/moe_offload.py)は試していない

検証後の感想

一番の学びは、「小さい版を探す前に、何がメモリを使っているかを数える」でした。フル版の111.5GBのうち32GBは、1トークンで数行しか引かない表でした。その表をディスクから読むだけで、品質も文脈長も手放さずに収まりました。しかも仕組みはmlx-vlmにすでにあって、足りなかったのは目次ファイル1つです。

REAP-288の崩れ方は、正直に言って衝撃でした。HumanEvalでは91.5%と元とほぼ同じなのに、三重県の県庁所在地は「不存在」です。モデルカードのベンチマークは「校正したものが保たれているか」しか教えてくれない、ということを身をもって知りました。自分の用途の問題を十数問用意して試すだけで分かることなので、削った版を選ぶときはぜひやってみてください。

量子化したMoEが計算の刻み方だけで揺れる、というのも面白い発見でした。数字が基準を超えたときに、すぐにバグだと決めつけず、キャッシュなし同士でどれくらい揺れるかを先に測る。これは他のモデルのキャッシュを検証するときにも使えそうです。

長い資料を丸ごと読ませて質問したり、エージェントに大きなリポジトリを任せたりするのが、手元のMacでも現実的になりました。自宅のMacが、ちょっと頼もしい相棒になった気分です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?