Gemma 4 E2B vs E4B 徹底ベンチマーク|E2B が 20 倍遅くなる隠れ Thinking モードの正体
Gemma 4 のエッジ向けバリアント E2B と E4B は、「スマホや Raspberry Pi でも動く」層として位置付けられている小型オープンウェイトモデルです。単純に考えれば、小さい方が速いはずです。gemma4:e2b の有効パラメータは 2B、E4B は 4B なので、E2B が常に先行するように見えます。
ところが実際はそう単純ではありませんでした。RTX 3070 8GB 環境でのテストでは、E2B の純粋な token 生成速度は確かに E4B より 35〜40% 速いのですが、分類・抽出・翻訳・commit message といった短い実用タスクでは E4B の方が 5〜10 倍速く完了し、短い prompt の TTFT に至っては E2B の方が 20 倍も悪化します。この矛盾を追い込んでいった先に、Ollama の gemma4 renderer が E2B に対してだけこっそり <|think|> トークンを注入しているという、公式ドキュメントと食い違う挙動が見つかりました。
本記事ではベンチマーク全体、thinking モードの追跡、そしてベストプラクティスのチートシートを一通り紹介します。「どれを使えばいいのか」だけ知りたい方は末尾の ベストプラクティス へどうぞ。
テスト環境
すべて同一マシンで実施しています。
- CPU — AMD Ryzen 5 5600X(6C/12T)
- GPU — NVIDIA RTX 3070、8GB VRAM
- RAM — 32GB DDR4-3200
- OS — Windows 11 Pro
- Ollama — LAN に公開し、別マシンから Python で REST API を叩く構成
比較対象の 2 モデル:
- gemma4:e2b — total 5.1B、effective 2B、Q4_K_M
- gemma4:e4b — total 8.0B、effective 4B、Q4_K_M(
gemma4:latestと同じ SHA digest)
「E」は effective parameters の意味です。Ollama は総パラメータ数を表示しますが、MatFormer 構造により推論時はその一部だけが活性化されます。
/api/generate から TPS を取り出す Python はこのような形です。
import requests
resp = requests.post('http://192.168.51.202:11434/api/generate', json={
'model': 'gemma4:e4b',
'prompt': 'Explain what Docker is in 100 words.',
'stream': False,
})
data = resp.json()
eval_tps = data['eval_count'] / (data['eval_duration'] / 1e9)
print(f'Generation speed: {eval_tps:.1f} t/s')
eval_duration は nanoseconds、eval_count は生成された token 数。割れば generation phase の tokens per second が得られます。
TPS ベースライン
まずは出力長で生成速度が劣化するかを gemma4:e4b で確認します。
| テスト | 生成 tokens | Prompt 処理 | 生成速度 | 総時間 |
|---|---|---|---|---|
| 短文 | 15 | 263.5 t/s | 34.0 t/s | 5.02s(cold start 4.38s 含む) |
| 中文 | 183 | 13.5 t/s | 31.1 t/s | 8.19s |
| 長文 | 2,652 | 662.5 t/s | 29.7 t/s | 91.25s |
ポイント:
- 生成速度は出力長に関わらず ~30 t/s で安定しています。
- 初回ロードは 4〜5 秒の cold start。以降は VRAM に常駐するため即応答します。
- warm cache での prompt 処理は十分高速で、ボトルネックにはなりません。
- 30 t/s は会話用途なら快適ですが、大量の token を吐く agent loop では手狭に感じます。
Context length は速度に影響するか
num_ctx を VRAM の限界まで上げると遅くなるのでは、という懸念があります。実測:
| Context length | 生成速度 | Prompt 処理 |
|---|---|---|
| 4,096 | 30.2 t/s | 294.6 t/s |
| 8,192 | 29.6 t/s | 297.4 t/s |
| 16,384 | 30.0 t/s | 297.5 t/s |
| 32,768 | 30.2 t/s | 288.9 t/s |
| 65,536 | 30.0 t/s | 269.1 t/s |
ほぼ影響なしです。4K から 64K まですべて ~30 t/s。64K の prompt 処理が若干落ちる(269 vs ~295)程度で、誤差レベル。RTX 3070 で Gemma 4 E4B Q4_K_M を動かす場合は compute bound であり memory bandwidth bound ではなく、短い prompt なら KV cache も十分小さいので context length を削るメリットはほぼありません。
実用的な結論: num_ctx は VRAM が許す最大値まで上げて構いません。 Claude Code のような context を食うワークロードのためにマージンを確保できます。
Time To First Token(TTFT)
スループットだけでは体感を語れません。agent 系ワークフローでは TTFT(リクエスト送信から最初の token 到達までの時間)の方が効きます。最終的に 30 t/s で流れても、最初の 7 秒間黙っていたら体験は悪化します。
streaming mode で 3 回ずつ測定:
| テスト | 平均 TTFT | 個別結果 |
|---|---|---|
| 短文 | 1,068ms | 2,427ms (cold), 391ms, 386ms |
| 中文 | 7,301ms | 9,753ms, 10,222ms, 1,927ms |
| 長文 | 22,009ms | 23,000ms, 23,921ms, 19,106ms |
短文は cold start を除けば ~390ms と俊敏。しかし中文・長文は 7〜22 秒と重めです。prompt 処理は速いはずなのに、最初の token が出るまで何をしているのか?この疑問は thinking モード編で解決します。覚えておいてください。
E2B vs E4B 速度比較
直感的には E2B の方が全面的に速そうです。実測:
| 指標 | gemma4:e2b | gemma4:e4b |
|---|---|---|
| 生成速度(短文) | 46.0 t/s | 33.6 t/s |
| 生成速度(中文) | 40.0 t/s | 30.5 t/s |
| 生成速度(長文) | 41.9 t/s | 30.2 t/s |
| TTFT(短文) | ~5,863ms | ~300ms |
| TTFT(中文) | ~6,338ms | ~7,501ms |
| TTFT(長文) | ~14,641ms | ~16,987ms |
E2B の raw 生成速度は確かに 35〜40% 速い。ところが短文 TTFT は E4B の 約 20 倍遅い(5.8s vs 0.3s)。インタラクティブ用途で E2B を選ぶと、数字上は速いのに体感は重いという罠に嵌まります。
この矛盾は thinking モードを覗けば説明がつきますが、先に品質評価を片付けておきます。
難問での品質比較
プログラミング、論理、数学、要約、創作の 5 問。各 10 点満点で採点しました。
- プログラミング — list の 2 番目に大きい unique 値を返す Python 関数。edge case 対応込み。
- 論理推論 — A>B、B>C、D>A、C>E の条件から 5 人の身長順を導く。
- 数学 — リンゴ 3 個で $2、10 個買うといくらか。計算過程も。
- 要約 — Git の説明文を 2 文に圧縮。
- 創作 — 深夜 3 時の debug をテーマに俳句。
| 課題 | E2B | E4B | コメント |
|---|---|---|---|
| プログラミング | 8 | 8 | 両者とも正しく edge case も処理 |
| 論理推論 | 9 | 8 | どちらも D>A>B>C>E に到達。E2B の導出が明快 |
| 数学 | 7 | 9 | どちらも $6.67。E4B は整数単位でのグルーピングも考慮 |
| 要約 | 7 | 9 | E2B は Linus Torvalds と Bitbucket を落とす、E4B は保持 |
| 創作 | 7 | 9 | E4B はイメージが具体的(冷たい画面、コーヒー)、E2B は抽象的 |
| 合計 | 38/50 | 43/50 |
E4B は理解力・ディテール・表現力で明確に上。E2B もコード・論理は及第点ですが、要約や創作で細部を落とします。トレードオフは「生成速度 35〜40% 速い」vs「品質 ~10% 悪い」。用途次第です。
どんでん返し:実用タスクの結果
上の 5 問は 5B クラスには酷な設問でもあります(本来は 70B+ のクラウドモデルの領域)。そこで「入力短い・目的明確・出力短い」という小型モデル本来の得意領域に切り替えて再測定しました。
- 感情分類 — 商品レビューを positive/negative/neutral に分類。
- キーワード抽出 — Docker の説明文から 5 つ抽出。
- 構造化抽出 — email を JSON へ(
sender/date/subject/action_required)。 - シェルコマンド生成 —
/var/log配下で 24 時間以内に変更された.logを見つけるfind。 - 短文翻訳 — 英語のシステムメッセージを他言語に翻訳。
- Commit メッセージ — 変更記述から Conventional Commits 1 行を生成。
結果:
| 課題 | E2B 時間 / tokens | E4B 時間 / tokens |
|---|---|---|
| 感情分類 | 15.4s / 165 | 13.9s / 2* |
| キーワード抽出 | 7.4s / 280 | 0.74s / 13 |
| 構造化抽出 | 7.9s / 299 | 2.28s / 63 |
| シェルコマンド | 7.8s / 301 | 0.91s / 19 |
| 短文翻訳 | 14.9s / 588 | 1.10s / 23 |
| Commit メッセージ | 0.5s / 11 | 0.76s / 15 |
*cold start を含む
E4B が E2B より 5〜10 倍速い。raw TPS では遅いはずの E4B です。注目すべきは token 数で、E2B は 165〜588 tokens も消費している一方、E4B は 2〜63 tokens。つまり E2B はユーザーに見えない内部生成を大量に行っているわけです。
品質はほぼ同等。キーワード抽出では E2B が Docker, containers, virtualization, software, packages、E4B が Docker, virtualization, containers, OS-level, packages で、後者の方が OS-level の分だけ的確。commit message では E2B が feat: add exponential backoff retry to API client、E4B が feat(api): add retry mechanism with exponential backoff for network failures で、scope 付きの E4B の方が Conventional Commits として整っています。
明確に定義されたタスクでは E4B の方が優秀です。実時間も速く、品質もわずかに上で、余計な思考もしません。
パラメータ早見表
thinking モードの解剖に入る前に、サンプリング系パラメータを整理しておきます。Ollama の gemma4 既定値:
temperature 1
top_k 64
top_p 0.95
temperature— ランダム性。0 は常に最確率 token(決定的)、1 は raw 分布、>1 は発散的・創造的。コード生成や構造化抽出なら 0〜0.3、創作なら 0.8〜1.2 が目安。top_k— 上位 k 候補からのみサンプリング、残りは捨てる。小さいほど保守的。Gemma 4 既定の 64 はやや寛容寄り。top_p(nucleus sampling)— 累積確率が p に達するまでの候補を採用。top_kと併用され、両者の交集合からサンプリング。think— Ollama が chat models で提供する boolean。Gemma 4 固有の「思考してから回答する」モードを切り替えます。上の 3 つとは直交します。
最初の 3 つは「候補からどう 1 つ選ぶか」を変えるだけで、思考挙動には関与しません。E2B を遅くしている真犯人は think です。
Thinking モードの追跡
think パラメータで検証
Ollama の /api/generate は think boolean を受け付けます。
resp = requests.post('http://192.168.51.202:11434/api/generate', json={
'model': 'gemma4:e2b',
'prompt': '...',
'stream': False,
'think': False, # thinking を強制的に無効化
})
data = resp.json()
print(data['response']) # 最終回答
print(data['thinking']) # thinking トレース(think=True のときに入る)
短文翻訳の prompt で 5 パターン回したところ:
| 設定 | 時間 | tokens | 備考 |
|---|---|---|---|
| E2B デフォルト(think 未指定) | 20.0s | 583 | thinking 内容は parser が除去 |
E2B think=False | 0.73s | 18 | thinking をスキップ |
E2B think=True | 13.8s | 541 | thinking トレースが thinking フィールドへ(1,589 文字) |
| E4B デフォルト | 6.35s | 20 | thinking なし |
E4B think=True | 0.84s | 18 | thinking 挙動なし |
主な発見:
- E2B はデフォルトで thinking 有効。 約 500 個の内部 token を追加生成し、Ollama の
gemma4parser がそれを除去してから response を返します。見えないだけで、計算コストは払っています。 think=Falseを指定すると E2B は 20 倍速くなり、E4B と同等の応答時間に。think=Trueを明示すると最終回答はresponseに、thinking トレース(1,589 文字)はthinkingフィールドに分離されます。- E4B は thinking モードをまったくサポートしない。
think=Trueでもthink=Falseでも挙動は変わりません。
Thinking を切ると品質は落ちるか
20 倍高速化は魅力的ですが、代償は?実用 6 タスクで E2B デフォルト vs think=False を比較:
| 課題 | E2B デフォルト | E2B think=False | 差異 |
|---|---|---|---|
| 感情分類 | NEGATIVE | NEGATIVE | 同一 |
| キーワード抽出 | Docker, containers, virtualization, software, packages | Docker, virtualization, containers, software, packages | 順序のみ |
| 構造化抽出 | "Submit your reports by Friday" | "submit your reports by Friday" | 大文字小文字 |
| シェルコマンド | find ... -mtime -1 | find ... -mtime -1 | 同一 |
| 短文翻訳 | 自然な語順 | やや不自然な語順 | 軽微 |
| Commit メッセージ | feat: add exponential backoff retry... | feat: add retry mechanism with exponential backoff... | どちらも良い |
明確に定義されたタスクなら thinking を切っても品質低下はほぼゼロで、速度は 10〜20 倍。この種のワークロードでは実質ノーコスト。一方で推論や創作のような難題では thinking を残す方が安全で、E2B + think=True か、もっと大きなモデルに切り替えるのが妥当です。
なぜ E2B は既定で思考するのか:ドキュメントと実測のずれ
ここで疑問が出ます。なぜ E2B は既定で思考し、E4B はしないのか?Google 公式の Thinking mode in Gemma には、Gemma 4 の thinking モードは opt-in と明記されています。system prompt の先頭に <|think|> 制御トークンを付けて有効化する仕様です。Ollama 公式の gemma4:e2b ページにも同じ記述があります。
ところが実測では E2B は Ollama 上で明らかに thinking が既定で動いています。ドキュメントは「opt-in」、現実は「デフォルト ON」。では誰がトークンを注入しているのか?
raw=true で真犯人を特定
raw=true を使うと Ollama の内蔵 chat template と renderer をバイパスし、prompt をそのまま model に渡せます。公式フォーマットに従って 2 種類の template を手動構築:
# thinking なし
<|turn>user
[Prompt]<turn|>
<|turn>model
# thinking あり
<|turn>system
<|think|><turn|>
<|turn>user
[Prompt]<turn|>
<|turn>model
同じ短文翻訳 prompt で model ごとに 3 構成を比較:
| テスト | E2B tokens | E4B tokens |
|---|---|---|
Ollama 既定(raw=False) | 577(thinking) | 20(なし) |
手動 no-think + raw=True | 18(なし) | 19(なし) |
手動 with-think + raw=True | 552(thinking) | 19(なし) |
推論チェーン:
- E2B「Ollama 既定 577」≈ E2B「手動 with-think 552」→ Ollama が既定で E2B の prompt に
<|think|>を注入している証拠。 - E4B は
<|think|>を手動で入れても thinking しない → E4B はそもそもこのトークンに応答するよう訓練されていない。 - E2B に手動 no-think template を渡すと 18 tokens に戻り、API の
think=Falseと一致。
結論: Ollama の gemma4 renderer が E2B に対してだけ <|think|> を既定で注入している。Google と Ollama 両方のドキュメントが言う「opt-in」と矛盾する挙動です。意図的な特殊処理なのか bug なのかは不明ですが、実用上の教訓は一つ: E2B を速く使いたければ think=False を必ず渡すこと。
Claude Code をローカルで動かす話
自然に浮かぶ次の疑問: Claude Code をローカル Ollama に向ければクラウド API 代を浮かせられるのか?できますが、注意点があります。
基本的な設定はシンプルです。モデルを pull して Claude Code に Ollama のエンドポイントを教えるだけ:
ollama pull gemma4:e4b
Claude Code の settings.json で base URL をローカル Ollama に設定し、model を gemma4:e4b に指定します。実際に重要なのは次の 2 点:
- E2B ではなく E4B を使う。 本記事で示した通り、E2B の既定 thinking モードは tool call のたびに数秒の待ちを生みます。Claude Code は tool call が多いので致命的です。
num_ctxは最低 32K。 Claude Code のシステムプロンプトと context 累積はすぐ膨らむため、これ未満だと途中で切れる問題が出ます。
ただし体験はクラウド版 Claude より明らかに遅く、信頼性も落ちます。ローカル 4B モデルはターゲット編集ではなくファイル全体の上書きを時々やらかしますし、tool use の判断力もフロンティアモデルには及びません。「スタック全体の仕組みを学ぶ」「軽い text-processing を自動化する」用途と割り切り、実プロジェクトのメインアシスタントとしては期待しない方が良いです。gemma4:e4b の最適な居場所は cron job、Git hook、Slack bot など「無料でオフライン」という条件が品質以上に価値を持つ場所です。
ローカル edge モデルのベストプラクティス
ここまでの知見を実用ガイドにまとめます。
- 推論が必要なら E2B +
think=True。 深い回答と引き換えに速度を犠牲に。 - 明確なタスクなら E4B(または E2B +
think=False)。 分類・抽出・翻訳・commit message は 10〜20 倍速く、品質もほぼ同等。 - Claude Code などの agent ワークフローなら E4B。 E2B の既定 thinking はインタラクション体験を大きく損ないます。
- Context length は VRAM の最大値まで上げる。 4K と 64K で速度差がないため、overflow 防止のためにも大きく取っておく。
- サンプリングパラメータは既定でほぼ OK。 決定的な出力が欲しい場合は
temperatureを 0.1〜0.3 に、創作用途では 1.0 以上に。
これを Python でまとめたテンプレート:
import requests
def quick_task(prompt: str) -> str:
"""明確なタスク向けの最速設定。"""
resp = requests.post('http://192.168.51.202:11434/api/generate', json={
'model': 'gemma4:e4b',
'prompt': prompt,
'stream': False,
'think': False,
'options': {
'temperature': 0.2,
'num_ctx': 8192,
},
})
return resp.json()['response'].strip()
msg = quick_task('Write a conventional commit message for: added retry logic to API client')
print(msg)
まとめ
「RTX 3070 で Gemma 4 はどのくらい速いのか」という素朴な疑問から始めたベンチマークが、最終的には「Ollama の gemma4 renderer が E2B にだけ thinking を既定で注入している」という、公式ドキュメントに反する挙動を偶然掘り当てる結果になりました。この 1 点が E2B の矛盾をほぼすべて説明します。紙面上は E2B が速いのに実運用では遅い理由、短文 TTFT が極端に悪化する理由、そして think=False で 20 倍速くなりつつ品質がほぼ落ちない理由。
正味のところ、8GB の単一コンシューマー GPU で回す小型モデルワークロードなら、gemma4:e4b + think=False が最適解です。E2B + thinking は本当に難しい問題だけに取っておき、重い reasoning はクラウドモデルに任せるのが賢い分担でしょう。