制約付きデコーディング:LLMを高速な判断モデルにする
制約付きデコーディングでLLMを高速な分類器として使う方法を解説。logitマスキング、キャリブレーション、温度スケーリングの要点をまとめました。

ちょっとした裏技ですが、これが地味に役立ちます。LLMが最初に出力するトークンだけに関心があるなら、選択式の質問を投げるだけで、順伝播1回で答えを得られます。制約付きデコーディング、つまり語彙のうち許可したいごく一部のトークン以外をすべてマスクする手法を使うと、生成モデルがほぼ分類器のように振る舞います。JSONのパースも再試行ループも不要で、11文字を出すのに11回の自己回帰ステップを踏む必要もありません。1回の順伝播とsoftmaxで、各選択肢に確率のついた答えが返ってきます。
このアイデアは「判断モデル」や「システム1」的な推論といった名前で以前から語られており、最近は1.7Bパラメータの Qwen モデルを使って約40行のPythonで実装する解説記事がHacker Newsで話題になりました。コメント欄の古参の機械学習エンジニアたちは、案の定「それは分類器だろう、パーセプトロンの時代からあるぞ」と反発しました。確かにその通りで、少しポイントを外しています。新しいのは概念ではなく、汎用の言語モデルを、何も学習させずにゼロショット分類器として使えるという点です。本当に面白い工学的な問いは、この制約付きアプローチがモデルに自由に話させるより優れるのはいつか、そして、いつ過信した確率で静かに嘘をつくのか、という点にあります。
LLMから答えを取り出す2つの方法
言語モデルと対話するときの基本モードは生成です。質問を投げると、モデルがトークンを1つずつ出力し、その後で出力を解析します。構造化された出力が必要なら、JSONモードや文法制約付きサンプリング、outlines系の有限状態機械などをあとから組み込みます。これらはうまく機能しますが、モデルは応答全体をトークンごとに歩いていくことに変わりはありません。単純な選択式の答えでも11回のデコーディングステップが必要になることがあり、各ステップでは数十億パラメータに対する順伝播が丸ごと1回走ります。
もう一つの方法は、そもそもモデルに歩かせないことです。プロンプトを処理した後、最終位置での語彙全体のlogitを見て、選択肢に対応するトークンID(たとえば「A」「B」「C」「D」「E」)以外をすべて捨て、それらだけでsoftmaxを取ります。argmaxが予測結果、softmaxの値がスコアになります。総コストは順伝播1回で、もともと払っていたプレフィル処理と同じです。最近話題になっているQwenベースの手法を元にした、核心部分は次のとおりです。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "Qwen/Qwen3-1.7B"
options = ["A", "B", "C", "D", "E"]
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name, torch_dtype="auto", device_map="auto"
)
# First token the model would emit for each option
option_token_ids = [
tokenizer.encode(opt, add_special_tokens=False)[0] for opt in options
]
prompt = (
"What color is the sky?\n"
"A. Red\nB. Blue\nC. Green\nD. Purple\nE. I don't know\nAnswer:"
)
messages = [{"role": "user", "content": prompt}]
text = tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True,
enable_thinking=False
)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
with torch.no_grad():
logits = model(**inputs).logits[0, -1]
# Constrained decoding: softmax over only the option tokens
probs = torch.softmax(logits[option_token_ids].float(), dim=-1)
print(options[probs.argmax().item()]) # -> B
エンジン部分はこれだけです。語彙は15万を超えるトークンがありますが、判断は5つの数値にまで絞られています。生成時のサンプリング温度をいじる必要もなく、失敗しうるパーサーもなく、リストにない選択肢をモデルが捏造する余地もありません。出力空間は構造上、閉じているのです。
分類器であり、それで構わない
懐疑派の意見にも耳を傾けましょう。我々が作ったものは、固定ラベル集合に対する識別型分類器です。1950年代のRosenblattのパーセプトロンに遡り、ロジスティック回帰を経て、ディープラーニング時代のsoftmax出力層を持つあらゆるニューラルネットへと続く系譜の一つです。目を細めて見れば、制約付きデコーディングは最終隠れ状態に対する線形読み出しにすぎず、それは分類ヘッドが昔からやってきたことそのものです。何年もチームに分類器を学習させてほしいと頼み続けてきたMLエンジニアが、これが「判断モデル」として再ブランド化されるのを見て、少し苛立つのも無理はありません。
しかし歴史は、新しいバージョンが重要な理由も示しています。古い分類器は狭いものでした。ラベル付きデータを集め、学習させ、一つのタスクだけを知っていて他は何も知らないモデルができあがります。本来なら専用の分類器を使うべきタスクを、LLMベースのシステムが次々と置き換えているのは、ゼロショットという性質のためです。ベースモデルは世界についてすでに十分な知識を吸収しており、プロンプトそのものが学習データの役割を果たします。私がこの手の構成をCommonsenseQAのホールドアウトで試したところ、ファインチューニングなしで1.7Bモデルは約59%の正解率に達し、学習split で軽くチューニングすると約62%まで上がりました。最先端とは言えませんが、かかったのは半日程度で、自前のラベル付きデータも不要でした。専用の分類器ならもっと良い結果が出るかもしれませんが、その場合はパイプライン、データセット、ラベルが変わるたびの再学習の仕組みが必要になります。
ここで役立つのが、古くからある生成型と識別型の議論に基づく見方です。生成型分類器は分布全体をモデル化するため無駄が多いものの柔軟で、識別型は決定境界だけをモデル化するため効率的ですが硬直的です。制約付きデコーディングは、少し変わった混成型と言えます。生成モデルを推論時に識別的な役割へ転用しているのです。識別的な読み出しの効率と、生成的な事前学習の幅広さを両方取り込めます。部品自体は古くても、この組み合わせは以前には実質的に存在しませんでした。
制約付きデコーディングが勝る場面
- レイテンシ。自己回帰のN回のステップではなく、順伝播1回で済みます。小さなモデルでは、「リクエスト経路で十分速い」と「キューが必要」の差になります。ブラウザで純粋な判断モデルを動かしている人たちは、200ms未満の応答を報告しています。生成型のJSONでは同じことはできません。
- 構造的な正しさ。モデルは許可された集合の外の出力を文字通り生成できません。不正なJSONも、「おそらくBだと思います、なぜなら…」といった回りくどい文章もなく、形式違反を捕まえるためのガードレール層も不要です。
- スループット。すべてのリクエストが同じ形の1回のパスなので、バッチ処理が単純で予測しやすくなります。長さの変わる回答を生成すると、バッチ処理の効率が大きく落ちます。
- 選択肢ごとのスコア。勝者だけでなく分布全体が得られます。それにより棄権ロジックが可能になります。もっとも高い確率が閾値を下回れば、人間やより大きなモデルに回せます。
棄権の仕組みは特に強調しておきたい点です。生成型の回答は、信頼するか否かを決めるひとつの成果物にすぎません。選択肢に対する確率分布があれば、ルーティングを組めます。確信度の高いケースは自動で通し、低いケースは上位へエスカレーションします。これは、サポートチケットの振り分け、コンテンツモデレーションの事前スクリーニング、インテント判定など、多くの本番トリアージシステムの背後にあるパターンであり、この手法が力を発揮するところです。タスクが自然に「K個のラベルから一つ選び、どれくらい確信があるかも教えて」に分解できるなら、制約付きデコーディングはほぼ確実に適した道具です。
生成型の出力が勝る場面
now反対側を見てみましょう。タスクが固定ラベル集合に収まらなくなった瞬間、制約付きデコーディングは崩れます。答えが自由形式の固有表現、数値、コード、あるいは合成的なものであれば、生成が必要です。構造化出力の制約を併用するかもしれませんが、いずれにせよ生成です。さらに、見落とされがちな弱点として推論があります。モデルが答える前に思考の連鎖を生成すると、難しい問題で目に見えて良い結果が出ることが多いのです。単発の判断ヘッドには下書き用の場所がありません。あなたが求めているのは速くて直感的なシステム1的思考であり、まさにそれを手に入れることになります。その特徴的な失敗も含めて。
言い回しへの敏感さという問題もあります。「A/B/C/D/E」に対する制約付き分類器は、実際にはその位置にある各選択肢の文面に対するモデルの選好を測っているにすぎません。選択肢Cを少し言い換えたり、リストの順番を入れ替えたり、「Answer:」を「The best answer is」に変えたりするだけで、スコアが動きます。推論を伴う生成型の回答は、モデルがトークンだけでなく内容にコミットしなければならないため、表層的な摂動に対してより頑健な傾向があります。どちらのアプローチを評価するにしても、提示の仕方を揺さぶって何が壊れるか確かめてください。安価な頑健性テストであり、同時に居心地の悪いテストでもあります。

