Gemma 4 12B Q4_K_MをCodex CLIローカルモデルとして評価したメモ

Gemma 4 12B Q4_K_M の GGUF を llama-server で起動し、OpenAI-compatible proxy を挟んで Codex CLI の local model として使えるかを試した。

後続では、同じ環境で Gemma 4 E4B QAT Q4_0 の軽量 backend 評価 も行った。

見たかったのは、単なる短文応答ではなく次の 3 点だった。

  • Codex CLI の大きめの agent prompt を context に収められるか
  • JSON-only final answer が壊れないか
  • shell tool call と tool result の読み取りが成立するか

先に結論を書くと、12GB class GPU でも ctx=16384, ngl=28, --reasoning off なら短時間の JSON/tool smoke は通った。ただし、長文、温度、tool schema の増加、別の chat template ではまだ崩れる余地がある。

構成

抽象化すると構成はこうなる。

flowchart LR
    Codex[Codex CLI] --> Proxy[OpenAI-compatible proxy]
    Proxy --> Llama[llama-server]
    Llama --> Model[Gemma 4 12B Q4_K_M GGUF]
    Codex --> Tools[Codex tool executor]

今回の評価では adapter を挟まず、proxy の Responses API 変換から llama-server の Chat Completions へ流した。

最終的に安定した runtime baseline は次の組み合わせだった。

項目
context16384
GPU offload--n-gpu-layers 28
batch--batch-size 256
micro batch--ubatch-size 128
parallel--parallel 1
reasoning--reasoning off

ngl=36 も一部では動いたが、ctx=16384 と組み合わせた時に GPU fault で落ちたため、手元の検証環境では baseline から外した。

contextは複数層にある

Codex CLI 経由で local model を使う場合、context は 1 箇所だけ揃えればよいわけではなかった。

少なくとも次を同じ実起動値へ寄せる必要がある。

意味
llama-server --ctx-sizemodel server の実 context
proxy の model metadata/v1/models などで client に見える context
Codex profileCodex CLI が profile として認識する context
local model catalogCodex CLI が model metadata として使う context / max context

一方、truncation limit は runtime context と同じ値にしない方がよかった。

今回の安定値では、runtime context は 16384、Codex 側の truncation limit は 12000 にした。これは、input を上限いっぱいまで詰めると final answer、tool loop、retry の余地がなくなるためだ。

ctx の不一致は、検証途中ではよく起きる。特に model server だけ再起動して、proxy metadata や CLI catalog を更新し忘れると、実際には動いているのに client 側の切り詰めや上限判定だけが古いままになる。

reasoning offが効いた理由

※ これは私のマシン性能が低いということもあると思います(CPU,I/O。I/Oについてはチューニングの余地がある)

今回の一番大きい改善は --reasoning off だった。

--reasoning off は Codex CLI の model_reasoning_effort ではなく、llama-server / chat template 側の thinking 制御である。Gemma 4 の template で thinking が有効なままだと、Chat Completions では content ではなく reasoning_content 側だけが進むことがあった。

その状態で proxy の Responses 変換を通すと、Codex CLI から見る本文が空になりやすい。

観測としては次のような差があった。

条件観測
reasoning oncontent が空、または reasoning 側だけが進む
request ごとの enable_thinking=falseraw Chat Completions では改善
--reasoning offraw Chat Completions / proxy Responses / Codex CLI で本文が返る

Codex CLI の model_reasoning_effort="low" は、少なくともこの経路では llama-server の Gemma thinking を無効化する設定ではなかった。local Gemma 4 を Codex CLI の Responses 経路で使うなら、server 側で reasoning を切る方が効果が明確だった。

変なtokenの観測

検証中に <unused49><|channel> が出ることがあった。

重要なのは、これを単純な OOM や context overflow と見なさないことだ。truncated=0 でも出ていたため、少なくとも一部は context 不足ではない。

