Transformer 内部構造ガイド入力から出力までを図・形状・コードで追う

全体図での位置処理順 20 番目 / 全 20 ブロック全体図で確認する

出力ヘッド抽象度: アプリケーション難易度: 標準推定学習時間: 約15分

次トークンの確率と逐次生成Next Token Prediction

30秒で分かる要約確率分布から実際に出力する1トークンを選び、それを入力の末尾に足して次のステップへ回す工程です。ここで初めてモデルの出力が文になります。学習時の並列計算との違いが最も表れる部分です。

probabilities[B, 1, V]next_token_id[B, 1]

Level 1概要役割と処理の流れ

何をする機能か

確率分布 [B, 1, V] から、実際に出力する1 個のトークンを選びます。 そして、選んだトークンを入力系列の末尾に足し、Decoder をもう一度実行します。

この繰り返しが「文章生成」の実体です。 図で「選択後、再び入力」と書かれているループがこれにあたります。

選び方には次のような方法があります。

  • 貪欲法(greedy): 最も確率の高いトークンを選ぶ。毎回同じ結果になる
  • サンプリング: 確率に従ってランダムに選ぶ。多様な出力になる
  • top-k: 上位 k 個に絞ってからサンプリング
  • top-p(nucleus): 累積確率が p に達するまでの候補に絞ってサンプリング
  • ビームサーチ: 複数の候補系列を並行して保持し、系列全体のスコアが高いものを選ぶ

なぜ必要か

モデルが直接出せるのは「次の 1 トークンの確率分布」だけです。 文章を作るには、そこから 1 個を確定させ、その結果を踏まえて次を予測する、 という手順を繰り返す必要があります。

この「1 つ前の出力が次の入力になる」性質を 自己回帰(autoregressive) と呼びます。

全体の中でいつ呼ばれるか

推論(生成)のときだけ、1 トークンごとに実行されます。 学習時には行いません。学習では全位置の正解が最初からわかっているため、 選択もループも不要です(右シフト を参照)。

Level 2内部処理Tensor形状・内部ステップ・設計上の意味

前段から受け取るもの

Softmax が出力した probabilities。 推論時は最後の位置だけが必要なので [B, 1, V] です。

後段へ渡すもの

next_token_id [B, 1]。これは 2 か所へ渡されます。

  1. 出力バッファ(最終的な生成結果の一部になる)
  2. Decoder の入力の末尾(次のステップの入力になる)

2 のループがあるため、この工程だけは「後段」が前段へ戻ります。

例文での処理

共通例文

The keys on the table are lost.

概念上のトークン列(S = 8): [The, keys, on, the, table, are, lost, .]

The keys on the table まで生成済みで、次を選ぶ場面です。

  1. The
  2. keys
  3. on
  4. the
  5. table
現時点の系列(T=5)。この最後の位置の出力から、次のトークンを選びます。
候補確率greedytemperature=0.8 のサンプリング
are0.94選ばれるほぼ選ばれる
is0.042—まれに選ばれる
were0.019—まれに選ばれる

are が選ばれると、系列は The keys on the table are になり、T は 6 になります。 そしてもう一度 Decoder 全体を実行して、次は lost を予測します。

  1. The
  2. keys
  3. on
  4. the
  5. table
  6. are
1 ステップ進んだ状態。末尾に are が足され、次は lost を予測します。

この繰り返しは、終了トークン(</s> など)が選ばれるか、 最大長に達するまで続きます。

学習時と推論時の違い

このページの内容は推論時のみです。両者の違いをもう一度整理します。

学習時推論時
Decoder の実行回数1 回(全位置を並列に計算)生成トークン数ぶん繰り返す
次の入力正解トークン(Teacher Forcing)自分が選んだトークン
選択処理なし(全語彙の確率で損失を計算)あり(greedy / サンプリング等)
誤りの影響次の位置には影響しない次以降の入力が汚染される
並列性高い(GPU を使い切れる)低い(逐次実行がボトルネック)

推論が遅いのは、この逐次性が原因です。 1 トークンごとに Decoder を通す必要があり、系列長ぶんの往復が発生します。

推論を速くする仕組み

  • KV Cache: 過去の位置の Key・Value を再計算せず使い回す。 詳しくは マスク付き自己注意 を参照
  • 最後の位置だけを Linear に通す: [B, T, V] ではなく [B, 1, V] にする
  • 投機的デコーディング: 小さいモデルで複数トークンを先読みし、 大きいモデルでまとめて検証する

