用PyTorch从零实现GPT:核心组件与训练生成实战
各位读者朋友大家好。今天我们来聊一个既热门又硬核的话题用 PyTorch 从零构建一个 GPT 风格的大语言模型。很多朋友在学习大语言模型时要么直接调用现成的 API要么使用 HuggingFace 上的预训练模型进行微调很少有机会深入模型内部理解 Token 是怎么变成词向量的注意力机制到底在算什么以及“下一个词预测”这个看似简单的任务是如何造就 ChatGPT 这类应用的。本文会带着大家把 GPT 的核心组件逐一拆解并用 PyTorch 实现一个可以在 CPU 上跑起来的小型 GPT 模型。文章内容适用于有一定 Python 基础、想深入理解 Transformer 和大语言模型原理的开发者也适用于正在学习 PyTorch 并希望尝试 NLP 项目的同学。我们会从一个很小的模型开始例如参数量在 1000 万到 3000 万级别的小型 GPT然后在自定义的文本数据集上训练它生成文本。整个过程中你会理解nn.Embedding、nn.MultiheadAttention或手写注意力层、nn.LayerNorm、nn.Linear等 PyTorch 核心模块在大语言模型中的真实作用也会掌握训练过程中的数据加载、损失计算、采样生成等关键环节。整篇教程都围绕“如何用 PyTorch 亲自实现 GPT”展开文章里的所有代码你都可以复制到本地 Jupyter Notebook 或 Python 脚本中运行。1. 背景与核心概念1.1 什么是 GPTGPT 的全称是Generative Pre-trained Transformer即“生成式预训练 Transformer”。它是由 OpenAI 提出的一系列大语言模型架构核心思想可以概括为两点使用Transformer 解码器Decoder作为基础结构。通过自回归语言建模任务进行预训练也就是根据前面的 Token 预测下一个 Token。所谓“自回归”简单说就是模型在生成文本时每生成一个词都会把之前生成的词再次作为输入逐步生成后面的内容。例如模型已经生成了“今天天气”下一步会根据这些词预测“真不错”或“很糟糕”。这种“根据上文预测下文”的训练方式并不需要人工标注数据只需要大量纯文本语料即可。1.2 GPT 与 Transformer 的关系Transformer 是 2017 年论文《Attention Is All You Need》中提出的架构它本身包含编码器Encoder和解码器Decoder两部分。BERT 使用的是编码器部分适合理解类任务GPT 使用的是解码器部分适合生成类任务。GPT 使用的解码器和原始 Transformer 解码器略有不同。原始 Transformer 解码器中有“编码器-解码器注意力层”用于让解码器关注编码器的输出而 GPT 因为只做语言建模没有独立的编码器所以只需要掩码自注意力Masked Self-Attention和前馈神经网络Feed-Forward NetworkFFN这两大子层。1.3 大语言模型的本质从宏观角度看大语言模型Large Language ModelLLM做的事情非常单一输入一段文本 - 模型计算 - 输出下一个词的概率分布这里的“文本”和“词”在进入模型之前会被转换为数字也就是 Token ID。模型内部通过这些数字查找对应的向量表示然后经过多层 Transformer 块的计算最终在输出层得到一个词表大小的概率分布。我们根据概率分布采样就能得到一个“预测出的下一个词”。整个过程听起来并不复杂但真正让它产生智能效果的原因在于模型参数规模大可以记忆大量语言模式和世界知识。训练语料覆盖广泛从代码、书籍到网页内容都有。多层注意力机制可以捕捉长距离依赖关系。1.4 为什么用 PyTorch 从零实现很多人在学习大语言模型时习惯于直接使用 HuggingFace 的transformers库几行代码就能加载一个 GPT-2 模型。这种方式虽然快但不利于理解内部细节。用 PyTorch 从零实现 GPT至少有以下几个好处深入理解每个模块的作用而不是把整个模型当作黑盒。便于自定义模型结构比如改变层数、头数、隐藏维度等。为后续阅读源码、论文和进行模型优化打下坚实基础。训练和推理过程完全可控适合教学和实验。2. 环境准备与版本说明在开始写代码之前我们需要准备好 Python 和 PyTorch 环境。这里假设你已经安装了 Python 3.8 或更高版本并且能够正常安装 PyTorch。建议使用虚拟环境避免和系统全局环境冲突。2.1 创建虚拟环境推荐使用conda或venv创建独立环境。以 conda 为例conda create -n gpt-tutorial python3.10 conda activate gpt-tutorial如果你的机器上还没有安装 conda也可以使用 Python 自带的 venvpython -m venv gpt-tutorial source gpt-tutorial/bin/activate # Windows 下为 gpt-tutorial\Scripts\activate2.2 安装 PyTorchPyTorch 的安装命令会根据操作系统、CUDA 版本不同而变化。如果你有 NVIDIA GPU 并且希望用 GPU 训练可以到 PyTorch 官网选择对应的安装命令。这里给出 CPU 版本的通用安装方式pip install torch torchvision torchaudio如果你需要安装 GPU 版本例如 CUDA 11.8 或 12.1可以参考 PyTorch 官网的安装命令。例如pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118安装完成后可以运行下面的代码检查 PyTorch 是否正常工作import torch print(torch.__version__) print(torch.cuda.is_available())如果torch.cuda.is_available()返回True说明 GPU 可用返回False则使用 CPU 训练速度会慢一些但对于我们的小型模型来说完全足够。2.3 其他依赖本文示例还需要numpy和tqdm库。如果没有安装可以执行pip install numpy tqdmtqdm用于显示训练进度条numpy用于数据处理和随机采样。3. GPT 核心组件原理解析在写代码之前我们先梳理 GPT 模型的内部结构。一个完整的 GPT 模型由以下部分组成Token 嵌入层Token Embedding位置编码层Positional Encoding多层 Transformer 解码器块Decoder Block输出投影层Output Projection下面逐一介绍每个部分的作用和 PyTorch 实现思路。3.1 Token 嵌入层计算机无法直接处理文字我们需要把文本转换成数字。最简单的做法是建立一个词表Vocabulary把每个词或子词映射为一个整数 ID。例如你好 - [42, 17]这里的 42 和 17 就是 Token ID。Token 嵌入层的作用是把每个 Token ID 映射为一个稠密向量。在 PyTorch 中我们可以直接用nn.Embedding实现import torch.nn as nn vocab_size 10000 # 词表大小 embed_dim 512 # 嵌入维度 token_embedding nn.Embedding(vocab_size, embed_dim)输入形状为(batch_size, seq_len)的 Token ID 张量输出形状为(batch_size, seq_len, embed_dim)的向量序列。3.2 位置编码层Transformer 结构本身不具备处理序列顺序的能力因为注意力机制对输入位置不敏感。即使你把一句话中的词序打乱注意力计算的结果依然相同。为了让模型感知词的先后顺序我们需要在输入中加入位置信息。原始 Transformer 论文中使用的是正弦余弦位置编码而 GPT 模型通常使用可学习的位置编码Learned Positional Encoding。也就是说我们初始化一个形状为(max_seq_len, embed_dim)的矩阵让模型在训练过程中自己学习每个位置应该对应的向量。在 PyTorch 中实现如下class PositionalEncoding(nn.Module): def __init__(self, max_seq_len, embed_dim): super().__init__() self.pe nn.Embedding(max_seq_len, embed_dim) def forward(self, x): seq_len x.size(1) positions torch.arange(seq_len, devicex.device).unsqueeze(0) return self.pe(positions)最终输入向量等于 Token 嵌入加上位置编码x token_embedding(token_ids) positional_embedding(x)3.3 掩码自注意力机制自注意力机制是 GPT 的核心。它的作用是让序列中的每个 Token 都能“关注”序列中的其他 Token从而捕捉上下文信息。对于 GPT 这种自回归模型我们在计算注意力时不能让当前位置看到未来的 Token否则就相当于在考试时提前看到了答案。为此我们需要使用因果掩码Causal Mask也称为上三角掩码。自注意力的计算过程可以拆解为四步输入向量x分别经过三个线性变换得到Q、K、V。计算Q和K的点积得到注意力分数。对注意力分数应用掩码将未来位置的分数设置为负无穷大。经过 Softmax 归一化后与V加权求和。用公式表示就是Attention(Q, K, V) softmax(QK^T / sqrt(d_k) Mask) V在 PyTorch 中我们可以利用nn.Linear实现 Q、K、V 的投影也可以使用nn.MultiheadAttention封装好的模块。为了便于理解这里给出一个手动实现单头注意力层的示例import torch import torch.nn as nn import torch.nn.functional as F class CausalSelfAttention(nn.Module): def __init__(self, embed_dim, num_heads): super().__init__() self.num_heads num_heads self.head_dim embed_dim // num_heads self.qkv nn.Linear(embed_dim, 3 * embed_dim) self.proj nn.Linear(embed_dim, embed_dim) def forward(self, x): batch_size, seq_len, embed_dim x.shape qkv self.qkv(x) # (batch, seq, 3 * embed_dim) qkv qkv.reshape(batch_size, seq_len, 3, self.num_heads, self.head_dim) qkv qkv.permute(2, 0, 3, 1, 4) q, k, v qkv[0], qkv[1], qkv[2] attn_score q k.transpose(-2, -1) / (self.head_dim ** 0.5) mask torch.triu(torch.ones(seq_len, seq_len, devicex.device), diagonal1).bool() attn_score attn_score.masked_fill(mask, float(-inf)) attn_weight F.softmax(attn_score, dim-1) context attn_weight v context context.transpose(1, 2).reshape(batch_size, seq_len, embed_dim) out self.proj(context) return out这里有几个关键点需要说明head_dim是每个注意力头的维度等于embed_dim // num_heads。permute操作是为了把维度调整成(头数, 批次大小, 序列长度, 头维度)的形式方便批量计算。triu生成上三角矩阵diagonal1表示从主对角线上一行开始置为 True从而实现“当前位置只能看到当前位置及之前位置”。3.4 前馈神经网络每个 Transformer 解码器块中还包含一个前馈神经网络子层。它由两个线性层和一个激活函数组成通常使用 ReLU 或 GELU 激活函数。这个子层的作用是对注意力输出做非线性变换增强模型的表达能力。实现非常简单class FeedForward(nn.Module): def __init__(self, embed_dim, hidden_dim): super().__init__() self.fc1 nn.Linear(embed_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, embed_dim) def forward(self, x): return self.fc2(F.gelu(self.fc1(x)))hidden_dim通常设置为4 * embed_dim这也是 Transformer 论文中常用的设置。3.5 残差连接与层归一化为了让深层网络更容易训练GPT 中每个子层都使用了残差连接Residual Connection和层归一化Layer Normalization。残差连接的核心思想是output x sublayer(x)这里的sublayer可以是注意力层或前馈网络。残差连接可以有效缓解梯度消失问题让信息在网络中更顺畅地流动。层归一化的作用是对每个样本的特征维度做归一化使训练过程更稳定。在 PyTorch 中可以使用nn.LayerNormclass TransformerBlock(nn.Module): def __init__(self, embed_dim, num_heads, hidden_dim, dropout0.1): super().__init__() self.ln1 nn.LayerNorm(embed_dim) self.attn CausalSelfAttention(embed_dim, num_heads) self.ln2 nn.LayerNorm(embed_dim) self.ffn FeedForward(embed_dim, hidden_dim) self.dropout nn.Dropout(dropout) def forward(self, x): x x self.dropout(self.attn(self.ln1(x))) x x self.dropout(self.ffn(self.ln2(x))) return x这里采用了 GPT-2 中使用的Pre-LayerNorm结构即先做归一化再进入子层。相比 Post-LayerNorm这种结构在训练深层模型时更稳定。4. 完整实战案例用 PyTorch 构建小型 GPT到这里我们已经完成了 GPT 所有核心组件的介绍。接下来我们将这些组件组合起来构建一个完整的小型 GPT 模型并在一个简单的文本语料上进行训练和生成。4.1 创建项目结构首先创建一个项目目录并在其中新建文件gpt-tutorial/ ├── data.py # 数据加载与处理 ├── model.py # GPT 模型定义 ├── train.py # 训练脚本 └── generate.py # 文本生成脚本当然你也可以直接在一个 Jupyter Notebook 中编写代码。但拆分成多个文件更贴近实际项目开发习惯。4.2 准备数据为了让示例简单且可复现我们使用一个非常小的文本语料中文名言警句集合。在data.py中我们实现以下功能收集文本语料。建立字符级别的词表。将文本转换为 Token ID。代码如下# 文件路径data.py import torch class CharDataset: def __init__(self, texts): self.texts texts # 建立字符词表 chars sorted(set(.join(texts))) self.stoi {ch: i for i, ch in enumerate(chars)} self.itos {i: ch for i, ch in enumerate(chars)} self.vocab_size len(chars) self.data self.encode(.join(texts)) def encode(self, text): return [self.stoi[ch] for ch in text] def decode(self, token_ids): return .join([self.itos[i] for i in token_ids]) def get_batch(self, block_size, batch_size, device): # 随机采样起始位置 ix torch.randint(len(self.data) - block_size - 1, (batch_size,)) x torch.stack([torch.tensor(self.data[i:i block_size]) for i in ix]) y torch.stack([torch.tensor(self.data[i 1:i block_size 1]) for i in ix]) return x.to(device), y.to(device)这里的get_batch方法每次随机采样batch_size个长度为block_size的输入序列同时生成对应的目标序列每个位置的下一个 Token。这种数据采样方式非常高效也是 GPT 训练中常用的做法。4.3 编写 GPT 模型在model.py中我们定义完整的 GPT 模型。# 文件路径model.py import torch import torch.nn as nn import torch.nn.functional as F class CausalSelfAttention(nn.Module): def __init__(self, embed_dim, num_heads, dropout0.1): super().__init__() self.num_heads num_heads self.head_dim embed_dim // num_heads self.qkv nn.Linear(embed_dim, 3 * embed_dim) self.proj nn.Linear(embed_dim, embed_dim) self.attn_dropout nn.Dropout(dropout) self.resid_dropout nn.Dropout(dropout) def forward(self, x): batch_size, seq_len, embed_dim x.shape qkv self.qkv(x).reshape(batch_size, seq_len, 3, self.num_heads, self.head_dim) qkv qkv.permute(2, 0, 3, 1, 4) q, k, v qkv[0], qkv[1], qkv[2] attn_score q k.transpose(-2, -1) / (self.head_dim ** 0.5) mask torch.triu(torch.ones(seq_len, seq_len, devicex.device), diagonal1).bool() attn_score attn_score.masked_fill(mask, float(-inf)) attn_weight F.softmax(attn_score, dim-1) attn_weight self.attn_dropout(attn_weight) context attn_weight v context context.transpose(1, 2).reshape(batch_size, seq_len, embed_dim) out self.resid_dropout(self.proj(context)) return out class FeedForward(nn.Module): def __init__(self, embed_dim, hidden_dim, dropout0.1): super().__init__() self.fc1 nn.Linear(embed_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, embed_dim) self.dropout nn.Dropout(dropout) def forward(self, x): return self.dropout(self.fc2(F.gelu(self.fc1(x)))) class TransformerBlock(nn.Module): def __init__(self, embed_dim, num_heads, hidden_dim, dropout0.1): super().__init__() self.ln1 nn.LayerNorm(embed_dim) self.attn CausalSelfAttention(embed_dim, num_heads, dropout) self.ln2 nn.LayerNorm(embed_dim) self.ffn FeedForward(embed_dim, hidden_dim, dropout) def forward(self, x): x x self.attn(self.ln1(x)) x x self.ffn(self.ln2(x)) return x class GPT(nn.Module): def __init__(self, vocab_size, embed_dim, num_heads, num_layers, block_size, dropout0.1): super().__init__() self.token_embedding nn.Embedding(vocab_size, embed_dim) self.position_embedding nn.Embedding(block_size, embed_dim) self.blocks nn.Sequential(*[ TransformerBlock(embed_dim, num_heads, 4 * embed_dim, dropout) for _ in range(num_layers) ]) self.ln_f nn.LayerNorm(embed_dim) self.head nn.Linear(embed_dim, vocab_size) self.block_size block_size self.apply(self._init_weights) def _init_weights(self, module): if isinstance(module, nn.Linear): nn.init.normal_(module.weight, mean0.0, std0.02) if module.bias is not None: nn.init.zeros_(module.bias) elif isinstance(module, nn.Embedding): nn.init.normal_(module.weight, mean0.0, std0.02) def forward(self, idx): batch_size, seq_len idx.shape token_emb self.token_embedding(idx) pos_emb self.position_embedding(torch.arange(seq_len, deviceidx.device).unsqueeze(0)) x token_emb pos_emb x self.blocks(x) x self.ln_f(x) logits self.head(x) return logits def generate(self, idx, max_new_tokens): for _ in range(max_new_tokens): idx_cond idx[:, -self.block_size:] logits self(idx_cond) logits logits[:, -1, :] probs F.softmax(logits, dim-1) next_token torch.multinomial(probs, num_samples1) idx torch.cat([idx, next_token], dim1) return idx这个模型中几个值得注意的细节初始化权重时使用了均值 0、标准差 0.02 的正态分布这是 GPT 论文中常用的初始化方式。generate方法使用torch.multinomial进行采样而不是直接取概率最大的 Token。这样生成结果更具多样性。当生成长度超过模型最大长度时idx_cond idx[:, -self.block_size:]会只取最后block_size个 Token避免位置编码超出范围。4.4 编写训练脚本在train.py中我们加载数据、创建模型并进行训练。为了让大家在普通电脑上也能运行这里使用一个小模型配置。# 文件路径train.py import torch import torch.nn as nn from tqdm import tqdm from data import CharDataset from model import GPT # 超参数 batch_size 64 block_size 128 max_epochs 100 eval_interval 10 learning_rate 3e-4 embed_dim 256 num_heads 8 num_layers 4 dropout 0.1 device cuda if torch.cuda.is_available() else cpu texts [ 学而时习之不亦说乎, 温故而知新可以为师矣, 学而不思则罔思而不学则殆, 知之为知之不知为不知是知也, 三人行必有我师焉, 己所不欲勿施于人, 工欲善其事必先利其器, 人无远虑必有近忧, 千里之行始于足下, 天行健君子以自强不息, 不积跬步无以至千里, 锲而不舍金石可镂, ] dataset CharDataset(texts) model GPT( vocab_sizedataset.vocab_size, embed_dimembed_dim, num_headsnum_heads, num_layersnum_layers, block_sizeblock_size, dropoutdropout, ).to(device) optimizer torch.optim.AdamW(model.parameters(), lrlearning_rate) criterion nn.CrossEntropyLoss() model.train() for epoch in range(max_epochs): x, y dataset.get_batch(block_size, batch_size, device) logits model(x) # logits: (batch, seq, vocab), y: (batch, seq) loss criterion(logits.view(-1, dataset.vocab_size), y.view(-1)) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % eval_interval 0 or epoch max_epochs - 1: print(fepoch {epoch}/{max_epochs}, loss {loss.item():.4f}) torch.save(model.state_dict(), gpt_model.pt) print(模型已保存到 gpt_model.pt)训练过程中loss会逐渐下降。由于我们的数据集很小可能几十个 epoch 后损失就会降到较低水平。4.5 编写文本生成脚本训练完成后我们需要写一个脚本加载模型并生成文本。在generate.py中实现# 文件路径generate.py import torch from data import CharDataset from model import GPT device cuda if torch.cuda.is_available() else cpu # 和训练时保持一致的数据集 texts [ 学而时习之不亦说乎, 温故而知新可以为师矣, 学而不思则罔思而不学则殆, 知之为知之不知为不知是知也, 三人行必有我师焉, 己所不欲勿施于人, 工欲善其事必先利其器, 人无远虑必有近忧, 千里之行始于足下, 天行健君子以自强不息, 不积跬步无以至千里, 锲而不舍金石可镂, ] dataset CharDataset(texts) model GPT( vocab_sizedataset.vocab_size, embed_dim256, num_heads8, num_layers4, block_size128, dropout0.1, ).to(device) model.load_state_dict(torch.load(gpt_model.pt, map_locationdevice)) model.eval() # 初始提示词 start_text 学 start_tokens dataset.encode(start_text) x torch.tensor([start_tokens], dtypetorch.long, devicedevice) with torch.no_grad(): output_tokens model.generate(x, max_new_tokens20) generated_text dataset.decode(output_tokens[0].tolist()) print(生成文本, generated_text)运行脚本后你会看到类似下面的输出生成文本 学而时习之不亦说乎温故而新可以为师矣学而不思则罔虽然生成的内容存在重复但对于一个小型模型、小数据集来说它已经能够学到一些基本的中文句法和词汇搭配。4.6 结果说明从生成结果可以看出模型已经学会了一些规律能够产生训练语料中出现过的词和短语。能够根据“学”这个开头生成“学而时习之不亦说乎”这样的完整句子。能够将多个句子拼接起来形成较长文本。如果要改善生成质量可以考虑以下几种方式增加数据量和数据多样性。增大模型容量比如增加embed_dim和num_layers。增加训练轮数并调低学习率。使用更先进的采样策略如 Top-k 采样、Top-p 采样或温度采样。5. 常见问题与排查思路在实际运行代码的过程中可能会出现各种问题。下面整理了一些高频问题和对应的排查方法。问题现象常见原因解决思路训练时 loss 不下降学习率过大或过小数据量太小调整学习率使用 1e-4 到 1e-3 的范围增加训练数据生成文本全是重复字符模型欠拟合采样策略单一增大模型容量或训练轮数使用 Temperature 采样显存不足OOMbatch_size 或 block_size 过大减小 batch_size、block_size改用梯度累积位置编码越界报错生成文本长度超过 block_size在生成时截取最后 block_size 个 TokenPyTorch 版本不同导致 API 变化环境版本差异检查torch.__version__参考对应版本文档中文编码乱码控制台编码问题在 Windows 下设置PYTHONIOENCODINGutf-8或使用 UTF-8 文件保存当你遇到错误时建议按以下顺序排查查看报错堆栈确定错误发生在哪一行。检查输入张量的形状是否正确尤其是batch_size、seq_len、embed_dim。检查设备是否一致避免 CPU 张量和 GPU 张量混合运算。确认模型和优化器的zero_grad是否调用。打印中间嵌入和注意力分数的形状定位问题模块。6. 最佳实践与工程建议从“能跑”到“跑得好”中间需要很多工程上的思考。下面分享一些在构建、训练和部署 GPT 模型时的经验。6.1 数据质量决定模型上限无论模型结构多先进、参数量多大如果训练数据质量不高模型效果很难提升。在实战中我们应关注以下几点去除重复、低质量、格式混乱的文本。统一字符编码避免混入不可见字符。对中文语料做好清洗和分句。如果做字符级模型不需要分词如果做子词级模型需要引入 BPE 等分词器。6.2 超参数调整策略对于小型 GPT 模型超参数的选择可以参考以下经验embed_dim常用值有 128、256、512。num_heads通常设置为 8 或 12且embed_dim必须能被num_heads整除。num_layers可以在 2 到 12 之间调整。block_size表示最大上下文长度一般设置为 64、128、512。dropout通常设置为 0.1 或 0.2数据量小时可以适当调低。学习率使用 AdamW 优化器时通常在3e-4到1e-3之间。不要盲目追求大参数先在小的配置上把训练流程跑通再逐步放大。6.3 训练稳定性优化训练 GPT 时最怕遇到 loss 爆炸或 NaN 值。为了提升训练稳定性可以尝试使用梯度裁剪例如torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)。使用 LayerNorm 和残差连接。使用 Warmup 学习率调度器前若干步学习率从 0 线性升到目标值。定期保存 checkpoint方便恢复训练。梯度裁剪的示例代码torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)6.4 模型保存与加载在训练过程中建议每隔一段时间保存一次模型。可以使用 PyTorch 的state_dict方式保存torch.save({ model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), epoch: epoch, loss: loss.item(), }, checkpoint.pt)加载时checkpoint torch.load(checkpoint.pt) model.load_state_dict(checkpoint[model_state_dict]) optimizer.load_state_dict(checkpoint[optimizer_state_dict])这种方式比只保存模型权重更完整便于从断点继续训练。6.5 性能优化如果在 CPU 上训练可以启用 PyTorch 的优化后端torch.set_num_threads(4)如果使用 GPU可以将batch_size设置得更大一些并使用混合精度训练。PyTorch 2.0 之后提供了torch.compile方法可以显著提升训练速度model torch.compile(model)但需要注意的是torch.compile在不同硬件和 PyTorch 版本下的表现不同建议在较新版本中尝试。6.6 安全与合规提醒训练和部署大语言模型时需要注意合规与安全问题训练数据来源要合法合规避免使用含有个人隐私、敏感内容的语料。生成能力的边界要设置清楚对不适合生成的内容进行过滤。如果模型会对外提供服务建议加入内容审核环节。在涉及生产环境部署时先在小流量场景测试保证稳定性后再扩大范围。7. 总结与学习路线通过本文的讲解和代码实践我们亲手搭建了一个基于 PyTorch 的小型 GPT 模型。回顾一下关键内容理解了 GPT 作为生成式预训练 Transformer 的核心思想。掌握了 Token 嵌入、位置编码、掩码自注意力、前馈网络、层归一化和残差连接等模块的原理与实现。完成了一个中文文本数据集上的训练与生成闭环。掌握了常见的训练问题排查方法和工程优化手段。如果你希望继续深入大语言模型方向可以按照下面的路线逐步学习阅读原始论文Transformer 论文《Attention Is All You Need》和 GPT-1、GPT-2、GPT-3 论文。学习分词器了解 BPE、WordPiece 等子词分词算法尝试用tiktoken或 HuggingFacetokenizers处理中文语料。阅读开源实现阅读 NanoGPT、GPT-2 的官方实现源码对比不同模型的细节差异。尝试微调使用 HuggingFacetransformers加载预训练模型在小规模领域数据上进行微调。扩展模型能力尝试加入 LoRA、QLoRA 等参数高效微调方法用有限的 GPU 资源适配大模型。部署与服务化研究模型量化、推理加速、流式输出等内容将模型部署到实际业务中。从零构建 GPT 是一次非常值得的旅程。它不仅能帮你打好大语言模型的基础也能让你对 PyTorch 的模型编写、张量操作和训练流程有更深刻的理解。希望你在阅读完这篇文章后能亲自动手运行一遍代码甚至基于自己的中文语料训练一个专属的小型文本生成模型。如果这篇文章对你有所帮助可以收藏备用也欢迎在评论区交流你在运行过程中遇到的各种问题和思考。动手写代码比看多少教程都更有效。