準備 — 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
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の実装を本格的に理解するための、推奨ステップです。
- Harvard NLP "The Annotated Transformer" の
attention()8行を写経 → 形状 (B, H, T, d_k) を体に入れる - 本章の (A)(B)(C) を Jupyter で動かし、
F.scaled_dot_product_attentionとの一致を確認 - Karpathy minGPT の
mingpt/model.pyを読む(教育目的の素朴な書き方) - Karpathy nanoGPT の
model.pyで実運用パターンを学ぶ(QKV一括射影、sdpa一発呼び出し) - Karpathy "Let's build GPT from scratch" 動画+
build-nanogptのコミット履歴で訓練ループまで通す - 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ハイブリッド、そして高校生から始める学習ロードマップ。
理解度チェック
PyTorch で因果マスク(上三角がTrueの行列)を作る正しいコードはどれですか?
キーボード: 1〜4 で選択、Enter で回答