Skip to main content

Transformer

  • GPT Model
    • 组成
      • Token Embedding(Vocab Size, Embedding Dimension)
      • Positional Embedding(Context Length, Embedding Dimension)
      • N × Transformer Block
        • Transformer Block
      • Layer Norm(Embedding Dimension)
        • Normalization Activation
      • OutputHead = Linear(Embedding Dimension, Vocab Size)
    • 计算流程
      • Tokenization - Tokenizer(input) -> Token IDs
        • Text -> Token IDs
        • BPE
        • CPU Bound
      • Embeding Lookup -> Input Embeddings
        • (vocab_size, hidden_dim)
        • Embedding Matrix
      • Positional Embedding(Sequence Length) -> Positional Embeddings
      • Input Embeddings = Token Embeddings + Positional Embeddings
      • Input Embeddings = Dropout(Input Embeddings)
      • Outputs = N × Transformer Block(Input Embeddings)
      • Outputs = Layer Norm(Outputs)
      • Logits = OutputHead(Outputs)
    • 采样 Logits 得到下一个 Token,将其添加到输入序列中,重复上述过程直到生成结束
  • Transformer Block = Attention + Feed Forward Network + Layer Norm + Residual
    • 模型层数为 Block 数量
  • Tokenization - Embedding - Positional encoding - Transformer block - Attention
  • Context QKV
    • Attention Pattern
  • Self Attention
  • Cross Attention
    • KV 不同 dataset
  • Multi-head Attention
  • Single Head Attention
  • Sparse Attention Mechanism
  • Blockwise Attention
  • Linformer
  • Reformer
  • Ring Attention
  • GQA - Grouped Query Attention
  • SwiGUL
  • RMSNorm
  • YARN(Yet Another RoPE extensioN)
  • DCA - Dual Chunk Attention - 双块注意力
  • 参考
tip
  • parameters 表示可训练的参数数量

abbr.stand formeaningnotes
BOSBeginning Of Sequence
EOSEnd Of Sequence<|endoftext|>
PADPadding填充

attn_scores = torch.empty(6, 6)

# 矩阵乘法
# for i, x_i in enumerate(inputs):
# for j, x_j in enumerate(inputs):
# attn_scores[i, j] = torch.dot(x_i, x_j)

# 等同于
attn_scores = inputs @ inputs.T

attn_weights = torch.softmax(attn_scores, dim=-1)
attn_context_vecs = attn_weights @ inputs

伪代码

  • Prefill 阶段
    • 计算密集
  • Predict next token 阶段
    • 内存密集
# KV 缓存, 模型的每一层都有自己的 K 缓存和 V 缓存
# 它的形状大致是: [层数, 2 (K/V), 批次大小, 头数, 序列长度, 每个头的维度]
KV_CACHE = initialize_empty_cache()
LLM_MODEL = load_model()

# 阶段一:Prefill
def prefill(prompt_tokens):
# 输入: 一个 token 序列, 例如 [T1, T2, ..., TN],长度为 N

# 1. 并行计算:一次性通过模型处理所有 N 个 token。
# 在这个过程中,对于每一层,每个 token Ti 都会与它自己和它之前的所有 token (T1...Ti)
# 进行自注意力计算,并生成自己的 K 和 V 向量。
# all_hidden_states 的形状是 [N, hidden_dim]
all_hidden_states, all_k_vectors, all_v_vectors = LLM_MODEL.forward(prompt_tokens)

# 2. 填充 KV 缓存 (关键步骤)
# 将一次性计算出的、属于这 N 个 token 的所有 K, V 向量,整体存入全局缓存
KV_CACHE.fill_with(all_k_vectors, all_v_vectors)

# 3. 预测第一个新词
# 只用最后一个 token (TN) 的最终输出状态来预测下一个词
last_token_state = all_hidden_states.last()
next_token_logits = calculate_logits(last_token_state)
first_new_token = sample(next_token_logits) # 例如生成了 T(N+1)

return first_new_token

# 阶段二:解码 (循环调用,直到生成结束)
def decode_one_token(new_token):
# 输入: 上一步刚生成的单个 token, 例如 T(N+1)

# 1. 增量计算:只通过模型处理这一个新 token。
# 关键点:在 forward pass 内部,模型会接收完整的 KV_CACHE 作为历史信息。
# hidden_state 的形状是 [1, hidden_dim]
hidden_state, new_k, new_v = LLM_MODEL.forward(new_token, past_kv_cache=KV_CACHE)

