Level 1概要役割と処理の流れ
何をする機能か
Decoder 側の各位置が、Encoder の出力(入力文の文脈表現)を参照して、 必要な情報を取り込みます。
Self-Attention との違いは、Query・Key・Value の出どころです。
| Query(問い合わせ) | Key・Value(参照先) | |
|---|---|---|
| Self-Attention | 自分の系列 | 自分の系列 |
| Cross Attention | Decoder の系列 [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 本のベクトル(各入力トークンの文脈表現)です。
- The
- keys
- on
- the
- table
- are
- lost
- .
Decoder 側のある位置が「主語にあたる語を出力しよう」としているとき、
その Query は Encoder 側の keys に対応する位置と強く一致します。
Decoder の現在位置 の Query から見た重みの例
3種類のAttentionの整理
このページまでで 3 種類が出そろいました。取り違えないよう整理します。
| Encoder Self-Attention | Decoder Masked Self-Attention | Cross 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_states | Query の元になる Decoder 側のベクトル列 | float32 Tensor | [B, T, D] | B = バッチサイズ(同時に処理する文の数) / T = 出力系列長(Decoder側のトークン数) / D = モデル内部のベクトル次元(d_model) | [1, 6, 512] | W_Q による変換 |
encoder_states | Key・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数式・疑似実装・計算量・実装差
数式
、 として、
の形が になる点が Self-Attention との違いです。 は 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 形状の見え方を掴むための擬似実装です。そのまま学習・推論に使えるものではありません。
計算量
です。Self-Attention の に対して になります。
推論時には有利な性質があります。Encoder 出力は生成中ずっと変わらないため、 Key と Value を 1 回だけ計算してキャッシュできます。 毎ステップ計算するのは Query(1 トークンぶん)だけです。
実装差と派生
- K/V の事前計算: 実装ではほぼ必ず Encoder 側 K/V をキャッシュします。
- Attention の可視化: 翻訳モデルでは、この重みを並べて 入力語と出力語の対応図として示すことがあります。 ただし、重みが言語的な対応そのものを表す保証はありません。
- Perceiver / Flamingo 系: 画像などの別モダリティの特徴を Key・Value に使い、 テキスト側から Cross Attention で参照する構成が使われます。 「2 つの系列をつなぐ汎用の仕組み」として応用されています。