从零到一进行pretrain

目录

  1. 背景篇

  2. 数据篇

  3. 训练篇

  4. 评估篇

  5. 总结篇

  6. 解决方案

  7. 技术架构

  8. 案例参考


1. 背景篇

时至今日,dense模型有qwen,MOE模型有deepseek,小尺寸模型有minicpm。无论是个人还是大厂,都很难训出同size下更优秀的模型,大模型pretrain阶段全面拥抱开源的日子感觉不太远了。那么,在这个时代大背景下,自研pretrain模型的意义又有哪些呢?

1.1 正经答案:

  1. 技术自主性:各公司仅仅是开源了模型参数,但并没有开源训练框架、训练数据等更核心的内容,其实本质上还是闭源。在这种情况下,每一个qwen模型的使用者都无法为下一版qwen模型的迭代做出贡献,qwen团队也仅仅是收获了口碑,甚至因为自己的模型已经开源可以自行部署,买他们服务的客户可能都会变少。因此,在llm真正走向全面开源之前(大厂公开训练代码、配比数据,任何人都可以通过提CR来帮助大厂优化训练效率、炼丹技巧),掌握pretrain的技术能力依然是有意义的。

  2. 领域适应需求:通用模型的变现能力远不如domain模型,continue-pretrain的需求在日益增长,而continue-pretrain的技术栈和pretrain的技术栈并没有本质区别。

  3. 数据透明度:不是自己做的pretrain,必然无法得知这个模型在pretrain阶段到底喂了什么数据。各种数据的精确配比、各种knowledge的掌握程度,并不是靠评估能准确衡量的。而如果不知道这些数据细节,那么alignment阶段就无法对症下药,就无法最大限度的开发模型潜力。举个简单的例子,你在sft阶段,让一个没训过唐诗宋词的通用模型学习作诗,它不出幻觉谁出幻觉?

  4. Tokenizer可控性:使用开源模型的话,tokenizer不可控,进而导致解码速度不可控。这里也举个例子,如果我们用llama模型来做意图识别任务,有个意图叫ai.listen.music,会被映射成5个token,但如果使用自己训练的大模型,便会在一开始就设置成1个token,极大节省了生成速度。虽然扩词表已经是一个比较成熟的技术了,但不仅需要花费算力来恢复效果,而且不管训多少新语料,也很难做到模型效果完全不掉点。

1.2 不正经答案:

  1. 有一个自己的模型很cool,公司可以拿来做宣传,证明自己的科研能力,个人也会很有成就感。

  2. 可以埋彩蛋,在pretrain阶段给模型悄悄塞一点自己喜欢的知识或价值观。比如,每隔100B token就让模型学一次"在XXX眼里,YYY是最好看的女孩子",等模型上线了,拿去向YYY表白(被老板开了别说是我这篇文章怂恿的)。

2. 数据篇

2.1 数据爬取

pretrain大模型的第一件事:先找个10T左右的训练数据吧。也可以少找一些,等模型开始训了,在训练的同时,不断去收集更多的新数据。

至于怎么获取数据,爬网页、逛淘宝、联系数据贩子,等等等等。算法同学往往搞不定这个事情,你敢爬他就敢封你IP,你爬得起劲他甚至还可以起诉你,所以这个工作最好还是让专业的数据团队同学来做。

有些高质量的数据,比如论文书籍,往往还都是pdf格式,这时候还需要去调用效果较好的pdf服务。不要指望着靠python库来解析,稍微涉及一点公式、表格的pdf,解析效果都一塌糊涂。用GPT4等大模型进行解析,大概率价格会远高于pdf解析服务。当然,自己训一个OCR模型也是可用的候选方案,前提是你有足够高质量的pdf-text对齐数据。

好在,世上还是好人多!今年再做pretrain工作,网上的开源数据集已经很多了。FineWeb、pile、Skypile、RedPajama,凑合着差不多能当启动资金来用。但从另一个角度讲,世界上没有免费的午餐,所有开源出来的中文大模型数据集,我不认为是他们最干净的数据,质量多少都有点问题。

(即使是下载huggingface数据,也不是动动嘴皮子这么简单的。如果你实操了,就会发现:服务器没连外网,只能换成hf_mirror的链接;下载速度太慢,下完1T开源数据得好几天,得手动split要下载的数据集合,起多个进程在多台服务器上下载;下载完之后,文件量多的你执行ls都会卡死,你得用大数据集群技术来处理)

准备数据还要懂得一个基础概念:数据的知识密度是有差异的。“唐诗三百首"的知识量要远远大于"中国新闻网的三百篇新闻”。而这种高知识密度的训练数据,往往都是需要花钱的。最近,一种新的数据趋势是"合成高知识密度数据",把几千字的新闻概括成几百字喂给模型,四舍五入也等于训练速度提高了十倍。

