10月3日、xbillが「Gemma 4 at Over 70 Tokens/s on a 2021 Laptop's 4 GB GPU: The Live Demo, Step by Step」と題した記事を公開した。クラウド不使用、VRAM 4GBのノートPC、それで76〜77トークン/秒。数字だけ見ると疑いたくなるが、xbillはその手順を再現可能な形で完全に公開した。
なぜ4GBに収まるのか
Gemma 4 E2Bのbf16ウェイトは9.5GiBある。これを4GBカードに載せるには、2つの仕組みが組み合わさっている。
QAT(量子化認識トレーニング/Quantization-Aware Training):Googleが4ビットでの動作を前提に学習させたモデルであるため、4ビットに変換してもbf16に近い品質を保てる。単純な量子化とは精度の劣化具合が根本的に異なる。
per_layer埋め込みテーブルのホストメモリ配置:モデル名の「E」(E2B)が示す大規模な埋め込みテーブルper_layer_token_embdは、llama.cpp(C++で実装されたLLMローカル推論エンジン)がメモリマップで読み込む。これはシステムRAM上に置かれ、GPU VRAMを一切消費しない。結果として、GPUに載せるべき部分は1.20GiBまで縮む。
「再パック」GGUFが速い理由
ここが記事の核心だ。xbillはGoogleが公開しているGGUFをそのまま使わず、gguf_exact.pyというスクリプトで再パックしたファイルを作成した。なお、GGUFとはllama.cppが採用するモデルファイル形式で、量子化設定やメタデータをひとつのバイナリにまとめる仕様だ。
GoogleのオリジナルGGUFでは、トランスフォーマー層はQ4_0形式だが、埋め込みテーブルの大半がQ6_K形式で格納されている。Q6_KはQ4_0より精度は高いが、ファイルの**67.7%**をこれが占めている。
gemma-4-E2B_q4_0-it.gguf(Google版)
Q6_K 2.257 GB (67.7%)
Q4_0 1.048 GB (31.4%)
F16 0.028 GB ( 0.8%)
再パック版はすべてQ4_0に統一する。token_embdはそのままアウトプット層としても機能するため、トークン生成のたびにテーブル全体を読み込む必要がある。Q6_Kでは330.3MBだったこのテーブルが、Q4_0では226.5MB(31%削減)になる。読み込みバイト数が減るので、そのまま速度向上につながる。
gemma-4-E2B-it-q4_0-exact.gguf(再パック版)
Q4_0 2.603 GB (100.0%)
F32 0.001 GB ( 0.0%)
ファイルサイズは3,349,516,256バイトから2,620,370,912バイトへ、21.8%縮小。llama-benchでの計測では、再パック版が81.75 tok/s、Google版が73.26 tok/sと、1.12倍の速度差が出た。
精度面でも差がある。bf16モデルと比較したとき、Google版は次トークンの最有力候補が約8回に1回異なる。再パック版では50回に1回以下に抑えられている。
実際の手順
Step 1 — GGUFのダウンロード
hf download xbill9/gemma-4-E2B-it-qat-q4_0-exact-gguf gemma-4-E2B-it-q4_0-exact.gguf \
--local-dir ~/models/gemma-4-E2B-it-qat-q4_0-exact-v2
sha256sum ~/models/gemma-4-E2B-it-qat-q4_0-exact-v2/gemma-4-E2B-it-q4_0-exact.gguf
# 419db9a6bf3bc15d770c85ccf9216827aa88c64fe8f3d72fbe5674f48efe2dc8
Hugging Faceのxbill9/gemma-4-E2B-it-qat-q4_0-exact-ggufから取得する。必要なディスク容量は約3GBである。
Step 2 — サーバー設定
N_GPU_LAYERS=99
CONTEXT_SIZE=8192
KV_CACHE_TYPE=f16
FLASH_ATTENTION=1
PARALLEL_SLOTS=1
REASONING=off
REASONING=offがデフォルトになっている理由は明確だ。「9.11と9.9のどちらが大きいか」という質問でthinkingをオンにすると9.36秒かかる。オフなら1秒以下で返ってくる。
Step 3 — 起動と確認
gpu start
ask -q "hi"
llama.cppがファイルをメモリマップするため、起動は4秒で完了する。VRAMの使用量は1488MiB、カード全体の36%である。消費電力は40Wだ。
実測値
ブラウザのチャット画面では462トークンを6.0秒で生成し、77.35トークン/秒を記録した。サーバーのログでも安定した数字が出ている。
{"run":1,"predicted_per_second":76.1367071253587}
{"run":2,"predicted_per_second":76.3564025991796}
{"run":3,"predicted_per_second":76.20224702680754}
{"run":4,"predicted_per_second":76.87420440850953}
{"run":5,"predicted_per_second":77.1396194517872}
効果がなかった設定
xbillが実測して「使わない」と判断した設定も明記されている。
- KVキャッシュのq8_0量子化:テンソルコアがないカードではデコードが12%遅くなる
- **
GGML_CUDA_FORCE_MMQ**:llama.cpp自体がテンソルコアなしカードに推奨しているオプションだが、わずかに遅かった -nglを下げてVRAMを節約:そもそもVRAMには余裕があるため、CPUに処理を逃がすだけ損になる
注意点
再パックはGoogleが公開するgoogle/gemma-4-E2B-it-qat-q4_0-unquantized(bf16 safetensors)に依存している。またgguf_exact.pyは、ソースが4ビットグリッド上に学習されていないテンソルに対してはビルドを停止するため、QATモデル以外では機能しない。
コンテキストは8192トークンで、プリフィルは約345トークン/秒だ。4000トークンのテキストを流し込むと11秒以上の無音時間が生じるため、入力は短く保つことが推奨されている。
詳細はGemma 4 at Over 70 Tokens/s on a 2021 Laptop's 4 GB GPU: The Live Demo, Step by Stepを参照していただきたい。