# --- 在 forward pass 内部的自注意力计算细节 ---
# a. 为 new_token (T(N+1)) 计算出它的 Query 向量: Q(N+1)
# b. 从 KV_CACHE 中快速读取出之前所有 token 的 Key 向量: [K1, K2, ..., KN]
# c. 计算 Attention Scores:
# 对于 T(N+1) 来说,它需要和包括它自己在内的所有历史 token 计算相关性。
# score_i = dot_product(Q(N+1), Ki) (其中 i 从 1 到 N+1)
# 这里的 K1..KN 直接从缓存读取,极大节省了计算!
# d. 计算 Attention Weights (Softmax), 然后根据权重对 V1..V(N+1) 进行加权求和...
# --------------------------------------------------

# 2. 更新 KV 缓存
# 只把这个新 token 的 K, V 向量追加到缓存的末尾
KV_CACHE.append(new_k, new_v)

# 3. 预测下一个词
next_token_logits = calculate_logits(hidden_state)
next_token = sample(next_token_logits) # 例如生成了 T(N+2)

return next_token

# --- 整体流程 ---
prompt = "在一个漆黑的暴风雨之夜"
prompt_tokens = tokenize(prompt)

# 执行 Prefill,生成第一个词
next_token = prefill(prompt_tokens)
generated_sequence = [next_token]

# 循环执行 Decode,生成后续的词
while next_token is not "[END_OF_SEQUENCE]":
next_token = decode_one_token(next_token)
generated_sequence.append(next_token)

  1. Input Text
  2. Tokenize Text
  3. Token IDs
  4. Input Embeddings = Token Embeddings + Positional Embeddings
  5. Transformer Blocks
    • Multi-Head Self-Attention
    • Feed-Forward Network
  6. Post-processing
    • Layer Normalization
    • Linear Layer
    • Softmax
  7. Output Probabilities
  8. Sample Next Token
  9. Repeat from Step 3 until EOS or max length
  10. Output Text

架构参数

  • parameters
    • 模型参数数量
  • data type / quantization
    • 数据类型 / 量化
  • attention
    • 注意力类型
    • GQA
  • vocab size / vocabulary size / 词汇表大小
    • 过小的词汇表会再多语言场景导致性能问题
  • context length
    • 上下文长度
  • embedding dimension / d_model / residual stream dimension / Hidden Size
    • 嵌入维度、模型维度
    • 一个 token 的表示向量维度
  • Number of QKV Heads
    • 注意力头数
    • GQA 例如 80/8/8
  • Head Size - 每个注意力头的维度
  • layers / hidden layers
    • Transformer 层数 / transformer blocks
  • dropout rate
    • Dropout 率
    • 避免 overfitting
  • learning rate
    • 学习率
  • qkv bias
    • QKV 偏置
    • 是否在计算 Q、K、V 的线性层中包含偏置向量
  • dense or MoE
    • dense 全激活
    • MoE 部分激活 - 可能质量会差,但计算效率更高
  • activation function - 激活函数
    • MoE 时候的 门控机制
    • SwiGLU
  • Active Experts / 活跃专家数
  • Expert Size / 专家大小
  • RoPE Base Frequency
    • RoPE 基础频率
    • 影响模型对长序列的处理能力
    • 例如 1e7
  • 语言数量
  • 语料大小

Papers

Attention Is All You Need


  1. 生成 Q, K, V
  • 对于输入序列中的每一个词嵌入向量,都通过乘以 WQW_Q, WKW_K, WVW_V 矩阵生成对应的 Q, K, V 向量。
  1. 计算注意力分数 (Score)
  • 当前处理的词的 Q 向量与所有词的 K 向量进行点积,得到注意力分数。
Score(Q,K)=QKTScore(Q, K) = Q \cdot K^T
  1. 归一化 (Normalization)
  • 将分数转换成一个总和为 1 的概率分布
  • 得到 注意力权重
  • 使用缩放点积注意力的归一化公式:
Attention(Q,K,V)  =  softmax ⁣(QKdk)V\text{Attention}(Q, K, V) \;=\; \mathrm{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right) V
  • 说明:
    • 除以 dk\sqrt{d_k} 可以抑制内积值在维度较大时变得过大,避免 softmax 进入极端区间导致梯度消失。
      • dkd_k 是键 kk 向量的维度
    • scale dot attention
    • softmax 在最后一维上归一化,得到对每个 query 的注意力权重分布。
    • QKV 是从 信息学 借用的词语
      • query
        • 模型当前关注点
        • 模型尝试理解的对象
        • 用于探查输入序列中的其他部分,以确定应当给予它们多少注意力。
      • key
        • 每个输入都有一个关联的 key
        • 用于匹配 query
      • value
        • 表示输入项的实际内容或表征。
        • 当模型确定了哪些 key(也即输入中最与当前 query(当前关注项)相关的部分)时,就会取出相应的 value。
  • Causal Attention - Masked Attention - Decoder Masked Self-Attention
    • 在自回归模型中,当前词只能看到之前的词。
    • 在计算注意力分数时,确保每个词只能看到它之前的词(或当前词),以保持自回归特性。
    • 通过在计算注意力分数时使用掩码(mask)来实现。
    • Improving Language Understanding by Generative Pre-Training
  • Encoder-Decoder Cross-Attention
