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

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

共通抽象度: Transformer Block難易度: 標準推定学習時間: 約15分

残差接続 + LayerNormAdd & Layer Normalization

30秒で分かる要約サブ層の入力をその出力に足し戻し(残差接続)、特徴次元方向に正規化します。深い層を安定して学習させるための仕組みで、原典のPost-Normと近年主流のPre-Normでは適用位置が異なります。

residual[B, S, D]sublayer_output[B, S, D]hidden_states[B, S, D]

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

何をする機能か

2 つの処理をまとめたものです。

  1. 残差接続(Add): サブ層(Attention や Feed-Forward)の入力を、その出力にそのまま足す
  2. Layer Normalization(Norm): 足した結果を、各トークンの特徴次元方向に正規化する

入力も出力も [B, S, D] で、形は変わりません。 図の中でこのブロックが何度も出てくるのは、各サブ層のあとに必ず適用されるためです。

なぜ必要か

残差接続は、深いネットワークの学習を成立させるための仕組みです。 層を重ねるほど勾配が小さくなって学習が進まなくなりますが、 足し算の経路があると勾配がそこを通って下層まで届きます。 また「サブ層は差分だけを学習すればよい」形になるため、層を増やしても性能が落ちにくくなります。

LayerNorm は、層を通るたびに値の大きさが偏るのを抑えます。 値の分布が層ごとに大きく変わると学習率の調整が難しくなりますが、 毎層そろえておけば安定します。

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

Encoder では「Self-Attention のあと」と「Feed-Forward のあと」の 2 回、 Decoder では「Masked Self-Attention のあと」「Cross Attention のあと」「Feed-Forward のあと」の 3 回です。 図で Encoder に 2 個、Decoder に 3 個描かれているのはこのためです。

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

前段から受け取るもの

2 つの [B, S, D] を受け取ります。

  • residual: サブ層に入る前の値
  • sublayer_output: サブ層の出力

図の茶色い破線が、residual を迂回させている経路です。

後段へ渡すもの

hidden_states [B, S, D]。次のサブ層、または次の層へ渡されます。

LayerNorm が正規化する方向

ここが誤解の多いところです。正規化は特徴次元 D の方向に対して行われます。

  • バッチ方向(B)でも系列方向(S)でもありません
  • つまり、トークン1個ぶんの D 次元ベクトルごとに、平均 0・分散 1 にそろえます
  • そのあと、学習可能なパラメータ γ\gamma(スケール)と β\beta(シフト)を掛けて足します

γ,β\gamma, \beta があるため、出力の平均が 0、分散が 1 のままとは限りません。 「値を 0〜1 に収める処理」ではない点に注意してください。

LayerNorm 前

位置ごとにベクトルの大きさがバラバラ(例: ノルムが 3 の位置と 40 の位置が混在)

LayerNorm 後

各位置の D 次元ベクトルが同じ尺度にそろう(そのうえで γ・β で再スケール)

例文での処理

共通例文

The keys on the table are lost.

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

Self-Attention のあと、位置1の keys のベクトルには周囲の情報が混ざっています。 残差接続はここで、Attention に入る前の keys 自身の情報を足し戻します。

  1. The
  2. keys
  3. on
  4. the
  5. table
  6. are
  7. lost
  8. .
位置1の keys。Attention の出力(周囲の情報)と、元の keys 自身のベクトルを足し合わせます。

これにより、次の 2 つが両立します。

  • keys は「テーブルの上にある、失くした複数個のもの」という文脈情報を得る
  • 同時に、keys が keys であるという元の情報も失わない

残差接続がないと、層を通るたびに元の語の情報が薄まっていきます。

Post-Norm と Pre-Norm

適用する順序に 2 つの流儀があり、混同しやすい重要な違いです。

Post-Norm(原典 Transformer)Pre-Norm(近年の主流)
式LayerNorm(x+Sublayer(x))\mathrm{LayerNorm}(x + \mathrm{Sublayer}(x))x+Sublayer(LayerNorm(x))x + \mathrm{Sublayer}(\mathrm{LayerNorm}(x))
正規化の位置サブ層のあと(残差を足したあと)サブ層の前(残差は正規化しない)
学習の安定性深くすると不安定。Warmup が必須深くしても安定しやすい
採用例原典 Transformer、BERTGPT-2 以降、Llama など

このサイトの図は原典に合わせた Post-Norm です。 図では「サブ層 → Add & Norm」の順に並んでいます。 一方、現代の LLM のコードを読むと、LayerNorm がサブ層の手前に置かれていることが多いはずです。 同じ「Add & Norm」という名前でも位置が違う点に注意してください。

Pre-Norm では、残差の経路に正規化が挟まらないため、 入力から出力まで何も掛からない加算の道ができます。これが深い層での安定性につながります。

EncoderとDecoderでの違い

処理内容は同じです。呼ばれる回数だけが異なります(Encoder は 2 回、Decoder は 3 回)。

入力と出力

入力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
residualサブ層に入る前の値(迂回してきた経路)float32 Tensor[B, S, D]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[1, 8, 512]加算
sublayer_outputAttention や Feed-Forward の出力float32 Tensor[B, S, D]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[1, 8, 512]加算

