最近做了几个大模型微调的项目,从BERT到GPT-2,从文本分类到文本生成,踩了无数的坑。

最开始以为,大模型微调就是"调包侠",用Hugging Face的Transformers库,加载预训练模型,喂数据,训练,完事。但真正做起来才发现,坑太多了:数据格式不对、模型不收敛、显存溢出、生成效果差、评估指标看不懂……

有好几次,为了调一个bug,熬到凌晨两三点。本文分享这些踩坑经历,包括数据处理、模型选择、训练技巧、性能优化、评估方法等方面的问题和解决方案。希望能帮你少熬夜,少踩坑。

一、数据处理的坑

数据是微调的基础,数据处理不好,后面全白搭。

坑一:数据格式不对

最开始,我用自己的CSV数据训练,结果模型不收敛。

查了半天,发现是数据格式的问题。Hugging Face的数据集,要求特定的格式:

  • 文本分类:textlabel字段
  • 文本生成:text字段,或者input_idslabels
  • 问答:questioncontextanswers字段

我用的CSV,字段名是contentcategory,和预期的不一样。虽然代码里做了映射,但tokenizer的时候出了问题。

解决方法:

  • datasets库加载数据,统一格式
  • 仔细看文档,确认每个任务需要的字段名
  • 打印几条数据,确认格式正确

坑二:数据清洗不彻底

我的训练数据是从网上爬的,里面有很多脏数据:

  • HTML标签没有去掉
  • 有重复数据
  • 有乱码和特殊字符
  • 标签有错误

这些脏数据,导致模型学不到真正的规律,效果很差。

解决方法:

  • 用BeautifulSoup去掉HTML标签
  • 用dedupe或简单的哈希去重
  • 过滤掉乱码和异常字符
  • 人工抽检一部分数据,修正标签
  • 数据清洗后,再训练,效果提升明显

坑三:数据分布不均

文本分类任务,我的数据分布很不均匀:有的类别有几万条,有的类别只有几百条。

模型训练后,对多数类的效果很好,对少数类的效果很差,几乎全预测成多数类。

解决方法:

  • 过采样:少数类重复采样
  • 欠采样:多数类随机采样
  • 类别权重:损失函数中给少数类更高的权重
  • 数据增强:对少数类做同义词替换、回译等增强

我最后用了类别权重 + 数据增强,少数类的F1提升了20%。

坑四:训练集和测试集泄漏

有一次,我发现模型在测试集上的效果特别好,但在真实数据上效果很差。

查了半天,发现是训练集和测试集有重叠。我在划分数据集的时候,用了随机划分,但数据中有很多相似的文本,被分到了不同的集合里。

解决方法:

  • 按某个唯一标识划分(比如用户ID、时间),而不是随机划分
  • 去重后再划分
  • 检查训练集和测试集的相似度

二、模型选择的坑

坑五:模型太大,显存不够

最开始,我想用GPT-2 XL做微调,结果一运行就OOM(显存溢出)。

我的GPU是RTX 3090,24G显存。GPT-2 XL有1.5B参数,光是加载模型就要6G(fp32),加上梯度和优化器状态,24G根本不够。

解决方法:

  • 用更小的模型:GPT-2(124M)、GPT-2 Medium(355M)
  • 用混合精度训练(fp16),显存减半
  • 用梯度累积,减小batch size
  • 用LoRA或Adapter,只训练少量参数

我最后用了GPT-2 Medium + fp16 + 梯度累积,24G显存刚好够用。

坑六:模型和任务不匹配

有一次,我用BERT做文本生成,效果很差。

后来才明白,BERT是双向编码器,适合理解类任务(分类、命名实体识别、问答),不适合生成类任务。生成类任务要用自回归模型(GPT-2、T5等)。

解决方法:

  • 理解任务类型:理解类用BERT/RoBERTa,生成类用GPT-2/T5
  • 理解模型架构:Encoder-only、Decoder-only、Encoder-Decoder
  • 看模型的论文和文档,确认适用场景

坑七:预训练模型和领域不匹配

我用通用的BERT做医疗领域的文本分类,效果一般。

因为通用BERT是在通用语料上预训练的,对医疗领域的术语和表达不熟悉。

解决方法:

  • 用领域预训练模型:医疗领域用BioBERT、PubMedBERT,法律领域用Legal-BERT
  • 继续预训练:在领域语料上继续预训练,然后再微调
  • 领域自适应微调:先用领域无标注数据做继续预训练,再用标注数据微调

