打开导航 打开导航 Open menu
量化交易系统开发(C++)/ 期权策略与风控 / 行业研究分析
量化交易系统開發(C++)/ 期权策略与风控 / 行业研究分析
C++ trading systems / options strategy & risk controls / equity research
adrian@adrianxv.cn

参考 nanoGPT 学习 GPT 的基本原理 参考 nanoGPT 學習 GPT 的基本原理 参考 nanoGPT 学习 GPT 的基本原理

这篇文章整理自自然语言处理课程实验报告,主要结合 GPT 原始论文和 src/gpt/gpt_reference.py 的实现,理解 GPT 的模型结构、关键模块与生成流程。这里的实现可以看作一个单文件版本的 decoder-only Transformer:模型接收前文 token,并预测下一个 token。

一、实验目的

  1. 理解 GPT 模型的基本概念、发展背景及其在自然语言生成任务中的应用意义。
  2. 掌握自回归语言模型的基本原理,理解基于上下文预测下一个 token 的建模思想。
  3. 掌握 Transformer 解码器结构的核心组成,包括词嵌入、位置编码、多头自注意力机制、前馈神经网络、残差连接和层归一化。
  4. 理解因果注意力掩码在 GPT 模型中的作用,掌握它防止模型利用未来信息的基本原理。
  5. 能够阅读 nanoGPT 的核心代码结构,理解模型定义中各模块的功能及其相互关系。
  6. 能够将源码实现与 GPT 原始论文中的描述对应起来,形成对 GPT 基本原理的系统性理解。

二、实验内容

  1. 了解 nanoGPT 项目的基本结构和主要功能。
  2. 学习 GPT 自回归语言模型和 Transformer 解码器的基本原理。
  3. 阅读 GPT 参考源码,理解词嵌入、位置编码、注意力、前馈网络、残差连接和层归一化等模块。
  4. 结合 GPT 原始论文,对源码中的关键实现进行对照分析。
  5. 整理源码阅读笔记和理论总结,不要求实际训练或运行模型。

三、实验要求

  1. 能够理解 GPT 模型的主要结构和基本工作流程。
  2. 能够阅读 nanoGPT 的核心代码并说明其主要作用。
  3. 能够将源码实现与 GPT 原始论文中的概念进行对应说明。
  4. 能够记录源码阅读过程、关键理解点和个人总结。

四、实验步骤

  1. 下载并查看 nanoGPT 项目,熟悉主要文件结构。
  2. 阅读 GPT 原始论文,梳理模型提出的背景、目标和核心方法。
  3. 阅读数据处理、模型定义和推理相关代码,重点分析 GPT 的关键组成部分。
  4. 将源码中的实现与论文中的概念逐项对应,整理阅读笔记。
  5. 总结 GPT 的基本原理、结构特点和个人理解。

五、代码实现及配置

本次实验主要结合 GPT 原始论文与 src/gpt/gpt_reference.py 的实现,梳理 GPT 的模型结构、关键模块和生成流程。学习过程中还参考了 The Illustrated GPT Transformer

5.1 模型整体结构

GPT 的网络结构包含词嵌入层、位置嵌入层、多层 Transformer Block 和输出层,其中多层 Transformer Block 是最核心的部分。每一个 Block 本质上就是 Transformer Decoder,从前到后分别是多头注意力层、层归一化、MLP 层和层归一化;与 Encoder 相连的交叉注意力层在 GPT 中被省略。

GPT 的网络结构

上图从输入开始,经过 Embedding 和 Positional Encoding,依次通过多层 Decoder Layer,最后由 Linear 与 Softmax 得到输出概率。

GPT 在完整 Transformer 中的位置

第二张图把 GPT 标在完整 Transformer 结构的右侧:它只保留了解码器路径,并使用 masked multi-head attention 保证自回归预测。

5.2 因果自注意力

GPT 的关键在于因果自注意力。在 CausalSelfAttention 中,输入首先通过一个线性层一次性生成 qkv,然后拆分成多个头并行计算注意力权重。由于 GPT 是自回归语言模型,任意位置只能利用当前位置左侧的历史信息,因此注意力矩阵必须施加因果掩码。

多头注意力

多头注意力把同一个输入分别映射为多个 QKV 子空间,在每个头中计算 scaled dot-product attention,再把各头结果拼接并线性变换。

因果掩码确保 GPT 只用上文进行预测

