一文要約:訓練データには、各位置の正しい前文とターゲットがすでに書かれているので、一つのSequence内の全位置をまとめて計算できます。推論では次のTokenがまだ存在しないため、それを選んでからでなければ次のStepへ進めません。直列なのは一つの回答内のDecode列であって、すべてのRequestが一列に並ぶわけではありません。
16.1 訓練と推論:本質的な違い
16.1.1 まず表で比べる
| 訓練 | 推論 | |
|---|---|---|
| 目的 | 次Token予測のParameterを学ぶ | 学習済みParameterで生成する |
| 入力 | 既知の文をInputとTargetに1位置ずらす | Promptと、それまでに生成したToken |
| Target | 各位置の正しい次Tokenが既知 | 次Tokenは未知で、Decoderが選ぶ |
| Sequence内の計算 | 1回のForwardで全位置のlogitsを得る | PromptはまとめてPrefillし、その後は前Stepに依存 |
| 勾配とParameter | 勾配を計算し、Optimizerが更新する | 勾配もParameter更新もない |
ここでいう「並列」と「直列」は、一つのSequence内の位置についての話です。
- 訓練にはBackwardもあるので、1回の推論より安いという意味ではありません。
- 推論Serverは複数のRequestをBatchにして同時に実行できます。
- ただし一つの回答では、新しいToken 2はToken 1が選ばれるまで完全な前文を持てません。
16.1.2 なぜ違いが生まれるのか
日本語版では、少し珍しい例を使いましょう。
- 完成した訓練文:「蒔絵師が硯箱を修復した。」
- 推論時のPrompt:「蒔絵師が硯箱を」
- 訓練文には各位置の続きが書かれていますが、推論時にはまだありません。
この表示は読みやすい文字列であって、「漢字1文字=Token」と主張しているわけではありません。区切り方はTokenizer次第で、byte-level Tokenizerでは一つの漢字が単独では読めないbyte断片に分かれることもあります。
16.2 訓練の詳細
16.2.1 ここでのTeacher Forcingとは
Tokenizerの出力を次のように置きます。
全Sequence:[x0, x1, x2, ..., xT]
Model入力: [x0, x1, x2, ..., xT-1]
Target: [x1, x2, x3, ..., xT]
Targetを曖昧に「右へずらす」と覚えるより、同じ列のInputより1Token先と考える方が確実です。
- 位置0はx0を読み、x1を予測
- 位置1はx0、x1を読み、x2を予測
- ...
- 位置T-1はx0からxT-1を読み、xTを予測
訓練では、前の位置でModelがSampleした結果ではなく、Datasetにある正しい前文を入力します。これは一般に Teacher Forcing(教師強制) と呼ばれ、Decoder-only Language Modelでは標準的なnext-token trainingです。
16.2.2 なぜ全位置をまとめて計算できるのか
x0からxTまでがSample内にすでに存在するため、Causal Maskを使った一つの行列計算でT位置分のlogitsを得られます。
import torch.nn.functional as F
def train_step(model, optimizer, tokens):
"""tokens: [batch, T + 1]。ここではPaddingを省略する。"""
model.train()
optimizer.zero_grad(set_to_none=True)
input_ids = tokens[:, :-1]
target_ids = tokens[:, 1:]
logits = model(input_ids) # [batch, T, vocab_size]
loss = F.cross_entropy(
logits.reshape(-1, logits.size(-1)),
target_ids.reshape(-1),
)
loss.backward() # ここでは勾配を計算するだけ
optimizer.step() # Parameterが変わるのはここ
return loss.detach()
BatchにPaddingがある場合は、AttentionがPad Keyを読まないようにし、Padに対応するTargetもlossへ入れないようにします。PyTorchでは、そのTargetを-100へ置き換える方法がよく使われます。
16.2.3 Causal Maskは何を隠すのか
位置tはx0からxtまでを読み、x(t+1)を予測します。
位置0が見る:[x0, -, -, -, ...] → x1を予測
位置1が見る:[x0, x1, -, -, ...] → x2を予測
位置2が見る:[x0, x1, x2, -, ...] → x3を予測
対角線上の「現在のToken」は見えます。隠すのはその後ろです。Maskがなければ、位置tが未来位置にある自分の答えx(t+1)を直接読めてしまいます。
16.3 推論の詳細
16.3.1 PrefillとDecode
標準的な自己回帰推論は二段階です。
- Prefill:Prompt全体はすでに存在するため、そのToken位置をまとめて処理します。
- Decode:最後の位置のlogitsから新しいTokenを選び、前文へ追加してから次を予測します。
Prompt:「蒔絵師が硯箱を」
↓ Prefill
次のTokenを選ぶ(読みやすい文字へDecodeすると「修」へ進むかもしれない)
↓ Token IDを前文へ追加
さらに次のTokenを選ぶ(表示文字列が「修復」へ伸びるかもしれない)
↓
EOS、Stop条件、max_new_tokensのいずれかまで続ける
「修」「復」は説明のための表示です。Programが追加するのはToken IDであり、1Tokenが1文字や1単語に対応するとは限りません。
16.3.2 KV Cacheが変えるもの
最も単純なCodeは、Stepごとに可視Window全体を再計算します。KV Cacheは各Layerの過去のKeyとValueを保存します。
- PrefillでPromptのK、Vを計算して保存
- Decodeでは通常、最新Tokenだけを各Layerへ送り、Cacheを読む
- Tokenを選ぶ順序は変わらない。消えるのは重複計算であって、依存関係ではない
第22章で、この仕組みを詳しく分解します。
2026年補足:標準Decodeの論理は順序付きですが、Speculative Decoding は小さなモデルに複数Tokenを下書きさせ、Target Modelがまとめて検証します。最初の草稿が却下されても、補正StepはTarget分布から1Tokenを出力します。草稿が合えば、1回の検証で複数Token分進めます。これはTarget Modelの反復回数を減らしますが、Target Modelを非自己回帰Modelに変えるものではありません。
16.3.3 Context Lengthは自動Sliding Switchではない
Prompt Tokenと生成済みTokenの合計は、Modelが対応するContext内に収まる必要があります。超えたときは、Application側で方針を決めます。
- 生成を止める、または長すぎる入力を拒否する
- 古いTokenを切り落とす
- 古い内容を要約してContextへ戻す
- Modelが本来対応するSliding-window Attentionを使う
「常に直近N Tokenだけ残す」は一つの方針であり、すべてのGPTが自動で行うわけではありません。切り落とされたTokenは、そのForward Passの入力に存在しません。「心理的に忘れた」と言うより正確です。
16.4 KV Cacheを使わない基準Loop
次のCodeは、直列依存を見やすくするために意図的に単純化しています。Batch size 1だけを扱い、各Stepで現在のWindowを再計算します。Production用Inference Engineではありません。
import torch
@torch.inference_mode()
def generate_greedy(
model,
prompt_ids,
context_length,
eos_token_id,
max_new_tokens=50,
):
"""prompt_ids: [1, prompt_length]"""
if prompt_ids.ndim != 2 or prompt_ids.size(0) != 1:
raise ValueError("This teaching loop expects batch size 1")
model.eval()
generated = prompt_ids.clone()
for _ in range(max_new_tokens):
model_input = generated[:, -context_length:]
logits = model(model_input)
next_token = logits[:, -1, :].argmax(dim=-1, keepdim=True)
generated = torch.cat((generated, next_token), dim=1)
if next_token.item() == eos_token_id:
break
return generated
混同しやすい点を四つ確認します。
- model.eval() はDropoutなどの挙動を切り替えます。最大確率Tokenを自動選択する命令ではありません。
- torch.inference_mode() は勾配追跡とAutogradの追加処理を無効にします。
- 次Tokenの選択には、最後の位置のlogitsだけを使います。
- argmaxを使うからGreedy Decodingです。Samplingへ変えれば、eval modeでも出力は変わります。
16.5 PaddingとBatch推論
16.5.1 左Paddingが便利な理由
PromptごとにToken数が異なるため、Batch行列へ入れる前に同じ長さへ揃えます。Decoder-only生成では左Paddingがよく使われます。
Request A:[a0, a1, a2, a3] mask = [1, 1, 1, 1]
Request B:[PAD, PAD, b0, b1] mask = [0, 0, 1, 1]
これなら最後の列は全Requestで実Tokenなので、logits[:, -1, :]を取りやすくなります。a、bがTokenであり、元の文字列は「蒔絵師が硯箱を」と「続きを」のような内容です。
16.5.2 二つのMaskを混同しない
- Causal Mask:未来位置を読ませない。
- Padding Mask:PADをContextとして読ませない。
実装は通常この二つを統合し、Softmax前にMaskされたKey scoreを負の無限大へ変えます。そのAttention weightは0になります。推論中にModelが「Paddingを無視するよう学習する」のではなく、その呼び出しでMaskが強制します。
tokenizer.padding_side = "left"
batch = tokenizer(prompts, padding=True, return_tensors="pt")
logits = model(
batch["input_ids"],
attention_mask=batch["attention_mask"],
)
next_token_logits = logits[:, -1, :]
右Paddingでは最後の列がPADになり得るので、全行に対して無条件に-1を取るのは誤りです。各行の最後の実位置を集めます。Learned absolute position embeddingを使う自作Modelでは、左Paddingに対応するposition IDsも正しく作る必要があります。成熟したgenerate helperは通常処理してくれますが、自作Codeで決めつけてはいけません。
BatchingやContinuous Batchingは複数Requestを同じHardware上で走らせます。利用率は上がりますが、各回答内部のToken依存は残ります。
16.6 計算と実行Modeの正確な比較
| 訓練 | 自己回帰推論 | |
|---|---|---|
| Sampleごとの主処理 | 1回のForward + Backward + Optimizer更新 | 1回のPrefill + N個の依存するDecode Step |
| 並列化できる次元 | Batch、Sequence位置、Attention head | Batch、Prompt位置。別Requestも同時実行可能 |
| 保持する状態 | Activation、勾配、通常はOptimizer state | Weightと、一般にはKV Cache |
| よくあるボトルネック | 計算量、Memory容量、通信 | Prefillは計算寄り、DecodeはMemory帯域と1 Step latency寄り |
| Parameterは変わるか | optimizer.step()で変わる | 変わらない |
したがって「推論は訓練より遅い」とだけ覚えるのは誤りです。Model全体の訓練は、一つの回答生成よりはるかに大きな総計算を使います。ここでの要点は、一つの回答の新Tokenを、既知の訓練Target位置のように全部まとめて計算できないことです。
16.6.1 Dropoutの挙動
ArchitectureにDropoutがある場合:
model.train() # Dropoutがランダムな無効化に参加
model.eval() # Dropoutを無効化
現代のLLMにはDropoutを0に設定したものもあります。Dropoutを無効にしてもSamplingはランダムです。逆に低Levelの数値非決定性がGreedy実行へ影響する場合もあります。eval()の意味は「評価時の挙動へ切り替える」であって、「毎回同じ文章を保証する」ではありません。
16.7 Decoding Strategy
Modelが出すのはlogitsです。そこから次Tokenをどう選ぶかはDecoderの仕事です。
16.7.1 Greedyと直接Sampling
# GreedyはSoftmax不要:argmax(logits) == argmax(Softmax(logits))
next_token = logits.argmax(dim=-1, keepdim=True)
# Samplingでは確率が必要。temperatureは正でなければならない
probs = F.softmax(logits / temperature, dim=-1)
next_token = torch.multinomial(probs, num_samples=1)
- Greedy:各Stepで最大logitを選びます。再現しやすい一方、局所的な最大がSequence全体の最善とは限らず、反復も起こり得ます。
- Sampling:分布から抽選します。多様になる一方、低品質な低確率Tokenを引くこともあります。
- Temperature:1未満で分布を尖らせ、1より大きいと平らにします。APIの「temperature = 0」はGreedyへ分岐すべきで、logitsを本当に0で割ってはいけません。
16.7.2 正しいTop-K Index
top_k_probs, top_k_ids = torch.topk(probs, k=50, dim=-1)
sampled_slot = torch.multinomial(top_k_probs, num_samples=1)
next_token = top_k_ids.gather(-1, sampled_slot)
以前のIndex式はBatch次元があると別の軸を選びました。gatherなら、各行で「候補の何番目か」を語彙Token IDへ戻せます。
16.7.3 正しいTop-P(Nucleus Sampling)
def sample_top_p(logits, top_p=0.9, temperature=1.0):
if not 0.0 < top_p <= 1.0:
raise ValueError("top_p must be in (0, 1]")
if temperature <= 0:
raise ValueError("temperature must be positive")
sorted_logits, sorted_ids = torch.sort(
logits / temperature, descending=True, dim=-1
)
sorted_probs = F.softmax(sorted_logits, dim=-1)
cumulative = torch.cumsum(sorted_probs, dim=-1)
# 累積確率を初めてtop_pへ届かせるTokenは残す
remove = cumulative > top_p
remove[..., 1:] = remove[..., :-1].clone()
remove[..., 0] = False
filtered_logits = sorted_logits.masked_fill(remove, float("-inf"))
filtered_probs = F.softmax(filtered_logits, dim=-1)
sampled_slot = torch.multinomial(filtered_probs, num_samples=1)
return sorted_ids.gather(-1, sampled_slot)
以前のcumsum <= 0.9という概略Codeは、閾値を跨いだTokenを捨て、極端な場合は候補を一つも残しません。Top-Pは累積確率がPへ達する最小集合を残します。これはNucleus Samplingの論文で導入された方法です。実運用ではTemperature、Top-K、Top-Pを組み合わせることも多く、互いに排他的なSwitchではありません。
16.8 なぜGPT系Modelは自己回帰なのか
16.8.1 「言語には順序がある」だけでは足りない
「蒔絵師が硯箱を修復した」と「硯箱が蒔絵師を修復した」は当然違います。しかし、順序を表せるModelは自己回帰Modelだけではありません。自己回帰Modelingが選ぶのは、次の確率分解です。
p(x1, x2, ..., xT) = Π p(xt | x1, ..., x(t-1))
この形には次の利点があります。
- 各訓練位置に明確なnext-token objectiveがある
- 可変長Sequenceを生成できる
- Promptと生成済みPrefixの両方を条件にできる
- LikelihoodをTokenごとに分解して訓練・評価できる
ただし、文章の一貫性や事実性を保証するものではありません。以前のContextを条件にできるだけで、品質はModel、Data、Decoding、Promptに左右されます。
16.8.2 非自己回帰生成は消えていない
非自己回帰機械翻訳、反復Mask予測、Diffusion Language Modelは、より並列な生成を研究しています。たとえば2025年のLLaDA論文は、ゼロから訓練した大規模Diffusion Language Modelを報告しました。
したがって「非自己回帰は必ず品質が低い」「最良Modelはすべて自己回帰」と断言するのは強すぎます。長く使える結論は、自己回帰Decoderが今も成熟した一般的な経路であり、並列・反復生成も異なる速度と品質のTrade-offを持つ現実的な代替案だ、ということです。
16.9 章のまとめ
16.9.1 中核の比較
| 観点 | 訓練 | 推論 |
|---|---|---|
| Targetは既知か | はい。訓練Textにある | いいえ。その場で選ぶ |
| Sequence内処理 | 全Target位置をまとめて計算 | Prefill後、依存順にDecode |
| 計算Stage | Forward + Backward + Optimizer | Forwardのみ。一般にはKV Cacheを使う |
| Dropout | 設定に応じて有効 | eval()で無効 |
| Parameter更新 | optimizer.step()で更新 | 更新しない |
16.9.2 中核となる理解
訓練で位置を並列計算できるのは、各位置の正しい前文がDataに存在するからです。標準推論のDecodeが順序付きなのは、選んだTokenが次Stepの前文になるからです。KV Cacheは重複計算を減らし、BatchingはRequestを並列化し、Speculative DecodingはTarget Modelの反復回数を減らしますが、この条件依存の鎖そのものは消しません。
章のチェックリスト
この章を終えたら、次を説明できるはずです。
- x0からxTを使い、InputとTargetの1Token差を書ける
- Causal Maskが現在Tokenを見せ、次Tokenを隠す理由を説明できる
- Prefill、Decode、KV Cacheを区別できる
- 「単一回答のDecodeは直列」と「推論は全部直列」が違うと説明できる
- 閾値を跨ぐTokenを落とさないTop-Pを実装できる
次章予告
推論の流れは見えました。KV Cacheの内部は第22章で詳しく扱います。その前に、訓練で最も敏感な調整器の一つ、Learning Rateを理解しておきましょう。大きすぎれば良い領域を飛び越え、小さすぎれば進みません。第17章では、この値が実際に何を制御しているのかを見ていきます。