ローカルLLMが「バカ」に見えるとき、本当の原因はモデルではない
ローカルLLMが「バカ」に見えるとき、本当の原因はモデルではない
同じオープンウェイトのモデルなのに、自分のPCで動かすとクラウド版より目に見えて答えが悪くなる。これはモデルが元々その程度だからなのか、それとも我々がそれを誤った設定で動かしているからなのか。
導入
オープンウェイトのLLMを自分でダウンロードして動かしたことのある者なら、一度は味わう失望がある。ベンチマークの順位表では上位で、クラウドAPIで同じモデルを呼べば問題なく答えるのに、いざローカルのGPUに載せると答えがどこかずれる。フォーマットを無視し、推論を途中で切り、同じ文を繰り返し、ツール呼び出し(tool call)を妙な形で閉じる。だから「やはりローカルはまだまだだ」という結論に飛びつきやすい。
Level1Techsのフォーラムに投稿された「Why your local LLM feels dumber than it is」という記事は、まさにその結論を正面から反駁する。筆者の主張は単純である。ローカルのモデルが悪く見えるのは、たいていモデル重みそのものの根本的な性能問題ではなく、その重みを実際に実行する推論スタック(inference stack)の構成問題だ、というのだ。ハードウェアが違い、カーネルが違い、量子化の方式が違い、サンプリング設定が違えば、同じ重みも違うトークンを吐く。この記事はその「違い」を感想ではなくロジット(logit)単位で計測して見せる。ローカルLLMのエコシステムが「元々その程度」だという通念が、どれほど実行環境に汚染されているかを問い直す時が来た。
症状 — 同じ重み、違う答え
まず現象から見よう。ユーザーが体感する「バカさ」は、たいてい幾つかの定型化された症状として現れる。指示した出力フォーマットを守らず、思考(thinking)を短く切ったり、逆に思考ブロックから抜け出せないまま無限に堂々巡りし、同じ節を繰り返し、ツール呼び出しの引数や終了トークンを壊す。表面上はすべて「モデルの出来が悪くて」そうなっているように見える。
フォーラムの筆者は、これらの症状を実際の業務ワークロード上でロジット単位で追跡した。彼が用いた手法は二つある。一つはtop-1不一致(top-1 disagreement)の追跡で、異なる実装がグリーディ(greedy argmax)で選び取るトークンがどれだけ食い違うかを数える。もう一つはKLダイバージェンス(KL divergence)分析だが、彼はここに明確な但し書きを付ける。基準となるチェックポイントと実行環境をまるごと開示しなければ、その数値は解釈すること自体が不可能だというのだ。決定的な区間では統計的な推定の代わりにロジットを100%キャプチャし、分岐が始まった地点から実際の出力がどう分かれていくかをそのまま追った。
そうして追跡した失敗は、抽象的な確率の話ではなく具体的な破損として現れた。筆者の挙げる例を見ると、あるアテンションカーネルではCiscoネットワークコマンドのインターフェース名 GigabitEthernet0/0/1.201 が GigabitEthernet0/1/4 に置き換わり、PostgreSQLのポート番号 5432 が 543ql に壊れ、ホスト名 tenant がただの - 一文字に化けた。自動化パイプラインにおけるこうしたトークン一つのねじれは、そのまま誤った機器へコマンドを投げる事故につながる。興味深いのは、こうしたねじれが滑らかに少しずつ増えるのではなく「かたまり」として集中して現れ、無作為ではなくプロンプトの内容に応じて変わったという観察である。
とりわけ印象的なのは量子化グレード間の格差だ。筆者はBF16の基準版、FP8、INT8 W8A16、NVFP4、AWQ W4A16など複数の重み量子化をツール使用の正確さで比較したが、よりによってNVIDIAが出した「公式」のNVFP4配布版が最下位を記録し、88kコンテキストの地点でトークンのフリップ率が約50%に達したと報告している。逆にコミュニティが作ったINT8量子化が全グレードを圧倒した。「公式だから信頼できる」とか「数字が大きいから精密だ」という直感がここで崩れる。どのグレードの量子化が自分のワークロードでうまく回るかは、ラベルではなく実測でしか分からないということだ。
同じ計測は、重みに手を入れたファインチューニングの変種にも適用される。筆者は安全フィルタを取り除いた、いわゆる「abliterated」系の人気の変種を元モデルと並べて比較した。その結果、ある変種(Heretic-ARA、Huihui系)はtop-1フリップ率を1.5%未満に保ち、元モデルが強く好んでいたトークンを一つも覆さなかった一方、別の変種(AEON系)は構造的に無効な出力を数十件生み出し、元モデルが確信していた選択を何度も覆した。同じベースモデルから分かれた変種の間でさえ、品質はここまで開く。ユーザーが名前だけを見て「これは検閲のない版だからより良い」と選んだ瞬間、知らぬ間に大きく劣化した重みを握っているかもしれないということだ。そしてこの違いもまた勘ではなくフリップ率という数字で露わになる、というのがこの記事の一貫した態度である。
原因 — 推論スタックのどこで品質が漏れるのか
ではなぜこうしたことが起きるのか。原因は一つではなく推論経路のあちこちに散らばっており、それぞれが品質とVRAM・速度の間のトレードオフを抱えている。
第一に、重みの量子化による損失である。ローカルで大きなモデルを狭いVRAMに押し込むには重みを4ビットや8ビットに圧縮するが、その過程で表現力が削られる。HNの議論では、Ollamaのようなツールを使う人々が、自分がQ4量子化版を動かしているという事実すら知らないまま、その結果がBF16の原本とかなり異なる点を見落としている、という指摘が出た。圧縮率を上げればメモリと速度は稼げるが品質が漏れる、というのが核心のトレードオフだ。
第二に、より致命的なのがKVキャッシュの量子化である。フォーラムの筆者は、KVキャッシュをint4に量子化するとツール呼び出しのエラーが再現可能に(reproducibly)発生し、int8に上げると回復したと記している。HNの議論でも、モデルがその方式で学習されたのでない限りKVキャッシュの量子化は避け、速度を犠牲にしてでも可能な限り大きいファイルサイズ(例:Q8のGGUF)を使え、という助言が出た。アテンションが毎ステップ参照するキャッシュを粗く圧縮すれば、長い文脈ほど誤差が累積するということだ。
第三に、chat・promptテンプレートとパーサーの問題である。HNの議論では、ある推論エンジンのパーサーが思考ブロックを処理する際に改行文字(\n)一つを余分に飲み込んだせいで、複数ターンが連なる長いセッションで自己修正が連鎖的に起き、出力が徐々に崩れたという事例が共有された。あるパターンが文脈に多く現れるほど次のターンでもそのパターンが出やすくなる自己強化ループが生じ、単発のプロンプトでは見えなかった劣化が対話形式でのみ露わになるというのだ。チャットテンプレートが少しでもずれれば、モデルは学習時に見た形式と異なり、指示を無視しているように見える。
第四に、コンテキストウィンドウとtruncationである。HNの議論では、Ollamaのデフォルトのコンテキスト長が2,000〜4,000トークン程度で、ユーザーが手を入れなければ長い入力が静かに切り捨てられ、モデルが無能に見える、という指摘が繰り返された。さらに、十分に整ったハードウェアでもコンテキストがおよそ80Kを超えると応答が不安定で遅くなる、という観察も出た。広告された最大コンテキストが、そのまま実使用の品質を保証するわけではないということだ。
第五に、サンプリングパラメータの誤設定である。フォーラムの筆者はtemperatureを低くしすぎることこそ「QwenがTHINK出力から抜け出せず堂々巡りする」原因だと指摘し、HuggingFaceのモデルカードがたいていそのモデルに合ったサンプラー設定(例:temperature 1.0、top-p 0.95など、モデルごとに異なる)を明記しているのだから、それに従えと述べた。temperature・top-p・repeat penaltyを勝手にいじると、繰り返し抑制と多様性のバランスが崩れ、ループに陥ったり過度に散漫になったりする。推論強度(reasoning effort)の設定も同様だ。HNの議論では、あるQwen系のモデルがデフォルトで設定された過剰な推論強度のせいで延々と考え込み止まったように見えたが、これを中程度に下げると際限なく堂々巡りしなくなった、という事例が出た。ただし強度を下げると複雑な問題の機微を取りこぼしうる、という逆方向のトレードオフも併せて指摘された。ここにシステムプロンプトの欠落が重なれば、モデルは自分がどんな役割と形式で答えるべきかを知らぬままデフォルト値へ流れていく。
第六に、より深い層にはカーネルと並列化そのものの非決定性がある。フォーラムの筆者によれば、FlashAttention 2、FlashInfer、Triton といった異なるアテンションバックエンドは、プリフィル(prefill)段階でビット単位で異なるロジットを生み、トークン選択を分岐させる。テンソル並列の設定をTP1・TP2・TP4に変えるだけでもツール呼び出しの結果が変わったが、これはGPU間通信(NCCL)の差から来る。彼が使ったvLLMスタック一つだけでも734個(うちPythonパッケージ252個)の依存関係が絡み合っていたという事実は、どこかで静かに数値がずれる余地がそれだけ多いということを意味する。HNの議論では、推論エンジンの単体テストの許容誤差(例:rtol=5e-03)が緩すぎて、行単位のアテンション誤差があってもテストは通ってしまう「沈黙の劣化」がある、という指摘も出た。
示唆 — 「ローカルは元々ダメ」という誤解のコスト
この診断を実務に移せばチェックリストになる。モデルを載せる前に、(1) 今動かしている量子化グレードが何かを明示的に確認し、必要なら大きいグレードへ上げる。(2) KVキャッシュの量子化は、そのモデルがそう学習されたのでなければ切る。(3) チャットテンプレートとシステムプロンプトがモデルカードの形式と一致するか照合する。(4) コンテキスト長をデフォルトに任せず、ワークロードに合わせて明示的に広げる。(5) サンプラー値は勝手にいじらず、モデルカードが推奨する値から始める。(6) 何より、ベンチマークの順位やモデルカードの約束を信頼の最終根拠とせず、自分のワークロードで再現可能な私的ベンチマークを作って実測する。
フォーラムの筆者の結論もここにある。「彼らのハードウェアはあなたのものと違い、彼らのソフトウェアはあなたのものと大きく違う」。HNの議論でも、モデルはプレスリリースで言及したベンチマークに合わせて最適化される傾向があるのだから、公開された数値を信じるより自分だけの非公開ベンチマークを作れ、という助言が出た。ツール選びにも示唆がある。HNの議論では、llama.cpp が最も信頼でき、vLLMとSGLangが機能面でOllamaより先を行き、AppleのハードウェアではMLXが速い、という評が集まった。Ollamaは使うのは易しいが、デフォルト値が問題だというのだ。
楽観的に見れば、これらの問題はほとんどが設定で直せる、すなわち原理的に解決可能な欠陥である。悲観的に見れば、この微細な落とし穴がオープンウェイトのエコシステム全体の評判を蝕む。きちんと設定すればクラウド版に迫るモデルが、誤ったデフォルト値のせいで「やはりローカルはダメだ」という印象を残し、その印象が再びオープンウェイトの代わりにクローズドなAPIへと人々を押しやる。現実のシナリオはその間のどこかだろう。ツールのデフォルト値が次第に良くなり、モデルカードのサンプラー仕様が標準化されていくにつれ、今日の落とし穴の相当数は明日の常識へ吸収されていくはずだ。
結論
では、ローカルLLMは本当にバカなのか。この記事の答えは「おおむね違う」に近い。我々が見たのはモデルの無能ではなく、デプロイの無秩序であった。量子化グレード、KVキャッシュの精度、チャットテンプレート、コンテキスト長、サンプラー値、そしてカーネルと並列化の非決定性 — この六つの層のどこでも品質は漏れうるし、たいていは複数の場所で同時に漏れる。そしてこれらの漏れはすべて、感想ではなくロジット単位で計測可能だ。
実務者に残る行動は明確である。モデルを責める前に、まず自分のスタックを計測せよ。自分のワークロードで作った小さなベンチマークを一つ用意し、量子化グレードを変えたりサンプラーをいじったりするたびに、その結果を目で確かめよ。「ローカルは元々ダメだ」という文は、正確に言えば「私はまだ自分のローカルをきちんと計測していない」の別の言い方であることが多い。あなたのモデルが最後にバカに見えたとき、それは本当にモデルだったのか、それともデフォルト値だったのか。
出典: