ローカルLLMのreasoning effort設定とは
reasoning effortで変わるのは重みやquantisationではなく推論トークン数です。VPSや自前GPUで、応答時間とcontext windowの実コストを測る方法を解説します。
ローカル LLM で reasoning effort により変わるもの
Reasoning effort は、モデルが回答前にどれだけ長く思考するかを指定する設定です。変わるのは reasoning セグメントの長さだけです。ディスク上の weights はすべてのレベルで同一です。quantisation も同一で、回答は同じ forward pass から生成されます。変わるのは、モデルが最初に自身の scratchpad に費やすトークン数です。
この違いが重要なのは、これらのトークンが占める場所が異なるためです。ホスト型 API では、reasoning tokens は請求額に反映されます。自身で所有する VPS では、自分の CPU または GPU の生成時間と、context window 内の容量として消費されます。モデルを最高の effort に設定したままにすると、回答の最初の単語が現れる前に、出力の大部分を reasoning に費やすことがあります。self-hosted hardware では、これにより返信時間が 2 秒から 2 分に延びる場合があります。
レベルが定義される場所: 重みではなくチャットテンプレート
思考モデルは、最終回答の前に、通常は <think> タグと </think> タグで囲んだ推論セグメントを出力するように訓練されています。推論の強度は、モデルのチャットテンプレートがプロンプトに書き込む指示です。このテンプレートは、モデルに付属する Jinja ファイルです。reasoning_effort などの変数を読み取り、値ごとに異なるシステムレベルの行を生成します。モデルは、その行に応じてスクラッチパッドを短くしたり長くしたりするように訓練されています。
ここから 2 つの点が分かります。レベル名はランタイムではなくモデルに属するため、あるモデルのカードにある名前が別のモデルでは意味を持たない場合があります。また、処理チェーンのどこかでモデルのチャットテンプレートが汎用テンプレートに置き換わると、変数は生成されず、設定は何もせずに無効になります。
2026-08-20 に確認したところ、Qwen3.8-27B のモデルカードには、3 つの推論強度レベル low、medium、xhigh が記載されており、デフォルトは xhigh です。high はありません。思考機能自体は enable_thinking で切り替えます。デフォルトで有効です。モデルカードには、デフォルトで有効な preserve_thinking も記載されています。これは、以前のターンの推論を会話履歴に残します。gpt-oss では、代わりに low、medium、high を使用します。多くの他のモデルファミリーは、ブール値だけを受け付けます。これらの名前は標準化されていないため、取得した正確なバージョンのモデルカードを確認してください。まず VPS で 27B モデルを起動する を参照してください。このページでは、モデルが応答するようになった後に何を設定するかを説明します。
VPS で高負荷な推論にコストがかかる理由
出力トークン。 推論トークンは生成されるトークンです。回答と同じデコード処理を通り、ハードウェアが処理できる同じトークン毎秒で生成されます。回答が 200 トークン、推論が 4,000 トークンのタスクを考えます。生成したトークンは 4,200 ですが、読者に表示されたのは 200 トークンです。デコード速度はメモリ帯域幅と 選択した量子化方式 で決まるため、残る調整手段はトークン数そのものです。
経過時間。 利用者が待つのは、回答 の最初のトークンです。それより前は空白画面か、折りたたまれたスピナーが表示されるだけだからです。推論は先に出力されるため、待ち時間はおおむね推論トークン数をデコード速度で割った時間に、プロンプト処理時間を加えたものになります。推論の長さを 2 倍にすると、その待ち時間も 2 倍になります。
コンテキスト。 推論トークンは、他のトークンと同様にコンテキストウィンドウを占有します。preserve_thinking を有効にすると、1 回目のターンのスクラッチパッドが 5 回目のターンでもプロンプトに残ります。そのため、ウィンドウが両端から埋まるにつれて、プロンプト処理は毎ターン遅くなります。保持できるよう num_ctx を増やす と KV キャッシュのメモリを消費します。GPU のない VPS では、そのメモリは余裕のないシステム RAM である可能性があります。
レベルを上げる場合と、低く保つ場合
中間段階を誤ると結果全体が崩れる作業では、レベルを上げます。複数段階の算術計算や単位変換、複数ファイルにまたがる編集の計画、コンパイルが必要なコード、複数の条件を同時に満たす1つの解を求める制約問題などが該当します。これらでは思考用の作業領域が実際の役割を果たすため、長めに設定すると、モデルがそのまま確定してしまうエラーを低コストで見つけられます。
答えがすでに入力にあり、それを移すことが目的の場合は、低く保ちます。抽出、分類、タグ付け、翻訳、書き換え、要約、書式設定はいずれもこの範囲に含まれます。推論部分は主にタスクを言い換えるだけであり、モデルが正しい最初の判断を自ら覆す余地を与えてしまいます。
対話的な作業でも低く保ちます。チャットボックスやエディターでは利用者が処理に関与しているため、待つ必要がある遅い回答より、すぐに得られて修正できる回答の方が優れています。これが、コーディングエージェントをローカルモデルに接続する場合の本当のトレードオフです。エージェントは小さな呼び出しを何度も行うため、推論のコストがそのたびに発生します。
llama.cpp でレベルを設定する方法
llama.cpp はこの変数をテンプレートに直接書き込むため、レベルが確実に渡ったことを確認できる実行時環境になります。-m には、すでに用意している GGUF を指定します。
llama-server -m ./qwen3.8-27b-Q4_K_M.gguf \
--jinja \
--reasoning-effort medium \
--reasoning-format deepseek \
-c 32768 \
--host 127.0.0.1 --port 8080--jinja はモデル独自のチャットテンプレートを使用し、現在のビルドではデフォルトで有効です。--reasoning-effort には default、minimal、low、medium、high、xhigh、max を指定できます。default はテンプレート固有のデフォルト値を変更しないことを意味します。この一覧はモデルではなく llama.cpp の語彙です。そのため、カードに記載された名前だけを指定してください。テンプレートで定義されていないレベルを指定すると、リクエスト時にテンプレートエラーが発生することがあります。--reasoning-format deepseek を使用すると、推論が message.content の外側へ移動し、message.reasoning_content に格納されます。これにより、次のセクションで分離した状態を測定できます。
推論を短くするのではなく無効にするには、テンプレート変数を自分で設定します。
llama-server -m ./qwen3.8-27b-Q4_K_M.gguf --jinja \
--chat-template-kwargs '{"enable_thinking": false}'--reasoning-budget は別の仕組みです。推論セグメントのトークン数に上限を設定します。0 は推論を直ちに終了させ、-1 は上限を設定しません。これは、モデルに短い計画を求める設定ではありません。どちらのフラグもサーバー全体に適用されます。llama-server は reasoning_effort をリクエスト単位のフィールドとして受け付けません。そのため、2 つの推論レベルを同時に提供するには、2 つのポートで 2 つのプロセスを実行します。
vLLM では、OpenAI 互換のリクエスト本文内で、リクエストごとに同じ変数を指定できます。
{"model": "Qwen/Qwen3.8-27B",
"messages": [{"role": "user", "content": "Summarise this changelog in two lines."}],
"chat_template_kwargs": {"reasoning_effort": "medium"}}Ollama でレベルを設定する方法
Ollama には think 自体のフィールドがあり、/api/chat と /api/generate で使用できます。true、false、または low、medium、high、max のいずれかを指定できます。max は、モデルが提供する最高レベルを要求します。思考機能に対応するモデルでは、デフォルトで有効になっています。
ollama run qwen3.8:27b --think=low "Draft a one line commit message for a README typo fix"{"model": "qwen3.8:27b",
"messages": [{"role": "user", "content": "Which HTTP status code means the request body was too large?"}],
"think": "low",
"stream": false}推論結果は message.thinking に、回答は message.content に返されます。すでに分離されているため、個別に処理できます。対話型の ollama run セッション内では、/set think と /set nothink で再起動せずに切り替えられます。
ここで不一致に注意してください。Ollama の用語は low、medium、high、max です。一方、Qwen3.8 のテンプレートでは low、medium、xhigh が定義されています。一方を他方に対応付ける必要があります。また、Ollama のモデルには、元のリポジトリにある Jinja ファイルではなく、タグ内にパッケージ化されたテンプレートが含まれています。そのため、指定したレベルがモデルに届くかどうかは、そのパッケージ化されたテンプレートに依存します。動作したと決めつけないでください。測定には約 1 分かかります。
実際にレベルが適用されたかを測定する方法
temperatureを0に設定し、複数のレベルで同じプロンプトを送信してから、トークン数を比較します。ここではjqが本文を組み立てるため、引用符を手動でエスケープする必要はありません。
for level in low medium max; do
body=$(jq -n --arg lvl "$level" '{
model: "qwen3.8:27b",
messages: [{role: "user", content: "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take? Answer in minutes."}],
think: $lvl,
stream: false,
options: {temperature: 0, num_ctx: 8192}
}')
echo "== $level"
curl -s http://localhost:11434/api/chat -d "$body" | jq '{
thinking_chars: (.message.thinking // "" | length),
answer_chars: (.message.content | length),
eval_count: .eval_count,
seconds: (.total_duration / 1e9),
tok_per_sec: (.eval_count / (.eval_duration / 1e9))
}'
doneeval_countは推論を含む生成トークンの総数です。そのため、2つのレベル間の差はほぼすべて推論によるものです。thinking_charsを使うと、その内訳を直接取得できます。確認すべき点は2つあります。レベル間で数値が変化することと、低いレベルでも回答が正しいことです。3回の実行すべてでeval_countがノイズの範囲内に収まる場合、そのレベルは無視されています。別のレベル名を試すのではなく、レベルを渡すランタイムを使用してください。
合計時間だけでは不十分です。ストリーミングし、最初の空でないcontentチャンクで停止して、最初の回答トークンまでの時間も測定します。この測定にはjqとbcが必要です。
start=$(date +%s.%N)
curl -sN http://localhost:11434/api/chat -d '{
"model": "qwen3.8:27b",
"messages": [{"role": "user", "content": "Explain what a reverse proxy does, in three sentences."}],
"think": "low",
"stream": true
}' |
while IFS= read -r line; do
if [ -n "$(printf '%s' "$line" | jq -r '.message.content // ""')" ]; then
echo "first answer token after $(echo "$(date +%s.%N) - $start" | bc)s"
break
fi
donelowで実行し、続けてmaxでも実行します。その差が、追加で支払う待ち時間です。llama.cppでは、同じ数値がレスポンス内に返るため、shellで計算する必要はありません。
curl -s http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model": "local", "temperature": 0,
"messages": [{"role": "user", "content": "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take?"}]}' | jq '{
reasoning_chars: (.choices[0].message.reasoning_content // "" | length),
answer_chars: (.choices[0].message.content | length),
predicted_n: .timings.predicted_n,
tok_per_sec: .timings.predicted_per_second
}'自分のサーバーで実行してください。公開された推論負荷の比較は、自分の環境とは異なるハードウェアで測定されています。トークン数を秒数に換算するのは、使用環境のデコード速度です。自分のサーバーで1秒あたりのトークン数を測定する方法でこの値を取得できます。推論トークン数をデコード速度で割ると、追加した待ち時間になります。
発生する問題
回答が途中で切れる、または content が空で thinking だけが埋まる。 推論によって生成上限を使い切っています。Ollama の num_predict は、推論を含む生成全体に上限を設定します。推論が先に行われるため、高いレベルで上限が 512 tokens だと、回答が始まる前に応答が終了することがあります。Ollama は、その応答について "done_reason": "length" を報告します。上限を引き上げるか、レベルを下げてください。num_predict が tokens を数える方法で、この相互作用を詳しく説明しています。
レベルを変えても何も変わらない。 どのレベルでも token 数が同じ場合、runtime が変数を渡していないか、template がその変数を読み取っていません。元の repository にある template ではなく、実際に runtime が使用している template を確認してください。llama.cpp は --jinja と --chat-template-kwargs を使って変数を手動で埋め込むため、比較対象として適しています。そこでレベルが機能し、それ以外では機能しないなら、model に問題はなく、別の runtime が変数を破棄しています。
レベル名が拒否される。 request 時の template error、または正常な server が最初の message で失敗する場合は、通常、template に定義されていないレベルを渡しています。たとえば、model card に low、medium、xhigh だけが記載されている model に high を渡す場合です。
複数ターンの chat で、ターンを重ねるたびに遅くなる。 過去の推論が history に保持されています。model が対応している場合は preserve_thinking を false に設定するか、返送する messages から thinking フィールドを削除してください。そうしないと、回答の長さが変わらなくても、ターンごとに prompt processing が増加します。
単純だと思っていたタスクで、低いレベルにすると品質が低下する。 一部の抽出処理は、単なる抽出ではありません。入力に単位変換や、順序に従ったルールの適用が必要なら、出力が短くても reasoning task です。server 全体の設定を変更するのではなく、その call だけレベルを上げてください。
同時に2つのレベルを実行する
llama.cpp は起動時にレベルを固定します。そのため、エディターと夜間のバッチジョブの両方に対応するサーバーでは、2つのポートで2つのプロセスを実行し、それぞれに独自の --reasoning-effort を設定する必要があります。2つのプロセスを実行すると、ジョブを時間帯で分離しない限り、メモリ上に重みが2つ分必要になります。1台の VPS では通常、人が待っている処理には低い負荷設定のサーバーを使い、誰も監視していない処理には高い負荷設定でスケジュール実行する構成が最も低コストです。複数のユーザーが1つのローカルモデルを共有するとどうなるかもここに当てはまります。推論トークンはデコード処理なので、負荷設定を高くするとトークン数が増えるのとほぼ同じ割合で、実効的な同時実行数が減少します。
FAQ
デフォルトではどの reasoning effort level を使うべきですか?
モデルが提供する最低レベルから始め、実際に失敗したタスクに対してのみ引き上げてください。複数の thinking model では高い値がデフォルトになっており、2026 年 8 月時点で Qwen3.8-27B のデフォルトは最上位レベルの xhigh です。このデフォルトはベンチマーク表で良い結果を出すために設定されていますが、ベンチマーク表は処理時間を課金しません。自分のハードウェアでは秒単位でコストが発生するため、高いレベルはすべてのリクエストが継承する設定ではなく、タスクごとに明示的に選択するものにしてください。
reasoning token はコンテキストウィンドウに含まれますか?
はい。通常の出力 token として扱われ、ほかのすべての内容とともにコンテキストウィンドウに格納されます。次のターンでも保持されるかどうかは、runtime と model によって異なります。Qwen3.8 の card には、デフォルトで有効な preserve_thinking が記載されています。これは以前の reasoning を履歴に保持するため、長い会話では生成されたすべての scratchpad が蓄積します。これを false に設定するか、再送する messages から thinking field を削除すると、prompt processing の増加を止められます。
thinking level を変更しても token 数が変わらないのはなぜですか?
その設定が chat template に渡っていません。level は template variable であるため、runtime が値を渡し、使用する template がその値を読み取る場合にだけ機能します。一部の runtime は、元の repository にある Jinja file ではなく、独自の template を model とともに提供します。この場合、variable はどこにもエラーを出さずに破棄されます。最低レベルと最高レベルで同じ prompt を送信し、temperature を 0 に設定したうえで eval_count を比較すれば確認できます。値がノイズの範囲内で一致するなら、level は無視されています。
reasoning effort を下げると model の精度は低下しますか?
タスクによって異なるため、前提を置かずに測定してください。入力に答えがすでに含まれている extraction や rewriting では、通常、scratchpad を短くしても結果は変わりません。一方、最終結果の前に中間ステップを正しく処理する必要がある multi-step arithmetic や、compile が必要な code では、scratchpad を短くすると精度が低下します。実際の workload から 20 個の prompt を用意し、temperature を 0 に設定して 2 つの level で実行し、誤答数を数えてください。その数値は workload に固有のものであり、公開された表から求めることはできません。