出力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
hidden_states残差を加算し、特徴次元方向に正規化した値float32 Tensor[B, S, D]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[1, 8, 512](形は変わらない)次のサブ層、または次の層
B
バッチサイズ(同時に処理する文の数)
S
入力系列長(Encoder側のトークン数)
D
モデル内部のベクトル次元(d_model)

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

学習対象のパラメータは γ\gamma [D] と β\beta [D] の 2 本だけで、非常に軽量です。

よくある誤解

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

数式

残差接続と Post-Norm:

y=LayerNorm(x+Sublayer(x))y = \mathrm{LayerNorm}(x + \mathrm{Sublayer}(x))

LayerNorm 本体は、dd 番目の要素について次のように計算します (HH ではなく特徴次元 DD の中で統計を取ります)。

μ=1D∑d=1Dxd,σ2=1D∑d=1D(xd−μ)2\mu = \frac{1}{D}\sum_{d=1}^{D} x_d, \qquad \sigma^2 = \frac{1}{D}\sum_{d=1}^{D} (x_d - \mu)^2LayerNorm(x)d=γd⋅xd−μσ2+ϵ+βd\mathrm{LayerNorm}(x)_d = \gamma_d \cdot \frac{x_d - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta_d

ϵ\epsilon はゼロ除算を避けるための小さな値(10−510^{-5} 程度)です。

Pre-Norm の場合は次のようになります。

y=x+Sublayer(LayerNorm(x))y = x + \mathrm{Sublayer}(\mathrm{LayerNorm}(x))

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

import torch
from torch import Tensor, nn
 
 
class LayerNorm(nn.Module):
    """特徴次元 D の方向に正規化する。"""
 
    def __init__(self, d_model: int, eps: float = 1e-5) -> None:
        super().__init__()
        self.gamma = nn.Parameter(torch.ones(d_model))   # [D] スケール
        self.beta = nn.Parameter(torch.zeros(d_model))   # [D] シフト
        self.eps = eps
 
    def forward(self, x: Tensor) -> Tensor:
        """
        Args:
            x: [B, S, D]
 
        Returns:
            [B, S, D]。最後の次元(D)の中で平均0・分散1にそろえたあと、γ・βを適用。
        """
        mean = x.mean(dim=-1, keepdim=True)              # [B, S, 1]
        var = x.var(dim=-1, keepdim=True, unbiased=False)# [B, S, 1]
        normalized = (x - mean) / torch.sqrt(var + self.eps)
        return self.gamma * normalized + self.beta       # [B, S, D]
 
 
def add_and_norm(residual: Tensor, sublayer_output: Tensor, norm: LayerNorm) -> Tensor:
    """残差を加算し、Layer Normalization を適用する(原典の Post-Norm)。
 
    Args:
        residual: [B, S, D]。サブ層に入る前の値。
        sublayer_output: [B, S, D]。サブ層の出力。
        norm: LayerNorm 層。
 
    Returns:
        [B, S, D]
    """
    return norm(residual + sublayer_output)
 
 
def pre_norm_block(x: Tensor, sublayer, norm: LayerNorm) -> Tensor:
    """近年主流の Pre-Norm 版。正規化してからサブ層へ入れ、残差は素通しする。"""
    return x + sublayer(norm(x))
 
 
# 最小使用例
B, S, D = 1, 8, 512
norm = LayerNorm(d_model=D)
 
residual = torch.randn(B, S, D)          # Attention に入る前の値
sublayer_output = torch.randn(B, S, D)   # Attention の出力
out = add_and_norm(residual, sublayer_output, norm)
 
print(out.shape)                          # torch.Size([1, 8, 512]) == [B, S, D]
# 正規化は D 方向。位置ごとに平均がほぼ0(γ=1, β=0 の初期状態の場合)
print(out[0, 0].mean().abs() < 1e-5)      # tensor(True)

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

  • Dropout(原典ではサブ層出力に適用してから加算)を省略しています。
  • nn.LayerNorm を使わず、計算内容がわかる形で書いています。
  • Pre-Norm 版は下に別関数として示しています。

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

計算量

O(B⋅S⋅D)O(B \cdot S \cdot D) です。Attention や Feed-Forward に比べると小さいですが、 要素ごとの読み書きが多くメモリ帯域を使うため、 実行時間に占める割合は計算量の比率より大きくなることがあります。

実装差と派生

  • RMSNorm: 平均の減算を省き、二乗平均平方根だけで割る方式。 β\beta も持ちません。計算が軽く、Llama などで採用されています。
  • Dropout の位置: 原典ではサブ層の出力に Dropout を適用してから加算します。
  • Sandwich Norm / DeepNorm: 非常に深いモデルを安定させるための変種です。
  • 最終層の LayerNorm: Pre-Norm 構成では、最後のブロックの出力にもう一度 LayerNorm を掛けてから出力ヘッドへ渡すのが一般的です(Post-Norm では不要)。

この記事の位置づけ

前提となる知識

マルチヘッド自己注意

このページ

残差接続 + LayerNorm

次に読むとよい記事

位置ごとの全結合処理

関連記事