总之,如果要认真做pretrain工作,我建议要组建数据团队,爬虫或购买是必须的,否则网上那几个翻来覆去的数据集在清洗之后根本不够用。

2.2 数据清洗

"清洗"是数据环节最最核心的工作,没有之一!

目前,利用模型对pretrain数据的质量进行打分,已经成了数据清洗工作的标配,llama3、qwen2的技术报告都有提及。需要注意的是,基本上大家都认同:同等size下,BERT结构的模型的表征能力是强于transformer-decoder模型的,因此打分模型最好还是从BERT家族中选一个来训,效果好、速度还快。至于训练数据怎么搞,还是老一套,让GPT4标注一下,或者是利用"某个源的数据是高质量数据,某个源的数据是低质量数据"这种规则生产一些。特别强调,任何打分器,都会给code、markdown、latex等格式数据打很低的分数,我们必须把这些数据摘出来,免得被直接洗没了。(FineWeb这个数据集,就洗的基本没有code数据了)

训打分器这个工作的难点是要学会放低心态,别那么执拗,不要执着于打分器100%的准确率,凑合能用就行了,有打分器总比没打分器强,但你要花一个月来训打分器,那就还不如没打分器。此外要学会变通,你有32K的语料不代表你要训32K的打分器,训个4K就差不多了,你非要纠结存在"前4K低质量,后28K高质量"这种特殊情况,我只能说算你牛逼。

打分器结果只是众多数据特征中的一个特征,并不一定要完全依赖它来洗数据,可以和其他特征结合使用。这也引出了数据清洗的另一个大杀器:规则。

不要瞧不起规则!不要瞧不起规则!不要瞧不起规则!

数据长度是否少于某个值,数据中某个token的比例超过某个阈值,数据的zh占比、en占比、数字占比,数据是否有"http"字段,数据是否包含了"新冠"、"疫情"等低质量关键词,数据是否包含某些反动词汇,数据是否包含某些黄色字眼,等等等等。用启发式的规则过滤数据并不丢人,洗不干净数据才丢人。

但同时,必须注意到,用规则清洗或者过滤数据的时候,一定不要把数据搞成分布有偏的数据。比如、你觉着:“包含网址的数据质量低,而网址的英文占比高”,所以你把英文占比高的数据都去掉了。整挺好,模型成了单语模型。因此,用规则的时候,一定要多check下被滤出去的数据长什么样子,勤vim一下!

另外,数据脱敏也是数据清洗环节必须要做的一个工作。我们要尽可能的把训练数据中涉及到的人名、电话号码、邮箱等剔除出去,一旦被模型说出来,就构成了隐私侵犯,公司被罚的钱足够雇人把数据脱敏N遍了。更广义的,把数据的"转载自……"删掉,黄色信息、反动信息,references等剔除出去,都可以视作数据脱敏工作的一部分。这个工作好像没任何奇淫巧技,老老实实的写正则匹配吧。

2.3 数据去重

数据环节最考研工程能力的环节到了:对T级别的数据进行去重。

不要心存任何幻想:能不能不做数据去重。答案肯定是不行的!网上基本所有的开源数据,都是来自common crawl,你不去重如何混合使用呢。就算你只使用单一数据源或者自己爬取数据,也应该注意到:网页A引用了网页B,网页B引用了网页C……,网页Z又引用了网页A。这种url循环调用的现象,在互联网屡见不鲜,你的训练数据集大概率会把一个网页翻来覆去的使用。即使能确保是不同的网页,一篇文章也会被知乎、CSDN、博客、微信公众号、小红书等不同软件反复转载。

去重工作唯一可以让步的地方是:是做sentence去重还是做document去重,这个我也不好断定,我的建议是量力而为。能做sentence去重,谁不愿意呢?可是数据量和工作难度也会陡增。

那么如何去重呢?首先,你一定要有一个大数据处理集群,hadoop也好、spark也罢,只要是一个map/reduce的框架就都可以。这个属于汽车的轮子,想要靠python写for循环完成这个工作,确实是勇气可嘉。

然后,就去实现一个简单的minhash代码,没啥难度,ChatGPT一定会写。

数据去重工作有一个比较重要的意识:要先确定需要多少训练数据,再确定去重的粒度。去重工作是没有尽头的,任何时候你都能把数据继续洗下去,所以必须明确自己需要多少训练数据。需要10T训练数据,就卡相似度在80%的阈值进行去重;需要5T的训练数据,就卡相似度在90%的阈值进行去重;以此类推。

