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

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

Decoder抽象度: Attention難易度: 発展推定学習時間: 約16分

Encoder–Decoder AttentionCross Attention

30秒で分かる要約DecoderがQueryを出し、EncoderのKey・Valueを参照する層です。Self-Attentionと違い2つの系列をつなぎます。Decoder-only構成には存在せず、Encoder–Decoder構成に固有の仕組みです。

decoder_states[B, T, D]encoder_states[B, S, D]hidden_states[B, T, D]attention_weights[B, H, T, S]

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

何をする機能か

Decoder 側の各位置が、Encoder の出力(入力文の文脈表現)を参照して、 必要な情報を取り込みます。

Self-Attention との違いは、Query・Key・Value の出どころです。

Query(問い合わせ)Key・Value(参照先)
Self-Attention自分の系列自分の系列
Cross AttentionDecoder の系列 [B, T, D]Encoder の出力 [B, S, D]

図で Encoder から Decoder へ伸びる紫の線が、この Key・Value の受け渡しです。

なぜ必要か

翻訳を例にすると、出力側で 1 語を決めるたびに 「入力文のどの部分を訳しているのか」を選ぶ必要があります。 Cross Attention はこの対応付けを担います。

Decoder の Masked Self-Attention は 「これまでに出力した内容」しか見られません。 入力文の情報を取り込む経路がこの層だけなので、 Encoder–Decoder 構成では欠かせない部分です。

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

Decoder 各層の2 番目のサブ層です。 Masked Self-Attention → Add & Norm のあとに実行され、 その後また Add & Norm を経て Feed-Forward へ進みます。

Decoder の N 層すべてが、同じ「Encoder 最終層の出力」を参照します。

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

前段から受け取るもの

  • decoder_states [B, T, D]: 直前の Add & Norm の出力(Query の材料)
  • encoder_states [B, S, D]: Encoder出力(Key・Value の材料)
  • encoder_mask [B, S]: 入力側のパディングを参照しないためのマスク

後段へ渡すもの

hidden_states [B, T, D] と attention_weights [B, H, T, S]。 渡し先は Add & Norm です。

形に注目する

Cross Attention では、Query 側と Key 側で系列長が違います。

行列形由来
Query[B, H, T, Dk]Decoder の系列(長さ T)
Key[B, H, S, Dk]Encoder の出力(長さ S)
Value[B, H, S, Dv]Encoder の出力(長さ S)
スコア[B, H, T, S]正方行列ではない
出力[B, H, T, Dv] → [B, T, D]Decoder 側の長さに戻る

出力の系列長は必ず Query 側(T)になります。 S と T が違っていてもよいのは、この形のおかげです(翻訳で語数が変わっても成立します)。

例文での処理

共通例文

The keys on the table are lost.

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

例文を入力として Encoder が処理し、Decoder が別の系列を生成している状況を考えます。 Encoder 出力は S = 8 本のベクトル(各入力トークンの文脈表現)です。

  1. The
  2. keys
  3. on
  4. the
  5. table
  6. are
  7. lost
  8. .
Encoder 側の系列(Key・Value の出どころ)。8 本すべてが Decoder から参照できます。因果マスクはかかりません。

Decoder 側のある位置が「主語にあたる語を出力しよう」としているとき、 その Query は Encoder 側の keys に対応する位置と強く一致します。

Decoder の現在位置 の Query から見た重みの例

  • The (enc 0)0.05
  • keys (enc 1)0.52
  • on (enc 2)0.06
  • the (enc 3)0.04
  • table (enc 4)0.19
  • are (enc 5)0.08
  • lost (enc 6)0.05
  • . (enc 7)0.01

3種類のAttentionの整理

このページまでで 3 種類が出そろいました。取り違えないよう整理します。

Encoder Self-AttentionDecoder Masked Self-AttentionCross Attention
場所Encoder 各層 1 番目Decoder 各層 1 番目Decoder 各層 2 番目
Query入力系列出力系列出力系列
Key・Value入力系列出力系列入力系列(Encoder 出力)
因果マスクなしありなし
スコアの形[B, H, S, S][B, H, T, T][B, H, T, S]
Decoder-only での有無なしありなし