我最后用了PubMedBERT,医疗文本分类的F1提升了8%。

三、训练技巧的坑

坑八:学习率太大,模型不收敛

最开始微调,我用了预训练时的学习率(1e-4),结果损失不下降,反而上升。

后来才知道,微调的学习率要比预训练小很多。因为预训练模型已经有了很好的参数,微调只需要稍微调整,学习率太大会破坏预训练的知识。

解决方法:

  • 微调的学习率一般用2e-5到5e-5
  • 用学习率预热(warmup),前几步学习率从小到大
  • 用学习率衰减,训练过程中学习率逐渐减小
  • 不同层用不同的学习率,底层小,顶层大

我最后用了3e-5的学习率,warmup比例10%,线性衰减,模型正常收敛了。

坑九:batch size太小,效果差

因为显存不够,我把batch size设成了2,结果模型效果很差,训练也不稳定。

batch size太小,梯度估计不准确,训练波动大。尤其是有Batch Normalization的模型,batch size太小会影响BN的统计量。

解决方法:

  • 用梯度累积:模拟大batch size。比如实际batch size=2,累积8步,等效batch size=16
  • 用Layer Normalization代替Batch Normalization(Transformer模型一般用LN)
  • 调整学习率,batch size小的时候学习率也要小一些

坑十:训练轮数太多,过拟合

有一次,我训练了20个epoch,发现训练集损失一直在降,但验证集损失先降后升。这是过拟合了。

大模型微调,一般不需要太多epoch。因为模型已经预训练过了,微调只是适配下游任务,1-5个epoch就够了。

解决方法:

  • 用早停(Early Stopping):验证集损失连续N步不下降就停止
  • 保存最佳模型:只保存验证集效果最好的模型
  • 用dropout和weight decay正则化
  • 一般微调2-3个epoch就够了

坑十一:冻结层不对

最开始,我冻结了BERT的所有层,只训练分类头,结果效果不好。

后来全部解冻,效果好了很多,但训练慢了,也容易过拟合。

解决方法:

  • 数据少的时候:冻结大部分层,只训练顶层和分类头
  • 数据多的时候:全部解冻,用小学习率微调
  • 分层学习率:底层学习率小,顶层学习率大
  • 用LoRA/Adapter:只训练少量参数,效果接近全参数微调

四、性能优化的坑

坑十二:训练速度慢

最开始,训练一个epoch要十几个小时,太慢了。

排查后发现,数据处理是瓶颈。我在CPU上做tokenize,速度很慢。

解决方法:

  • datasets库的map函数,多进程处理数据
  • 预处理后缓存到磁盘,避免重复处理
  • 用GPU做tokenize(如果支持)
  • 用DataLoader的num_workers,多进程加载数据
  • 用混合精度训练(fp16),计算更快

优化后,一个epoch从十几个小时降到了两个小时。

坑十三:显存占用越来越大

训练过程中,显存占用越来越大,最后OOM。

查了半天,发现是梯度累积的时候,没有正确清零梯度。还有一个原因是,我在循环里累积了tensor,没有释放。

解决方法:

  • 每步结束后调用optimizer.zero_grad()
  • 不要在循环里累积tensor,用.item()取标量
  • torch.cuda.empty_cache()清理缓存
  • 检查是否有内存泄漏

坑十四:多GPU训练问题

我尝试用多GPU训练,结果遇到了很多问题:

  • 数据并行(DataParallel)速度提升不明显
  • 分布式训练(DistributedDataParallel)配置复杂
  • 多GPU下的学习率和batch size调整

解决方法:

  • 用Accelerate库,简化多GPU训练
  • 学习率和batch size要按GPU数量线性缩放
  • 用DDP而不是DP,DDP更快更稳定
  • 先在单GPU上调通,再上多GPU

五、文本生成的坑

文本生成比文本分类坑更多。

坑十五:生成的文本重复

用GPT-2微调后,生成的文本经常重复,同一句话反复说。

这是自回归模型的常见问题,尤其是在小数据集上微调的时候。

解决方法:

  • norepeatngram_size参数,禁止重复的n-gram
  • repetition_penalty参数,惩罚重复的token
  • 增加训练数据,数据越多重复越少
  • 用更好的解码策略:beam search、top-k、top-p

坑十六:生成的文本不通顺

生成的文本,语法错误多,不通顺。