目前没有任工作能证明,一条数据在pretrain阶段训多少遍对模型是最友好的。因此,大胆的按需去重,即使去重粒度小,导致一篇文档出现多次,也可以通过让两篇相似文档之间隔尽量多的token来降低影响。

2.4 数据配比

前面提到,我们要在数据清洗的时候把code等格式化数据摘出来,怎么实现呢?训练一个数据分类器!对每一个document进行类别判断,不用特别精准,把数据划分成新闻、百科、代码、markdown、等类目即可,分类器模型依然可以选择使用BERT家族。

不同的数据源,在上文中介绍清洗和去重的时候也要有不同的阈值:

  • 清洗的时候,"代码"和"知识类文本"当然要使用不同的阈值来决定是否是高质量;
  • 去重的时候,"新闻"类可能70%的重复度就不要,"知识"类则可以85%的相似度才丢弃,在丢去重复文档的时候,优先保留数据打分器比较高的数据。

好了,引子环节说完了,默认大家已经都给自己的数据打好了类别,我们继续往下讲配比工作。

大部分的技术报告里,应该都提及了自己的数据是如何配比的,基本上都是"知识 + 代码 + 逻辑"三个大类目,其中知识数据分文中文知识和英文知识,逻辑数据则可以认为是math数据和cot数据的混合体。整体上,大部分中文模型的配比都在这个区间左右:中:英:code = 4:4:2(逻辑数据的比例我没有写进去,加入多少取决于你能收集多少,其他三类数据应该是要多少有多少的存在)。

我们可以根据自己的实际情况调整配比,但英文的比例一定不能太低。目前中文数据的质量不如英文数据质量基本已经成功共识,导致这个现象可能有两个原因:

  1. 中文确实比英文难学,语言空间的复杂度更高;
  2. 中文语料无论是干净程度还是数量级,都无法与英文语料相比较。

2.5 数据顺序

pretrain的本质是一个教模型学知识的过程,既然是学习,那么知识的顺序就显得很重要,总不能先学微积分,再学数字加减法吧。这也就是"课程学习"的核心思想。

课程学习的内容很宽泛,无论是先学难知识、再学脏知识,还是先学好数据、再学脏数据,都可以视为是课程学习。其本质就是在阐述一件事情:"同样1个T的训练数据,通过调整训练顺序得到的不同模型,能力是不同的。"这个观点基本已经被很多团队论证多次了,因此课程学习目前也可以认为是pretrain的标配。

虽然next_token的训练方法,基本不存在模型学不会某条数据的情况。但从另外一个角度来分析,灾难性遗忘可能始终在发生,A + B的学习顺序可能导致A知识遗忘了30%,B + A的学习顺序可能导致B知识遗忘了20%,那后者忘得少自然能力更强啊。而且,如果B是一个简单的知识,那就代表B在训练语料中会出现非常多的次数,即使遗忘了后续也会被重新捡起来,困难知识在全部训练数据中出现的次数自然也会小很多。(全局训练语料中,蜀道难全文出现的次数一定比静夜思全文出现的次数少)。

说了这么多,只是为了强调一件事:数据顺序真的很重要,那么如何敲定呢?

这里我推荐的llama的In context pretrain工作:利用语义相似度,优先将最相似的document进行拼接,从而构成语义更加连贯流畅的上下文,详见论文https://arxiv.org/pdf/2310.10638。

需要强调的一个地方是,llama坚定的认为:在同一条pretrain语料中,无关文档之间不能相互看见。具体来说,sentenceA + “” + sentenceB这样一条训练语料,llama认为sentenceB看不见sentenceA非常重要且效果很好,它在这篇论文和llama3.1技术报告中均有提及。

但在实操中,除了llama,我没听说过还有哪个团队在pretrain阶段做attention_mask,大家的实验结论基本都是做不做mask没什么区别。而且,我个人认为,pretrain阶段应该要培养模型切换topic的能力,在llm的实际应用场景中,我们也不会每切换一个新话题,就起一个新的聊天窗口,模型需要有判断上文信息和当前信息是否相关的能力。因此,如果使用了In context pretrain这篇工作的论文,要不要做attention_mask还是要做实验去斟酌的。

2.6 数据流水线

首先要明确一个概念,pretrain模型一定是动态加载数据的,读1B、训1B、再读1B、再训1B……原因很简单,你不知道你要训多少数据,即使知道你也没那么大的内存空间一下子读取好几T的数据。

再明确一个概念,pretrain阶段模型获取的是token_id,而不是token本身,我们的tokenization、concatenation操作肯定是要提前做好的。当机器读取了一个新数据块之后,如果不能直接去训练,而是还要花时间去转token,去concat、去pad,这简直是对GPU的一种侮辱。