EncoderとDecoderでの違い

この工程は Decoder 側(出力ヘッド)だけにあります。 Encoder-only モデル(BERT など)は系列を生成しないため、このループを持ちません。

Encoder–Decoder 構成では、生成ループの中で Encoder は再実行しません。入力文は変わらないので、 Encoder出力 は 1 回計算すれば使い回せます。

入力と出力

入力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
probabilities最後の位置における次トークンの確率分布float32 Tensor[B, 1, V]B = バッチサイズ(同時に処理する文の数) / V = 語彙数(Tokenizer が扱うトークンの総数)[1, 1, 32000]選択(greedy / top-k / top-p)
generated_idsこれまでに生成したトークン列int64 Tensor[B, T]B = バッチサイズ(同時に処理する文の数) / T = 出力系列長(Decoder側のトークン数)[1, 5]末尾に next_token_id を連結

出力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
next_token_id実際に出力する 1 トークンのIDint64 Tensor[B, 1]B = バッチサイズ(同時に処理する文の数)[[10]](are のID)出力バッファ、および Decoder 入力の末尾
generated_ids1 トークン伸びた系列int64 Tensor[B, T+1]B = バッチサイズ(同時に処理する文の数)[1, 6]次ステップの Decoder 入力
B
バッチサイズ(同時に処理する文の数)
V
語彙数(Tokenizer が扱うトークンの総数)
T
出力系列長(Decoder側のトークン数)

このサイトでは Tensor 形状を PyTorch の `batch_first=True` 相当、つまり `[B, S, D]`(バッチ次元が先頭)で統一しています。実装によっては `[S, B, D]` を採用するものもあります。

よくある誤解

Level 3数式・Python数式・疑似実装・計算量・実装差

数式

系列 y1,…,yTy_1, \ldots, y_T を生成する確率は、条件付き確率の積に分解できます。

p(y1:T∣x)=∏t=1Tp(yt∣y<t, x)p(y_{1:T} \mid x) = \prod_{t=1}^{T} p(y_t \mid y_{<t},\, x)

各ステップで計算しているのは、この右辺の 1 項 p(yt∣y<t,x)p(y_t \mid y_{<t}, x) です。

貪欲法は各ステップで局所的に最大のものを選びます。

yt=arg⁡max⁡v∈V  p(v∣y<t, x)y_t = \arg\max_{v \in V} \; p(v \mid y_{<t},\, x)

これは系列全体の確率を最大化する保証がありません。 ビームサーチは幅 kk の候補を保持して、系列全体のスコアを近似的に最大化します。

top-p(nucleus)サンプリングは、確率の高い順に並べて累積が pp を超えるまでの集合 VpV_p に絞り、その中で再正規化してサンプリングします。

Vp=min⁡{V′⊆V  ∣  ∑v∈V′p(v)≥p}V_p = \min\Big\{ V' \subseteq V \;\Big|\; \sum_{v \in V'} p(v) \ge p \Big\}

Pythonで表したインターフェース

from typing import Callable, Optional
import torch
from torch import Tensor
 
 
def select_next_token(
    probabilities: Tensor,      # [B, V]
    top_k: Optional[int] = None,
    top_p: Optional[float] = None,
    greedy: bool = False,
) -> Tensor:
    """確率分布から次の 1 トークンを選ぶ。
 
    Args:
        probabilities: [B, V]。合計が 1 の確率分布。
        top_k: 上位 k 個に絞る。None なら絞らない。
        top_p: 累積確率が p に達するまでに絞る。None なら絞らない。
        greedy: True なら最大確率のトークンを決定的に選ぶ。
 
    Returns:
        [B, 1] int64。選ばれたトークンID。
    """
    if greedy:
        return probabilities.argmax(dim=-1, keepdim=True)
 
    probs = probabilities.clone()
 
    if top_k is not None:
        threshold = probs.topk(top_k, dim=-1).values[:, -1:]
        probs = torch.where(probs < threshold, torch.zeros_like(probs), probs)
 
    if top_p is not None:
        sorted_probs, sorted_idx = probs.sort(dim=-1, descending=True)
        cumulative = sorted_probs.cumsum(dim=-1)
        # 累積が p を超えた「後ろ」を落とす(先頭1つは必ず残す)
        remove = cumulative - sorted_probs > top_p
        sorted_probs[remove] = 0.0
        probs = torch.zeros_like(probs).scatter(-1, sorted_idx, sorted_probs)
 
    probs = probs / probs.sum(dim=-1, keepdim=True)
    return torch.multinomial(probs, num_samples=1)   # [B, 1]
 
 