原因可能是:

  • 训练数据质量差
  • 训练轮数不够
  • 模型太小
  • 解码策略不对

解决方法:

  • 提高训练数据质量
  • 增加训练轮数,但不要过拟合
  • 用更大的模型
  • 调整解码参数:temperature、topk、topp

坑十七:生成的文本和输入不相关

做条件生成的时候,生成的文本和输入不相关,模型在"自由发挥"。

原因可能是:

  • 训练数据的输入输出对应关系不好
  • 模型没有学会条件生成
  • 解码时没有正确传入prompt

解决方法:

  • 检查训练数据,确保输入输出对应
  • 用Encoder-Decoder模型(T5)做条件生成,比GPT-2更合适
  • 解码时正确构造prompt,用特殊符号分隔输入和输出

六、评估方法的坑

坑十八:只看准确率

文本分类任务,我最开始只看准确率,结果发现准确率90%,但模型根本没用。

因为数据分布不均,模型全预测成多数类,准确率就有90%。但少数类完全识别不出来。

解决方法:

  • 看混淆矩阵,了解每个类别的表现
  • 看精确率、召回率、F1,尤其是少数类的
  • 看宏平均(macro-F1),而不是微平均(micro-F1)
  • 根据业务需求,选择合适的评估指标

坑十九:生成文本的评估

文本生成的评估,比分类难多了。

最开始我用BLEU分数,但发现BLEU高的文本,人读起来不一定好。BLEU低的文本,可能反而更通顺。

解决方法:

  • 人工评估:找人读生成的文本,打分
  • 用多个指标:BLEU、ROUGE、METEOR、BERTScore
  • 做人工对比实验:把模型生成的和人工写的混在一起,让人判断
  • 关注实际使用效果,而不只是指标

坑二十:测试集和真实场景不一致

模型在测试集上效果很好,但上线后效果很差。

原因是测试集和真实场景的数据分布不一样。测试集是干净的、规范的,真实数据是嘈杂的、多样的。

解决方法:

  • 测试集要尽量模拟真实场景
  • 用真实数据做A/B测试
  • 上线后持续监控,收集bad case,迭代优化

七、部署的坑

坑二十一:模型太大,部署困难

微调后的模型,几百MB甚至几个GB,部署的时候加载慢,占用内存大。

解决方法:

  • 模型量化:INT8量化,模型体积减小75%
  • 模型蒸馏:用大模型教小模型
  • 用ONNX或TorchScript优化推理
  • 用专门的推理框架:ONNX Runtime、TensorRT、FasterTransformer

坑二十二:推理速度慢

模型上线后,推理速度慢,用户等待时间长。

解决方法:

  • 用更小的模型
  • 用批量推理,一次处理多个请求
  • 用GPU推理,比CPU快很多
  • 用KV Cache优化生成速度
  • 限制生成长度,避免生成太长

八、我的经验总结

踩了这么多坑,总结一些经验。

1. 先跑通baseline

不要一开始就追求最优效果。先用最简单的方式跑通baseline,确认数据、模型、训练流程都没问题,再慢慢优化。

2. 小数据先验证

先用一小部分数据验证流程,确认没问题了,再用全量数据训练。避免全量数据训练了几个小时,最后发现数据格式错了。

3. 记录实验

每次实验都记录:用了什么模型、什么参数、什么数据、效果如何。用WandB或TensorBoard记录实验,方便对比和复现。

4. 多看文档和教程

Hugging Face的文档很详细,遇到问题先查文档。GitHub的issue里也有很多解决方案。不要自己瞎琢磨,先看看别人有没有遇到过同样的问题。

5. 不要迷信大模型

大模型不是万能的。有些任务,小模型就够了,甚至传统机器学习方法效果更好。根据任务选择合适的模型,不要盲目追求大。

九、写在最后

大模型微调,看起来简单,做起来坑很多。

从数据处理到模型选择,从训练技巧到性能优化,从评估方法到部署上线,每一步都可能出问题。但踩坑的过程,也是学习的过程。每解决一个问题,对大模型的理解就深了一层。

2022年了,大模型越来越火,微调大模型成为很多人的必备技能。但不要被"调包侠"的表象迷惑,真正做好微调,需要理解数据、理解模型、理解训练过程。

最后,用一句话总结:"大模型微调的坑,90%来自数据和细节。把数据处理好,把细节做到位,效果自然不会差。"

愿大家的大模型微调之路,少踩坑,少熬夜,都能训出好模型。