キャリブレーションの問題:信頼度スコアは嘘をつく
これを作る人が必ずはまる罠があります。softmaxから確率が得られるのだから、それは確率なのだろう、と思うでしょう。ところが違います。それは、あるトークンが次に来ることをモデルがどれだけ確信しているかであり、言語についての主張であって、正しさについての主張ではありません。「コウモリはどこにいるのが最も考えられるか?」という質問に、「洞窟」と「野球場」を選択肢として出すと、モデルは「洞窟」に0.998のような値を割り当てます。明確に正解と言える答えのないあいまいな質問に、ほぼ完全な確信で答えているのです。
実際の評価で信頼度ごとに分けてみると、状況はさらに悪くなります。あるCommonsenseQAの実行では、信頼度0.9〜1.0のバケットの正解率は約70%しかなく、0.8〜0.9のバケットはかろうじて40%に届く程度でした。よくキャリブレーションされたモデルなら、0.9と言ったときに約90%は正しいはずです。このモデルは体系的に過信しています。これは、現代の深層ネットワークは全般的に過信しやすいという古くからの観察とも一致します。Guoらは2017年に、素のResNetのsoftmax出力が、1990年代の浅いネットと比べて著しく誤較正されていることを示しました。古い問題が再び現れたわけです。われわれは17億パラメータの規模で、それを再発明してしまったのです。
幸い、解決策も古く、ほとんど拍子抜けするほど単純です。温度スケーリングです。softmaxの前に、logitを単一の学習済みスカラーTで割ります。
def scaled_probs(logits, option_ids, temperature):
selected = logits[option_ids].float() / temperature
return torch.softmax(selected, dim=-1)
# Fit T on a validation set by minimizing negative log-likelihood
# of the correct option. For the CommonsenseQA run above, T ~= 3.8
temperature = 3.7973
probs = scaled_probs(logits, option_token_ids, temperature)
Tが1より大きければ分布は平らになり、1より小さければ鋭くなります。検証用のデータに対して1つの数値をフィットさせるだけで、あの惨めな較正表が誠実なものに変わりました。0.9〜1.0のバケットは約95%の正解率に、0.5〜0.6のバケットは約55%になりました。正解率はまったく変わりません。argmaxは単調なスケーリングに対して不変だからです。しかし、スコアが本来の意味を持つようになります。確率に基づいてルーティングするつもりなら、まず較正してください。さもなければ、そもそも確率を集める意味がありません。
ベンチマークで温度を調整するのはズルではないか?
議論の中で出た、もっともな疑問があります。ベンチマーク上でモデルが較正されているように見えるようにTをフィットさせるのは、p-hacking的ではないか、というものです。テストセットでフィットさせるなら、確かにそうでしょう。しかし正しく行えば、つまりTを検証splitでフィットし、キャリブレーションをホールドアウトのテストsplitで報告するなら、それは単なる1パラメータの回帰であり、まさにこの目的のために文献で標準的に使われている方法です。大切なのは、MLのあらゆる場面と同じ規律です。分割を誠実に保ち、フィッティングに触れたデータで測ったキャリブレーションの主張は疑ってかかることです。デプロイ先のドメインが評価ドメインからずれれば、Tもずれます。そのため、再較正は他の全てと同じように運用の保守サイクルに組み込むべきです。
これは実のところ、より大きな問題の一例です。それは、システムが何を意味するよう仕様化されているかと、実際に何を計算しているかとの間のギャップです。このギャップこそがバグが潜む場所だと、私は以前にも書いたことがあります。「信頼度」は正しさの確率として仕様化されていますが、実装が出すのは次トークンの尤もらしさです。温度スケーリングはそのギャップへのパッチであって、解決ではありません。softmaxの出力をアラートの閾値に組み込みたくなったときは、この違いを必ず念頭に置いてください。
実践的な判断手順
今、2つのモードのどちらを選ぶか考えるときは、次のチェックリストを使っています。
- 出力は固定されたラベル集合か?そうなら制約付きデコーディングが候補になります。そうでなければ生成します。
- 許容できる精度を得るのに推論が必要か?両方を試作してください。単発の判断ヘッドが思考の連鎖に大きく劣るなら、レイテンシを犠牲にしても生成の方が勝ちます。
- ルーティングや棄権のために選択肢ごとのスコアが必要か?必要なら、制約付きデコーディングと温度スケーリングの組み合わせはほぼ無料で、生成では同等のものが得られません。
- ラベル集合はどれくらい大きいか?5個なら簡単ですが、5千個ともなれば検索の領域です。ラベルが大きな空間にあるなら、ラベルを埋め込み、ベクトル検索で候補を絞り込み、そのうえで最終候補の中からモデルに選ばせてください。推薦システムの時代から定番となっている分類器のスケーリングの工夫です。
- ラベルはどれくらい頻繁に変わるか?ゼロショットという性質こそが要点です。ラベルが毎週入れ替わるなら、再学習する分類器は保守の負担になります。一方、プロンプトを編集するだけで済みます。
モデルのsoftmaxは、次にどのトークンが来るかについての主張であって、何が真実かについての主張ではありません。較正するか、信用しないか、どちらかにしてください。
もう一つ考慮すべき点があります。モデルのサイズは、この選択と相互に影響します。小さなモデルの単発の判断ヘッドは、すべてのリクエストで、クライアント側でも実行できるほど安価です。実際、200ms未満で応答するブラウザ上の判断モデルを既に出荷している人たちがいます。簡単な80%は小さな制約付きモデルで、難しい残りはより大きな生成モデルで処理する、というカスケード設計は、コストと精度の両面で両極端のどちらよりも優れることが多いのです。これは投機的デコーディングと同じ発想を、トークン単位ではなくシステムの単位で適用したものです。
推奨
私の立場は次のとおりです。タスクが本当に固定された選択肢の判断、つまりルーティング、トリアージ、インテント判定、選択式評価であれば、制約付きの判断ヘッドを作り、あとは振り返らないことです。レイテンシの利点だけで十分価値があり、構造的な保証は解析の失敗という一群の問題を消し、選択肢ごとのスコアは生成では真似できないルーティングロジックを可能にします。ただし、生のsoftmaxは較正されていない計器として扱ってください。少しでもまとまったデータセットを集められるなら、実際のタスクでファインチューニングし、きれいな検証splitで温度をフィットさせ、フィッティングに一度も使っていないデータで較正を検証してください。
合成的なタスクや、目に見える推論の恩恵を受けるタスクには、必要に応じて構造化出力の制約を付けたうえで生成出力を使ってください。そして、「判断モデル」を新しいカテゴリとして売り込まれても鵜呑みにしないでください。それはLLMの外套をまとった分類器であり、60年にわたる識別型モデリングの系譜に連なるものです。古参の人たちがいつも求めてきたキャリブレーションの作業を、その系譜を尊重して行うときにこそ、最も役立ちます。道具は新しくなりましたが、規律は新しくなっていません。