def generate(
    model: Callable[[Tensor], Tensor],   # [B, T] -> logits [B, T, V]
    prompt_ids: Tensor,                  # [B, T0]
    max_new_tokens: int,
    eos_id: int,
    temperature: float = 1.0,
    top_p: Optional[float] = 0.9,
) -> Tensor:
    """自己回帰的に系列を生成する。
 
    Returns:
        [B, T0 + n] int64。プロンプトを含む生成結果。
    """
    generated = prompt_ids
    for _ in range(max_new_tokens):
        logits = model(generated)                          # [B, T, V]
        last_logits = logits[:, -1, :] / temperature       # [B, V]
        probabilities = last_logits.softmax(dim=-1)        # [B, V]
 
        next_token_id = select_next_token(probabilities, top_p=top_p)  # [B, 1]
        generated = torch.cat([generated, next_token_id], dim=1)       # [B, T+1]
 
        if bool((next_token_id == eos_id).all()):
            break
 
    return generated
 
 
# 最小使用例(model はダミー。are 相当のIDを高確率で返す)
V = 6
ARE_ID, EOS_ID = 0, 5
 
 
def dummy_model(ids: Tensor) -> Tensor:
    batch, length = ids.shape
    logits = torch.full((batch, length, V), -5.0)
    logits[:, :, ARE_ID] = 8.0
    return logits
 
 
prompt = torch.tensor([[1, 2, 3, 4]])          # [B=1, T0=4]
out = generate(dummy_model, prompt, max_new_tokens=3, eos_id=EOS_ID, top_p=0.9)
 
print(prompt.shape, out.shape)                  # torch.Size([1, 4]) torch.Size([1, 7])
print(out[0, 4:].tolist())                      # [0, 0, 0]  (are が繰り返し選ばれる)

この例で単純化している点

  • KV Cache を使わず、毎ステップ系列全体を通す素朴な実装にしています(動きを追いやすくするため)。
  • ビームサーチ、繰り返しペナルティ、終了条件の細かい制御は省略しています。
  • model は [B, T] を受け取り logits [B, T, V] を返すものとしています。

型と Tensor 形状の見え方を掴むための擬似実装です。そのまま学習・推論に使えるものではありません。

計算量

1 ステップあたりのコストは、KV Cache の有無で大きく変わります。

1 ステップの計算量n トークン生成の合計
Cache なしO(T2D+TD2)O(T^2 D + T D^2)O(n3D)O(n^3 D) 程度に膨らむ
Cache ありO(TD+D2)O(T D + D^2)O(n2D+nD2)O(n^2 D + n D^2)

実運用では KV Cache が前提です。 その場合、生成速度はメモリ帯域(重みと Cache の読み出し)で決まることが多く、 「計算量」よりも「1 トークンあたり何バイト読むか」が支配的になります。

生成パラメータの整理

パラメータ効果典型値
temperature分布の鋭さ。小さいほど決定的0.0〜1.0
top_k上位 k 個に限定40〜100
top_p累積確率 p までに限定0.9〜0.95
repetition_penalty既出トークンの logit を下げ、繰り返しを抑える1.0〜1.2
max_new_tokens生成の上限用途による

temperature=0 と greedy は実質的に同じ挙動になります (実装では 0 除算を避けるため、内部で greedy に切り替えます)。

Decoder-only LLM での違い

  • プロンプトの処理(prefill)と生成(decode)の 2 段階に分かれます。 prefill は全位置を並列に計算できるため高速で、decode が逐次的で遅い部分です。
  • Encoder がないため、Cross Attention はありません。 プロンプトは同じ系列の前半として Masked Self-Attention で参照されます。
  • 生成ループの構造そのものは、原典の Decoder とまったく同じです。

この記事の位置づけ

このページ

次トークンの確率と逐次生成

次に読むとよい記事

なし

関連記事