import math

scores = (Q @ K.transpose(-2, -1)) / math.sqrt(d_k) # [*, seq_q, seq_k]
weights = torch.softmax(scores, dim=-1) # 注意力权重
context = weights @ V # 加权求和得到上下文向量

#
keys = inputs @ W_key
values = inputs @ W_value
scores = query @ keys.T
d_k = keys.shape[-1]
# math.sqrt(d_k) = d_k ** 0.5
weights = torch.softmax(scores / d_k ** 2, dim=-1)
context = weights @ values


import torch.nn as nn

# Single-Head
class SelfAttention_v1(nn.Module):
def __init__(self, d_in, d_out):
super().__init__()
self.d_out = d_out
self.W_q = nn.Parameter(torch.randn(d_in, d_out))
self.W_k = nn.Parameter(torch.randn(d_in, d_out))
self.W_v = nn.Parameter(torch.randn(d_in, d_out))
def forward(self, x):
K = x @ self.W_k
Q = x @ self.W_q
V = x @ self.W_v
scores = (Q @ K.T)
weights = torch.softmax(scores/ math.sqrt(self.d_out), dim=-1)
context = weights @ V
return context

class SelfAttention_v2(nn.Module):
def __init__(self, d_in, d_out,qkv_bias=False):
super().__init__()
self.d_out = d_out
self.W_q = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_k = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_v = nn.Linear(d_in, d_out, bias=qkv_bias)
def forward(self, x):
K = self.W_k(x)
Q = self.W_q(x)
V = self.W_v(x)
scores = Q @ K.T
weights = torch.softmax(scores/ K.shape[-1] ** 0.5, dim=-1)
context = weights @ V
return context

class CausalAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, qkv_bias=False):
super().__init__()
self.d_out = d_out
self.W_q = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_k = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_v = nn.Linear(d_in, d_out, bias=qkv_bias)
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(cotext_length, cotext_length), diagonal=1))

def forward(self, x):
b, num_tokens, d_in = x.shape
K = self.W_k(x)
Q = self.W_q(x)
V = self.W_v(x)
scores = Q @ K.transpose(1, 2)

# 使用遮罩防止看到未来位置(因果/自回归)
scores.masked_fill_(self.mask.bool()[:num_tokens, :num_tokens] == 0, torch.float('-inf'))
weights = torch.softmax(scores / K.shape[-1] ** 0.5, dim=-1)
weights = self.dropout(weights)

context = weights @ V
return context

# Multi-Head
class MultiHeadAttention_stack(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
self.heads = nn.ModuleList([CausalAttention(d_in, d_out, context_length, dropout, qkv_bias) for _ in range(num_heads)])

def forward(self, x):
head_outputs = [head(x) for head in self.heads]
context = torch.cat(head_outputs, dim=-1)
return context

# 单一矩阵计算 Multi-Head
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert d_out % num_heads == 0

self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads
self.W_q = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_k = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_v = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out)
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(cotext_length, cotext_length), diagonal=1))

def forward(self, x):
b, num_tokens, d_in = x.shape
K = self.W_k(x)
Q = self.W_q(x)
V = self.W_v(x)

K = K.view(b, num_tokens, self.num_heads, self.head_dim).transpose(1, 2) # [b, num_heads, num_tokens, head_dim]
Q = Q.view(b, num_tokens, self.num_heads, self.head_dim).transpose(1, 2) # [b, num_heads, num_tokens, head_dim]
V = V.view(b, num_tokens, self.num_heads, self.head_dim).transpose(1, 2) # [b, num_heads, num_tokens, head_dim]

scores = Q @ K.transpose(-2, -1) # [b, num_heads, num_tokens, num_tokens]

scores.masked_fill_(self.mask.bool()[:num_tokens, :num_tokens] == 0, -torch.inf)
weights = torch.softmax(scores / K.shape[-1] ** 0.5, dim=-1)
weights = self.dropout(weights)

context = (weights @ V).transpose(1, 2) # [b, num_tokens, num_heads, head_dim]
context = context.contiguous().view(b, num_tokens, self.d_out)
context = self.out_proj(context) # optional projection
return context
  1. 加权求和 (Weighted Sum)