明确这两个概念之后,我们就应该知道,pretrain的两个进程是独立的:“数据处理进程"和"模型训练进程”。前者要保证后者始终有最新的数据可用,除了save_checkpoint的时候,GPU的空闲是一种极大的浪费。

pretrain阶段的数据是可以复用的,高质量数据训多遍对模型并没有坏处。因此,数据处理进程在生产part-00000.jsonl的同时,它也应该标记清楚每一条原始的document数据被使用了多少次,被标记次数多的数据,后续要降低它再被选中的概率。

每个数据块不要太大,因为我们训练的时候,经常有烧卡、loss炸、数据配错了,等不可控的天灾人祸,所以回退到上个数据块进行续训是一个很频繁的操作。较大的数据块自然会导致模型版本回退时损失的算力也较多。这里,我推荐每个数据块都以B为单位,正好是1B、2B、4B等。

每个数据块在训练代码中,自然会对应着一个save_checkpoint的操作,原因也是为了便于训练回退。这里可以分享一个以前的小技巧,曾经因为warmup阶段,数据块大小是动态增长的,阴差阳错地导致模型的保存逻辑始终为False。我们眼睁睁看着tensorboard美丽的符合预期的loss曲线,但就是没办法让它save,浪费了好一通算力。汲取教训之后,组里大佬就发明了一个机制,在训练代码加了个逻辑:如果检测到某个文件夹下存在一个叫"save"的文件,则立刻save_checkpoint,我们将其称为模型动态保存机制(一脸骄傲)。

2.7 数据实验

当把以上的所有环节都串起来后,不要盲目的去开始训练,一定要先在小模型上做好实验,把scaling_law的这个理念先搞懂。具体的实验内容,可以根据自己的时间、人力来三个阶段走:

  1. 粗糙一点的工作:在小模型上起多个数据配比、数据顺序,训练500B左右的数据量,然后选择loss曲线最完美,或者loss下降最低的那个模型(这个阶段刷benchmark意义不大,模型小,训得少,大概率都是瞎蒙);

  2. 专业一点的工作:额外起多个size的小模型,跑出loss结果,结合scaling_law公式,去推算大模型最适合的数据配比、学习率、训练token量等参数;

  3. 创新一点的工作:像llama和deepseek技术报告里提到的一样,去绘制出loss到benchmark的scaling_law,提前预知模型训多少token量能在某个benchmark达到什么样的能力。

这个地方展开说的话,能说巨多的东西,但不方便细说,感兴趣的同学还是多读读各公司的技术报告吧。scaling_law只是放缓了,不是死了,在没有新的技术指引的情况下,scaling_law你不信也得信,它毕竟是用某种规则在做训练,按照自己的直觉来做训练基本等于"random"。

开弓没有回头箭,pretrain的实验阶段一定要做的鲁棒一些。

3. 训练篇

3.1 Tokenizer

我的同事提醒过我:扩词表容易把词表扩错,这和字典树的逻辑有关。简单来说,就是你加入"中华人民"这个新token,并且引入相对应的merge token的逻辑,就可能导致"中华人民共和国"这个旧token永远不会被encode出来,那"中华人民共和国"这个token对应的知识也就丢失了。

因此,提前准备好自己的tokenizer真的非常重要,这就是一个打地基的工作。你如果想着后期可以扩词表解决,那和房子歪了你再加一根柱子撑着有啥区别呢?llama + 扩中文词表 + continue pretrain训出来过效果惊艳的中文模型吗?

至于怎么训tokenizer:找一个内存空间很大的cpu机器,再找一份很大的common数据集,然后利用BPE/BBPE算法去跑(ChatGPT会写),这里只提醒一些细节。

  • 数字切分(虽然不知道OpenAI为什么不做了,但我们还是做吧,避免9.9 > 9.11的问题回答不正确);
  • 控制压缩率,1个token对应多少个汉字:压缩率太低,那就是字太多、词太少,很影响解码效率;压缩率太大,也就是词太多,又会影响模型的知识能力。通常,压缩率越低的模型,loss也会低,大部份中文大模型的1个token会映射成1.5个汉字左右;
  • 手动移除脏token,之前GPT4o的token词表泄漏,就被发现有很多中文的色情、赌博token;
  • 如果提前知道自己的业务场景,那就应该补充业务场景对应的token,增加业务场景文本的压缩率,比如医疗场景,就提前把阿莫西林、青霉素等作为一个token;
  • 词表的中、英覆盖率要足够大,至于其他小语种是否要加,则看业务需求;
  • tokenizer的vocab_size和模型的embedding_size之间,要有一千个左右的buffer,后续的alignment环节,需要大量在pretrain阶段没见过的全新token来做训练。

