
1. 为什么个人开发者也要走一遍LLM全流程1.1 从“调API”到“自己训”的分水岭很多人接触LLM是从调用在线接口开始的写几行代码传个prompt等几秒就能拿到结果。这种方式确实能快速做出产品原型但时间一长问题就暴露出来了你不知道模型为什么在某些输入上表现好、某些输入上胡言乱语你不知道上下文窗口到底怎么被消耗的你更不知道当业务需要模型理解你所在领域的专有术语时该怎么让它“学会”。我自己是从GPT-2时代开始折腾预训练的。当时手里只有一张RTX 309024GB显存放在今天看不算什么但在个人开发者的预算范围内它是一张非常均衡的卡。我拿它跑过GPT-2的预训练复现也做过RoBERTa中文模型的继续预训练后来又把训练好的模型拿去做领域适配整个过程踩了不少坑也积累了一些在文档里找不到的经验。这篇文章想做的事情很简单把“从预训练到领域适配”这条链路完整地讲一遍。不是泛泛地介绍概念而是把每一步为什么这么做、参数怎么选、显存怎么算、数据怎么处理、训练中怎么判断模型是不是在学东西都摊开来说。适合手里有一张消费级显卡、想真正理解LLM训练全貌的个人开发者也适合那些已经在调API但想往下再走一步的人。1.2 全流程到底包含哪些环节先把地图画出来。一个完整的LLM实践链路从零到可用大致会经过这几个阶段数据准备收集原始文本、清洗、去重、分词、打包成训练样本。预训练从随机初始化开始让模型学会语言的基本统计规律。继续预训练在已有基座模型的基础上用领域数据继续训练注入领域知识。领域适配通过指令微调、LoRA等方式让模型学会按照特定格式和风格回答问题。评估与迭代用困惑度、下游任务指标、人工抽查等方式判断模型是否达标。这条链路里预训练是最耗资源的但也是最能让你理解LLM本质的环节。继续预训练和领域适配则更贴近实际业务需求个人开发者往往把精力集中在这两块。RTX 3090的24GB显存在预训练阶段只能跑小规模模型但在继续预训练和LoRA微调阶段可以覆盖7B甚至13B参数量的模型配合量化或梯度检查点。提示不要一上来就想着训一个“自己的大模型”。个人开发者的优势在于灵活和专注先把小模型的全流程跑通再逐步放大规模比直接硬刚大模型要务实得多。2. 预训练阶段的核心细节与实操要点2.1 数据清洗比模型结构更重要的隐形工程预训练的数据质量直接决定模型的上限。我见过太多人把精力花在调模型结构上结果数据里全是重复文本、乱码、广告训出来的模型张口就是垃圾话。数据清洗没有捷径但有一套可复用的流程。第一步是去重。文本级去重可以用MinHash或SimHash段落级去重可以用精确匹配。我自己的做法是先用精确匹配去掉完全重复的行再用SimHash做近似去重阈值设在0.85左右。这个阈值不是拍脑袋来的太低会误删相似但不同的内容太高则去重不干净。实测下来0.85在中文网页数据上表现比较均衡。第二步是过滤低质内容。常见的过滤规则包括长度过短少于20个字符、特殊符号占比过高超过30%、包含大量重复字符如“哈哈哈哈”连续出现、以及明显的模板化文本如“点击查看更多”。这些规则看起来简单但能过滤掉相当一部分噪声。第三步是分词与打包。中文分词可以用SentencePiece或BPE我习惯用SentencePiece训练一个领域相关的分词器词表大小设在32000左右。打包时把多条短文本拼接成固定长度的序列比如1024或2048个token中间用特殊token分隔。这样做的好处是训练时不需要频繁padding显存利用率更高。# 一个简化的数据打包示例 def pack_sequences(tokenized_texts, max_len1024): packed [] buffer [] for tokens in tokenized_texts: buffer.extend(tokens) while len(buffer) max_len: packed.append(buffer[:max_len]) buffer buffer[max_len:] return packed注意打包时一定要记录每条样本的来源方便后续排查问题。我习惯在每条打包序列前加一个来源ID的embedding虽然会增加一点参数量但调试时非常有用。2.2 模型结构选择GPT-2还是RoBERTa预训练模型的结构选择取决于你的下游任务。如果要做文本生成、对话、续写GPT-2这类自回归模型是首选如果要做分类、抽取、匹配RoBERTa这类自编码模型更合适。GPT-2的结构并不复杂多层Transformer Decoder每层包含自注意力、前馈网络、层归一化和残差连接。它的优势在于生成能力强训练目标就是预测下一个token非常直观。RoBERTa则是在BERT基础上做了优化去掉了NSP任务用了更大的batch size和更长的训练时间在理解类任务上表现更好。对于个人开发者我建议从GPT-2的小规模版本开始比如124M参数或355M参数。RTX 3090跑124M模型batch size可以设到32甚至64训练速度很快一天就能看到明显的loss下降。355M模型则需要把batch size降到16左右配合梯度累积来模拟更大的batch。模型参数量显存占用训练适用场景GPT-2 Small124M约6GB快速验证、学习流程GPT-2 Medium355M约14GB生成任务、领域预训练RoBERTa Base125M约7GB分类、抽取、匹配RoBERTa Large355M约16GB高精度理解任务显存占用的估算方法是参数量乘以4字节float32再乘以3模型参数、梯度、优化器状态加上激活值占用的显存。实际训练时用混合精度fp16可以把显存占用降到大约一半。RTX 3090的24GB显存跑355M模型用fp16加梯度检查点基本能稳住。2.3 训练参数设置学习率、batch size与warmup预训练的学习率通常设在1e-4到5e-4之间具体取决于模型大小和batch size。有一个经验公式学习率与batch size的平方根成正比。如果你把batch size从32调到128学习率可以相应调大2倍左右。warmup步数一般占总训练步数的1%到5%。warmup的作用是让模型在训练初期不要更新太猛避免梯度爆炸。我自己的习惯是设2000步warmup配合余弦退火学习率调度训练结束时学习率降到接近0。# 学习率调度示例 from transformers import get_cosine_schedule_with_warmup optimizer torch.optim.AdamW(model.parameters(), lr3e-4) scheduler get_cosine_schedule_with_warmup( optimizer, num_warmup_steps2000, num_training_stepstotal_steps )实操心得训练初期loss可能会先上升再下降这是正常的。因为模型随机初始化时输出的分布很均匀随着训练进行它开始学习到语言的统计规律loss才会稳步下降。如果loss一直不降先检查数据有没有问题再检查学习率是不是太大。3. 领域适配让基座模型学会你的行话3.1 继续预训练与指令微调的区别领域适配有两条路径继续预训练和指令微调。继续预训练是在基座模型上用领域文本继续做语言建模让模型熟悉领域的词汇和表达方式指令微调则是用“问题-答案”对训练模型让它学会按照指令回答问题。继续预训练的数据不需要标注只要领域文本就行比如医疗病历、法律文书、技术文档。指令微调的数据需要人工构造或从现有数据中转换成本更高但效果更直接。我的建议是两步走先用领域文本做继续预训练让模型“见过”领域词汇再用指令数据做微调让模型“会用”这些知识。两步之间可以加一个评估环节用困惑度判断模型是否已经适应了领域文本的分布。3.2 LoRA微调个人开发者的性价比之选全量微调一个7B模型即使用fp16也需要大约80GB显存个人显卡根本扛不住。LoRALow-Rank Adaptation的思路是在原模型的权重旁边加一个小矩阵只训练这个小矩阵原模型权重冻结。这样可训练参数量降到原来的1%甚至更少显存占用大幅下降。LoRA的秩rank是一个关键参数。秩越大可训练参数量越多拟合能力越强但也更容易过拟合。我通常从秩8开始试如果欠拟合就调到16或32。Alpha参数一般设为秩的2倍这是社区里比较常用的经验值。from peft import LoraConfig, get_peft_model lora_config LoraConfig( r8, lora_alpha16, target_modules[q_proj, v_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM ) model get_peft_model(base_model, lora_config)注意target_modules的选择很关键。对于GPT-2类模型通常选注意力层的query和value投影对于LLaMA类模型除了q_proj和v_proj还可以加上k_proj和o_proj。选得越多可训练参数越多显存占用也越大。RTX 3090上跑7B模型的LoRA微调秩8、target_modules只选q_proj和v_proj显存占用大约在18GB左右比较稳。3.3 领域数据的构造与配比领域适配的数据质量比数量更重要。我见过有人用几万条低质问答去微调结果模型学会了胡说八道。构造指令数据时要注意几点多样性问题类型要覆盖领域内的常见场景不要全是同一种问法。准确性答案必须经过人工校验错误答案会直接教坏模型。难度梯度从简单的事实性问题到复杂的推理问题都要有让模型逐步提升。配比领域数据和通用数据的比例建议在1:1到3:1之间。领域数据太多会导致模型遗忘通用能力太少则领域适配效果不明显。我自己的做法是先用领域数据做继续预训练再用“领域指令通用指令”混合数据做LoRA微调。混合比例控制在2:1左右实测下来模型既能回答领域问题也不会把通用能力丢掉。4. 训练过程中的常见问题与排查技巧4.1 Loss不下降或震荡怎么办Loss不下降是最常见的问题原因可能有很多。先按这个顺序排查数据是否有问题打印几条训练样本看看是不是乱码、空文本、或者标签错位。学习率是否太大学习率太大会导致loss震荡甚至发散试着调小10倍看看。梯度是否爆炸加梯度裁剪阈值设在1.0左右能解决大部分梯度爆炸问题。模型是否太小如果数据复杂度远超模型容量loss也会降不下去这时候需要换更大的模型。Loss震荡但整体趋势向下通常是学习率偏大或batch size偏小。可以试着调小学习率或者增大batch size用梯度累积模拟。如果震荡幅度越来越大那基本是学习率太大了赶紧调小。4.2 显存不够用的几种解法RTX 3090的24GB显存在个人卡里算大的但跑大模型还是紧张。显存不够时可以按这个优先级尝试混合精度训练用fp16或bf16显存占用直接减半。梯度检查点用时间换空间显存占用能降到原来的三分之一左右但训练速度会慢20%到30%。梯度累积用小batch size模拟大batch size显存占用不变但训练更稳定。LoRA只训练少量参数显存占用大幅下降。模型量化用4bit或8bit量化加载模型显存占用进一步降低但可能影响训练效果。# 梯度检查点开启方式 model.gradient_checkpointing_enable() # 混合精度训练 from torch.cuda.amp import autocast, GradScaler scaler GradScaler() with autocast(): outputs model(input_ids, labelslabels) loss outputs.loss scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()实操心得梯度检查点和混合精度可以同时用但要注意有些操作在fp16下会溢出比如softmax。遇到NaN loss时先关掉混合精度试试如果正常了再逐步开启并调整。4.3 模型过拟合的识别与缓解过拟合的表现是训练loss持续下降但验证loss开始上升。个人开发者往往没有大规模验证集可以用一小部分训练数据作为验证集比如留出5%。如果验证loss连续多个epoch不降反升基本就是过拟合了。缓解过拟合的方法包括增加dropout、减小模型规模、增加数据量、早停。我自己的习惯是设一个patience值比如3个epoch验证loss连续3次不降就停止训练。这样既能防止过拟合又不会浪费太多时间。问题现象可能原因解决方法训练loss下降验证loss上升过拟合增加dropout、早停、增加数据训练loss和验证loss都不降欠拟合增大模型、调大学习率、检查数据loss出现NaN梯度爆炸或fp16溢出梯度裁剪、关掉混合精度显存溢出batch size太大减小batch size、梯度累积、梯度检查点5. 评估与迭代怎么判断模型训好了5.1 困惑度最直观的预训练指标困惑度Perplexity是预训练阶段最常用的指标它衡量模型对文本的“惊讶程度”。困惑度越低说明模型对文本的预测越准确。计算方法是取交叉熵损失的指数。在领域适配中困惑度可以用来判断模型是否适应了领域文本。具体做法是在领域验证集上计算基座模型的困惑度再计算继续预训练后模型的困惑度。如果后者明显低于前者说明模型确实学到了领域知识。但困惑度不是万能的。它只反映模型对文本的建模能力不反映模型回答问题的能力。所以还需要下游任务的评估。5.2 下游任务评估从自动指标到人工抽查下游任务评估取决于你的具体应用。如果是分类任务看准确率、F1值如果是生成任务看BLEU、ROUGE或者用另一个模型做评分。但自动指标往往和人类判断有差距所以人工抽查必不可少。我自己的做法是从验证集里随机抽100条人工判断模型输出是否合理。重点看三类问题事实性错误、格式错误、以及“看起来对但实际不对”的幻觉。如果这三类问题的比例超过10%说明模型还需要继续调。提示评估时一定要用模型没见过的数据。我见过有人用训练数据做评估结果指标高得离谱上线后一塌糊涂。训练集、验证集、测试集必须严格分开。5.3 迭代策略小步快跑还是一次到位个人开发者的资源有限我建议采用小步快跑的策略。先跑一个baseline比如用基座模型直接推理记录表现然后做继续预训练再评估最后做指令微调再评估。每一步都记录指标这样才能知道哪个环节带来了提升。不要一次性把所有技巧都用上否则出了问题你根本不知道是哪个环节导致的。每次只改一个变量观察效果再决定下一步。这样虽然慢一点但每一步都走得踏实。6. 个人开发者的资源管理与工具链6.1 RTX 3090的极限在哪里RTX 3090的24GB显存在fp16加梯度检查点的条件下可以训练的最大模型大约是1.5B参数。如果只用LoRA可以微调7B甚至13B的模型13B需要更激进的量化。预训练阶段124M到355M的模型是比较舒适的范围再大就需要多卡或者云资源了。训练速度方面124M模型在3090上大约每秒处理3000到5000个token355M模型大约每秒1000到2000个token。这意味着训练一个epoch假设10亿token需要几十个小时。所以个人开发者要精打细算尽量用更少的数据达到可用的效果。6.2 工具链选型HuggingFace生态够用吗HuggingFace的Transformers、Datasets、Peft、Accelerate这套组合基本覆盖了个人开发者的全部需求。Transformers提供了各种预训练模型的实现Datasets负责数据加载和处理Peft实现了LoRA等参数高效微调方法Accelerate则简化了混合精度和分布式训练。我自己的工具链是用Datasets做数据清洗和打包用Transformers加载模型和训练用Peft做LoRA微调用Accelerate管理训练循环。这套组合的优点是文档全、社区活跃、遇到问题容易找到答案。缺点是抽象层次较高有时候想改底层逻辑会比较麻烦。# 安装核心依赖 pip install transformers datasets peft accelerate pip install torch --index-url https://download.pytorch.org/whl/cu118注意PyTorch版本要和CUDA版本匹配。RTX 3090支持CUDA 11.8及以上装之前先确认驱动版本。我遇到过因为CUDA版本不匹配导致训练速度慢一半的情况排查了半天才发现是环境问题。6.3 训练日志与实验管理训练日志是排查问题的生命线。我习惯用TensorBoard记录loss、学习率、梯度范数等指标用Weights Biases做实验对比。每次实验都记录配置、数据版本、代码commit方便复现。梯度范数是一个容易被忽略但很有用的指标。如果梯度范数突然增大说明可能有异常样本或者学习率太大。如果梯度范数一直很小说明模型可能已经收敛或者学习率太小。把梯度范数画出来能提前发现很多问题。7. 从训练到部署的最后一公里7.1 模型导出与格式转换训练完的模型需要导出成推理友好的格式。PyTorch的原始权重可以用ONNX导出也可以用HuggingFace的save_pretrained保存。如果要做量化推理可以转成GGUF或GPTQ格式。ONNX导出的好处是跨平台可以在没有PyTorch环境的机器上推理。但ONNX对动态形状的支持有限导出时需要指定输入长度。我自己的做法是导出两个版本一个固定长度比如512用于批量推理一个动态长度用于单条推理。# ONNX导出示例 torch.onnx.export( model, dummy_input, model.onnx, input_names[input_ids, attention_mask], output_names[logits], dynamic_axes{ input_ids: {0: batch, 1: sequence}, attention_mask: {0: batch, 1: sequence}, logits: {0: batch, 1: sequence} }, opset_version14 )7.2 推理优化量化与批处理推理阶段的优化目标是在保证效果的前提下降低延迟和显存占用。量化是最直接的手段8bit量化能把显存占用降到一半4bit量化能降到四分之一但效果会有一定损失。我通常先用8bit量化如果效果达标就不继续降了。批处理是另一个优化点。把多条请求拼成一个batch能显著提升吞吐量。但batch size太大会增加延迟需要根据实际场景权衡。对于在线服务batch size设在4到8比较合适对于离线批量处理可以设到32甚至更大。7.3 持续迭代上线不是终点模型上线后要持续收集用户反馈和bad case定期做迭代。我自己的习惯是每周抽一批线上请求人工评估模型表现把bad case加入训练数据重新做一轮微调。这样模型能逐步适应用户的真实需求而不是停留在实验室指标上。迭代时要注意数据泄露问题新加入的训练数据不能和验证集重叠否则评估指标会虚高。另外每次迭代都要保留一个基线模型方便对比效果。如果新模型在某些场景下表现下降可以快速回滚。8. 一些踩过的坑和真实体会8.1 数据质量决定一切我最早做领域适配时急着跑通流程数据只做了最简单的清洗结果模型学会了大量噪声模式。后来花了两周时间重新清洗数据效果提升比调任何参数都明显。这件事让我明白在LLM训练里数据是1模型和参数是后面的0。没有好的数据再好的模型也训不出有用的东西。8.2 不要迷信大batch size大batch size能提升训练稳定性但也会降低模型的泛化能力。我试过把batch size从32调到256训练loss降得更快但验证loss反而更高。后来查了资料才知道大batch size会导致模型收敛到更尖锐的极小值泛化能力下降。现在我通常把batch size控制在32到64之间配合梯度累积来模拟更大的batch。8.3 学习率是最重要的超参数如果只能调一个超参数我会选学习率。学习率太大loss震荡甚至发散学习率太小训练慢且容易陷入局部最优。我自己的经验是先用一个较大的学习率比如5e-4跑几百步观察loss变化如果震荡就减半直到loss平稳下降。然后再用这个学习率跑完整训练。8.4 早停比多训几个epoch更划算个人开发者的时间宝贵不要为了“多训几个epoch可能更好”而浪费算力。验证loss连续几个epoch不降就停把时间花在数据清洗和参数调优上收益更大。我见过有人训了三天三夜结果最好的模型出现在第一天后面两天全是过拟合。8.5 记录每一次实验这一点怎么强调都不为过。我早期做实验时没有记录习惯过了一个月回头看完全不记得当时用了什么参数、什么数据。后来用Weights Biases做实验管理每次实验自动记录配置和指标效率提升了很多。现在我可以随时对比不同实验的效果快速找到最优配置。8.6 社区是最好的老师遇到问题时先搜HuggingFace论坛、GitHub Issues、Reddit的LocalLLaMA板块。大部分问题别人都遇到过而且有现成的解决方案。我自己的很多技巧都是从社区里学来的比如梯度检查点的开启方式、LoRA的target_modules选择、ONNX导出的动态轴设置。不要闭门造车多看看别人怎么做的。8.7 从最小可行方案开始不要一上来就追求完美。先用小模型、小数据跑通全流程确认每个环节都能正常工作再逐步放大规模。我自己的第一个LLM项目只用了100MB数据和124M模型跑通后才发现数据清洗有问题、评估方法不完善。如果一开始就用大模型这些问题会被掩盖等到后期才发现就来不及了。8.8 硬件不是借口RTX 3090在今天的硬件市场上不算顶级但它足够让你走完LLM训练的全流程。我见过很多人纠结于“没有A100就做不了”结果一直停留在调API的阶段。实际上小模型的全流程实践能让你学到的东西比用大模型调API多得多。硬件限制反而会逼你去思考更高效的方案比如LoRA、量化、梯度累积这些技巧在大规模训练里同样适用。8.9 领域适配的关键是“懂业务”技术只是手段领域适配的核心是理解业务需求。你要知道用户会问什么问题、期望什么格式的回答、哪些错误是不能接受的。这些信息只能从业务方和真实用户那里获取不是靠调参能解决的。我自己的做法是在构造指令数据之前先和业务方聊半天把常见问题、边界情况、禁忌话题都列出来再动手写数据。8.10 保持耐心接受不完美LLM训练是一个需要耐心的过程。loss不会一直降模型不会一次就训好评估指标也不会每次都提升。接受这种不确定性把每次实验都当作学习的机会。我训废过好几个模型但每次失败都让我更清楚哪些做法行不通。现在回头看那些失败的经历比成功的经验更有价值。