ContextVector=AttentionWeightsVContext Vector = Attention Weights \cdot V

softmax

给定一个实数向量 x=[x1,x2,...,xn]x = [x_1, x_2, ..., x_n],softmax 函数将其转换为另一个向量 y=[y1,y2,...,yn]y = [y_1, y_2, ..., y_n],其中每个 yiy_i 的计算公式为:

yi=exij=1nexj y*i = \frac{e^{x_i}}{\sum*{j=1}^{n} e^{x_j}}
  • 用途
    • 将任意实数向量转换为概率分布
    • 机器学习中常用于分类任务的输出层
import torch

# 代码表示
def softmax_naive(x):
return torch.exp(x) / torch.exp(x).sum(dim=0)
weights = torch.softmax(scores, dim=-1)
print("Sum of weights:", weights.sum()) # =1

采样

  1. 贪婪搜索 (Greedy Search)
  • 方法:总是选择概率最高的那一个。
  • 效果:确定性、无创造力、极其容易重复。基本不用于创意生成
  1. Top-K 采样
  • 方法:在概率最高的 K 个选项中进行随机采样。
  • 效果:排除了低概率的怪词,但候选池大小固定,不够智能。
  1. Top-P (Nucleus) 采样
  • 方法:在累积概率超过 P 的最小核心词汇集合中进行随机采样。
  • 效果目前最主流、最平衡的方法。候选池大小自适应,既能保证连贯性又能提供多样性。
  1. 温度采样 (Temperature)
  • 方法:它不是一个独立的采样方法,而是一个调节器。通过调整温度值,来改变原始概率分布的“形状”,从而控制 Top-K 或 Top-P 采样的随机性程度。

  1. repetition_penalty - 重复惩罚 - 乘法/除法
    • 降低那些在上下文中已经出现过的词再次出现的概率
    • 用一个大于1的数除以重复词的 logit 分数
    • 一个参数控制所有重复
  2. frequency_penalty / presence_penalty - 加法/减法
    • 从重复词的 logit 分数中减去一个惩罚值
    • frequency_penalty - 与词出现的次数有关
    • presence_penalty - 只要出现过一次就惩罚

  • next token logits
    • 一维向量
    • 向量的长度等于模型词汇表的大小 (Vocabulary Size)
    • 向量每个元素的值:每个元素都是一个原始的、未经归一化的实数分数(logit),分别对应词汇表中的每一个token。分数越高,代表模型认为这个token是下一个词的可能性越大。
  • 常用 Top-P 采样 + Temperature 的组合,这能在“准确性”和“创造性”之间达到最佳的、可控的平衡。
  • 并行/推测解码 (Parallel / Speculative Decoding)
  • 集束搜索 (Beam Search)
    • 跟踪多个路径

BPE

  • BPE (Byte Pair Encoding)
    • 一种用于文本编码的算法
    • 通过迭代地合并最常见的字节对来构建词汇表
    • 生成的词汇表包含了常用词和子词单元,能够有效处理未登录词(OOV)
  • 主要用于自然语言处理任务中的文本预处理
  • 参考

GPT-2

一般研究学习会选择 GPT-2 作为入门模型,因为它是开源的,且具有较小的模型版本(如 124M、355M、774M、1558M),适合在个人电脑上进行实验。

caution
modelvocabcontext lengthembeddingsheadslayersdrop rate
GPT-2 117M50,257102476812120.1
GPT-2 345M102410242424
GPT-2 762M102412803636
GPT-2 1542M102416004848

GPT-3

GPT-3 是闭源的,GPT-2 是开源的,因为 GPT-3 和 GPT-2 的架构和原理基本相同,所以可以先学习 GPT-2 的原理和代码实现,然后再了解 GPT-3 的细节。

  • Language Models are Few-Shot Learners
    • 2020-05, OpenAI
    • 提出 GPT-3
    • 展示了巨大模型规模下惊人的 上下文学习(In-Context Learning)少样本(Few-Shot) 能力,用户无需微调,仅通过提供几个示例就能让模型完成任务。
  • GPT-3 175B
    • Q 12,288x128
    • K 12,288x128
    • V 12,288x128
    • 96 heads
    • 96 layers
    • 12 layers
    • 128 dim per head
    • 175B params
    • 12288 d_model
    • Batch size 3.2M tokens
    • 0.6 × 106 LR
    • 与 GPT-2 相同的模型和架构
    • FT, Few-Shot, Zero-Shot, One-Shot

Instruct Fine-Tuning

  • https://arxiv.org/abs/2203.02155
    • Training language models to follow instructions with human feedback
    • InstructGPT
  • SFT
  • Reward Modeling
  • RLHF
  • “文本补全” -> “智能助手”