(说句题外话,我不太理解strawberry有几个"r"这种问题有啥好讨论的。这不是典型的训过就会,没训过就不会的问题嘛。你让模型背一下"strawberry的字母组成是s, t, r, a, w, b, e, r, r, y"不就解决了,这种tokenizer天生解决不了的问题,模型答出来了只能说明它训过,模型答不出来才是常态。希望这种噱头问题以后少点吧,不要天天拿着这种case吸引外行人,让路人都觉着llm实际上蠢的要命。)

3.2 模型结构

一个原则:能抄llama的结构就不要随便创新,就rope + gqa + rms_norm + swiglu,少创新 = 少踩坑,创新的前提是大量鲁棒的实验。如果是1B左右很小的模型,那么embedding和lm_head还需要共享参数,目的是让layer的参数占全局参数的比例大一些,大一点的模型则没有这个必要。

pretrain是个成本极高的工作,一切都要以稳健为主。假设你是pretrain负责人,你为了写论文,为了强行宣传自己的模型比llama更先进,做了一两周实验,在0.5B这种size发现新结构的loss下降更快。你大喜,也没去找两个数学系博士做做理论证明或推导,就去草草的去更改llama结构。等训练一个多月之后发现这个新结构有某个致命缺陷,几千万的算力资金已经投进去了。老板问你什么情况,你说当初在小模型上改结构没出问题啊!老板给你打了低绩效,自己心里又一百个不服气。

我觉着,国内99%的大模型技术岗,并不鼓励创新和试错,老板们只鼓励用最快的速度、最少的钱去追赶OpenAI。除非你的老板真的支持你积极创新,否则不考虑试错成本盲目追求创新的人真的有点大病。

3.3 模型参数

3.3.1 模型的size

我建议不要根据自己的场景需求来敲定模型size(除非是Math等复杂任务必须是接近千亿参数的模型),小模型的极限在哪里目前仍然是个未知数,qwen2.5也贴了一张图,在benchmark上能达到某个分数的模型size,在持续变小。因为模型的size选小了,导致后续应用的时候业务场景扛不住,这种现象其实并不常见,alignment基本都能救回来,无非就是sft的时候在业务能力上过拟合一些罢了。

因此,选模型size主要看两个因素,训练算力和推理算力:

  • 训练算力:手头有多少机器,能做多久pretrain,有多少训练数据,使用的训练框架一天能吃多少token,这些事情都是提前能确定的。假设要在2个月后训完模型,目标训2T数据,那么便可以计算出自己该训什么size的模型。尽量和大厂的模型size保持一致,不瞎创新就不会踩奇奇怪怪的坑,而且模型效果对比的时候也有说服力;

  • 推理算力:实操过模型的同学都知道,用AutoModelForCausalLM加载33B模型基本是卡着一张80G显存的极限在推理,加载72B模型基本上是卡着两张80G显存的极限在推理,稍微多一点seq_len,立刻OOM(暂不讨论量化等因素)。换句话也就是说,假设你有个40B的模型,他和72B模型效果一样,那又怎样呢?他还是得用两张卡推理,在部署成本上和72B模型还是一个量级的。所以,敲定模型size的时候,我们需要知道自己的推理机器是什么,不要出现1张推理卡刚好装不下模型的尴尬现象。适当给模型增大1B、减小1B参数是可以的,这也能解释了为什么不是所有公司都用70B,而是65B、70B、72B、75B等size都有的现象。

3.3.2 模型的超参数size

目前学界都有一个共识:一个好模型是"身材匀称"的。也就是说标准的llama结构应该是横向和纵向呈某种比例的,所以在增大模型size的时候,layer_num和hidden_size要一起递增。具体如何递增,llama论文和李牧老师视频里都有介绍,这里不在赘述了。我的观点依然是普通人没必要研究这个,超参数size的量级就和llama保持一致即可,少创新就等于少犯错。

但具体到该使用什么值的参数,还是有说法的:尽量能被2/4/8/64/128等数整除。不是有什么理论证明,而是训练框架要求你这样。

  • layer_num:有尽量多的质因数,它与pipeline_parallel有关。pipeline_parallel的要求是:assert layer_num % pipeline_size == 0。如果你的layer_num = 30,那它就不能支持pipeline_size = 4。当然,你可以修改训练代码,让pipeline_parallel的时候,不同的卡放置不同数量的layer,但这不就又增加开发成本了;

  • num_head:8的整数倍,它与tensor_parallel有关。tensor_parallel的极限一般是8,因为大于8就引入了机间通讯,训练效率就低了。tensor_parallel的效率很高,能开大就尽量开大一点;

  • hidden_states:128的整数倍,目前没有理由,保不齐以后有;

  • vocab_size:128的整数倍,目前没有理由,保不齐以后有。

