ARTICLE DETAIL

资讯详情

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

一张3090从零预训练GPT-2到领域适配全流程实战

一张3090从零预训练GPT-2到领域适配全流程实战 个人开发者想碰大模型最劝退的从来不是算法本身而是我到底该从哪一步开始。网上要么是动辄千卡集群的工业级方案要么是调个API就号称全流程的水文中间那段真正属于个人开发者的路——用一张消费级显卡从零预训练一个小模型再把它适配到自己的领域任务上——反而很少有人完整讲清楚。我自己用一张 RTX 3090从零跑通了 GPT-2 级别的预训练又做了领域适配中间踩的坑足够写满一个笔记本。这篇就把整条链路拆开讲预训练到底在训练什么、数据怎么准备、显存怎么算、领域适配该选全参微调还是 LoRA、怎么评估效果。适合有一定 Python 基础、手里有一张 24G 显存卡、想真正搞懂 LLM 底层而不是只会调接口的开发者。1. 先想清楚个人开发者做 LLM 全流程到底图什么1.1 为什么不直接调 API 而要自己训很多人第一反应是现在 API 这么便宜为什么还要自己折腾预训练这个问题我认真想过答案取决于你的目标。如果你只是要做一个问答产品那确实没必要自己训调接口又快又省。但如果你想知道模型内部到底发生了什么、想拥有一个完全可控、可以随意改结构、可以离线部署、可以针对自己领域深度定制的模型那自己走一遍全流程是唯一的路。我自己的动机很实际我手上有一批垂直领域的文本数据通用模型在这个领域表现一般而我又不想把数据传到别人的服务器上。这种情况下从预训练到领域适配走一遍得到的模型虽然小但在我的任务上比通用大模型更贴合而且完全归我掌控。这就是个人开发者做全流程的核心价值——可控性和领域贴合度而不是追求刷榜。1.2 一张 3090 能撑起多大的模型这是所有人最关心的问题。RTX 3090 有 24GB 显存这个数字决定了你的模型规模上限。先给结论在混合精度fp16/bf16训练下一张 3090 可以比较舒服地预训练 1.5 亿参数以内的模型GPT-2 small124M完全没问题GPT-2 medium355M需要一些优化技巧再往上就要靠梯度累积、梯度检查点、ZeRO 这些手段硬挤了。显存占用主要分四块模型参数、梯度、优化器状态、激活值。用 Adam 优化器时每个参数大约要占 16 到 20 字节fp16 参数 2 字节 fp16 梯度 2 字节 fp32 优化器动量 4 字节 fp32 方差 4 字节 fp32 主权重 4 字节。所以 124M 参数的模型光参数相关就要 124M × 16 ≈ 2GB加上激活值和中间张量实际峰值在 8 到 12GB 之间3090 绰绰有余。355M 的模型参数相关就要 5.7GB激活值随 batch size 和序列长度线性增长这时候就要精打细算了。提示显存不够时优先降 batch size 配合梯度累积而不是无脑上梯度检查点。梯度检查点用计算换显存会让训练速度掉 20% 到 30%个人开发者时间宝贵能不用就不用。1.3 全流程的四个阶段与各自的时间成本整条链路我拆成四段数据准备、预训练、领域适配、评估部署。时间成本上数据准备往往被低估实际上它可能占整个项目一半以上的精力。预训练在 3090 上跑 124M 模型、10 亿 token 左右的数据大概需要几天到一周取决于你的优化程度。领域适配如果是 LoRA几小时就能出结果全参微调则要一两天。评估和部署相对快但也不能省。我建议个人开发者不要一上来就追求大数据。先用 1 到 2GB 的文本把整条链路跑通确认每一步都能出结果再逐步加数据。跑通比跑大重要得多很多人卡在第一步就是因为想一步到位。2. 数据准备决定模型上限的隐形战场2.1 预训练数据的来源与清洗逻辑预训练的本质是让模型学会语言的统计规律所以数据要的是量大、多样、干净。个人开发者拿不到 Common Crawl 那种级别的东西但可以用公开数据集比如中文的维基百科 dump、开源书籍语料、新闻语料或者你自己领域内的文本。我当时的做法是混合通用语料打底保证语言能力领域语料占一定比例让模型提前接触领域词汇。清洗这一步绝对不能省。原始文本里全是 HTML 标签、乱码、重复段落、广告。我的清洗流程是先去 HTML 标签再按标点做句子切分然后做去重用 MinHash 或者简单的哈希去重最后过滤掉长度过短和乱码比例过高的样本。去重特别重要重复数据会让模型过拟合到某些片段上我实测下去重后模型的生成多样性明显更好。2.2 分词器为什么我不建议从零训练 BPE分词器决定了文本怎么变成 token。理论上你可以从零训练一个 BPE 分词器但我不建议个人开发者这么做原因有两个一是训练分词器需要大量数据和时间二是自训分词器会让你的模型和现有生态脱节没法直接复用别人预训练好的权重。更实际的做法是直接复用 GPT-2 的中文分词器或者开源的中文 BPE 词表。这样你的 tokenizer 是成熟的词表大小固定比如 50257 或 21128模型结构也好对齐。我一开始图新鲜自己训了个分词器结果发现词表覆盖不好很多常见词被切得稀碎后来老老实实换回开源词表问题立刻消失。2.3 把文本变成训练样本分块、拼接与注意力掩码模型训练时吃的是固定长度的 token 序列。常见做法是把所有文本 token 拼成一条超长序列然后按固定长度比如 1024切块。这样做的代价是句子边界被打断但对预训练来说无所谓因为预训练本来就是学语言规律不需要句子完整性。这里有个细节容易踩坑注意力掩码attention mask的处理。如果你做了 padding一定要确保 padding 位置的 mask 是 0否则模型会学到 padding token 的规律生成时冒出莫名其妙的填充符。我早期就因为这个模型生成时老是吐出一串无意义的 token排查了半天才发现是 mask 没设对。# 简化的数据分块逻辑示意 def chunk_tokens(token_ids, block_size): # 将所有 token 拼成一条长序列后按 block_size 切块 total len(token_ids) chunks [] for i in range(0, total - block_size, block_size): chunks.append(token_ids[i:i block_size]) return chunks2.4 数据质量比数据量更致命的几个证据我做过一个对比实验同样 124M 模型A 组用 2GB 清洗过的数据B 组用 5GB 没怎么清洗的数据训练同样的步数。结果 A 组的验证集困惑度perplexity明显更低生成也更通顺。这说明对个人开发者来说清洗的收益远大于堆量。另一个证据是重复数据的影响。我在 B 组里混入了一部分重复的新闻文本训练后期模型开始背诵这些段落一给相关开头就原样吐出来泛化能力明显下降。所以去重不是可选项是必选项。3. 预训练实操在 3090 上把 GPT-2 跑起来3.1 模型结构选型为什么从 GPT-2 起步最稳GPT-2 是个人开发者最友好的起点。它的结构简单纯 decoder 的 Transformer代码实现成熟社区资源多而且规模适中。相比之下LLaMA 那类结构虽然更强但涉及 RoPE、RMSNorm、SwiGLU 等一堆细节对新手不友好。我的建议是先用 GPT-2 结构把流程跑通理解每一步在干什么再去碰更复杂的结构。GPT-2 small 的配置是12 层、12 个注意力头、隐藏维度 768、上下文长度 1024、词表 50257总参数约 124M。这个规模在 3090 上训练非常舒服单步时间短迭代快适合反复实验。3.2 训练框架与关键超参设置框架上我用的是 HuggingFace 的 transformers 配合 accelerate或者直接用 nanoGPT 那种精简实现。nanoGPT 的好处是代码短、逻辑清晰适合理解原理transformers 的好处是生态全、工具多。个人开发者我推荐先用 nanoGPT 跑通再迁移到 transformers。关键超参我的经验值学习率用 3e-4 到 6e-4配合 warmup前 2000 步线性升温和余弦退火batch size 在 3090 上用 8 到 16配合梯度累积到等效 64 以上权重衰减 0.1梯度裁剪 1.0。这些不是拍脑袋是 GPT-2 论文和后续复现工作的常见配置实测下来很稳。# 关键超参配置示意 config { learning_rate: 6e-4, warmup_steps: 2000, max_steps: 100000, batch_size: 12, gradient_accumulation_steps: 8, # 等效 batch 96 weight_decay: 0.1, grad_clip: 1.0, block_size: 1024, }3.3 混合精度、梯度累积与梯度检查点的取舍3090 支持 bf16我强烈建议用 bf16 而不是 fp16。fp16 需要 loss scaling容易出 NaNbf16 的动态范围和 fp32 一样省心很多。开启混合精度后显存占用能降 30% 到 40%速度还能提升。梯度累积是为了在显存受限时模拟大 batch。比如你想用 batch 96 但显存只够 12那就累积 8 步再更新一次参数。注意梯度累积时loss 要除以累积步数否则等效学习率会变大。梯度检查点我前面说了能不用就不用除非模型实在放不下。3.4 训练过程中的 loss 曲线怎么读loss 曲线是训练的眼睛。健康的预训练 loss 应该是先快速下降然后进入缓慢下降的平台期。如果 loss 一直不降检查学习率是不是太大或太小如果 loss 突然飙升成 NaN多半是学习率过大或数据里有异常样本如果训练 loss 降但验证 loss 升那就是过拟合了该加数据或加正则。我自己的经验是124M 模型在 1 亿 token 左右时验证困惑度能降到 30 上下中文再往下就需要更多数据。不要盯着 loss 的绝对值要看趋势和验证集表现。3.5 我踩过的三个预训练坑第一个坑是学习率没 warmup。我一开始直接上 6e-4结果前几百步 loss 直接炸成 NaN。加了 warmup 后立刻稳定。第二个坑是数据没打乱。我按文件顺序喂数据结果模型学到的分布严重偏斜同一批数据反复出现。后来加了 shuffle buffer 才正常。第三个坑是保存 checkpoint 太频繁。我每 100 步存一次硬盘很快被塞满而且 IO 拖慢了训练。后来改成每 2000 步存一次只保留最近几个。注意预训练是长跑中途断电或崩溃很常见。一定要开 checkpoint 自动保存并且验证过能正确恢复训练否则几天白跑。4. 领域适配让通用模型变成你的专属模型4.1 领域适配到底在适配什么预训练出来的模型是个通才它懂通用语言但不懂你领域的黑话、术语、表达习惯。领域适配就是拿你领域的文本在预训练模型基础上继续训练让它把通用能力和领域知识结合起来。这一步的数据量需求比预训练小得多通常几百 MB 到几 GB 就够。适配的目标不是让模型记住领域事实那是 RAG 的活而是让它熟悉领域的语言分布。比如医疗领域模型要学会主诉既往史这类词的搭配法律领域要学会法条的表达方式。适配好了模型在你领域的困惑度会显著下降。4.2 全参微调 vs LoRA个人开发者的选择依据这是领域适配最核心的决策。全参微调是更新模型所有参数效果上限高但显存需求大、容易过拟合、每个领域要存一份完整模型。LoRA 是冻结原模型只训练一小部分低秩矩阵显存需求小、训练快、可以多个领域各存一个小适配器。我的建议很明确个人开发者优先用 LoRA。124M 模型全参微调在 3090 上虽然能跑但 LoRA 更省资源而且效果在领域适配这种任务上差距不大。只有当你有大量领域数据、追求极致效果时才考虑全参微调。对比维度全参微调LoRA显存需求高约 3 到 4 倍模型大小低约 1.2 倍模型大小训练速度慢快过拟合风险高低存储成本每个领域一份完整模型每个领域一个小适配器效果上限略高接近全参4.3 LoRA 的秩、alpha 与目标模块怎么定LoRA 有几个关键参数秩 r、缩放 alpha、dropout、目标模块。r 决定低秩矩阵的容量常用 8 到 64领域适配我一般用 16 或 32。alpha 是缩放系数通常设成 r 的两倍比如 r16 时 alpha32。dropout 用 0.05 到 0.1 防过拟合。目标模块指的是把 LoRA 加在哪些层上。最常见的是加在注意力的 query 和 value 投影上效果和性价比都不错。也有人加在所有线性层上效果略好但参数更多。我实测下来q 和 v 两个模块对领域适配已经够用。# LoRA 配置示意 lora_config { r: 16, lora_alpha: 32, lora_dropout: 0.05, target_modules: [c_attn], # GPT-2 的注意力投影 bias: none, }4.4 适配数据配比与灾难性遗忘的规避领域适配最大的风险是灾难性遗忘模型在学领域知识时把通用能力忘了。表现就是模型在你领域内很溜但一聊通用话题就变傻。规避方法是在适配数据里混入一定比例的通用语料比如 80% 领域数据加 20% 通用数据。这个比例可以调领域越窄通用数据比例可以适当提高。另一个技巧是用较小的学习率。领域适配的学习率通常比预训练小一个数量级比如 1e-5 到 5e-5。学习率太大模型会被领域数据带偏通用能力掉得厉害。4.5 适配效果怎么验证困惑度之外还要看什么困惑度是最直接的指标领域适配后在你领域的验证集上困惑度应该明显下降。但困惑度不是全部还要看生成质量和通用能力保留。我的做法是准备两组测试一组领域问题看模型回答是否专业一组通用问题看模型有没有变傻。两组都过关才算适配成功。我踩过一个坑只看困惑度结果模型困惑度降得很低但生成时开始复读训练数据里的句子。后来加了生成多样性检查比如看不同 prompt 下的输出差异才发现问题。所以困惑度低不等于模型好一定要人工看生成结果。5. 评估、部署与个人开发者的现实取舍5.1 除了困惑度还有哪些能落地的评估手段困惑度是内部指标真正要落地还得看任务表现。如果你有下游任务比如分类、抽取、问答直接在这些任务上测准确率、F1 最实在。如果没有标注数据可以用人工评估找几十个 prompt让模型生成人工打分通顺度、相关性、专业性。还有一个便宜的办法是用大模型当裁判。拿你的小模型和基线模型的输出让一个能力强的模型来对比打分。虽然不完美但比纯人工快得多。我用这个办法快速筛过好几版模型。5.2 量化与推理加速让模型跑得动训练完的模型要部署推理速度和显存是问题。量化是最有效的加速手段把 fp16 权重转成 int8 或 int4显存占用能降一半到四分之三速度也能提升。GPT-2 这种小模型int8 量化后质量损失很小个人部署完全够用。推理框架上可以用 ONNX Runtime 或者专门的推理引擎。ONNX 的好处是跨平台、优化成熟。我实测把模型导出成 ONNX 后推理速度比原生 PyTorch 快不少尤其是在 CPU 上。5.3 从实验到可用我建议的最小闭环个人开发者最容易犯的错是追求完美再上线。我的建议是先搭一个最小闭环数据准备脚本、训练脚本、适配脚本、评估脚本、推理接口每个都先跑通哪怕效果一般。有了闭环你才能快速迭代。我第一版模型效果很烂但闭环搭好后改数据、调参数、重训一周就迭代了好几版效果提升很快。5.4 个人开发者做 LLM 的边界与心态最后说点实在的。个人开发者做 LLM不要和工业级模型比能力那是资源差距决定的比不过。你的优势在于可控、可定制、可离线、成本低。把目标定在解决我自己的具体问题上而不是训一个超越 GPT 的模型。我自己的模型在很多通用任务上不如大模型但在我那个垂直领域它又快又准又私密这就够了。心态上把这件事当成学习过程。走一遍全流程你对 LLM 的理解会超过 90% 只会调 API 的人。这种理解本身就是回报模型效果是附带的。我踩过的那些坑、调过的那些参数最后都变成了我判断问题、设计方案的能力这比一个模型值钱得多。如果你也想走这条路我的建议是从最小的 GPT-2 开始用几百 MB 数据跑通全流程然后再逐步加码。别怕效果差第一版能跑起来就是胜利。
返回列表