Gemma 4 12B Q4_K_MをCodex CLIローカルモデルとして評価したメモ
Posted:
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 は次の組み合わせだった。
| 項目 | 値 |
|---|---|
| context | 16384 |
| 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-size | model server の実 context |
| proxy の model metadata | /v1/models などで client に見える context |
| Codex profile | Codex CLI が profile として認識する context |
| local model catalog | Codex 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 on | content が空、または reasoning 側だけが進む |
request ごとの enable_thinking=false | raw Chat Completions では改善 |
--reasoning off | raw 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-only | tool なしで JSON object だけを返す | pass |
| T2 shell tool + JSON final | read-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 世代のデスクトップである。
| 項目 | 値 |
|---|---|
| motherboard | MSI MS-7817 v2.0 |
| CPU | Intel Core i7-4790 CPU @ 3.60GHz |
| CPU topology | 4 cores / 8 threads |
| CPU max clock | 4.0GHz |
| L3 cache | 8MiB |
| GPU | NVIDIA 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 の量をまとめて見る必要がある。