另外一个比较重要的超参数是seq_len的选取:无论你的业务场景需不需要长文本,都不要一开始就使用32K/200K这种夸张的数据seq_len,算力根本承受不起,attention的seq_len^2的计算量实在可怕。

rope的NTK外推方法已经是各大厂标配的方案:4K/8K + rope小base + 90%数据量 --> 32K/64K + rope大base + 10%数据量。

3.4 训练框架

megatron和deepspeed该怎么选?直接说结论:从零开始的pretrain必须选megatron,continue-pretrain可以考虑使用deepspeed,换句话说,T级别的token训练量必须是megatron,B的级别token训练量无所谓。

3.4.1 megatron的优点
  • 训练速度快:tensor_parallel和pipeline_parallel被优化的炉火纯青,rope已经被开发成了apex算子,速度远高于llama里的实现方案,据说mlp层的apex算子也正在开发。
  • 参数清晰而明确:argument.py里,你能看见上百个参数配置,哪些层使用dropout等细节,训练框架早就帮你考虑好了,可微操的空间很大。
  • 启动训练的时候模型加载速度快,千亿级别的模型一分钟就加载完毕了,debug十分方便。
3.4.2 megatron的缺点
  • 上手成本很高,新手学习起来比较吃力,对torch的多机通讯需要从头开始学。而且基建工作比较多,trans_megatron_to_hf.py,trans_hf_to_megatron.py对新人也挺折磨的。
  • Nvidia的官方代码库,全是bug,不预留个一周时间进行debug,很难直接跑起来。我有一个同事,非常迷信官方,隔两天就要git pull megatron的官方代码库,只能说工作量一言难尽。
3.4.3 deepspeed的优点
  • 代码简单,超出想象的简单,而且有model.trainer这种大杀器框架。
  • 用户群体多,alignment阶段的开源代码90%都是deepspeed实现。
3.4.4 deepspeed的缺点
  • 训练速度慢,相比于megatron可能有10%~30%左右的算力损失。当然,现在有很多工作已经实现了deepspeed的pipeline_parallel和tensor_parallel,但相对的,用起来也就没那么简单了。
  • 加载速度无敌超级十分很非常慢!!!!!从bash train.sh到看见loss出现,小模型得十几分钟,大模型得半个小时,如果你需要debug,一个下午只够debug个三五次。
  • 代码简单的代价是"微操很难",尤其是使用model.trainer训练,对源码几乎无修改空间。
  • 微软的开源代码库在让人失望这件事上从不让人失望。megatron有bug只是让你跑不起来,你修了就好了。deepspeed的bug或者不合理的设置,并不影响你把代码跑起来,只有在你遇见解释不了的现象的时候才可能暴露。我的同事送给过我一句箴言:“遇见不合理的地方,不要质疑自己,无脑质疑deepspeed官方!”

哦对,无论是用哪个训练框架,都要记得把attention的默认方式换成flash_attention。

3.5 训练技巧

3.5.1 训练效率优化

在模型训练中,要始终谨记一条大原则,一条训练数据的训练速度:卡内通讯 > 卡间通讯 > 机间通讯。

能减小模型的通讯量就去减小,能避免机间通讯就去避免。在显存够用的情况下,能不引入tensor_parallel,pipeline_parallel,sequence_parallel就不要去引入,同样地、能不开offload就不要开offload,能不开重算就不开重算。总结下来就是:

  • data_parallel几乎是唯一不牺牲算力的并行方式;
  • 让数据在显存和内存之间切换,也是很耗时的,这种操作能避免就避免;
  • 同样的结果多次计算,傻子都知道速度会变慢,因此能存储就别重算。
3.5.2 训练loss分析

pretrain训练环节中,最最最最最重要的中间产出,就是tensorboard的那条loss曲线。记住几条:

  • 一定要有channel_loss,至少,中文知识,英文知识,代码这三类数据的loss得分开观察;
  • loss_spike不可忽视,虽然目前还没有工作去证明说loss_spike会对模型造成不可逆的效果伤害,但不出现loss_spike总是好的啊。无论是loss突然激增还是突然激减,都要重点留意,大概率是数据问题(脏数据不仅loss高,也会loss低,全是乱码的数据loss很高,全是换行符的数据loss很低)。如果数据过脏,就回退到上个checkpoint进行训练;
  • 即使数据没有问题,也是有可能出现loss_spike的,这时候要留意下数据的grad有没有异常,大概率和优化器有关,参考llama等的技术报告,好好调整adamw优化器的β1、β2这两个参数,基本上能解决大部分训练初期的loss_spike问题。一旦熬过了训练初期,loss平稳下降到了2~3左右,后续的训练也基本不会再有幺蛾子了。

