资讯动态

从零训练领域BERT底座:语料工程、动态掩码与训练工程化

发布时间:2026/10/26 5:29:38 来源:尧图企业网站定制
1. 先把从零训练这件事的边界划清楚1.1 从零到哪一步预训练、微调与大模型的真实含义很多人说从零训练Bert架构大模型脑子里想的是打开一个空文件夹写完代码跑几个epoch然后得到一个能答题的模型。实际干下来你会发现这条链路远比想象的长而且每一环都能单独劝退一批人。标题里的从零我理解为不加载任何公开预训练权重分词器、语料、初始化参数、优化器状态、训练调度全部自己生成大模型则是指参数量落在千万到十亿这个区间而不是直接对标当下的千亿级语言模型因为Bert这类编码器架构本身就不是为生成式任务设计的它的定位是双向语义编码器输出的是每个token的上下文化向量表示主战场在分类、抽取、检索、排序、句向量这些任务上。所以这件事的核心价值在于你手里有一份别人没有的领域语料通用预训练模型在这个领域的水土不服很明显专业术语被切得七零八落领域内的表达习惯和通用语料差得很远微调几轮之后指标就是上不去。这时候自建一个领域预训练底座收益远比在通用模型上堆微调技巧来得直接。这套流程适合谁适合有自有语料、有至少一张中高端显卡、愿意花一两周时间啃工程细节的团队或个人。如果只是想做个文本分类demo直接拿开源权重微调就行完全没必要走这条路我见过太多人在这上面浪费算力。还需要提前说清一件事预训练阶段你拿不到任何准确率这种直观指标。训练日志里只有loss曲线和掩码语言模型的预测准确率这两个数字好看不代表下游好用。真正的验收必须放到下游任务上去这一点在动手前就要有心理准备否则训练完你会陷入这模型到底行不行的自我怀疑。1.2 架构选型为什么依然是这套双向编码器当下的注意力大多在生成式模型上为什么自建底座还要选Bert这套架构我的判断依据很实际。第一双向注意力在理解类任务上的性价比极高。同一份语料、同样的算力预算双向编码器在文本分类、实体抽取、语义匹配上的表现普遍不需要靠生成式模型那种海量数据来撑。第二推理成本低一个数量级。一个一亿参数级别的编码器单条文本推理在普通CPU上都能跑到几十毫秒落到线上服务里几乎是免费的而生成式模型光是KV缓存就够你喝一壶。第三训练过程可控。掩码语言模型这个任务的目标函数非常干净损失曲线怎么动、什么时候该调学习率都有成熟的经验可循。这里有个常见的误判有人觉得编码器架构过时了。实际上在检索增强、向量召回、重排序这些场景里双编码器和交叉编码器依然是主力自建一个领域内表现更好的编码器落地价值非常明确。我做过一个对比通用底座在某个专业领域的语义匹配任务上正负样本打分区分度很差换成同规模但用领域语料自训的底座后同一套微调脚本、同样的超参排序指标上了一个明显的台阶。这个差距不是靠调参能补回来的。另外要说清楚的是大这个字。千万参数级别6层、隐维度384能在一张消费级卡上几天跑完适合验证流水线一亿参数级别12层、隐维度768是性价比甜点也是绝大多数自建项目的实际选择三亿参数以上24层、隐维度1024需要多卡或者大显存单卡训练周期拉长到两三周而且收益不一定线性增长。我的建议是先用小配置把全链路跑通再等比放大千万不要一上来就对着最大配置硬刚。1.3 算力盘账一次训练到底吃掉多少动手之前先把账算清楚这一步能省掉后面很多返工。参数量估算有个粗略公式以12层、隐维度768、12个注意力头、词表3万为例词嵌入约2300万参数每层注意力部分约236万、前馈部分约472万12层合计约8500万加上池化层和少量偏置总量落在1.1亿附近。同样的算法套到24层、隐维度1024、16头每层约1258万24层约3亿加词嵌入约3100万总量落在3.3亿附近。这个数字和你用工具打印出来的结果差不了太多可以拿来快速心算。显存这块混合精度加AdamW优化器每个参数的常驻开销大约是16字节半精度权重2字节、半精度梯度2字节、单精度主权重4字节、一阶动量4字节、二阶动量4字节。也就是说1.1亿参数光模型状态就要1.76GB左右3.3亿参数要5.4GB左右。这只是起点真正吃显存的是激活值。粗略估法批大小乘以序列长度乘以隐维度乘以层数再乘上几倍的中间张量系数半精度下还要再除以2。一个批32、序列512、隐维度768、12层的配置激活值轻松上到几个GB这也是为什么很多人模型能加载、一开训就炸显存。再看数据消耗。原版那篇论文的训练配置是一百万步、批大小256前九成步数用128的序列长度后一成用512折算下来总消耗在400亿subword token量级。这个量级对自建项目来说偏大实际落地我的经验是领域语料如果有十亿token量级配合充分的多轮训练已经能拿到相当可用的底座如果只有几亿token也能做但要接受效果上限并且要更谨慎地控制过拟合。算力上一张24GB显存的卡跑1.1亿参数、批大小控制在能塞满显存的水平一天大概能处理几千万token十亿token量级跑个把月是常态所以小配置先验证、再决定是否放大这条原则非常重要。2. 语料工程模型上限的八成在这里决定2.1 语料来源与采集边界预训练模型的能力上限基本由语料决定代码只是把上限兑现出来。自建项目最常见的语料来源是自有业务沉淀的文本、公开的领域文档、内部知识库、技术社区内容、产品说明书之类的结构化文本。采集阶段有几条硬约束必须写在规范里。第一是授权与合规只使用明确允许用于模型训练的文本来源渠道要有记录不要抱着反正没人查的心态这个风险在后面会以很难受的方式还回来。第二是剔除个人隐私信息手机号、证件号、住址、邮箱这类内容在清洗阶段就要用正则加规则过滤掉宁可错杀不可放过。第三是避免单一来源占比过高某个渠道的内容如果超过语料总量的三成模型的表达风格会被它带偏泛化能力明显下降。我吃过一次亏早期偷懒直接把某个论坛的抓取结果全灌进去结果模型在掩码预测时疯狂输出网络流行语和表情符号的文本形式做正经的下游任务时噪声很大。后来把这类内容按比例限流问题才缓解。所以采集阶段就要做来源维度统计按渠道、按文档类型分别记数量后面清洗完再对比一次能发现很多隐蔽问题。另外要预留验证集语料。从清洗后的语料里随机抽一部分从训练集里彻底剔除用来做后续的困惑度评估。这个动作很多人会忘等到训练完发现没有干净数据做评估只能拿训练集凑合得到的数字毫无意义。2.2 清洗流水线去噪、去重、去毒清洗流水线我一般分五道工序顺序不能乱。第一道是格式归一化。全角半角统一、连续空白压成一个、各种不可见字符和零宽字符清掉、编码统一成UTF-8并处理掉乱码片段。这一步看着琐碎但不做的话分词器训练阶段会学到一堆垃圾符号词表被这些符号占掉几百个坑。第二道是过滤。按文档粒度打几个分长度过短的丢掉比如低于50字符信息量太低符号占比过高的丢掉比如超过三成是非文字字符基本是表格残片或乱码重复字符过多、单字符重复超过一定比例的丢掉整篇只有数字和标点的丢掉。还可以加一个简单的语言识别把非目标语种的文档筛出去。第三道是去重也是最容易被低估的一步。精确去重用哈希就够了难的是近似去重。同一篇新闻被转载几十次、同一份文档有多个版本这些内容如果不处理模型会在这些样本上反复过拟合同时挤占其他样本的训练机会。我用的方案是MinHash加局部敏感哈希把每篇文档切成n-gram集合取若干组最小哈希签名相似度超过阈值的归到一个簇里只保留一篇。阈值怎么定我的经验是0.8左右比较合适定太高漏掉大量近似重复定太低会把同主题但内容不同的文档误杀。具体实现上建议用现成的库处理不要自己手撸哈希函数时间成本不值当。第四道是质量打分。用一个轻量规则组合给每篇文档打一个分句子平均长度、标点规范性、停用词比例、是否包含大量非常规符号。得分过低的文档丢掉。这一步能把广告、导航栏文本、页脚版权声明这类看起来是中文但其实没内容的东西清出去。第五道是敏感内容过滤。用词表加规则的方式把明显不合适的内容剔除掉。这一步不做的话模型在这些语料上训练出来的表示会带偏下游任务上会表现出很奇怪的倾向。清洗完一定要做数据统计回归文档数、总字符数、平均长度分布、去重率、各来源占比。我一般会对比清洗前后的统计如果去重率超过30%说明来源本身重复度很高需要重新审视采集策略。2.3 分句、段落拼接与样本构造分词器之前要先解决一条训练样本长什么样的问题。编码器架构的输入是定长序列而原始文档长度差异巨大所以要先把文档切成句再把句子拼成不超过目标长度的样本。分句不能只按句号切中文文本里省略号、感叹号、问号、分号、换行都要处理还要处理引号内的句号不该切的情况。我通常用规则加简单模型结合的方式规则负责90%的常规情况剩下的交给一个小的断句模型兜底。切完之后要过滤掉长度异常短的句子比如低于5个字符这些碎片拼起来会造成大量无意义边界。拼装策略上主流做法有两种。一种是按文档顺序连续拼接把同一文档的句子依次塞进一个样本塞满就开新样本。好处是上下文连贯段落级任务的语义完整性好。另一种是随机打散拼接从整个语料池里随机抽句子拼成一个样本。好处是样本间的主题分布更均匀坏处是破坏了文档结构。我倾向于混合策略八成样本用连续拼接两成用打散拼接兼顾结构性和分布均匀性。这个比例没有严格依据是我自己对比实验后定下来的。还有个细节样本长度分布要控制。如果全部样本都是最大长度短文本任务上会浪费算力如果全部按实际长度批次内长度差异大padding浪费严重。我的做法是把长度分档比如128、256、512三档按比例分配训练时按档位组批。这样既能控制填充浪费又能让模型见到不同长度的输入。2.4 分词器训练词表大小的取舍计算分词器是这一步最容易被草率对待的环节但它直接决定了模型对领域术语的处理能力。通用分词器在专业领域会把你最关心的术语切成五六个碎片模型要花大量参数去学这些碎片的组合关系效率极低。自建分词器的价值就在这儿。我用的方案是字节级字节对编码理由是它对未登录字符的鲁棒性好不会出现这个词整句都编不出来的情况。训练流程是把清洗后的语料喂给分词器训练器设定目标词表大小训练完保存词表文件和合并规则文件。词表大小怎么定这是个权衡。词表太小序列被拉长同样长度的输入信息密度降低训练和推理都变慢而且罕见词的表示质量差词表太大词嵌入层的参数量暴涨尾部词频极低相当于浪费参数还加重过拟合。参考经验是目标场景推荐词表词嵌入参数768维说明单一领域、语料少于5亿token2万到2.5万1500万到1900万覆盖核心术语即可尾部词收益低中文为主、通用领域3万到3.5万2300万到2700万接近常见配置平衡性好多语种混合5万到8万3800万到6100万语种越多越需要大词表代码和自然语言混合5万以上3800万以上标识符和自然语言符号都需要覆盖我自己的项目大多落在2.5万到3万这个区间。训练完一定要做压缩率验证拿一批领域文本走一遍分词看平均每个词被切成几个token。如果平均切分数超过1.6说明词表对领域覆盖不足要扩大词表或者增加领域语料权重如果低于1.2说明词表很可能过大尾部有大量低频碎片。这个指标非常直观几行代码就能算出来。一个实操心得分词器训练完成后绝对不要再去改语料清洗规则。很多人训练完分词器又回头改清洗脚本重新生成语料结果分词器和语料对不上出现大量未登录字符被拆成字节序列训练效果肉眼可见地变差。正确的顺序是清洗彻底定稿、再训分词器、再生成训练数据中间不回头。3. 模型架构搭建配置参数背后的推导逻辑3.1 层数、隐维度与头数的约束关系配置参数不是随便填的它们之间有硬性约束。隐维度必须能被注意力头数整除这是最基本的每个头的维度隐维度除头数一般控制在64附近太小表达能力不足太大会让注意力矩阵的计算和显存开销难以接受。12层配768维12头、每头64维24层配1024维16头、每头64维都是这个规律的体现。层数和隐维度的配比也有讲究。有个经验比例是前馈层隐维度约为隐维度的4倍这个4不是玄学它来自原始设计的参数平衡注意力部分的参数量约为隐维度的平方乘4前馈部分约为隐维度的平方乘8两者大致按2比1分配参数预算配上4倍扩张比例刚好平衡。实际调的时候3倍到4倍都能用4倍是稳妥起点。还有个容易忽略的参数是中间层归一化的位置。编码器架构主流是后置归一化也就是残差相加之后再归一化。这种结构训练稳定性好但对学习率更敏感需要足够的预热步数。前置归一化的变体训练更稳、允许更大学习率但改动结构会带来和现有工具链的兼容问题。我的建议是第一版就用标准结构把变量控制住跑通了再考虑结构变体。配置里还有几个坑。隐藏层dropout一般设0.1但如果你的语料只有几亿token、又要跑很多轮建议调到0.15甚至0.2对抗过拟合注意力dropout同理。最大位置编码长度要按你的实际需求定定512就够了就不要定1024位置编码表也是参数量。3.2 核心模块实现从嵌入到前馈整套结构可以拆成四个部分逐个说。嵌入层由三张表相加组成词嵌入、位置嵌入、句子段嵌入。词嵌入查表得到每个token的向量位置嵌入是每个位置一个可学习向量注意这里用的是绝对位置编码不是相对位置句子段嵌入用来区分两个句子段在只有单句输入的任务里全填同一个值。三张表相加之后过一层归一化和dropout得到进入编码器的输入。这里有细节归一化放在相加之后还是之前会影响训练初期的稳定性标准做法是相加之后归一化。多头注意力的实现要拆成投影、分头、打分、加权、合并五步。投影用三个独立的线性层分别得到查询、键、值然后按头数切分成多个子空间打分时用查询和键做点积除以每头维度的平方根做缩放这个缩放不能省省了之后点积数值随维度增大而暴涨softmax会退化成近似one-hot梯度直接消失接着做掩码处理把填充位置的分值设成一个很大的负数softmax之后和值相乘再把各头结果拼回去过一层输出投影。前馈网络就是两层线性加一个激活。激活函数选高斯误差线性单元这是标配。中间维度按上节的4倍取。这里有个小优化点如果实现框架支持可以把两个线性层的权重合并存储以减少访存但对训练稳定性没有影响属于性能优化范畴。残差与归一化贯穿整个块。每个子层输出都要和输入相加再归一化。要注意的是残差连接要求输入输出维度一致所以隐维度一旦定下整个模型里所有子层的输出维度都必须是这个值不能中途变。3.3 参数初始化与训练初期的数值稳定初始化这件事很多人直接吃框架默认值结果训练前几天loss几乎不动还以为是数据有问题。编码器架构的标准初始化是正态分布均值0标准差0.02所有线性层和嵌入层统一用这个。归一化层的缩放参数初始化为1偏置初始化为0。池化层和分类头需要单独处理池化层跟着0.02的标准差走如果是下游任务的分类头标准差要放到0.02乘上一个和层数相关的系数这是为了让初期输出不要过于确定梯度能正常回传。训练初期不稳定的典型表现是loss先降后突然跳起来或者直接变成非数值。原因通常是学习率预热不足或者梯度裁剪没开。我的配置是预热步数取总步数的1%到2%梯度裁剪的范数阈值取1.0。这两个设置不算精细但足够应付绝大多数情况。如果还是不稳定先检查语料里有没有超长样本或者异常字符再看混合精度有没有在softmax前后出现数值溢出把注意力打分强制走单精度计算通常能解决。一个实际踩过的坑位置嵌入表的初始化如果用了框架默认的截断正态且标准差偏大训练前几千步loss会剧烈震荡。统一改成0.02标准差之后曲线立刻平滑。这类问题不写进日志根本查不出来建议训练脚本里把每个模块的初始化统计打印一遍均值方差都对一遍。4. 预训练任务实现魔鬼藏在掩码细节里4.1 动态掩码为什么不能提前固化掩码语言模型的核心是随机遮住一部分token让模型预测。**掩码比例取15%**是通用经验值这个比例在给模型足够的学习信号和不破坏上下文完整性之间取得平衡。比例太低每个样本能提供的监督信号少收敛慢比例太高上下文被破坏得太厉害模型学不到有效的双向表示。关键在动态掩码。早期的做法是在数据预处理阶段就把掩码固化下来存进文件同一份数据每个轮次看到的掩码位置完全一样。这样做的后果是模型在几十轮训练里反复预测同一批位置等于把这批位置的答案背下来了泛化能力受损。正确做法是在训练时的数据加载环节现场生成掩码每个轮次、每个批次看到的掩码位置都不同。代价是数据加载线程的CPU开销上升但这点开销完全值得。掩码的具体替换规则也要严格遵守在被选中的token里80%替换成特殊掩码符号10%替换成词表里的随机token10%保持不变。为什么要保留10%不变因为下游微调时输入里根本没有掩码符号如果训练时100%都是掩码符号模型会学到看到掩码符号才需要推理这个虚假关联微调时性能会掉。保留10%原词、掺10%随机词都是为了让模型对输入分布的变化保持鲁棒。这个比例不要自己乱改改了之后的下游表现差异很微妙很难归因。还有一个细节特殊符号本身不能被选中做掩码。填充符号、句子分隔符号、掩码符号自身、类别符号这些在生成掩码时要先排除掉。我见过有人忘了排除填充符号结果模型花大量参数去学预测填充符号浪费得莫名其妙。4.2 段落级任务的取舍下一句预测还是句子顺序预测原版设计里除了掩码任务还有一个段落级任务给两个句子段判断第二段是不是第一段的真实下一句。这个任务后期被大量质疑认为它过于简单模型靠主题相似度就能猜对学不到真正的句间逻辑关系。后来的改进方案是句子顺序预测把一段连续文本的相邻两句交换顺序让模型判断是否被交换过。这个任务更难逼迫模型学习句间的因果和时序关系。我的实践结论是如果你的下游任务里有句子对匹配、语义相似度、检索排序这类需求段落级任务值得保留用句子顺序预测版本如果下游全是单句分类或序列标注这个任务可以直接去掉只留掩码任务训练效率能提升不少效果损失很小。这个判断依据来自任务形态单句任务不需要模型理解句间关系加了反而是干扰。实现上句子顺序预测的样本构造是取同一文档的连续三句五成概率保持原序、五成概率交换前两句标签是二分类。注意交换只能发生在相邻句之间跨段落交换会让任务变得太简单或者太无解。同时要保证正负样本比例严格1比1否则模型会倾向于预测多数类学不到东西。4.3 数据管道与动态组批数据管道的设计直接决定GPU利用率。我踩过的坑里数据加载成为瓶颈排在前三。表现是GPU利用率在30%到50%之间反复跳训练速度只有理论值的一半。解决办法分几层。第一层是数据格式把预处理好的样本序列化成内存映射格式不要用文本格式现场解析也不要一次性全读进内存。内存映射的好处是操作系统按需加载既能随机访问又不吃满内存。第二层是长度分桶把长度相近的样本放在同一个桶里批次内按桶取这样填充比例极低。分桶的粒度我一般按32个token一档桶数量控制在十几个。第三层是多进程预取加载进程数设成CPU核数的一半到三分之二预取批次设2到4个让数据准备和计算重叠起来。组批策略上我推荐固定token数而不是固定样本数。也就是说一个批次里所有样本的token总数控制在一个目标值附近样本数随长度浮动。这个做法能让每个批次的显存占用稳定避免长样本批次炸显存、短样本批次浪费显存。实现上就是按桶取样本累加长度到阈值就出一个批次。动态掩码和数据管道要一起考虑掩码在现场生成意味着每个批次都要做一次随机采样和替换这个操作如果写得低效比如在Python层逐样本循环会成为新的瓶颈。建议把掩码逻辑向量化或者放到数据加载进程里并行做。5. 训练工程化从单卡跑通到多卡跑满5.1 显存预算公式与并行策略选择前面算过模型状态的显存占用现在把完整公式列出来方便你代入自己的配置组成每参数字节混合精度AdamW1.1亿参数3.3亿参数半精度权重20.22 GB0.66 GB半精度梯度20.22 GB0.66 GB单精度主权重40.44 GB1.32 GB一阶动量40.44 GB1.32 GB二阶动量40.44 GB1.32 GB小计静态161.76 GB5.28 GB激活值估算与批大小、序列长度成正比视配置而定视配置而定临时缓冲区视算子而定0.5到1 GB1到2 GB激活值这块给个粗算方法批大小乘以序列长度乘以隐维度乘以层数乘以约10中间张量数量系数再除以2半精度单位是字节。批32、序列512、隐维度768、12层代入约9GB。也就是说这个配置总需求在12GB左右24GB的卡留有余量。如果序列拉到512、批拉到64激活值翻倍就得开始压缩。压缩手段按优先级排序先降批大小再上梯度累积因为梯度累积只影响训练速度不影响显存峰值再开激活重计算把不保存的中间激活在反向时重算能省掉一半以上的激活显存代价是多一次前向计算速度降两成左右最后才是模型并行或分片。分片方案对单机多卡场景很成熟把优化器状态和梯度切到各卡参数本身复制1.1亿参数在两卡上基本就够用了。5.2 混合精度、梯度累积与重计算混合精度是必开项。前向反向用半精度算主权重和优化器状态保持单精度这样既能拿到接近翻倍的吞吐又能保住数值稳定。要注意几个必须强制单精度的位置softmax之前的注意力打分、归一化层、损失计算。这几个地方在半精度下容易出现溢出或精度崩塌尤其是注意力打分序列一长点积数值就大必须转单精度再除以缩放因子。梯度累积的用法要清楚累积步数乘以单批大小等于有效批大小。为什么需要有效批大小因为批太小梯度噪声大训练不稳定。编码器预训练的常见有效批大小在256到1024之间。如果你单卡只能放32那累积8步就是256。这里有个副作用要注意学习率要跟着有效批大小走有效批翻倍学习率大致也要翻倍否则收敛会明显变慢。这条规律在对数尺度上成立不要线性外推太远。激活重计算的开关位置要挑对。不是所有层都值得重算通常只对前馈层和注意力输出做重算归一化和嵌入层保留。这样能省掉大概六成的激活显存速度损失控制在两成以内性价比最高。5.3 学习率调度与训练稳定性调度策略我用的是线性预热加线性衰减这也是最稳的方案。预热步数取总步数的1%到2%从0线性升到峰值学习率之后线性降到0。峰值学习率怎么定参数量小、批大小小的配置用大一点比如5e-4参数量大、批大小大的用1e-4到2e-4。判断标准很朴素前一万步loss曲线平滑下降没有反复震荡就是合适的如果震荡明显就降学习率如果下降太慢就提。优化器的配置一阶动量0.9二阶动量0.999epsilon取1e-6注意默认值通常是1e-8编码器预训练用1e-6更稳权重衰减0.01。权重衰减只作用在权重矩阵上不要作用在偏置和归一化参数上这个区分在多数框架里需要手动配置参数组忘了的话模型会略微欠拟合。稳定性监控的几个关键信号梯度范数如果在训练中期突然暴涨检查是否有异常样本或者学习率过高loss如果在某个点完全不动检查数据管道是否卡住或者学习率衰减过早loss如果缓慢上升基本可以确定是过拟合要么加dropout要么减轮次。5.4 断点续训与日志体系预训练动辄几天几周中途断电、抢占、机器故障是常态断点续训必须是第一天就做好的功能不能等出事了再补。要保存的东西包括模型权重、优化器状态、学习率调度器状态、当前步数、数据加载器的位置信息、随机数种子状态。少存任何一项续训后曲线都会出现接不上的跳变。我一般每两千步存一次保留最近三个检查点加一个最优检查点磁盘占用可控。日志方面必须记录的最小集合是训练损失、掩码预测准确率、学习率、梯度范数、每秒处理的token数、显存峰值。前四个用来判断训练是否健康第五个用来评估工程效率第六个用来提前发现显存泄漏。这些打到可视化面板上出问题的时候一眼能定位。一个血泪教训早期没记录数据加载器的位置状态续训后数据从头开始跑等于前几天的训练白做了一部分而且模型在重复数据上过拟合loss曲线看着很漂亮下游指标却很差。这件事让我后来在检查点里连随机数状态都存。6. 踩坑实录与排查速查表6.1 损失异常类问题的定位路径loss直接变成非数值。按这个顺序查学习率是不是太大先降到十分之一试混合精度下注意力打分有没有强制转单精度语料里有没有超长序列导致位置编码越界梯度裁剪是不是没开。这四个覆盖了九成情况。loss前几千步几乎不动。多半是预热步数设得太长或者学习率峰值太小。也可能是掩码生成有问题比如掩码比例实际远低于设定值模型收到的监督信号太少。打印几个批次的掩码统计就能确认。loss震荡幅度很大。有效批大小太小是主因加大梯度累积步数其次是学习率偏高再其次是数据里混入了长度或内容异常的样本按长度和字符分布做个异常检测。训练loss降得很好验证困惑度却在上升。这就是过拟合说明语料重复度高或者训练轮次过多。回去查去重率加大dropout减少训练步数三招一起上。6.2 显存与吞吐问题的排查表现象可能原因处理方式一开训就显存溢出激活值超预算或临时缓冲区不够降批大小、开激活重计算、缩短序列长度训练若干步后显存缓慢上涨有张量没释放通常是日志或指标累积指标只在指定步数同步检查是否有列表在无限追加GPU利用率长期低于50%数据加载是瓶颈内存映射格式、长度分桶、增加加载进程、向量化掩码多卡训练速度不成比例提升通信开销占比高或批太小增大单卡批、用梯度累积分摊、检查是否每步都做全量同步吞吐突然腰斩遇到超长样本批次或触发了内存交换检查分桶是否生效、监控主机内存使用6.3 效果不达标的归因顺序训练跑完下游指标不如预期按这个顺序归因命中率最高。第一看分词压缩率。平均切分数超过1.6说明词表对领域覆盖不足模型在把大量参数花在拼装碎片上。这个问题的修复成本最高因为要重训分词器、重生成数据、重训模型所以放在最前面查。第二看语料重复度。去重不彻底会导致模型在少量样本上过拟合表现为训练集困惑度很低、验证集困惑度不降。重新做一遍近似去重往往能带来明显改善。第三看训练token总量。如果语料只有一两亿token训练轮次又少模型根本没学够。这种情况下最有效的动作是增加数据或者增加轮次而不是调结构。第四看掩码策略。检查掩码比例是否真的在15%附近、特殊符号是否被排除、动态掩码是否真的生效。这几个都是可以几行代码验证的。第五才看结构和超参。层数、隐维度、学习率这些在前四项都正常的前提下才值得调。我见过太多人在结构上反复折腾实际问题出在数据和分词器上。独家避坑技巧判断模型是否学进去了不用等训练完。训练到总步数的一成左右时拿一批领域文本做掩码预测人工看二十条结果。如果模型能填出符合领域习惯的词说明方向对了如果填的全是高频虚词或者随机词说明数据或掩码有问题这时候止损比训练完再发现强太多。这个动作我每个项目都做救过好几次。7. 评测与下游落地验证这条路到底走没走通7.1 预训练阶段的自检指标预训练期间能看的指标有限但要看得细。掩码预测准确率是最直接的注意要区分随机基线如果词表3万随机猜的准确率约等于各token频率平方和实际在1%到3%之间你的模型要显著高于这个基线才有意义。我的一般经验是训练充分的领域模型在领域语料上能到55%到70%具体取决于掩码难度和语料复杂度。验证集困惑度要看趋势而不是绝对值。趋势应该随训练稳步下降如果在某个点之后平了说明要么学饱了要么数据分布有瓶颈。和训练集困惑度对比两者差距超过两成就要警惕过拟合。还有一个自检方法我自己很常用构造一批领域常识填空题。从领域文档里挑一些含关键术语的句子遮住术语看模型能不能填对。这个测试比困惑度更贴近实际能力几十条样本就够手动评估十分钟信息量很大。7.2 下游微调与效果对比评估自建底座的真正方式是把它和通用底座放在完全相同的下游任务和微调脚本下对比。控制变量同样的数据划分、同样的超参、同样的训练轮次、同样的评估指标。只换底座权重其他一律不动。下游任务选择上建议覆盖三类单句分类验证基础语义表示、句子对匹配验证句间关系建模、序列标注验证token级表示精度。三类都涨说明底座质量确实提升了只有一类涨说明提升是局部的要看是否和领域特性相关。微调的超参和预训练不同学习率要小一到两个数量级通常2e-5到5e-5训练轮次少3到10轮就够小样本场景要考虑冻结底层只训顶层或者用更小的学习率给底层。这些细节决定了下游能不能把预训练的价值榨出来很多人预训练做得不错微调配置照抄别人的结果指标上不去很冤枉。顺带提一句领域适配的增益不是均匀分布的。领域术语密集的任务增益大通用表达为主的任务增益小甚至可能因为语料规模不如通用底座而略降。所以评估要分任务类型看不要拿平均值下结论。7.3 模型导出与线上推理训练完的模型要落到线上还有几步。权重合并与格式转换训练时的分片权重和优化器状态要合并成单文件优化器状态直接丢掉节省空间。转换到推理格式时注意精度选择半精度基本无损但要注意某些算子在特定硬件上的支持情况。推理提速的常规手段把批处理和长度分桶搬到服务侧短文本走短序列开启算子融合如果服务端有图优化能力就开对于超大批量的离线任务可以考虑量化到八位整数精度损失通常在可接受范围内但要先在评估集上验证。服务化上要注意模型加载的内存占用。一个1.1亿参数的模型半精度权重加运行时开销在500MB左右多实例部署时要把这块算进容量规划。另外分词器要和模型一起打包版本必须严格对应用错分词器版本会导致输入错乱且很难排查建议在服务启动时做一次分词器与模型的兼容性校验。我自己在部署环节踩过最坑的一次是分词器版本不一致线上服务分词结果和训练时不同指标掉了好几个点排查了两天才定位。后来在模型包里写了一个校验接口加载时比对词表哈希不一致直接启动失败问题再没出现过。这种防御性设计看着多余实际能省掉大量深夜排查。最后说个后续可扩展的方向底座训好之后可以拿它做领域内的对比学习继续训练把句向量质量再拉一截这条路对接检索和聚类场景收益很明显也可以把它当作教师模型去蒸馏更小的学生模型在延迟敏感的场景里落地。这两个方向我都试过前者对数据质量要求高但上限也高后者工程复杂度低但收益有天花板选哪条看你的实际约束。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