ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零构建大语言模型:基于Transformer的字符级语言模型实践指南

从零构建大语言模型:基于Transformer的字符级语言模型实践指南 1. 从零开始为什么我们需要亲手构建一个大语言模型最近几年大语言模型LLM的风潮席卷了整个技术圈。从ChatGPT到Claude再到国内外的各种“通义”、“文心”这些模型展现出的对话、创作、推理能力常常让人惊叹。作为一个对技术底层有好奇心的开发者你可能和我一样不止一次地想过这些动辄千亿参数的“庞然大物”究竟是如何从无到有被构建出来的它的内部到底在发生什么我们是否有可能不依赖任何现成的框架或API从最基础的数学原理和代码开始亲手“捏”出一个哪怕是最简陋的语言模型这正是“从零构建大语言模型”这个项目的核心价值所在。它不是一个追求性能、追赶SOTAState-of-the-Art的工程而是一次深度的、回归本源的“技术考古”与“原理复现”。通过这个过程你将不再是一个只会调用model.generate()的API用户而会真正理解从一段文本数据到模型能够预测下一个词这中间每一步的数学转换和计算逻辑。你会明白什么是词嵌入Word Embedding为什么Transformer架构如此有效注意力机制Attention到底在“注意”什么以及模型是如何通过反向传播和梯度下降“学会”知识的。这个过程极具挑战但也充满乐趣。它要求你暂时放下对“大”的执着专注于“模型”本身。我们构建的将是一个参数可能只有几万到几十万的“微型”语言模型它可能连一句通顺的话都写不好但它的每一个组件——数据预处理、模型架构、训练循环、推理生成——都将由你亲手实现。当你看到这个小小的模型在经过训练后能够以高于随机猜测的概率预测出“I love”后面更可能是“you”而不是“banana”时那种对原理豁然开朗的成就感是任何调包都无法比拟的。这篇文章就是这份“从零开始”的实践指南。我将带你走过构建一个基于Transformer解码器的微型语言模型的全过程。我们会使用最基础的Python和PyTorch仅作为张量计算库从准备数据开始一步步实现Tokenizer、嵌入层、多头自注意力、前馈网络、位置编码最后组装成模型并完成训练和文本生成。过程中我会穿插大量“为什么这么做”的原理性解释以及我在复现过程中踩过的坑和调试心得。我们的目标不是造出ChatGPT而是亲手点亮一盏灯看清大语言模型这座宏伟殿堂最初的那块基石。2. 基石准备数据、词表与文本的数字化任何语言模型的起点都是数据。模型并不理解文字它只认识数字。因此我们的首要任务是将人类可读的文本转化为模型可处理的数字序列。这个过程包含三个核心步骤数据收集与清洗、构建词表Vocabulary、以及实现分词器Tokenizer。2.1 数据的选择与预处理对于“从零构建”这个目标我们不需要TB级别的大规模语料。一个小而精、干净且主题集中的文本集更为合适因为它能缩短训练时间让我们更快地看到迭代效果并进行调试。我选择了古登堡计划中《爱丽丝梦游仙境》的英文文本。它篇幅适中约3万个词语言规范且是公开可用的。拿到原始文本后直接扔给模型是不行的。我们需要进行清洗Cleaning。这一步的目标是减少噪声让模型专注于学习语言规律而不是无关的格式符号。import re def simple_clean_text(text): # 1. 统一转换为小写减少词表大小让模型不必分别学习“The”和“the” text text.lower() # 2. 保留基本标点如句号、逗号、问号但移除过多换行、多余空格等 # 我们可以选择将标点视为独立token这里先简单处理后续在分词时再细分 text re.sub(r\s, , text) # 将多个空白字符空格、换行、制表符替换为单个空格 text text.strip() return text # 读取文件并清洗 with open(alice_in_wonderland.txt, r, encodingutf-8) as f: raw_text f.read() cleaned_text simple_clean_text(raw_text) print(f原始文本长度: {len(raw_text)}) print(f清洗后文本长度: {len(cleaned_text)}) print(前500个字符:, cleaned_text[:500])注意这里的清洗策略极其简单。在真实的大规模训练中清洗流程要复杂得多可能包括去除HTML标签、处理特殊编码、过滤低质量内容、句子分割等。我们的简化是为了聚焦核心模型构建。2.2 构建词表与实现字符级分词器分词Tokenization是将文本切分成模型能处理的基本单元的过程。对于最“从零”的方式我们采用字符级Character-level分词。这意味着我们的词表由所有在文本中出现的唯一字符包括字母、数字、基本标点和空格构成。它的优点是词表极小通常几十到一百多个实现简单且永远不会出现未登录词OOV问题。缺点是序列长度会变得很长模型需要学习更长距离的依赖关系对算力要求更高。但对于我们的教学模型字符级是完美的起点。# 从清洗后的文本中提取所有唯一字符构建词表 vocab sorted(list(set(cleaned_text))) vocab_size len(vocab) print(f词表大小: {vocab_size}) print(f词表示例: {.join(vocab[:50])}...) # 创建字符到索引编码和索引到字符解码的映射 char_to_idx {ch: i for i, ch in enumerate(vocab)} idx_to_char {i: ch for i, ch in enumerate(vocab)} # 编码函数将字符串转换为整数列表 def encode(text): return [char_to_idx[ch] for ch in text] # 解码函数将整数列表转换回字符串 def decode(indices): return .join([idx_to_char[i] for i in indices]) # 测试 test_str hello alice! encoded encode(test_str) decoded decode(encoded) print(f原始字符串: {test_str}) print(f编码后: {encoded}) print(f解码后: {decoded}) assert test_str decoded, 编解码过程出错现在我们成功地将《爱丽丝梦游仙境》这本故事书变成了一个由0到vocab_size-1之间整数组成的超长列表。这就是模型的“原始粮食”。2.3 构建训练数据块Data Chunks语言模型的核心任务是预测下一个词或字符。给定一个字符序列模型需要输出下一个字符的概率分布。因此我们的训练数据需要被组织成(输入序列, 目标序列)对的形式其中目标序列就是输入序列向右移动一位。假设我们设定一个上下文长度Context Lengthblock_size为8。那么对于文本“hello alice!”我们可以创建以下数据块输入 (x):encode(‘hello al’)- [h, e, l, l, o, ‘ ‘, a, l] 目标 (y):encode(‘ello alic’)- [e, l, l, o, ‘ ‘, a, l, i]注意y就是x的每个位置的下一个字符。我们的模型将学习基于前8个字符来预测第9个字符。import torch # 将整个文本编码为张量 data torch.tensor(encode(cleaned_text), dtypetorch.long) print(f编码后数据形状: {data.shape}) # 应该是一个一维长向量 # 划分训练集和验证集简单按比例分割 split_ratio 0.9 n int(split_ratio * len(data)) train_data data[:n] val_data data[n:] def get_batch(split, block_size, batch_size): 生成一个批次的数据。 data train_data if split train else val_data # 随机选择batch_size个起始位置 ix torch.randint(len(data) - block_size, (batch_size,)) # 根据每个起始位置截取长度为block_size的序列作为x x torch.stack([data[i:iblock_size] for i in ix]) # y是对应的下一个字符序列 y torch.stack([data[i1:iblock_size1] for i in ix]) return x, y # 测试数据加载 block_size 8 batch_size 4 x_batch, y_batch get_batch(train, block_size, batch_size) print(f一个批次的输入x形状: {x_batch.shape}) # [4, 8] print(f一个批次的目标y形状: {y_batch.shape}) # [4, 8] print(第一个样本:) print(f 输入: {decode(x_batch[0].tolist())}) print(f 目标: {decode(y_batch[0].tolist())})至此数据准备的基石已经打好。我们有了一个微型但完整的文本数据集一个极简的词表一个字符级的分词器以及一个能够源源不断生成训练批次的数据加载器。接下来我们将进入模型的核心——Transformer架构的构建。3. 核心组件拆解亲手搭建Transformer的积木Transformer架构是当今大语言模型的绝对核心。它完全基于注意力机制摒弃了RNN的循环结构使得并行计算成为可能极大地提升了训练效率。我们的微型模型将实现一个GPT风格的架构即只使用Transformer的解码器Decoder部分并采用因果注意力掩码Causal Attention Mask来确保预测时只能看到当前位置之前的信息。3.1 词嵌入与位置编码为字符注入“位置感”首先我们需要将输入的整数索引token ids转换为连续的向量表示这就是词嵌入Token Embedding。同时由于Transformer本身不具备序列顺序信息我们必须显式地加入位置编码Positional Encoding。import torch.nn as nn import math class Head(nn.Module): 一个自注意力头。 def __init__(self, head_size, n_embd, block_size, dropout): super().__init__() self.key nn.Linear(n_embd, head_size, biasFalse) self.query nn.Linear(n_embd, head_size, biasFalse) self.value nn.Linear(n_embd, head_size, biasFalse) # 因果掩码确保位置i只能关注到位置j (j i) self.register_buffer(tril, torch.tril(torch.ones(block_size, block_size))) self.dropout nn.Dropout(dropout) def forward(self, x): B, T, C x.shape # Batch, Time (序列长度), Channels (特征维度) k self.key(x) # (B, T, head_size) q self.query(x) # (B, T, head_size) v self.value(x) # (B, T, head_size) # 计算注意力分数 wei q k.transpose(-2, -1) * (k.shape[-1] ** -0.5) # (B, T, T) # 应用因果掩码 wei wei.masked_fill(self.tril[:T, :T] 0, float(-inf)) wei torch.softmax(wei, dim-1) # (B, T, T) wei self.dropout(wei) # 聚合值向量 out wei v # (B, T, head_size) return out class MultiHeadAttention(nn.Module): 将多个注意力头的结果拼接起来。 def __init__(self, num_heads, head_size, n_embd, block_size, dropout): super().__init__() self.heads nn.ModuleList([Head(head_size, n_embd, block_size, dropout) for _ in range(num_heads)]) self.proj nn.Linear(n_embd, n_embd) # 最后的线性投影层 self.dropout nn.Dropout(dropout) def forward(self, x): # 并行计算所有头将结果在特征维度拼接 out torch.cat([h(x) for h in self.heads], dim-1) out self.dropout(self.proj(out)) return out class FeedForward(nn.Module): 简单的两层前馈网络通常用于每个注意力层之后。 def __init__(self, n_embd, dropout): super().__init__() self.net nn.Sequential( nn.Linear(n_embd, 4 * n_embd), # 通常先扩大4倍 nn.ReLU(), nn.Linear(4 * n_embd, n_embd), # 再投影回原维度 nn.Dropout(dropout), ) def forward(self, x): return self.net(x) class Block(nn.Module): Transformer解码器的一个基本块自注意力 前馈网络均带有残差连接和层归一化。 def __init__(self, n_embd, n_head, block_size, dropout): super().__init__() head_size n_embd // n_head self.sa MultiHeadAttention(n_head, head_size, n_embd, block_size, dropout) self.ffwd FeedForward(n_embd, dropout) self.ln1 nn.LayerNorm(n_embd) self.ln2 nn.LayerNorm(n_embd) def forward(self, x): # 残差连接x x self.sa(self.ln1(x)) x x self.sa(self.ln1(x)) x x self.ffwd(self.ln2(x)) return x现在我们有了构建模型所需的所有核心组件。Head实现了缩放点积注意力并加入了因果掩码这是生成模型的关键。MultiHeadAttention并行运行多个注意力头让模型可以同时关注来自不同表示子空间的信息。FeedForward为每个位置提供独立的非线性变换。Block则将它们与层归一化LayerNorm和残差连接Residual Connection组合在一起这是稳定训练深度网络的关键技术。3.2 组装模型定义我们的微型GPT接下来我们将这些组件像搭积木一样组装起来形成完整的模型。模型的结构是输入 tokens - 词嵌入 - 位置编码 - 多个 Transformer Block - 层归一化 - 线性投影到词表大小 - 输出 logits。class MiniGPT(nn.Module): 一个超小型的GPT-like语言模型。 def __init__(self, vocab_size, n_embd, block_size, n_head, n_layer, dropout): super().__init__() self.block_size block_size # 每个token的嵌入向量 self.token_embedding_table nn.Embedding(vocab_size, n_embd) # 每个位置的位置编码 self.position_embedding_table nn.Embedding(block_size, n_embd) # 堆叠多个Transformer Block self.blocks nn.Sequential(*[Block(n_embd, n_head, block_size, dropout) for _ in range(n_layer)]) # 最终的层归一化 self.ln_f nn.LayerNorm(n_embd) # 语言模型头将特征投影回词表空间用于预测下一个token self.lm_head nn.Linear(n_embd, vocab_size) def forward(self, idx, targetsNone): # idx 和 targets 的形状都是 (B, T) B, T idx.shape # 1. 获取token嵌入和位置嵌入 tok_emb self.token_embedding_table(idx) # (B, T, n_embd) pos_emb self.position_embedding_table(torch.arange(T, deviceidx.device)) # (T, n_embd) x tok_emb pos_emb # (B, T, n_embd) 广播机制让pos_emb加到每个批次上 # 2. 通过Transformer Blocks x self.blocks(x) # (B, T, n_embd) # 3. 最终层归一化和投影 x self.ln_f(x) # (B, T, n_embd) logits self.lm_head(x) # (B, T, vocab_size) # 计算损失如果提供了targets loss None if targets is not None: B, T, C logits.shape # 将logits和targets重塑为二维以符合交叉熵损失函数的输入要求 logits logits.view(B*T, C) targets targets.view(B*T) loss nn.functional.cross_entropy(logits, targets) return logits, loss def generate(self, idx, max_new_tokens): 基于给定的上下文idx生成max_new_tokens个新token。 for _ in range(max_new_tokens): # 裁剪上下文使其不超过block_size idx_cond idx[:, -self.block_size:] # 前向传播获取预测 logits, _ self(idx_cond) # 只关注最后一个时间步的logits logits logits[:, -1, :] # 变为 (B, C) # 用softmax转换为概率 probs torch.softmax(logits, dim-1) # (B, C) # 根据概率分布采样下一个token idx_next torch.multinomial(probs, num_samples1) # (B, 1) # 将新token拼接到序列上 idx torch.cat((idx, idx_next), dim1) # (B, T1) return idx模型的核心是forward函数。它清晰地展示了数据流动索引 - 嵌入 - 位置编码 - 多层Transformer处理 - 输出预测。generate函数实现了自回归生成每次用当前序列预测下一个token并将其追加到序列末尾循环往复。实操心得维度变换是调试的重灾区。在实现forward和generate时时刻用print(x.shape)检查张量的形状。特别是cross_entropy函数要求输入是(N, C)和(N,)其中N是样本总数B*TC是类别数vocab_size。形状不匹配是初期最常见的错误。4. 训练循环与超参数调优让模型“学会”说话有了模型和数据下一步就是通过训练让模型参数优化使其输出的概率分布越来越接近真实的下一个字符分布。这个过程就是最小化损失函数Loss Function。4.1 配置模型与优化器首先我们实例化模型并选择一个优化器。AdamW是目前训练Transformer的首选优化器它结合了Adam的自适应学习率和权重衰减一种正则化技术。# 超参数配置这些是“魔法数字”需要根据效果调整 vocab_size len(vocab) # 我们的字符词表大小 n_embd 64 # 嵌入维度每个token用64维向量表示 block_size 128 # 上下文长度模型能看到前面128个字符 n_head 4 # 注意力头的数量 n_layer 4 # Transformer Block的层数 dropout 0.1 # Dropout率用于防止过拟合 batch_size 32 # 每个训练批次的样本数 learning_rate 3e-4 # 学习率控制参数更新步长 max_iters 5000 # 最大训练迭代次数 eval_interval 500 # 每训练500步评估一次 eval_iters 200 # 评估时使用的迭代次数用于计算平均损失 # 设备选择优先使用GPUCUDA如果没有则用CPU device cuda if torch.cuda.is_available() else cpu print(f使用设备: {device}) # 实例化模型 model MiniGPT(vocab_size, n_embd, block_size, n_head, n_layer, dropout) model model.to(device) # 将模型移动到GPU/CPU print(f模型参数量: {sum(p.numel() for p in model.parameters())/1e6:.2f} M) # 打印参数量百万 # 创建优化器 optimizer torch.optim.AdamW(model.parameters(), lrlearning_rate) torch.no_grad() # 禁用梯度计算节省内存和计算资源 def estimate_loss(): 估算模型在训练集和验证集上的平均损失。 out {} model.eval() # 将模型设置为评估模式影响Dropout等层 for split in [train, val]: losses torch.zeros(eval_iters) for k in range(eval_iters): X, Y get_batch(split, block_size, batch_size) X, Y X.to(device), Y.to(device) _, loss model(X, Y) losses[k] loss.item() out[split] losses.mean() model.train() # 切换回训练模式 return out4.2 执行训练循环训练循环是机器学习的核心引擎。在每次迭代中我们获取一个批次的数据计算损失执行反向传播计算梯度然后用优化器更新模型参数。# 训练循环 for iter in range(max_iters): # 每隔一段时间评估一次损失 if iter % eval_interval 0: losses estimate_loss() print(f迭代次数 {iter}: 训练损失 {losses[train]:.4f}, 验证损失 {losses[val]:.4f}) # 获取一个批次的数据 xb, yb get_batch(train, block_size, batch_size) xb, yb xb.to(device), yb.to(device) # 前向传播计算损失 logits, loss model(xb, yb) # 反向传播 optimizer.zero_grad(set_to_noneTrue) # 清空上一轮的梯度 loss.backward() # 计算当前梯度 optimizer.step() # 根据梯度更新参数 # 训练结束后最终评估一次 losses estimate_loss() print(f最终 - 迭代次数 {max_iters}: 训练损失 {losses[train]:.4f}, 验证损失 {losses[val]:.4f})你会看到训练损失和验证损失随着迭代次数的增加而逐渐下降。验证损失用于监控模型在未见数据上的表现是判断模型是否过拟合Overfitting的关键指标。如果训练损失持续下降而验证损失开始上升通常意味着过拟合。踩坑实录损失不下降或为NaN。如果训练一开始损失就很高且不下降或者很快变成NaN请检查学习率3e-4是一个比较安全的起点。如果学习率太大如1e-2可能导致梯度爆炸损失变成NaN如果太小如1e-6损失下降会极其缓慢。梯度裁剪对于较深的网络梯度可能变得非常大。可以在loss.backward()之后、optimizer.step()之前加入torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)来裁剪梯度防止爆炸。初始化我们使用了PyTorch默认的初始化。对于更复杂的模型可能需要更精细的初始化如Xavier或Kaiming初始化。数据确保你的输入数据xb和yb是正确的。打印几个样本看看编码/解码是否正确。4.3 文本生成见证模型的“创造力”训练完成后最激动人心的时刻到了让模型生成文本我们提供一个初始上下文比如一个空格或“once upon a time”然后调用generate函数。# 将模型切换到评估模式关闭Dropout model.eval() # 生成文本 context torch.zeros((1, 1), dtypetorch.long, devicedevice) # 以一个起始token例如0可能对应空格开始 generated_indices model.generate(context, max_new_tokens500)[0].tolist() # 生成500个新token generated_text decode(generated_indices) print( 模型生成的文本 ) print(generated_text) print()第一次运行生成的文本很可能是一堆乱码或者不断重复几个字符。这完全正常我们的模型还很小训练数据也很少它可能只学会了最基本的统计规律比如“e”后面经常跟“ ”空格。降低预期是“从零构建”项目的重要心态。我们的目标不是流畅的文本而是验证整个pipeline是通的。5. 迭代、调试与深度理解从“能跑”到“略懂”如果模型成功运行并生成了文本哪怕是乱码恭喜你你已经搭建了一个完整的大语言模型训练和生成流水线但这只是开始。接下来我们需要通过观察、分析和调试来深化对模型行为的理解并尝试让它变得“更好一点”。5.1 诊断与可视化模型到底学到了什么我们可以通过一些简单的工具来窥探模型的内部状态。检查注意力模式注意力权重wei反映了模型在生成每个token时对之前各个token的“关注”程度。我们可以将其可视化。import matplotlib.pyplot as plt # 选取一个训练好的注意力头例如第一个Block的第一个头 block model.blocks[0] attention_head block.sa.heads[0] # 准备一小段输入 input_str the cat sat on the input_tensor torch.tensor([encode(input_str)], dtypetorch.long, devicedevice) # 前向传播并获取注意力权重需要修改Head类的forward以返回wei # 这里假设我们修改了Head类在forward中返回 (out, wei) # 实际中你可能需要临时修改代码或使用hook机制来捕获中间变量 model.eval() with torch.no_grad(): # ... 运行模型并获取注意力权重 ... # wei的形状是 (1, T, T)其中T是输入长度 # 可视化最后一个token‘ ’对前面所有token的注意力 # plt.matshow(wei[0, -1, :].cpu().numpy(), cmapviridis) # plt.xlabel(Key Position) # plt.ylabel(Query Position (last token)) # plt.title(Attention Weights for the last token) # plt.show()一个训练良好的模型其注意力图通常会显示出清晰的模式例如关注前一个词、句首词或具有语法关系的词。在我们这个初级模型上可能看不到清晰结构但这本身也是一个重要的观察。分析损失曲线绘制训练和验证损失随迭代次数的变化图。理想的曲线是两者同步平稳下降最后验证损失趋于平稳。如果验证损失开始上升说明过拟合了。5.2 超参数调优实验现在我们可以进行一些简单的对照实验直观感受不同超参数的影响。一次只改变一个变量并记录结果。实验改变的参数预期影响观察结果示例基线n_embd64, block_size128, n_layer4-损失缓慢下降生成文本为重复字符。增大模型容量n_embd128或n_layer8模型表达能力更强可能降低损失但也更容易过拟合训练更慢。训练损失下降更快但验证损失可能较早出现上升趋势。生成文本的重复模式可能更复杂。减小模型容量n_embd32或n_layer2模型表达能力弱损失可能较高但不易过拟合训练快。损失下降慢最终损失平台较高。生成文本可能更随机。增加上下文长度block_size256模型能看到更长的历史信息可能学习到更长距离的依赖。每个迭代的计算量和内存占用增加。可能需要更多数据才能体现出优势。调整学习率learning_rate1e-3学习率过大可能导致震荡甚至发散损失NaN。损失曲线剧烈波动可能崩溃。调整学习率learning_rate1e-4学习率过小导致收敛极慢。损失下降非常缓慢几千次迭代后变化不大。增加Dropoutdropout0.2更强的正则化可能减轻过拟合但也会减慢训练。训练损失下降稍慢但训练和验证损失的差距可能缩小。使用学习率调度torch.optim.lr_scheduler.CosineAnnealingLR动态降低学习率有助于在后期精细调优稳定收敛。损失曲线后期下降更平滑可能达到更低的平台。通过这样的实验你会对“超参数”这个抽象概念产生非常具体的体感。你会发现block_size受限于你的GPU内存n_embd和n_layer共同决定了模型的“脑容量”而learning_rate则是控制学习步伐的“油门和刹车”。5.3 从字符级到子词级迈向更实用的分词当模型表现稳定后我们可以考虑一个重要的升级将字符级分词替换为子词级分词例如Byte-Pair Encoding (BPE)。这是GPT、LLaMA等现代大模型实际采用的分词方式。BPE的核心思想是将常见的字符对或子词对合并成一个新的token从而在词表大小和序列长度之间取得更好的平衡。例如“hello”可能不会被拆成[‘h‘, ‘e‘, ‘l‘, ‘l‘, ‘o‘]而是被拆成[‘hell‘, ‘o‘]或[‘he‘, ‘llo‘]。实现一个简单的BPE分词器比想象中复杂但我们可以理解其步骤初始化词表为所有基础字符。在语料库中统计所有相邻符号对的出现频率。将出现频率最高的一对符号合并为一个新符号加入词表。重复步骤2-3直到词表大小达到预定目标或没有更多可合并的对。升级到BPE后我们的vocab_size会从~100变成几千block_size对应的实际文本长度会缩短模型效率和信息密度都会提升。这是从“玩具”模型向“实用”模型迈进的关键一步。由于篇幅所限这里不展开实现但强烈建议将其作为下一个探索目标。6. 总结与展望不止于“Hello World”通过这个项目我们从一段纯文本出发亲手实现了数据清洗、字符分词、词表构建、Transformer模型包括嵌入、位置编码、多头注意力、前馈网络、层归一化、残差连接、训练循环、损失计算、参数优化以及文本生成。这几乎涵盖了一个现代自回归语言模型最核心的所有环节。你得到的不仅仅是一个能输出乱码的脚本而是一个完整的、可解释的、可扩展的代码框架。你可以在此基础上更换数据集尝试用代码、诗歌、对话等不同风格的文本训练观察模型输出风格的变化。修改模型架构尝试不同的注意力机制如线性注意力、激活函数如GELU、归一化方式如RMSNorm。实现更高级的特性如梯度累积用小的batch size模拟大的batch size、混合精度训练节省显存加快速度、模型并行将大模型拆分到多个GPU上。向“大”模型迈进逐步增加n_embd、n_layer、block_size使用更大的语料库你会在损失曲线上看到明显的提升生成的文本也会逐渐变得有模有样。这个过程中最大的收获是对“黑箱”的祛魅。当你亲手实现过softmax(q k.T / sqrt(d_k)) v这一行注意力代码并看到它如何计算出上下文相关的表示后再听到“注意力机制”这个词你脑中浮现的将不再是魔法而是清晰的张量运算图。当你调整learning_rate并亲眼看到损失曲线从震荡变为平稳时你会对“优化”有最直观的理解。构建大语言模型就像盖房子从零开始意味着你要从烧制第一块砖、搅拌第一桶水泥做起。这个过程漫长而琐碎远不如直接住进精装房来得舒服。但当你住进自己一砖一瓦盖起的小屋时你对每一处结构、每一根管线都了如指掌这种扎实的掌控感和深刻的理解是任何捷径都无法给予的。现在你的“小屋”已经搭起了框架接下来的精装修就取决于你的好奇心和探索欲了。
返回列表