3.6 训练流程

pretrain训练流程目前基本是固定的,当训练数据和训练代码都准备就绪后,按照以下四个流程来设置学习率和超参数就可以啦:

  1. 开局warmup,学习率缓慢上升到最大;
  2. 中间cos/cos_decay/constant/constant_decay,学习率比较大,是否decay自己决定,多翻翻别人的技术报告,或者在小模型上做实验决定;
  3. 后期改变rope base + seq_len,开始让模型适应长文本;
  4. 收尾anneal,用高精数据、IFT数据等来给强化模型的考试能力,做完退火就该上考场刷benchmark了。

一般pretrain只要启动了,大家就可以去做别的工作了,只要模型训练没停,一般都不需要再有人为干预。烧卡了,loss爆炸了,loss陡然下降了(数据大概率重复了),就回退到上一个checkpoint重启。

4. 评估篇

pretrain的评估相对来说是llm全链路评估工作中最简单的环节,因为它并不需要考察模型的指令follow能力、安全能力、幻觉现象等,只需要看模型整体的知识掌握程度即可。

4.1 PPL

机器学习的基本课:看测试集的loss来衡量模型效果。

准备一些百科、逻辑、code等数据,每天观察模型在这些测试集合上的loss表现,偶尔浮动是正常的,但整体趋势肯定是持续下降,最后趋于稳定的。只要训练集中没这些语料,和训练曲线应该是基本一致的。

特别地、ppl只能是自己的模型和自己比,前面说过,由于tokenizer的压缩率不同,不同模型的loss没有明确可比性,全是字没有词的tokenizer,loss一定是最低的。不过另一方面,不管你的tokenizer压缩率多少,在通用知识测试集上的loss也是应该能降低到2以下的,否则就说明有点训练不充分。

4.2 Benchmark

如果pretrain阶段全程是自己在负责,那么benchmark还是有一定可信程度的。但无论是继承同事的checkpoint,还是下载开源模型的checkpoint,一律视为它们刷过榜。除了模型完全给自己用的公司,只要是有宣发需求的pretrain团队就一定会刷榜,否则根本和别的模型比不了。这种情况下,benchmark考察的就是谁遗忘的少,而不是谁具有的知识多,训的越多模型得分就越低。

即使排除了刷榜问题,现在最主流的benchmark,形式也都实在是有点单一了。全是选择题,而且是没有cot环节的选择题,衡量模型的能力肯定是有偏颇的。cot就是模型的草稿纸,就算是我们人,拿到一个选择题,不去演算一下就直接说出A、B、C、D该选哪一个,也是很匪夷所思的。

可是话说回来,benchmark毕竟是现成的具有高密度知识的question-answer的高质量语料,不使用着实可惜。这里我的建议是改造benchmark,无论是评估自己的模型还是评估别人的刷过榜的模型,尽量用生成式的方法去使用benchmark,而不是看A、B、C、D哪个概率高。

改造可以是任意形式的,我举几个例子:

  • Question + Answer_A,Question + Answer_B,Question + Answer_C,Question + Answer_D。请结合上述文本,回答Question;
  • 把Question的正确答案丢弃,假设B选项是正确的答案,那就把它改成"B. 其他答案全错",看模型还能不能把B选出来;
  • 把原始benchmark的A/B/C/D改成一/二/三/四;
  • 把多选题改造成单选题;
  • Question + A/B/C/D,先把这个知识让模型不知道答案的情况下训一遍,然后让模型直接说出Question的正确答案;
  • ……

如果担心模型不follow格式,那就用few_shot的方式让模型去抄袭格式,通常都能follow住;如果担心模型停止不了,就限制max_new_token,或者是使用StoppingCriteria,让模型见到换行符就停止。

总之,我们要灵活善变的来利用benchmark,既然你训过了,那我就各种花式改造benchmark的使用方法,来跳过你训过的数据,最起码让模型背过的背答案失效——考纲不变,考卷换了。

改造方式大家可以自己多多揣测,找到自己最喜欢的那一款,反正是用来证明模型能力的,不是打榜的,得分高低没有意义,得分是否持续升高才有意义。还有,不管改造成什么形式,一定要用ACC来衡量评估结果。BLEU和Rouge的得分还是太抽象了,连做case分析都不行。

4.3 概率探针

从概率的角度来监控模型的知识能力有没有遗忘或者提升,适用于我们要观察模型的某一项具体能力。思路也非常简单,我们就观察某个token的概率是否增加,某个句子的概率是否增加,唯一麻烦的地方是探针测试集往往需要训练者一条一条亲自去构造,而不能批量生成。