因果掩码把允许关注的位置保留为 0,把未来位置设为 -inf;经过 softmax 后,未来位置的权重就变成了 0。报告中的记号写作“让 Q >= K 的 Attention 不变,把 Q <= K 的 Attention 置为负无穷”,核心含义是每个 token 只能看到自己和之前的 token。

这也解释了 GPT 与 BERT 的侧重点差异:GPT 不能利用下文信息,在需要理解全文的任务中可能错失一部分信息;BERT 因此更适合理解型任务,而 GPT 在生成型任务上表现更好。即使在大模型时代,一些倚重文本理解的任务仍可以优先考虑 BERT 一类的双向编码器。

nanoGPT 中因果注意力的核心实现如下:

class CausalSelfAttention(nn.Module):
    def forward(self, x):
        B, T, C = x.size()
        q, k, v = self.c_attn(x).split(self.n_embd, dim=2)
        k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
        q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
        v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)

        if self.flash:
            y = torch.nn.functional.scaled_dot_product_attention(
                q, k, v, attn_mask=None,
                dropout_p=self.dropout if self.training else 0,
                is_causal=True
            )
        else:
            att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
            att = att.masked_fill(self.bias[:, :, :T, :T] == 0, float('-inf'))
            att = F.softmax(att, dim=-1)
            att = self.attn_dropout(att)
            y = att @ v
        y = y.transpose(1, 2).contiguous().view(B, T, C)
        y = self.resid_dropout(self.c_proj(y))
        return y

5.3 Transformer Block 当中的其他部分

除了多头注意力层之外,每个 Transformer Block 还有两个层归一化层和一个 MLP 层。层归一化把隐藏状态调整到更稳定的分布,帮助训练过程保持数值稳定。经过 Attention 后,结果会通过残差连接加回隐藏状态;因此进入 MLP 时,表示已经包含了上下文信息。

MLP 的作用是在已有上下文中进一步学习特征,对每个 token 的隐藏向量执行“升维 -> GELU -> 降维”,通过非线性变换提取更复杂的特征:

class MLP(nn.Module):
    def forward(self, x):
        x = self.c_fc(x)
        x = self.gelu(x)
        x = self.c_proj(x)
        x = self.dropout(x)
        return x

5.4 前向传播与语言建模目标

GPT.forward() 是整套模型的主流程。输入 idx 先经过 token embedding 和 position embedding 相加,再送入多层 Block,最后通过 ln_flm_head 得到词表上的 logits。训练时如果传入 targets,使用交叉熵损失;推理时只取最后一个位置的 logits,用于下一 token 预测。

def forward(self, idx, targets=None):
    device = idx.device
    b, t = idx.size()
    pos = torch.arange(0, t, dtype=torch.long, device=device)

    tok_emb = self.transformer.wte(idx)
    pos_emb = self.transformer.wpe(pos)
    x = self.transformer.drop(tok_emb + pos_emb)
    for block in self.transformer.h:
        x = block(x)
    x = self.transformer.ln_f(x)

    if targets is not None:
        logits = self.lm_head(x)
        loss = F.cross_entropy(
            logits.view(-1, logits.size(-1)),
            targets.view(-1),
            ignore_index=-1
        )
    else:
        logits = self.lm_head(x[:, [-1], :])
        loss = None
    return logits, loss

5.5 自回归生成

generate() 实现了 GPT 的逐 token 生成过程。每一步都会把当前上下文截断到 block_size 范围内,前向得到最后位置的分布后,再通过温度缩放和可选的 top-k 采样选出下一个 token,最后把新 token 拼接回输入序列继续生成。

@torch.no_grad()
def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None):
    for _ in range(max_new_tokens):
        idx_cond = idx if idx.size(1) <= self.config.block_size else idx[:, -self.config.block_size:]
        logits, _ = self(idx_cond)
        logits = logits[:, -1, :] / temperature
        if top_k is not None:
            v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
            logits[logits < v[:, [-1]]] = -float('Inf')
        probs = F.softmax(logits, dim=-1)
        idx_next = torch.multinomial(probs, num_samples=1)
        idx = torch.cat((idx, idx_next), dim=1)
    return idx

总结

从 nanoGPT 的实现回看 GPT,可以把它概括为一条清晰的链路:token embedding 与 position embedding 提供输入表示,多层 decoder-only Transformer Block 逐层融合上下文,因果掩码保证训练目标符合自回归约束,最后由语言模型头输出下一个 token 的概率分布。阅读源码时,把每个类和函数与论文中的概念逐项对应,比单独记忆公式更容易形成对 GPT 工作流程的整体理解。