切り分けでは、GPU offload を下げても改善せず、--reasoning off で本文への露出は止まった。最終 baseline でも logprob の候補には <|channel> が残ることがあったが、通常出力には出ていない。

このため、現時点では次のように扱うのが妥当だと見ている。

  • <unused49> / <|channel> は model、GGUF、chat template、llama.cpp 実装、reasoning channel の相性問題として見る
  • --reasoning off は本文空振りと channel token 露出の抑止に効いた
  • 長文や高温度では再確認が必要

JSON/tool smoke

最終 baseline で実行した smoke は 3 つ。

Test目的結果
T1 JSON-onlytool なしで JSON object だけを返すpass
T2 shell tool + JSON finalread-only shell tool を 1 回使い、結果を JSON に入れるpass
T3 strict JSON shape指定 key / type の JSON object を返すpass

T1 の形はこういうもの。

{
  "valid_json": true,
  "task": "json_only",
  "model": "gemma4-12b-llama-q4km",
  "answer": 42
}

T2 では shell tool が 1 回実行され、tool output を final JSON に反映できた。

{
  "called_tool": true,
  "observed": "codex-json-tool-ok",
  "valid_json": true
}

T3 も、以前は fenced code block になったことがあったが、ctx=16384 / ngl=28 / --reasoning off では JSON object のみを返した。

ただし、Codex CLI の表示では final answer が stdout の末尾に再掲されることがある。外部 runner で strict JSON として機械判定するなら、Codex のイベントまたは final answer 部分を抽出する必要がある。

CPU/GPUの観測

評価に使った PC は、DMI では MSI MS-7817 v2.0 Haswell 世代のデスクトップである。

項目
motherboardMSI MS-7817 v2.0
CPUIntel Core i7-4790 CPU @ 3.60GHz
CPU topology4 cores / 8 threads
CPU max clock4.0GHz
L3 cache8MiB
GPUNVIDIA GeForce RTX 3060, 12GB class

12GB class GPU での実測では、最終 baseline の llama-server はおおむね 5.1GiB 程度の GPU memory を使っていた。別の GPU 常駐 process があっても、全体としては数 GiB の余裕が残った。

一方、生成速度はまだ CPU 側に引っ張られている。ngl を上げると prompt eval と generation は段階的に速くなったが、安定性との釣り合いを見る必要があった。

今回の観測では、ngl=20 から ngl=32 までは速度が改善した。ngl=36 は一部条件で動いたが、ctx=16384 との組み合わせで crash したため baseline にはしなかった。

残った注意点

今回の smoke で分かったのは「短時間の Codex JSON/tool loop が成立する」までであり、local model として常用できるという意味ではない。

残っている確認点は次の通り。

  • 長文応答で <|channel> などが再露出しないか
  • 温度や top-p を変えた時も JSON-only が保てるか
  • tool schema が増えた時に tool selection が崩れないか
  • server 起動を手動ではなく systemd / Ansible で固定できるか
  • proxy timeout や context metadata を設定値として管理できるか

特に local Codex profile は、AGENTS.md、developer instruction、tool schema、会話履歴で prompt が大きくなりやすい。短い「こんにちは」でも数千 token の request になることがある。小型 local model では、曖昧な短文入力が「指示を理解しました」系の応答に寄ることもあった。

これは model crash ではなく、Codex agent prompt と local model の instruction priority の相性として扱う方がよい。

まとめ

Gemma 4 12B Q4_K_M は、llama-server + proxy + Codex CLI の local model として、限定的な JSON/tool smoke には耐えた。

今回の実用 baseline は次。

ctx=16384
n-gpu-layers=28
reasoning=off
truncation limit=12000

一番効いたのは --reasoning off だった。context や GPU offload だけでは、本文が空になる問題や channel token の露出を説明しきれない。

Codex CLI から local LLM を使う場合は、model の一般能力だけでなく、context metadata、truncation、Responses 変換、chat template、reasoning channel、tool schema の量をまとめて見る必要がある。