比较抽象,我举几个例子:

  • Prob(‘北京’|’中国的首都是’),就看一下这个概率值随着pretrain推进是否持续在增大;
  • PPL(‘台湾属于中国’),这个句子的ppl是否持续在增大;PPL(‘台湾不属于中国’),这个句子的ppl是否持续在减小;
  • 对比探针,PPL(‘尊重同性恋’) > PPL(‘反对同性恋’)是否成立;
  • 指令follow能力,Prob(‘{’ | ‘以json输出’);
  • ……

和benchmark改造类似,概率探针是可以根据自己的喜好任意构造的。我们重点观察的也是指标的变化趋势,而不是指标的绝对大小。

5. 总结篇

5.1 结语

pretrain的全环节大抵如此,我列出来的每个环节我认为都是同等重要的。之前看见有种说法说洗数据是脏简历的工作,恕我不能认同。如果infra团队已经帮忙调通了megatron的训练代码,那么训练才是真的最没技术含量的工作,改几个参数,然后bash train.sh,训练挂了就重启,这些工作谁做不来呢?反倒是洗数据时的灵光一现,往往能大大提升模型的效果。因此,"数据篇"也是我笔墨最多的篇章。

希望这篇文章能帮到对pretrain感兴趣的同学,至于细节就不要再追问了,更多东西是真不能说了。另外,本文章由我的饭搭子赞助完成,文章的大多数内容都是和他一起讨论、实践所得。


6. 解决方案

6.1 数据获取方案

  • 组建专业数据团队:负责爬虫、购买和清洗工作
  • 混合数据源策略:开源数据+自研高质量数据
  • PDF解析服务:建立专门的PDF处理流程

6.2 数据处理方案

  • 自动化清洗流水线:结合规则过滤+质量打分模型
  • 分布式去重系统:基于Hadoop/Spark的MinHash实现
  • 数据分类体系:BERT分类器进行文档类别识别

6.3 训练框架选择方案

  • 大型Pretrain项目:优先选择Megatron-LM
  • Continue-Pretrain项目:可考虑DeepSpeed
  • 并行策略优化:根据硬件条件定制化并行方案

6.4 Tokenizer设计方案

  • 业务场景定制:针对特定领域添加专业token
  • 压缩率控制:保持在1token≈1.5汉字左右
  • 脏token过滤:建立定期检查和清理机制

6.5 模型架构方案

  • 稳健优先原则:以Llama架构为基础进行微调
  • 超参数优化:遵循整除原则,便于并行训练
  • 长文本支持:采用NTK外推技术渐进式扩展

6.6 训练监控方案

  • 多维度loss监控:分channel观察训练效果
  • 自动化检查点:建立动态保存机制
  • 异常检测系统:实时监控loss spike和训练异常

6.7 评估体系方案

  • 多样性评估:PPL+改造benchmark+概率探针
  • 防刷榜策略:多样化的benchmark使用方法
  • 趋势分析:重点关注能力变化趋势而非绝对分数

7. 技术架构

7.1 数据工程架构

数据采集 → 初步清洗 → 质量打分 → 去重处理 → 分类标注 → 配比调整 → 序列优化 → 预处理存储

7.2 训练系统架构

  • 训练框架:Megatron-LM/DeepSpeed
  • 并行策略:Tensor Parallel + Pipeline Parallel + Data Parallel
  • 优化技术:Flash Attention + Apex算子 + 梯度检查点

7.3 监控评估架构

  • 训练监控:TensorBoard + 自定义监控面板
  • 质量评估:自动化测试流水线 + 人工分析
  • 性能分析:Profiling工具 + 性能优化建议

7.4 部署运维架构

  • 模型转换:HF格式 ↔ 训练框架格式转换工具
  • 版本管理:检查点版本控制 + 训练记录跟踪
  • 故障恢复:自动化回退机制 + 异常处理流程

8. 案例参考

8.1 成功案例特点

  1. 数据质量优先:注重数据清洗和去重工作
  2. 架构稳健:基于成熟架构进行优化,避免盲目创新
  3. 实验充分:在小模型上充分实验后再进行大规模训练
  4. 监控完善:建立全方位的训练监控和评估体系
  5. 流程规范:标准化的数据处理和训练流程

8.2 经验教训

  1. 数据是关键:80%的问题源于数据质量问题
  2. 稳健胜过创新:在大规模训练中,稳定性比创新性更重要
  3. 工具选型重要:合适的训练框架能极大提升效率
  4. 监控不可少:实时监控能及时发现并解决问题
  5. 评估要科学:合理的评估方法才能准确反映模型能力
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