尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零构建大语言模型:CS336实战路径与迷你GPT实现

从零构建大语言模型:CS336实战路径与迷你GPT实现 想系统掌握大模型核心原理却总觉得网上资料太散从 Transformer 结构到分布式训练每个环节都有大量“黑盒”概念。斯坦福的 CS336 课程之所以受欢迎就是因为它不满足于让你调 API而是带着你从数据准备、Tokenizer、模型定义、预训练、微调一路手写到底。本文结合 CS336 的学习路径梳理一份从零构建 LLM 的完整实操指南包含环境配置、核心原理、可运行的迷你 GPT 代码示例以及高频故障排查思路。无论你是准备入门大模型方向的学生还是希望在业务中真正掌握大模型训练细节的工程师都可以照着这条路径逐步落地。1. 背景与核心概念1.1 LLM 为什么值得亲手构建LLMLarge Language Model大语言模型本质上是一个超大规模的、基于自回归方式训练的概率语言模型。通俗地说它做的事情是“根据已经出现的词预测下一个词出现的概率”。训练数据足够多、模型参数足够大之后这类模型会涌现出对话、翻译、摘要、代码生成等能力。很多人会问现在开源模型这么多Hugging Face 上直接下载权重就能用为什么还要花时间自己去构建原因有三点理解深度不同。只调用model.generate()你很难理解为什么模型会“胡说八道”为什么显存会溢出为什么长文本生成效果变差。只有亲手写过训练循环、调过损失函数、处理过数据批次才能真正理解这些问题。定制能力不同。业务场景往往需要针对特定领域数据做继续预训练或微调。当你要改模型结构、改注意力掩码、改采样策略时不熟悉底层实现是无从下手的。工程能力提升。训练大模型背后涉及数据流水线、分布式并行、显存优化、模型保存与评估等工程问题。这些经验在调 API 时学不到但恰恰是大模型工程师的核心竞争力。1.2 CS336 是什么CS336 是斯坦福大学开设的 LLM 系统课程核心理念是“从零开始构建语言模型”。课程内容覆盖了构建大模型的完整技术栈语言模型基础与数据准备Tokenizer 的原理与实现Transformer 模型结构与实现预训练Pretraining监督微调SFT与人类偏好对齐模型评估分布式训练与推理优化。这门课最有价值的地方在于它不会直接丢给你一个封装好的框架而是让你在课程实践中逐步实现数据加载、Tokenization、模型前向传播、反向传播、分布式训练等模块。这和 Andrej Karpathy 的 NanoGPT 思路有相通之处先用最小代码跑通一个可训练的 GPT再逐步扩展。另外围绕 LLM 的学习很多开发者会建立自己的知识库比如使用 llm wiki 整理课程笔记。这样做的好处是当你遇到“DDP 为什么导致 loss 不一致”、“学习率 warmup 有什么作用”这类细节问题时可以快速定位到自己的笔记而不是重新翻几十个网页。1.3 需要掌握哪些前置知识在开始动手之前建议先具备以下基础前置知识掌握程度用途Python能熟练编写类、循环、函数实现模型与训练逻辑PyTorch熟悉 Tensor、nn.Module、自动求导构建神经网络深度学习基础了解损失函数、反向传播、过拟合理解训练过程Linux 基础会使用命令行、安装环境管理和运行训练任务数学基础了解矩阵乘法、概率基础理解 Transformer 计算过程如果某些知识点还不熟可以在实践过程中边做边补。下面进入正题先看从零构建 LLM 的整体流程。2. 从零构建 LLM 的整体流程在进入代码之前我们需要先建立一个全局认知。构建 LLM 不是一上来就写模型而是一条完整的数据与训练流水线。2.1 构建流程概览从零到可用的 LLM大致分为六个环节数据收集与清洗 → Tokenization → 模型结构设计 → 预训练 → 微调与对齐 → 评估与部署每个环节的职责如下。数据收集与清洗模型的“知识”全部来自训练数据。数据质量直接影响模型效果。原始文本需要做去重、过滤垃圾信息、处理编码错误、去除个人隐私数据等。对应到 CS336 的课程中这一部分会涉及数据集的采样与去重比如 MinHash 去重。TokenizationTokenizer 将原始文本切分为模型可以处理的 token 序列。常见方案有字符级、BPEByte Pair Encoding、WordPiece、SentencePiece 等。选择合适的分词方案直接影响词表大小、训练速度和模型效果。模型结构设计目前主流 LLM 基本都基于 Transformer 的 Decoder-only 架构。核心组件包括 Token Embedding、位置编码、多头自注意力、前馈网络、残差连接与 LayerNorm。在这个环节需要理解每个组件的输入输出形状。预训练预训练是让模型在海量文本上做自监督学习。最常用的训练目标是“下一个词预测”Next Token Prediction。这一阶段需要处理学习率调度、优化器选择、梯度累积、混合精度训练等问题。微调与对齐预训练模型只能学会“续写文本”并不一定擅长对话。为了让模型符合人类的指令和偏好还需要做 SFT监督微调进一步还可以使用 RLHF 或 DPO 进行偏好对齐。评估与部署训练完成后需要通过困惑度Perplexity、下游任务指标等方式评估模型。部署时则要考虑推理速度、显存占用、量化、服务化等问题。2.2 课程实践与真实项目的差异CS336 课程中的实现更偏向教学强调把每块逻辑讲清楚。真实项目则会大量借助成熟框架如 Hugging Face Transformers、DeepSpeed、vLLM 等来提升效率。因此建议的学习策略是先跟着课程手写核心代码理解原理再学习框架了解生产环境下的最佳实践最后回到自己的业务场景用框架快速实现方案。下面我们进入实践环节先把环境准备好。3. 环境准备与版本说明构建 LLM 对环境的要求比普通 Web 应用高很多。我们先明确推荐配置再给出可选的降级方案。3.1 硬件环境最理想的情况是拥有一块大显存 GPU配置项推荐配置最低可运行配置GPUNVIDIA A100 / RTX 4090RTX 3060 12GB显存24GB 以上8GB ~ 12GB内存64GB 以上16GB硬盘1TB SSD200GB 可用空间如果你没有本地 GPU也可以使用云 GPU 实例或者 Colab 的免费 GPU。课程中的小型实验完全可以在低配环境上运行关键是先跑通流程。3.2 软件环境以下版本以常见稳定组合为例具体版本请结合你的环境和官方文档确认# Python # 推荐 Python 3.9 或 3.10 # 创建虚拟环境 python -m venv llm_env source llm_env/bin/activate # 安装 PyTorch # 根据自己的 CUDA 版本选择对应命令示例为 CUDA 12.1 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # 安装基础依赖 pip install numpy tqdm transformers datasets安装完成后可以运行以下命令检查环境是否可用import torch print(PyTorch 版本:, torch.__version__) print(CUDA 是否可用:, torch.cuda.is_available()) print(当前设备:, torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU)一般来说只要torch.cuda.is_available()返回True表示你的 PyTorch 正确识别到了 GPU。如果这里返回False需要检查驱动版本和 PyTorch 的 CUDA 版本是否匹配。3.3 项目结构规划为了后续代码整洁建议按照下面的目录结构组织项目llm-from-scratch/ ├── input.txt # 训练语料 ├── tokenizer.py # 分词器实现 ├── model.py # Transformer 模型定义 ├── train.py # 训练脚本 ├── generate.py # 推理脚本 └── checkpoints/ # 模型保存目录下面我们先把核心原理解析清楚再进入完整代码实现。4. 核心原理拆解4.1 数据与 Tokenizer模型无法直接理解字符串需要把文本转换成整数序列。最简单的方案是字符级 Tokenizer把每一个字符映射到一个整数。例如文本hello去掉重复字符后得到字符集合{h, e, l, o}建立映射h - 0 e - 1 l - 2 o - 3那么hello就转换为[0, 1, 2, 2, 3]。字符级 Tokenizer 的优点是实现简单缺点也很明显序列长度会很长且模型难以学到有意义的词级语义。实际 LLM 更常使用 BPE 或 SentencePiece。以 BPE 为例它从字符级别开始不断合并出现频率最高的相邻 token形成一个既能覆盖常见子词、又能控制词表大小的编码方案。在 CS336 的课程中Tabular 阶段会要求实现 BPE 算法并对比不同词表大小对模型效果的影响。4.2 Transformer 核心组件目前主流的 LLM 结构是 Decoder-only Transformer。一个标准的 Transformer Block 由以下部分组成Token Embedding将 token 整数映射为稠密向量。位置编码为模型提供序列位置信息。多头自注意力让每个 token 关注序列中其他 token。前馈网络对注意力输出做非线性变换。残差连接缓解深层网络梯度消失问题。LayerNorm稳定训练过程。自注意力机制的公式可以简化为Attention(Q, K, V) softmax(QK^T / sqrt(d_k)) V其中Q、K、V分别代表查询、键、值矩阵。sqrt(d_k)的作用是缩放点积结果避免数值过大导致 softmax 梯度消失。值得强调的是LLM 使用的是带因果掩码的自注意力。也就是说在预测第t个 token 时模型只能看到前t-1个 token不能看到未来的信息。实现时通常用一个上三角矩阵来屏蔽未来位置。4.3 训练目标与优化器预训练阶段LLM 的任务是“根据前文预测下一个 token”。这本质上是一个多分类问题损失函数使用交叉熵。训练过程中模型对每个位置的输出都计算出对所有词表 token 的概率分布然后与真实的下一个 token 计算交叉熵。优化器方面目前 LLM 训练最常用的是 AdamW。相比于标准的 AdamAdamW 将权重衰减与梯度更新解耦能够更好地抑制过拟合训练更稳定。此外LLM 训练普遍使用学习率预热warmup与余弦退火策略。训练初期使用较小的学习率逐步增大到目标学习率可以减少早期训练的不稳定性训练后期学习率逐渐降低则有助于模型收敛到更优区域。4.4 训练与推理的区别训练时模型会同时计算所有位置的输出和损失并进行反向传播。推理时则通常采用自回归生成每次只生成一个 token将新生成的 token 拼接到输入序列末尾再继续预测下一个 token。推理阶段有一个非常重要的优化手段KV Cache。因为每个新 token 只需要计算与前面 token 的注意力缓存前序 token 的 K、V 矩阵可以避免重复计算大幅提升推理速度。这也是为什么训练时显存占用远高于推理的原因之一。5. 完整实战案例手搓一个迷你 GPT现在进入最核心的实操环节。我们从一个可运行的迷你 GPT 项目开始。为了确保代码能在普通电脑上运行我们选择小规模参数、字符级 Tokenizer 的方案。你可以先把这个跑通再按照 CS336 的思路逐步扩展。5.1 准备训练数据为了实验简单我使用一段英文文本作为示例。你可以在项目目录下创建input.txt放入任意英文小说或文章。这里以一小段文本为例Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it.实际训练时数据量越大越好。字符级模型需要的语料往往更多后续可以替换为更大规模的数据集比如使用中文维基百科或开源语料。5.2 编写 Tokenizer文件路径tokenizer.pyimport torch def create_tokenizer(text): 基于训练文本创建字符级 tokenizer。 返回 encode 和 decode 函数。 chars sorted(list(set(text))) stoi {ch: i for i, ch in enumerate(chars)} itos {i: ch for i, ch in enumerate(chars)} encode lambda s: [stoi[c] for c in s] decode lambda l: .join([itos[i] for i in l]) return encode, decode, len(chars) def load_data(file_path): with open(file_path, r, encodingutf-8) as f: text f.read() return text这里的关键点是set(text)可以提取文本中所有出现过的字符stoi负责把字符映射为整数itos负责把整数映射回字符vocab_size是词表大小也就是模型输出层的维度。5.3 实现 Transformer 模型文件路径model.py下面这段代码实现了一个简洁的 Decoder-only Transformer。它去掉了复杂的分布式逻辑但保留了完整的注意力、MLP、残差连接与 LayerNorm。import torch import torch.nn as nn import torch.nn.functional as F class LayerNorm(nn.Module): 自定义 LayerNorm用于理解内部实现 def __init__(self, dim, eps1e-5): super().__init__() self.eps eps self.gamma nn.Parameter(torch.ones(dim)) self.beta nn.Parameter(torch.zeros(dim)) def forward(self, x): mean x.mean(-1, keepdimTrue) var x.var(-1, keepdimTrue, unbiasedFalse) return self.gamma * (x - mean) / torch.sqrt(var self.eps) self.beta class CausalSelfAttention(nn.Module): 带因果掩码的多头自注意力 def __init__(self, n_embd, n_head, block_size, dropout): super().__init__() self.n_head n_head self.c_attn nn.Linear(n_embd, 3 * n_embd) # Q、K、V 合并计算 self.c_proj nn.Linear(n_embd, n_embd) # 输出投影 # 因果掩码上三角为 0 的位置被掩码 self.register_buffer( bias, torch.tril(torch.ones(block_size, block_size)).view(1, 1, block_size, block_size) ) self.attn_dropout nn.Dropout(dropout) self.resid_dropout nn.Dropout(dropout) def forward(self, x): B, T, C x.shape qkv self.c_attn(x) q, k, v qkv.split(C, dim-1) q q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs) k k.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) att (q k.transpose(-2, -1)) * (1.0 / (C // self.n_head) ** 0.5) 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 class MLP(nn.Module): 前馈网络 def __init__(self, n_embd, dropout): super().__init__() self.c_fc nn.Linear(n_embd, 4 * n_embd) self.gelu nn.GELU() self.c_proj nn.Linear(4 * n_embd, n_embd) self.dropout nn.Dropout(dropout) def forward(self, x): return self.dropout(self.c_proj(self.gelu(self.c_fc(x)))) class Block(nn.Module): Transformer Block def __init__(self, n_embd, n_head, block_size, dropout): super().__init__() self.ln1 LayerNorm(n_embd) self.attn CausalSelfAttention(n_embd, n_head, block_size, dropout) self.ln2 LayerNorm(n_embd) self.mlp MLP(n_embd, dropout) def forward(self, x): x x self.attn(self.ln1(x)) # 残差 注意力 x x self.mlp(self.ln2(x)) # 残差 前馈网络 return x class GPT(nn.Module): 迷你 GPT 模型 def __init__(self, vocab_size, n_embd192, n_head6, n_layer4, block_size128, dropout0.1): super().__init__() self.block_size block_size self.token_embedding nn.Embedding(vocab_size, n_embd) self.position_embedding nn.Embedding(block_size, n_embd) self.blocks nn.ModuleList([ Block(n_embd, n_head, block_size, dropout) for _ in range(n_layer) ]) self.ln_f LayerNorm(n_embd) self.lm_head nn.Linear(n_embd, vocab_size, biasFalse) # 简单初始化提升训练稳定性 self.apply(self._init_weights) def _init_weights(self, module): if isinstance(module, nn.Linear): torch.nn.init.normal_(module.weight, mean0.0, std0.02) if module.bias is not None: torch.nn.init.zeros_(module.bias) elif isinstance(module, nn.Embedding): torch.nn.init.normal_(module.weight, mean0.0, std0.02) def forward(self, idx, targetsNone): B, T idx.shape assert T self.block_size tok_emb self.token_embedding(idx) # (B, T, C) pos torch.arange(0, T, deviceidx.device).unsqueeze(0) pos_emb self.position_embedding(pos) # (1, T, C) x tok_emb pos_emb for block in self.blocks: x block(x) x self.ln_f(x) logits self.lm_head(x) # (B, T, vocab_size) loss None if targets is not None: B, T, C logits.shape loss F.cross_entropy(logits.view(B * T, C), targets.view(B * T)) return logits, loss def generate(self, idx, max_new_tokens, temperature1.0): 自回归生成 for _ in range(max_new_tokens): idx_cond idx[:, -self.block_size:] logits, _ self.forward(idx_cond) logits logits[:, -1, :] / temperature probs F.softmax(logits, dim-1) idx_next torch.multinomial(probs, num_samples1) idx torch.cat([idx, idx_next], dim1) return idx代码中有几个点需要特别解释为什么要合并 Q、K、V 的线性层c_attn nn.Linear(n_embd, 3 * n_embd)一次计算得到 Q、K、V然后再用split切分。这主要是为了代码简洁和计算效率实际项目中也很常见。为什么要使用因果掩码register_buffer(bias, torch.tril(...))注册了一个下三角矩阵。masked_fill会将上三角位置替换为-inf经过 softmax 后这些位置的注意力权重趋近于 0从而实现“只能看到过去和当前 token”的效果。为什么生成时要除以 temperaturetemperature控制生成随机性。大于 1 会让概率分布更平滑输出更随机小于 1 会让分布更尖锐输出更确定。5.4 编写训练脚本文件路径train.pyimport torch from tokenizer import create_tokenizer, load_data from model import GPT # ---------- 超参数 ---------- batch_size 32 block_size 128 max_iters 2000 eval_interval 200 learning_rate 3e-4 n_embd 192 n_head 6 n_layer 4 dropout 0.1 device cuda if torch.cuda.is_available() else cpu # ---------- 数据准备 ---------- text load_data(input.txt) encode, decode, vocab_size create_tokenizer(text) data torch.tensor(encode(text), dtypetorch.long) n int(0.9 * len(data)) train_data, val_data data[:n], data[n:] # ---------- 批次采样 ---------- def get_batch(split): data train_data if split train else val_data ix torch.randint(len(data) - block_size, (batch_size,)) x torch.stack([data[i:i block_size] for i in ix]) y torch.stack([data[i 1:i block_size 1] for i in ix]) x, y x.to(device), y.to(device) return x, y # ---------- 模型与优化器 ---------- model GPT( vocab_sizevocab_size, n_embdn_embd, n_headn_head, n_layern_layer, block_sizeblock_size, dropoutdropout ).to(device) optimizer torch.optim.AdamW(model.parameters(), lrlearning_rate, weight_decay0.1) print(f模型参数量: {sum(p.numel() for p in model.parameters()) / 1e6:.2f} M) # ---------- 训练循环 ---------- for step in range(max_iters): xb, yb get_batch(train) logits, loss model(xb, yb) optimizer.zero_grad(set_to_noneTrue) loss.backward() optimizer.step() if step % eval_interval 0: model.eval() with torch.no_grad(): _, val_loss model(*get_batch(val)) print(fstep {step}: train loss {loss.item():.4f}, val loss {val_loss.item():.4f}) model.train() # ---------- 保存模型 ---------- torch.save(model.state_dict(), checkpoints/mini_gpt.pt) print(训练完成模型已保存到 checkpoints/mini_gpt.pt)训练时你会看到类似下面的输出模型参数量: 5.42 M step 0: train loss 4.0872, val loss 4.0921 step 200: train loss 1.2345, val loss 1.5233 step 400: train loss 0.8921, val loss 1.2108 ...观察 train loss 和 val loss 的变化可以判断模型状态两个 loss 都在下降说明训练正常train loss 持续下降但 val loss 开始上升说明出现过拟合两个 loss 基本不动说明数据、学习率或模型结构可能有问题。5.5 编写推理脚本文件路径generate.pyimport torch from tokenizer import create_tokenizer, load_data from model import GPT device cuda if torch.cuda.is_available() else cpu text load_data(input.txt) encode, decode, vocab_size create_tokenizer(text) model GPT( vocab_sizevocab_size, n_embd192, n_head6, n_layer4, block_size128, dropout0.1 ).to(device) model.load_state_dict(torch.load(checkpoints/mini_gpt.pt, map_locationdevice)) model.eval() # 使用一个 start token 触发生成 start torch.zeros((1, 1), dtypetorch.long, devicedevice) output model.generate(start, max_new_tokens200, temperature0.8) print(decode(output[0].tolist()))如果你的训练数据是英文小说片段生成结果可能是语法上不太通顺但已经具备一定统计规律的文本。如果是字符级模型输出中偶尔会出现拼写错误这是正常现象。5.6 如何扩展到真实规模上面的代码是教学演示版本距离真正的 LLM 还有很大差距。从“跑通”到“可用”你可以在以下几个方向继续扩展Tokenizer 升级将字符级替换为 BPE接入tiktoken或 Hugging Facetokenizers库。数据管线升级使用流式数据加载避免把所有数据一次性读入内存。训练优化加入学习率 warmup、余弦退火、梯度裁剪、混合精度训练torch.cuda.amp。并行训练使用torch.nn.parallel.DistributedDataParallel进行多卡训练或使用 DeepSpeed 的 ZeRO 优化。工程化使用 Hugging Face Transformers 对接现有生态使用 vLLM 做推理加速。6. 常见问题与排查思路手搓 LLM 的过程中问题通常集中在显存、loss 不收敛、生成效果差三个方面。下面整理一个常见问题排查表。问题现象常见原因解决思路显存溢出OOMbatch_size 太大、序列太长减小 batch_size 或 block_size使用梯度累积开启混合精度训练 loss 不下降学习率不合适、数据量不足、模型初始化问题检查数据尝试更大学习率或使用 warmup增加训练步数train loss 下降但 val loss 不降过拟合增大数据量增大 dropout降低模型容量生成文本完全重复温度过低、上下文太短调高 temperature增加 block_size检查训练数据多样性训练很慢单卡算力不足、未开启优化使用torch.compile开启混合精度使用 Flash Attention模型输出乱码Tokenizer 编码不一致统一数据编码为 UTF-8检查decode逻辑保存模型后加载报错词表大小与模型维度不匹配确保推理时使用的 vocab_size 和训练时一致调用模型 API 返回请求失败请求格式或工具参数与模型不匹配参考模型官方文档检查请求 schema 和工具 payload 格式6.1 显存溢出的具体排查步骤显存溢出是最常见的问题。按以下顺序排查查看当前 GPU 显存使用情况nvidia-smi逐步减小batch_size直到程序能正常启动。4 句话后定位到显存不足的根本原因是显存占用峰值过高。使用梯度累积来模拟更大的 batchaccumulation_steps 4 loss loss / accumulation_steps loss.backward() if (step 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()开启混合精度训练from torch.cuda.amp import autocast, GradScaler scaler GradScaler() with autocast(): logits, loss model(xb, yb) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()6.2 Loss 不下降的排查清单如果 loss 始终不下降按照下面的清单检查数据是否正确编码打印几个 batch 的x和y确认y是x的下一个 token。学习率是否过小或过大日志中打印梯度范数正常情况下梯度过大会导致训练崩溃。模型是否在训练模式确认调用了model.train()。损失计算是否在正确的维度上logits.view(B * T, C)与targets.view(B * T)的维度必须匹配。7. 最佳实践与学习路线7.1 工程层面的最佳实践在实际项目中有几个经验值得坚持保持配置与代码分离训练脚本里的超参数不应该散落在代码各处。推荐使用 YAML 或 dataclass 统一管理。例如# config.yaml model: n_embd: 192 n_head: 6 n_layer: 4 block_size: 128 train: batch_size: 32 learning_rate: 3e-4 max_iters: 2000重视实验记录每次实验都应该记录数据版本、模型结构、超参数、loss 曲线、评估结果。推荐使用wandb或tensorboard记录训练过程。没有记录的训练等于没有训练。做好数据质量把控数据质量对模型效果影响巨大。建议在训练前对数据做统计分析token 数量、重复率、语言分布、特殊字符占比。数据清洗不是可有可无的步骤而是决定模型天花板的关键因素。遵循最小权限与安全原则如果训练任务涉及敏感数据务必在隔离环境中进行设置权限访问控制。模型训练完成后也要考虑模型可能带来的隐私泄露问题。注重版本可控训练框架、PyTorch 版本、CUDA 版本、数据预处理代码都要做好版本管理。推荐使用 Docker 镜像固化训练环境避免“换台机器跑不起来”的尴尬。7.2 从 CS336 出发的进阶学习路线如果你愿意系统学习建议按照下面的路线推进第一阶段跑通最小代码。完成本文的迷你 GPT并尝试修改模型层数、头数、词表大小观察对 loss 的影响。第二阶段研究 Tokenizer。阅读 Hugging Face Tokenizers 库文档实现一个 BPE Tokenizer理解词表大小与 OOV未登录词问题。第三阶段优化训练流程。学习学习率调度、混合精度、梯度累积、分布式训练使用多卡训练一个参数量过亿的模型。第四阶段对齐与微调。实现 SFT 数据构建尝试用 LoRA 微调开源模型理解人类偏好对齐的基本思路。第五阶段推理优化。学习 KV Cache、量化、vLLM 推理框架探索模型部署方案。在整个学习过程中强烈建议使用llm wiki或 Obsidian 这类工具维护个人知识库。每次遇到报错、读到一篇好文章、复现一个实验都把它整理成 markdown 笔记。知识库的意义不在于收集而在于“用自己的话重写一遍”这是真正把知识内化的过程。7.3 最后想说的话手搓 LLM 是一次非常值得的投入。早期你可能觉得直接调 Hugging Face 更高效但当你真的自己实现了一遍数据管道、注意力机制、训练循环之后再看任何开源模型代码都会从容很多。不要被“大模型”三个字吓到。再大的模型也是从最基本的数据采样、矩阵乘法和交叉熵开始长出来的。耐心把一个最小闭环跑通再逐步扩大规模你就会发现所谓“手搓大模型”其实并没有那么神秘。如果本文对你的学习有帮助欢迎收藏备用。也欢迎在评论区交流你训练过程中遇到的报错和问题很多坑只有踩过才知道怎么绕开。
返回列表