在前面我們學過 Encoder-only(BERT),他專注於「理解輸入」,適合各種自然語言理解(NLU)的任務,但是如果任務是「生成文字」的話,例如像是:自動補全、文字機器翻譯、對話生成等自然語言生成(NLG)任務,就需要一個能一步一步「產生詞」的模型,這時候就會需要 Decoder-only 模型。
2018 年 OpenAI 提出了 GPT (Generative Pre-trained Transformer),採用了只有 Decoder 的結構,透過 「自動回歸模型(Autoregressive LM)」 來學習,GPT 與 BERT 的最大差異就是,GPT 是「單向」的,只看「前文」來預測「下一個詞」。
這裡自動回歸的意思就是,會依賴於前面所有已知的 token,來生成下一個 token。
GPT 在 Decoder 上還有用到前面幾天有提到的 Masked Self-Attention,為了避免模型「偷看未來」,加入了 Mask 機制,只允許當前詞關注到前面出現過的詞,這就是 GPT 和 BERT 的差別。
BERT:雙向(可以看左邊 + 右邊)
GPT:單向(只能看左邊)
import torch
import torch.nn as nn
import torch.nn.functional as F
# ---- GPT Block ----
class GPTBlock(nn.Module):
def __init__(self, dim, num_heads, ff_dim=2048, dropout=0.1):
super().__init__()
self.attn = nn.MultiheadAttention(dim, num_heads, batch_first=True)
self.norm1 = nn.LayerNorm(dim)
self.ffn = nn.Sequential(
nn.Linear(dim, ff_dim),
nn.ReLU(),
nn.Linear(ff_dim, dim)
)
self.norm2 = nn.LayerNorm(dim)
self.dropout = nn.Dropout(dropout)
def forward(self, x, attn_mask=None):
# Masked Self-Attention
attn_out, _ = self.attn(x, x, x, attn_mask=attn_mask)
x = self.norm1(x + self.dropout(attn_out))
# Feed Forward
ffn_out = self.ffn(x)
x = self.norm2(x + self.dropout(ffn_out))
return x
# ---- GPT 模型 ----
class GPT(nn.Module):
def __init__(self, vocab_size, dim, num_heads, num_layers, max_len=128):
super().__init__()
self.embedding = nn.Embedding(vocab_size, dim)
self.pos_embedding = nn.Embedding(max_len, dim) # 位置編碼
self.blocks = nn.ModuleList([GPTBlock(dim, num_heads) for _ in range(num_layers)])
self.norm = nn.LayerNorm(dim)
self.fc_out = nn.Linear(dim, vocab_size)
self.max_len = max_len
def forward(self, x):
b, seq_len = x.shape
pos = torch.arange(0, seq_len, device=x.device).unsqueeze(0)
x = self.embedding(x) + self.pos_embedding(pos) # 加位置編碼
# 建立 causal mask (下三角矩陣)
attn_mask = torch.triu(torch.ones(seq_len, seq_len, device=x.device), diagonal=1)
attn_mask = attn_mask.masked_fill(attn_mask == 1, float("-inf"))
for block in self.blocks:
x = block(x, attn_mask=attn_mask)
x = self.norm(x)
return self.fc_out(x) # (batch, seq_len, vocab_size)
@torch.no_grad()
def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None):
for _ in range(max_new_tokens):
logits = self.forward(idx) # (batch, seq_len, vocab_size)
logits = logits[:, -1, :] / temperature
if top_k is not None:
v, _ = torch.topk(logits, top_k)
logits[logits < v[:, [-1]]] = -float("inf")
probs = F.softmax(logits, dim=-1)
next_token = torch.multinomial(probs, num_samples=1)
idx = torch.cat([idx, next_token], dim=1)
return idx
在 GPT 開啟了 Decoder-only 模型的潮流後,許多大型語言模型也都採用相同的架構,並在此基礎上做改進:
GPT-2 (2019)
參數比 GPT 大了非常多(從 1.5 億到 15 億參數),顯示了「規模化」帶來的能力提升,並首次讓生成文字看起來自然流暢,接近人類的口吻。
GPT-3 (2020)
參數再飆升到 1750 億,展示了 Few-shot learning 的能力,只要給幾個範例,就能完成下游任務,幾乎不需要微調。
GPT-3.5 / ChatGPT (2022)
加入了指令微調(Instruction tuning)與 RLHF(Reinforcement Learning from Human Feedback),讓模型更符合人類期望的回答,開始進入「對話式 AI」的時代。
GPT-4 (2023)
開始支援多模態(文字 + 圖像),展現了 LLM 的跨模態潛力。
LLaMA 系列 (2023~)
由 Meta 提出,強調小模型也能表現很好,是近年社群研究與開源社群的主力。