28
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

gpt-ossの推論を6倍速に。Thinking OFF設定と、Ollamaで効かない理由

28
Posted at

はじめに

GMOコネクトの永田です。

ローカルLLMの推論をもっと速くしたい!
LLMを利用するタスクによっては1件あたり数十秒〜かかり、処理件数が多いとかなりの処理時間になります。

gpt-ossの場合、推論時間の8割はreasoning tokens、つまりモデルが内部で生成する思考過程です。これをOFFにすればタスクによっては12秒が2秒まで縮みます。
ただし、設定方法は推論エンジンごとに違ったり、Ollamaでは見かけだけOFFになる罠もあったりします。

今回、gpt-ossを例に、Think OFFの仕組み、推論エンジン別の設定手順、速度と精度のベンチマーク結果をまとめました。

前回の記事「LLM出力の精度90%→98%に。LLM-as-judgeとClaude Codeで自律チューニング」で予告していた、Think OFF設定手順の記事です。

(最初に)まとめ

chat_templateを1箇所書き換えるだけで、gpt-oss:20bの推論が12.4秒から2.0秒に縮みます。llama-serverとSGLangで有効で、Ollamaでは効きません。

推論エンジン Think OFF 方法 Docker
llama-server --chat-template-file でカスタムテンプレート指定 不要
SGLang --chat-template でカスタムテンプレート指定 必要
Ollama 不可 チャネルトークンの独自パース層がテンプレート改変を無効化
設定 速度 高速化 精度影響
Think ON(デフォルト) 12.4s/件 基準
Think OFF 2.0s/件 6.2x タスク次第で-20pt
reasoning_effort=low 8.1s/件 1.5x Think OFFと同水準
Ollama nothink 10.4s/件 1.2x 変化なし(実質Think ON)

ただし精度はタスク次第です。メタデータ抽出では精度を落とさず7.6倍速くなりましたが、レビューコメント分類では20pt落ちました。

reasoning tokenの仕組み

gpt-ossのチャネル構造

gpt-ossは出力をanalysisとfinalの2つのチャネルに分けています。analysisに思考過程、finalに最終回答が入ります。

<|start|>assistant<|channel|>analysis<|message|>
(思考過程: 500〜800 tokens)
<|end|>
<|start|>assistant<|channel|>final<|message|>
(最終回答: 70〜150 tokens)
<|end|>

Think ONではanalysisに思考を書き出してからfinalで回答します。Think OFFではanalysisごとスキップして、いきなりfinalに入ります。

なぜThink OFFが速いのか

推論時間の内訳を見ると、トークン生成のDecodeが全体の79%を占めています。Think OFFにしてreasoningの500〜800 tokensを丸ごと消せば、ここが一気に縮みます。

フェーズ Think ON Think OFF
Prefill(入力処理) ~0.5s (3%) ~0.5s
Decode(トークン生成) ~11.9s (79%) ~1.2s
オーバーヘッド ~2.6s (18%) ~0.3s
合計 ~15s ~2.0s

DGX Sparkのメモリ帯域幅は273 GB/s、Decode速度は約59 tokens/秒。生成トークン数を減らすのが最も直接的な高速化です。Prefillは全体の3%なので、Flash AttentionやKVキャッシュ量子化を入れても体感は変わりません。

推論エンジン別の設定方法

llama-server(推奨・Docker不要)

GGUFモデルの準備

まずGGUF形式のモデルをダウンロードします。

# huggingface-hub でダウンロード
pip install huggingface-hub
huggingface-cli download \
  ggml-org/gpt-oss-20b-GGUF \
  gpt-oss-20b-mxfp4.gguf \
  --local-dir ~/models

注意点として、OllamaのGGUFファイルはアーキテクチャ名がgptoss(ハイフンなし)になっています。llama.cppはgpt-oss(ハイフンあり)を期待するため、Ollama由来のGGUFは読めません。ggml-org/gpt-oss-20b-GGUFから取得してください。

chat_templateの仕組み

Think OFFの核心はchat_templateの書き換えです。generation promptに空のanalysisブロックを事前に入れておくと、モデルは「思考はもう終わった」と認識して、直接finalチャネルから回答を始めます。

# オリジナル(Think ON)
<|start|>assistant

# Think OFF版
<|start|>assistant<|channel|>analysis<|message|><|end|><|start|>assistant<|channel|>final<|message|>

テンプレートファイルで書き換えるのは末尾のgeneration prompt部分だけです。

