準備 — PyTorchをインストール

本章は 手を動かして理解する 章です。Python と PyTorch があれば、自宅のPCで実行できます。

# PyTorchをインストール(CPU版でOK)
pip install torch

# Jupyter Notebook で実行するなら
pip install jupyter
jupyter notebook

GPUがなくても、本章のコードは1秒以内に動きます。プログラミング初学者の方は、Google Colab(無料)でブラウザから実行することもできます。

Scaled Dot-Product Attention — 10行で実装

まずは 第6章の核心式 をそのままコードにします。

import math
import torch
import torch.nn.functional as F

def scaled_dot_product_attention(Q, K, V, mask=None):
    """
    Q, K, V: (B, H, T, d_k)  ※ Bはバッチ、Hはヘッド数、Tは系列長、d_kは次元
    mask   : (B, 1, T, T)、True が「無視したい位置」
    return : 出力 (B, H, T, d_k), 注意重み (B, H, T, T)
    """
    d_k = Q.size(-1)
    # Step 1: QK^T 内積を計算 → (B, H, T, T)
    scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
    # Step 2: マスクがあれば -inf で埋める
    if mask is not None:
        scores = scores.masked_fill(mask, float("-inf"))
    # Step 3: softmaxで重みに変換
    attn = F.softmax(scores, dim=-1)
    # Step 4: 重みでVを加重平均
    out = torch.matmul(attn, V)
    return out, attn

これだけ。たった10行 で、ChatGPTの心臓部の半分が実装できました。各行が第6章の4ステップに対応しています。

graph LR
  L1[Step 1: QK^T 内積\n10行目] --> L2[Step 2: マスク\n12-13行目]
  L2 --> L3[Step 3: softmax\n15行目]
  L3 --> L4[Step 4: × V\n17行目]

  style L1 fill:#3b82f6,stroke:#1d4ed8,color:#fff
  style L2 fill:#8b5cf6,stroke:#6d28d9,color:#fff
  style L3 fill:#f97316,stroke:#ea580c,color:#fff
  style L4 fill:#14b8a6,stroke:#0d9488,color:#fff
10行のコードが第6章の4ステップにそのまま対応

Multi-Head Attention — 40行で実装

次に、第7章で学んだMulti-Head Attentionをクラスとして実装します。

import torch.nn as nn

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model: int, n_heads: int, dropout: float = 0.0):
        super().__init__()
        assert d_model % n_heads == 0, "d_model は n_heads で割り切れること"
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads

        # Q, K, V を作る重み行列(bias なし)
        self.W_q = nn.Linear(d_model, d_model, bias=False)
        self.W_k = nn.Linear(d_model, d_model, bias=False)
        self.W_v = nn.Linear(d_model, d_model, bias=False)
        # 最後の出力射影
        self.W_o = nn.Linear(d_model, d_model, bias=False)
        self.dropout = nn.Dropout(dropout)

    def _split_heads(self, x):
        """(B, T, d_model) -> (B, H, T, d_k)"""
        B, T, _ = x.shape
        # view で分割し、transpose でヘッド次元を前に
        return x.view(B, T, self.n_heads, self.d_k).transpose(1, 2)

    def _merge_heads(self, x):
        """(B, H, T, d_k) -> (B, T, d_model)"""
        B, H, T, d_k = x.shape
        return x.transpose(1, 2).contiguous().view(B, T, H * d_k)

    def forward(self, x_q, x_k, x_v, mask=None):
        """
        x_q, x_k, x_v: (B, T, d_model)  自己注意なら全部同じテンソル
        mask: (B, 1, T_q, T_k) ブロードキャスト可能、True = 無視
        """
        # 1. 射影してヘッド分割
        Q = self._split_heads(self.W_q(x_q))  # (B, H, T, d_k)
        K = self._split_heads(self.W_k(x_k))
        V = self._split_heads(self.W_v(x_v))

        # 2. Scaled Dot-Product Attention(さきほどの関数を使う)
        out, attn = scaled_dot_product_attention(Q, K, V, mask=mask)
        out = self.dropout(out)

        # 3. ヘッドを連結して出力射影
        out = self._merge_heads(out)  # (B, T, d_model)
        return self.W_o(out), attn

約40行。これがGPT/BERT/Llama/ClaudeすべてのMulti-Head Attentionの本体 です。実際のプロダクションコードも基本的にこの構造を踏襲しています。

動作確認 — 因果マスク付きで実行

実際に動かしてみましょう。

torch.manual_seed(0)
B, T, d_model, H = 2, 6, 32, 4  # バッチ2、系列長6、d_model=32、4ヘッド
x = torch.randn(B, T, d_model)

# 因果マスク作成: 上三角を True にする (GPT流)
causal = torch.triu(torch.ones(T, T, dtype=torch.bool), diagonal=1)
causal = causal.view(1, 1, T, T)  # ブロードキャスト用

mha = MultiHeadAttention(d_model=d_model, n_heads=H)
out, attn = mha(x, x, x, mask=causal)