Cross Attention に因果マスクがかからないのは、 入力文は最初から全部わかっているためです(未来を隠す必要がありません)。

Decoder-only 構成との違い

GPT や Llama のような Decoder-only モデルには、この層がありません。 入力(プロンプト)と出力を 1 本の系列につないでしまい、 Masked Self-Attention だけで両方を扱います。

  • Encoder–Decoder: 入力と出力を別の系列として扱い、Cross Attention で橋渡しする
  • Decoder-only: 入力と出力を 1 本の系列として扱い、Cross Attention は不要

現在も Encoder–Decoder が使われる場面はあります(翻訳、要約、音声認識など、 入力と出力の役割が明確に分かれるタスク)。

入力と出力

入力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
decoder_statesQuery の元になる Decoder 側のベクトル列float32 Tensor[B, T, D]B = バッチサイズ(同時に処理する文の数) / T = 出力系列長(Decoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[1, 6, 512]W_Q による変換
encoder_statesKey・Value の元になる Encoder 出力float32 Tensor[B, S, D]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[1, 8, 512]W_K / W_V による変換
encoder_mask入力側のパディング位置を参照させないbool Tensor[B, S]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数)[[1, 1, 1, 1, 1, 1, 1, 1]]スコアへ適用

出力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
hidden_states入力文の情報を取り込んだ Decoder 側のベクトル列float32 Tensor[B, T, D]B = バッチサイズ(同時に処理する文の数) / T = 出力系列長(Decoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[1, 6, 512](系列長は Query 側 T になる)残差接続 + LayerNorm → Feed-Forward

補助出力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
attention_weights出力位置から入力位置への重み(アライメントの可視化に使われる)float32 Tensor[B, H, T, S]B = バッチサイズ(同時に処理する文の数) / H = Attention Head 数 / T = 出力系列長(Decoder側のトークン数) / S = 入力系列長(Encoder側のトークン数)[1, 8, 6, 8]可視化・分析
B
バッチサイズ(同時に処理する文の数)
T
出力系列長(Decoder側のトークン数)
D
モデル内部のベクトル次元(d_model)
S
入力系列長(Encoder側のトークン数)
H
Attention Head 数

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

よくある誤解

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

数式

Xdec∈RB×T×DX_{dec} \in \mathbb{R}^{B \times T \times D}、Xenc∈RB×S×DX_{enc} \in \mathbb{R}^{B \times S \times D} として、

Q=XdecWQ,K=XencWK,V=XencWVQ = X_{dec} W^Q, \qquad K = X_{enc} W^K, \qquad V = X_{enc} W^VCrossAttention=softmax ⁣(QK⊤Dk)V\mathrm{CrossAttention} = \mathrm{softmax}\!\left(\frac{QK^{\top}}{\sqrt{D_k}}\right) V

QK⊤QK^{\top} の形が T×ST \times S になる点が Self-Attention との違いです。 WQ,WK,WVW^Q, W^K, W^V は Self-Attention とは別のパラメータで、層ごとにも別々に学習されます。

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

from dataclasses import dataclass
from typing import Optional
import math
import torch
from torch import Tensor, nn
 
 
@dataclass
class AttentionOutput:
    hidden_states: Tensor      # [B, T, D]
    attention_weights: Tensor  # [B, H, T, S]
 
 
class CrossAttention(nn.Module):
    def __init__(self, d_model: int, num_heads: int) -> None:
        super().__init__()
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        self.w_q = nn.Linear(d_model, d_model)   # Decoder 側から
        self.w_k = nn.Linear(d_model, d_model)   # Encoder 側から
        self.w_v = nn.Linear(d_model, d_model)   # Encoder 側から
        self.w_o = nn.Linear(d_model, d_model)
 
    def _split(self, x: Tensor) -> Tensor:
        """[B, L, D] -> [B, H, L, Dk](L は S でも T でもよい)"""
        batch, length, _ = x.shape
        return x.view(batch, length, self.num_heads, self.d_k).transpose(1, 2)
 
    def forward(
        self,
        decoder_states: Tensor,               # Query: [B, T, D]
        encoder_states: Tensor,               # Key/Value: [B, S, D]
        encoder_mask: Optional[Tensor] = None,# [B, S]
    ) -> AttentionOutput:
        """Decoder の状態から Encoder 出力を参照する。
 
        Returns:
            hidden_states [B, T, D] と attention_weights [B, H, T, S]。
        """
        q = self._split(self.w_q(decoder_states))   # [B, H, T, Dk]
        k = self._split(self.w_k(encoder_states))   # [B, H, S, Dk]
        v = self._split(self.w_v(encoder_states))   # [B, H, S, Dv]
 
        # [B, H, T, Dk] @ [B, H, Dk, S] -> [B, H, T, S](正方行列ではない)
        scores = q @ k.transpose(-2, -1) / math.sqrt(self.d_k)
 
        if encoder_mask is not None:
            # 入力側のパディングのみを隠す。因果マスクはかけない。
            scores = scores.masked_fill(~encoder_mask[:, None, None, :].bool(), float("-inf"))
 
        weights = scores.softmax(dim=-1)            # [B, H, T, S]
        context = weights @ v                       # [B, H, T, Dv]
 
        batch, _, length, _ = context.shape
        merged = context.transpose(1, 2).reshape(batch, length, -1)  # [B, T, D]
        return AttentionOutput(hidden_states=self.w_o(merged), attention_weights=weights)
 
 
def cross_attention(
    decoder_states: Tensor,   # Query: [B, T, D]
    encoder_states: Tensor,   # Key/Value: [B, S, D]
    layer: CrossAttention,
    encoder_mask: Optional[Tensor] = None,
) -> AttentionOutput:
    """Decoder の状態から Encoder 出力を参照する。"""
    return layer(decoder_states, encoder_states, encoder_mask)
 
 
# 最小使用例(入力 S=8、出力 T=6 と長さが違ってよい)
B, S, T, D, H = 1, 8, 6, 512, 8
layer = CrossAttention(d_model=D, num_heads=H)
 
decoder_states = torch.randn(B, T, D)   # [B, T, D]
encoder_states = torch.randn(B, S, D)   # [B, S, D]
out = cross_attention(decoder_states, encoder_states, layer)
 
print(out.hidden_states.shape)          # torch.Size([1, 6, 512])   == [B, T, D]
print(out.attention_weights.shape)      # torch.Size([1, 8, 6, 8])  == [B, H, T, S]
print(out.attention_weights[0, 0, 0].sum())  # tensor(1.0000)

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

  • Head の分割・結合は multi-head-self-attention のページと同じ処理のため、簡略に書いています。
  • 推論時に Encoder 側の K/V をキャッシュする最適化は省略しています。
  • Dropout とバイアス項を省略しています。

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

計算量

O(B⋅T⋅S⋅D)O(B \cdot T \cdot S \cdot D) です。Self-Attention の S2S^2 に対して T×ST \times S になります。

推論時には有利な性質があります。Encoder 出力は生成中ずっと変わらないため、 Key と Value を 1 回だけ計算してキャッシュできます。 毎ステップ計算するのは Query(1 トークンぶん)だけです。

実装差と派生

  • K/V の事前計算: 実装ではほぼ必ず Encoder 側 K/V をキャッシュします。
  • Attention の可視化: 翻訳モデルでは、この重みを並べて 入力語と出力語の対応図として示すことがあります。 ただし、重みが言語的な対応そのものを表す保証はありません。
  • Perceiver / Flamingo 系: 画像などの別モダリティの特徴を Key・Value に使い、 テキスト側から Cross Attention で参照する構成が使われます。 「2 つの系列をつなぐ汎用の仕組み」として応用されています。

この記事の位置づけ

このページ

Encoder–Decoder Attention

次に読むとよい記事

なし

関連記事