{#- Generation prompt - MODIFIED: skip reasoning by pre-filling empty analysis block #}
{%- if add_generation_prompt -%}
<|start|>assistant<|channel|>analysis<|message|><|end|><|start|>assistant<|channel|>final<|message|>
{%- endif -%}

ベースはgpt-ossの公式chat_template(openai/gpt-oss-20bchat_template.jinja)で、変更はこのgeneration prompt部分のみです。

起動コマンド

~/llama.cpp/build/bin/llama-server \
  --model ~/models/gpt-oss-20b-mxfp4.gguf \
  --jinja \
  --chat-template-file ~/chat_template_no_reasoning.jinja \
  --n-gpu-layers 99 --ctx-size 4096 \
  --host 0.0.0.0 --port 8080

--jinja--chat-template-fileの両方を付けます。起動ログにthinking = 0と出ればテンプレートが効いています。

Pythonからの呼び出し

OpenAI互換APIなので、ふつうのchat completions呼び出しです。

import json, urllib.request

body = {
    "model": "gpt-oss-20b",
    "messages": [
        {"role": "system", "content": "あなたはデータ分析アシスタントです。"},
        {"role": "user", "content": "このテキストを分類してください。"}
    ],
    "temperature": 0.3,
    "max_tokens": 4096,
}

req = urllib.request.Request(
    "http://localhost:8080/v1/chat/completions",
    data=json.dumps(body).encode("utf-8"),
    headers={"Content-Type": "application/json"},
)

with urllib.request.urlopen(req, timeout=300) as resp:
    result = json.loads(resp.read())
    print(result["choices"][0]["message"]["content"])
    print(f"completion_tokens: {result['usage']['completion_tokens']}")

Think OFFが効いていれば、completion_tokensは70〜150程度に収まります。Think ONだと500〜1000になるので、ここで判別できます。

SGLang(Docker環境)

SGLangはDockerが必要ですが、速度はllama-serverと同等です。

docker run --gpus all \
    --shm-size 32g \
    -p 30000:30000 \
    -v ~/.cache/huggingface:/root/.cache/huggingface \
    -v ~/chat_template_no_reasoning.jinja:/opt/chat_template.jinja:ro \
    --env "HF_TOKEN=$HF_TOKEN" \
    --ipc=host \
    lmsysorg/sglang:spark \
    python3 -m sglang.launch_server \
      --model-path openai/gpt-oss-20b \
      --host 0.0.0.0 --port 30000 \
      --chat-template /opt/chat_template.jinja

chat_templateファイルをコンテナにマウントして--chat-templateで指定します。

SGLangには--reasoning-parser gpt-ossというオプションもありますが、これはreasoning tokensをAPIレスポンス上で分離するだけで、生成自体は止まりません。Think OFFにするにはchat_templateの書き換えが要ります。

APIはllama-serverと同じOpenAI互換なので、クライアント側のコード変更は不要です。

Ollama(Think OFF不可)

Ollamaではchat_template改変によるThink OFFができません。4つの方法を試して、すべて失敗しています。

方法 結果 原因
Modelfileテンプレートに空analysisブロック注入 出力が空 チャネルトークンの独自パース層が先に消費
Modelfileで直接finalチャネル指定 出力が空 同上
システムメッセージに「Reasoning: none」 速度改善なし(14.8s) reasoning生成自体は抑制されない
Ollama API think: false 速度改善なし 非表示にするだけ

原因はOllamaのアーキテクチャだと思われます。

Ollamaはgpt-ossのチャネルトークンを処理する独自のパーサー(harmonyパッケージ)を持っていて、テンプレートに書いたチャネルトークンがモデルに届く前にOllama側で消費されるようです。Ollama Issueでもno thinkingが効かないという内容がありました。

なお、llama-serverやSGLangはJinjaテンプレートをそのままトークン列に変換するので、空analysisブロックの事前注入が効きます。

nothinkオプションの落とし穴

Ollamaのモデル定義ファイルにPARAMETER nothink trueを書くと、APIレスポンスからreasoningが消えます。一見Think OFFに見えますが、内部ではreasoningを生成してDecodeしています。

# Modelfile
FROM gpt-oss:20b
PARAMETER nothink true

completion_tokensと速度を並べると実態が見えます。

指標 Think ON Ollama nothink 本当のThink OFF
completion_tokens 524 503 73
速度 12.4s 10.4s 2.1s

nothinkのcompletion_tokensは503トークンで、Think ONの524トークンとほぼ変わりません。本当のThink OFFなら73トークンまで下がります。速度もThink ONに近い10.4秒のままで、本当のThink OFFの2.1秒とは別物です。

nothinkを使う場合、completion_tokensがThink ONの1/5以下に減っているか、速度が3倍以上になっているかなどを確認してください。どちらも満たさなければ、裏でreasoningが動いています。

reasoning_effortパラメータ

Think OFFとは別に、reasoningの量を加減するパラメータもあります。chat_templateのJinja変数reasoning_effortがシステムプロンプトにReasoning: low|medium|highを埋め込み、モデルの思考量を変えます。

{%- if reasoning_effort is not defined %}
    {%- set reasoning_effort = "medium" %}
{%- endif %}
{{- "Reasoning: " + reasoning_effort + "\n\n" }}

渡し方は推論エンジンごとに違います。

# SGLang: トップレベルパラメータ
body = {
    "model": "gpt-oss-20b",
    "messages": messages,
    "reasoning_effort": "low",  # SGLang はここを見る
}

# llama-server: chat_template_kwargs 経由
body = {
    "model": "gpt-oss-20b",
    "messages": messages,
    "reasoning_effort": "low",
    "chat_template_kwargs": {"reasoning_effort": "low"},  # llama-server はここを見る
}

両方入れておけばどちらのエンジンでも動きます。

ベンチマーク

速度比較

DGX Spark上での実測値です。

モデル エンジン Think設定 速度 completion_tokens (avg) 高速化
120b Ollama ON 18.5s/件 475 1x(ベースライン)
20b Ollama ON 12.4s/件 524 1.5x
20b Ollama nothink(見かけだけ) 10.4s/件 503 1.8x
20b SGLang ON 21.1s/件 1,014 0.9x
20b SGLang effort=low 8.1s/件 218 2.3x
20b llama-server OFF 2.1s/件 73 8.8x
120b llama-server OFF 4.2s/件 66 4.4x
20b SGLang OFF ~2.0s/件 ~80 ~7.6x

SGLangのcompletion_tokensが1,014と多いのは、--reasoning-parserでreasoning tokensがAPI上分離されるもののカウントには含まれるためです。OllamaはAPI上からreasoningを隠すので、completion_tokensの直接比較には注意が要ります。

精度比較(タスク別)

精度への影響はタスクで大きく変わります。2つのタスクでの実測値です。

タスク Think ON Think OFF 差分
メタデータ抽出(単純な構造化) 8/8 8/8 精度維持
レビューコメント分類(多段階推論) 96.5% 76.3% -20pt

メタデータ抽出は、研究データのタイトルやプロジェクト名を所定のフィールドに埋めるタスクです。入力と出力がほぼ1対1に対応していて、reasoningが要りませんでした。

レビューコメント分類は、Redmineチケットの本文やコメントからレビュー指摘を識別して4カテゴリに振り分けるタスクです。複数コメントを横断して文脈を読み、技術的指摘の有無を判断しなければなりません。Think OFFにすると、この多段階の推論が飛んで、複雑なチケットを安易にnoneと判定してしまいます。見落としは5件から18件に跳ね上がりました。

reasoning_effort=low

reasoning量を短くするreasoning_effort=lowも試しました。

構成 completion_tokens 速度 妥当率
Think ON(medium、デフォルト) 1,014 21.1s 98.1%
reasoning_effort=low 218 8.1s 78.0%
Think OFF(完全省略) 73 2.1s 76.3%

短くしても78.0%、完全に省略しても76.3%。精度低下の幅はほぼ同じです。このタスクでは十分な量のreasoningが必要で、中途半端に削っても意味がありませんでした。

explicit CoTによる精度回復の試み

Think OFFの精度低下を補おうと、プロンプト側にexplicit chain-of-thoughtを組み込みました。コメントを1件ずつスキャンしてから分類結果を出す2段階構成です。

構成 completion_tokens 速度 妥当率
Think ON(モデル内部のreasoning) 524 12.4s 96.5%
Think OFF(reasoning省略) 73 2.1s 76.3%
Think OFF + explicit CoT 380 13.4s 87.5%

精度は76.3%から87.5%に上がりましたが、出力トークンが73から380に膨らんだぶん、速度が2.1sから13.4sまで落ちました。Think ONの12.4sとほぼ同じです。精度を取り戻そうとすると速度が消える。しかも96.5%には届きません。

モデル内部のreasoningはanalysisチャネルで処理されるため、出力トークンとしてカウントされません。プロンプトでCoTを強制すると思考過程がfinalチャネルに出てしまい、そのぶんDecodeに時間がかかります。

まとめ

Think OFFの設定はchat_templateの書き換え1箇所です。速度は5〜8倍になりますが、精度はタスクの複雑さで決まります。reasoning_effort=lowで中間を取ろうとしても、精度低下はThink OFFと同じでした。Ollamaのnothinkは裏でreasoningが動いているので、completion_tokensと速度で検証してから使ってください。


最後に、GMOコネクトではサービス開発支援や技術支援をはじめ、幅広い支援を行っておりますので、何かありましたらお気軽にお問合せください。

お問合せ: https://gmo-connect.jp/contactus/

28
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?