JA EN
体系推論・高速化
·★ 会員·論文·15分で読めます

構造化出力と制約デコーディング — JSONを壊さない仕組み

「JSONで返して」とお願いする代わりに、文法的に許されないトークンを出す前に確率ゼロへ落とす — 制約デコーディングの仕組みを、logitマスクから語彙のインデックス化、function callingの裏側、そして「構文は守れても中身は守れない」限界まで1から解説する。

対象textタスクinference

Efficient Guided Generation for Large Language Models


「JSONで返して」とお願いしても壊れる理由

プロンプトに「必ずJSONで答えてください」と大文字で三回書いても、数百回に数回は壊れたものが返ってきます。閉じ括弧がひとつ足りない。末尾に余計なカンマが残る。Markdownのコードフェンスで丁寧に包まれてしまう。本文の前に「はい、承知しました。」という挨拶がついてくる。

原因は、生成が左から右へ1トークンずつ進み、後戻りできないことにあります。人間なら書き終えてから括弧の対応を数え直せますが、モデルにその工程はありません。「いま開いた { に対応する } を最後に出す」という規律は、学習データの中に正しいJSONが大量にあったおかげで身についた確率的な癖であって、機構的な保証ではないのです。

そして「だいたい当たる」は、本番でいちばん厄介な性質です。1000件のバッチのうち3件だけがパースに失敗し、その3件のためにリトライとアラートと当直の対応が必要になります。

比喩: 一筆書きで迷路を抜ける

生成を、鉛筆を紙から離さずに迷路を抜ける一筆書きだと考えてください。線を引いてしまった場所には戻れません。行き止まりに入ったら、そこで詰みです。

対策は原理的に二つしかありません。ひとつは、抜けたあとで正しいかを確認し、間違っていたら最初から引き直すこと。これがバリデーションとリトライで、実装は簡単ですが、成功は運任せで、失敗するたびに全部やり直しになります。

もうひとつは、行き止まりへ向かう道を、鉛筆が入る前に壁で塞いでしまうこと。これが制約デコーディング(constrained decoding、あるいはguided generation)です。壁があれば、迷路を抜けられないルートはそもそも選べません。

直感: 出せない手を、出す前に消す

モデルは各ステップで、語彙に含まれる全トークン(10万語規模)に「スコア」を付けます。この生のスコアがlogitです。ふだんはこれをsoftmaxで確率に直し、そこから1つ引いて次のトークンにします。

制約デコーディングがやることは一行で言えます。softmaxにかける前に、いま文法が許さないトークンのスコアを負の無限大に沈める。それだけです。確率がちょうど0になったトークンは、どんな温度設定でも、どんな乱数でも選ばれません。

こうして問題の質が変わります。「壊れたJSONが出る確率をどう下げるか」ではなく、「壊れたJSONは出せない」になる。この差は、運用してみるとかなり大きいものです。

仕組み: logitマスク

文法から作られるのは、各ステップごとのマスクです。語彙と同じ長さの、0と1が並んだ札だと思ってください。

z~v=zv+logmv,mv{0,1}\tilde{z}_v = z_v + \log m_v, \qquad m_v \in \{0, 1\}
(1)

zvz_v はトークン vv に対するモデルの生スコア、mvm_v は「文法がいま vv を許すなら1、許さないなら0」の札、z~v\tilde{z}_v が補正後のスコアです。log1=0\log 1 = 0 なので許されたトークンのスコアはそのままlog0=\log 0 = -\infty なので禁止されたトークンは負の無限大まで落ちます。足し算ひとつで「そのまま」と「消滅」を切り替えているわけです。

FIG 1棒の1本1本が次トークンの候補。マスクは、この図の何本かを高さゼロに落として、残りだけで100%を配り直す操作にあたる。温度スライダーで変わるのは「残った候補の中での尖り方」だけで、消えた候補は温度をいくら上げても復活しない

消えた分は、残りに配り直される

補正後のスコアは、あとは通常どおりsoftmaxに通します。

pv=exp(z~v/T)uexp(z~u/T)p_v = \frac{\exp(\tilde{z}_v / T)}{\sum_u \exp(\tilde{z}_u / T)}

TT は温度(大きいほど分布が平らになる調整つまみ)、pvp_vvv が選ばれる確率です。e=0e^{-\infty} = 0 なので禁止トークンの確率はちょうど0になり、分母から消えた分は残った候補に自動的に配り直されます。もともと確率が2%しかなかった候補でも、他が全部禁止されているなら100%になります。

この「softmaxの前に -\infty を足す」という道具は、Transformerの因果マスクとまったく同じものです(Attention機構を1から理解するで扱った、未来を覗かせないための処理)。使う場所が違うだけで、発想は共通しています。

実装上の注意がひとつ。本当に -inf を代入すると、バグや文法の書き間違いで全トークンが禁止された瞬間にsoftmaxの分母が0になり、確率が nan になって静かに壊れます。多くの実装が -1e9 のような有限の大きな負数を使うのはこのためで、代わりに「許されたトークンが1つも無い」状態を明示的に検出してエラーにする必要があります。

トークンと文法は、目盛りがズレている

ここからが実装の本番です。文法はJSONの文字{",)で定義されていますが、モデルが出すのはトークンであり、トークナイザが作るBPEトークンは文字の切れ目と一致しません。

実際の語彙には {" がひとつのトークンとして入っていたり、":", のような区切りの塊が入っていたりします。つまり「次に許される文字は " である」という文法側の情報を、「次に許されるトークンの集合」へ翻訳する工程が必要になります。

素朴にやるなら、毎ステップ語彙10万件を総当たりし、「このトークンを今の出力の末尾に繋げても文法違反にならないか」をパーサに聞きます。1トークン出すたびに10万回のパース試行。これでは制約の分だけ生成が遅くなり、速度が売り物の推論サーバーでは使い物になりません。

この先にあるもの

§

ここから先は会員限定です

解説記事371本・教科書26章・学生モード48単元・論文精読6本が、月額¥490ですべて読み放題になります。新しい解説は毎日3本ずつ増えます。いつでも解約でき、解約後も期間の終わりまで読めます。

会員の方はログインすると続きが表示されます

参考文献

  1. Efficient Guided Generation for Large Language Models. arXiv:2307.09702論文ページ·PDF
  2. XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Models. arXiv:2411.15100論文ページ·PDF

本記事は上記論文の本文にもとづいて執筆しています。数値・主張は原典を優先してください。

コメント

コメントにはログインが必要です