print("out shape :", out.shape)         # torch.Size([2, 6, 32])
print("attn shape:", attn.shape)        # torch.Size([2, 4, 6, 6])
print("行和=1 ?  :", attn.sum(-1)[0, 0])  # 各行ほぼ 1.0
print("未来は0 ? :", attn[0, 0, 0, 1:].sum().item())  # t=0 から未来は 0

実行すると、出力の形が (2, 6, 32) で、attention重みの行和が1、因果マスクのおかげで t=0 から未来位置への重みが0、となることが確認できます。

PyTorch標準のsdpa — Flash Attention自動使用

PyTorch 2.0以降では、標準の F.scaled_dot_product_attention が組み込まれています。これは内部で Flash Attention を自動的に使うため、自作版より高速です。

import torch.nn.functional as F

# 公式の Scaled Dot-Product Attention(is_causal=True で因果マスク内蔵)
out_official = F.scaled_dot_product_attention(Q, K, V, is_causal=True)

# 自作版との一致確認
out_mine, _ = scaled_dot_product_attention(Q, K, V, mask=causal)
print("差:", (out_official - out_mine).abs().max().item())  # ~1e-6 程度

初学者が必ずつまずく6つのポイント

# つまずき 原因と対策
1 softmaxの軸を間違える F.softmax(scores, dim=-1) の -1 は「キー側の長さT_k」。dim=-2 だと意味が壊れる。確認は attn.sum(-1) が全部1に近いか
2 マスクの向き 自作系は「True = 無視」で書くのが PyTorch公式と揃って便利。Annotated Transformerは「mask==0」の旧式規約なので混ぜると詰む
3 因果マスクの作り方 torch.triu(ones, diagonal=1) で「対角より上だけTrue」。diagonal=0 にすると自分自身まで消えて全行NaN
4 ヘッド分割の順序 (B,T,D) → view(B,T,H,d_k) → transpose(1,2) の順。先にtranspose してから view すると contiguous でないため落ちる
5 √dk スケーリング忘れ 忘れると d_model が大きいとき softmax が一点に飽和し勾配ほぼゼロ。学習が一切進まない症状で気づきにくい
6 -∞ で NaN 行全部がマスクされた行に -∞ を入れると softmax が 0/0 で NaN。対策は「全マスク行を作らない」、または -1e9 を使う

Hugging Faceで既存モデルを3行で動かす

自作したAttentionの理解を、既存モデルで体感しましょう。

# pip install transformers

from transformers import pipeline

# BERT で穴埋め
fill = pipeline("fill-mask", model="bert-base-uncased")
print(fill("Paris is the [MASK] of France."))
# → [{'sequence': 'paris is the capital of france.', ...}]

# GPT-2 で文章生成
gen = pipeline("text-generation", model="gpt2")
print(gen("Once upon a time"))

たった3行で、自分が実装したのと同じMulti-Head Attention(の本物版)が動きます。「これがあのGPT-2 か」と感動する瞬間です。

学習ロードマップ — 写経の順序

Attentionの実装を本格的に理解するための、推奨ステップです。

  1. Harvard NLP "The Annotated Transformer"attention() 8行を写経 → 形状 (B, H, T, d_k) を体に入れる
  2. 本章の (A)(B)(C) を Jupyter で動かし、F.scaled_dot_product_attention との一致を確認
  3. Karpathy minGPTmingpt/model.py を読む(教育目的の素朴な書き方)
  4. Karpathy nanoGPTmodel.py で実運用パターンを学ぶ(QKV一括射影、sdpa一発呼び出し)
  5. Karpathy "Let's build GPT from scratch" 動画+build-nanogpt のコミット履歴で訓練ループまで通す
  6. Hugging Face Transformers で BERT/Llama を pipeline 経由で動かし、自作と既製品の差を体感
リソース URL(参考) 特徴
Annotated Transformer nlp.seas.harvard.edu/annotated-transformer/ 論文を行ごとにPyTorch化した古典
minGPT github.com/karpathy/minGPT 教育目的の素朴な実装
nanoGPT github.com/karpathy/nanoGPT 実運用パターンの最小GPT
build-nanogpt github.com/karpathy/build-nanogpt Karpathyの動画と対応
LLMs-from-scratch github.com/rasbt/LLMs-from-scratch Sebastian Raschka著、5実装比較あり

この章のまとめ

Attentionは 意外なほど少ないコード で実装できます。Scaled Dot-Product Attention は10行、Multi-Head Attention は40行。コンセプトを理解していれば、ChatGPTの心臓部を自分の手で動かせるのです。

実装で躓くポイントは限られていて、本章で挙げた6つのつまずき(softmaxの軸、マスクの向き、ヘッド分割の順序、√dk忘れ等)を意識すれば多くの問題を回避できます。プロダクションでは F.scaled_dot_product_attention が最速で、Flash Attentionを自動使用します。

次のいよいよ最終章では、ここまで学んだAttentionが 2026年のLLM最前線で何を生み、これからどこへ向かうのか を俯瞰します。DeepSeek MLA、FlashAttention 4、Mamba/SSMハイブリッド、そして高校生から始める学習ロードマップ。

理解度チェック

問題 0 / 50%
Q1

PyTorch で因果マスク(上三角がTrueの行列)を作る正しいコードはどれですか?

キーボード: 1〜4 で選択、Enter で回答