GPTアーキテクチャを徹底解説:Decoder-onlyモデルの設計思想

GPT(Generative Pre-trained Transformer)は、OpenAIが開発した大規模言語モデルのシリーズです。TransformerのDecoder部分のみを使用するDecoder-onlyアーキテクチャを採用し、自己回帰的にテキストを生成します。

本記事では、GPTのアーキテクチャの設計思想と、GPT-1からGPT-3への進化を解説します。

本記事の内容

  • GPTとは何か
  • Decoder-onlyアーキテクチャの特徴
  • GPT-1, GPT-2, GPT-3の進化
  • 因果的言語モデリング(Causal LM)
  • トークン埋め込みと位置エンコーディング
  • Pythonでの簡易実装

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

GPTとは何か

GPTは “Generative Pre-trained Transformer” の略で、以下の特徴を持ちます:

  1. Generative(生成的): 次のトークンを逐次予測することでテキストを生成
  2. Pre-trained(事前学習済み): 大規模なテキストコーパスで事前学習
  3. Transformer: Transformerアーキテクチャを基盤とする

GPTの革新性は、大規模な事前学習によって「言語の一般的な理解」を獲得し、少量のデータで様々なタスクに適応できる点にあります。

Decoder-onlyアーキテクチャ

BERTとの違い

項目 GPT (Decoder-only) BERT (Encoder-only)
注意の方向 単方向(過去のみ参照) 双方向
主な用途 テキスト生成 文分類、NER等
事前学習タスク 因果的言語モデリング MLM + NSP
マスク 因果マスク パディングマスクのみ

GPTブロックの構成

GPTの各ブロックは以下の2つのサブレイヤーで構成されます:

入力
  ↓
Masked Multi-Head Self-Attention
  ↓ + 残差接続 + LayerNorm
Feed-Forward Network
  ↓ + 残差接続 + LayerNorm
出力

Cross-Attentionは使用しません。入力系列と出力系列を単一の系列として扱います。

因果的言語モデリング

GPTの学習目標は、文脈が与えられたときの次のトークンの確率を最大化することです。

$$ \mathcal{L} = -\sum_{t=1}^{T} \log P(y_t \mid y_1, \dots, y_{t-1}; \theta) $$

この目標は因果的言語モデリング(Causal Language Modeling)と呼ばれます。

学習と推論の統一

因果マスクにより、学習時には全位置の予測を並列計算できる一方、推論時には1トークンずつ生成します。同じモデル、同じマスクパターンで両方に対応できます。

GPT-1, GPT-2, GPT-3の進化

GPT-1 (2018)

項目
パラメータ数 117M
層数 12
d_model 768
ヘッド数 12
学習データ BooksCorpus (5GB)

GPT-1は「事前学習 + ファインチューニング」のパラダイムを確立しました。

GPT-2 (2019)

項目
パラメータ数 1.5B
層数 48
d_model 1600
ヘッド数 25
学習データ WebText (40GB)

GPT-2は「ファインチューニングなしでも多くのタスクをこなせる」ことを示しました(Zero-shot学習)。

GPT-3 (2020)

項目
パラメータ数 175B
層数 96
d_model 12288
ヘッド数 96
学習データ 数百GBのテキスト

GPT-3は「In-Context Learning」(文脈内学習)の能力を示しました。プロンプトに例を含めるだけで、新しいタスクに適応できます。

Pythonでの簡易実装

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


class GPTBlock(nn.Module):
    """GPTのTransformerブロック"""
    def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads

        # Multi-Head Self-Attention
        self.W_qkv = nn.Linear(d_model, 3 * d_model)
        self.W_o = nn.Linear(d_model, d_model)
        self.attn_dropout = nn.Dropout(dropout)

        # Feed-Forward Network
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.GELU(),
            nn.Linear(d_ff, d_model),
            nn.Dropout(dropout)
        )

        # Layer Normalization (Pre-LN)
        self.ln1 = nn.LayerNorm(d_model)
        self.ln2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        batch_size, seq_len, _ = x.size()

        # Pre-LN
        h = self.ln1(x)

        # QKV projection
        qkv = self.W_qkv(h)
        Q, K, V = qkv.chunk(3, dim=-1)

        # ヘッド分割
        Q = Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        K = K.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        V = V.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)

        # Attention
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        if mask is not None:
            scores = scores + mask
        attn = F.softmax(scores, dim=-1)
        attn = self.attn_dropout(attn)
        context = torch.matmul(attn, V)

        # ヘッド結合
        context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model)
        attn_out = self.W_o(context)

        # 残差接続
        x = x + self.dropout(attn_out)

        # FFN (Pre-LN)
        x = x + self.ffn(self.ln2(x))

        return x


class GPT(nn.Module):
    """GPTモデル"""
    def __init__(self, vocab_size, d_model=768, n_heads=12, d_ff=3072,
                 n_layers=12, max_len=1024, dropout=0.1):
        super().__init__()
        self.d_model = d_model

        # 埋め込み
        self.token_emb = nn.Embedding(vocab_size, d_model)
        self.pos_emb = nn.Embedding(max_len, d_model)
        self.dropout = nn.Dropout(dropout)

        # Transformerブロック
        self.blocks = nn.ModuleList([
            GPTBlock(d_model, n_heads, d_ff, dropout) for _ in range(n_layers)
        ])

        # 出力層
        self.ln_f = nn.LayerNorm(d_model)
        self.head = nn.Linear(d_model, vocab_size, bias=False)

        # 因果マスクをバッファとして登録
        mask = torch.triu(torch.ones(max_len, max_len), diagonal=1)
        mask = mask.masked_fill(mask == 1, float('-inf'))
        self.register_buffer('causal_mask', mask)

    def forward(self, x):
        batch_size, seq_len = x.size()

        # 位置インデックス
        positions = torch.arange(seq_len, device=x.device).unsqueeze(0)

        # 埋め込み
        x = self.token_emb(x) + self.pos_emb(positions)
        x = self.dropout(x)

        # 因果マスク
        mask = self.causal_mask[:seq_len, :seq_len]

        # Transformerブロック
        for block in self.blocks:
            x = block(x, mask)

        # 出力
        x = self.ln_f(x)
        logits = self.head(x)

        return logits


# 動作確認
vocab_size = 50000
model = GPT(vocab_size=vocab_size, d_model=768, n_heads=12, d_ff=3072, n_layers=12)
x = torch.randint(0, vocab_size, (2, 128))
logits = model(x)
print(f"入力形状: {x.shape}")
print(f"出力形状: {logits.shape}")
print(f"パラメータ数: {sum(p.numel() for p in model.parameters()):,}")

まとめ

本記事では、GPTアーキテクチャについて解説しました。

  • Decoder-only構造: TransformerのDecoder部分のみを使用し、因果マスクで未来の情報を遮断
  • 因果的言語モデリング: 次のトークンを予測する単純な目標で事前学習
  • スケーリング: GPT-1からGPT-3へとパラメータ数を増やすことで、In-Context Learning能力が創発
  • Pre-LN: GPT-2以降はLayerNormをブロックの先頭に配置するPre-LN構成を採用
  • 位置埋め込み: 学習可能な位置埋め込みを使用

次のステップとして、以下の記事も参考にしてください。