LLMs训练避坑帖——如何高效 LLMs pretrain?
共 7971字,需浏览 16分钟
·
2024-10-14 08:00
LLM训练-pretrain
作者:ybq
原文地址:https://zhuanlan.zhihu.com/p/718354385
这篇文章介绍下如何从零到一进行 pretrain 工作。
类似的文章应该有很多,不同的地方可能在于,我并不会去分析 pretrain 阶段的核心技术,而是用比较朴素的语言来描述这个大工程的每一块砖瓦。我的介绍偏方法论一些,主要目的是普及每个环节有哪些必须要做的琐碎工作、有哪些坑、以及有哪些避坑技巧。为了避免老板开了我,文中有一些内容的具体做法不会展开细说,请大家见谅。作为替代,我会推荐一些比较好的开源做法。
一、背景篇
时至今日,dense 模型有 qwen,MOE 模型有 deepseek,小尺寸模型有 minicpm。无论是个人还是大厂,都很难训出同 size 下更优秀的模型,大模型 pretrain 阶段全面拥抱开源的日子感觉不太远了。那么,在这个时代大背景下,自研 pretrain 模型的意义又有哪些呢?
正经答案:
-
各公司仅仅是开源了模型参数,但并没有开源训练框架、训练数据等更核心的内容,其实本质上还是闭源。在这种情况下,每一个 qwen 模型的使用者都无法为下一版 qwen 模型的迭代做出贡献,qwen 团队也仅仅是收获了口碑,甚至因为自己的模型已经开源可以自行部署,买他们服务的客户可能都会变少。因此,在 llm 真正走向全面开源之前(大厂公开训练代码、配比数据,任何人都可以通过提 CR 来帮助大厂优化训练效率、炼丹技巧),掌握 pretrain 的技术能力依然是有意义的; -
通用模型的变现能力远不如 domain 模型,continue-pretrain 的需求在日益增长,而 continue-pretrain 的技术栈和 pretrain 的技术栈并没有本质区别; -
不是自己做的 pretrain,必然无法得知这个模型在 pretrain 阶段到底喂了什么数据。各种数据的精确配比、各种 knowledge 的掌握程度,并不是靠评估能准确衡量的。而如果不知道这些数据细节,那么 alignment 阶段就无法对症下药,就无法最大限度的开发模型潜力。举个简单的例子,你在 sft 阶段,让一个没训过唐诗宋词的通用模型学习作诗,它不出幻觉谁出幻觉? -
使用开源模型的话,tokenizer 不可控,进而导致解码速度不可控。这里也举个例子,如果我们用 llama 模型来做意图识别任务,有个意图叫 ai.listen.music,会被映射成 5 个 token,但如果使用自己训练的大模型,便会在一开始就设置成 1 个 token,极大节省了生成速度。虽然扩词表已经是一个比较成熟的技术了,但不仅需要花费算力来恢复效果,而且不管训多少新语料,也很难做到模型效果完全不掉点。
不正经答案:
-
有一个自己的模型很 cool,公司可以拿来做宣传,证明自己的科研能力,个人也会很有成就感; -
可以埋彩蛋,在 pretrain 阶段给模型悄悄塞一点自己喜欢的知识或价值观。比如,每隔 100B token 就让模型学一次“在 XXX 眼里,YYY 是最好看的女孩子”, 等模型上线了,拿去向 YYY 表白(被老板开了别说是我这篇文章怂恿的)。
二、数据篇
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(逻辑数据的比例我没有写进去,加入多少取决于你能收集多少,其他三类数据应该是要多少有多少的存在)。
我们可以根据自己的实际情况调整配比,但英文的比例一定不能太低。目前中文数据的质量不如英文数据质量基本已经成功共识,导致这个现象可能有两个原因:
-
中文确实比英文难学,语言空间的复杂度更高; -
中文语料无论是干净程度还是数量级,都无法与英文语料相比较。
2.4 数据顺序
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 + "
但在实操中,除了 llama,我没听说过还有哪个团队在 pretrain 阶段做 attention_mask,大家的实验结论基本都是做不做 mask 没什么区别。而且,我个人认为,pretrain 阶段应该要培养模型切换 topic 的能力,在 llm 的实际应用场景中,我们也不会每切换一个新话题,就起一个新的聊天窗口,模型需要有判断上文信息和当前信息是否相关的能力。因此,如果使用了 In context pretrain 这篇工作的论文,要不要做 attention_mask 还是要做实验去斟酌的。
2.5 数据流水线
首先要明确一个概念,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.6 数据实验
当把以上的所有环节都串起来后,不要盲目的去开始训练,一定要先在小模型上做好实验,把 sacaling_law 的这个理念先搞懂。具体的实验内容,可以根据自己的时间、人力来三个阶段走:
-
粗糙一点的工作:在小模型上起多个数据配比、数据顺序,训练 500B 左右的数据量,然后选择 loss 曲线最完美,或者 loss 下降最低的那个模型(这个阶段刷 benchmark 意义不大,模型小,训得少,大概率都是瞎蒙); -
专业一点的工作:额外起多个 size 的小模型,跑出 loss 结果,结合 scaling_law 公式,去推算大模型最适合的数据配比、学习率、训练 token 量等参数; -
创新一点的工作:像 llama 和 deepseek 技术报告里提到的一样,去绘制出 loss 到 benchmark 的 scaling_law,提前预知模型训多少 token 量能在某个 benchmark 达到什么样的能力。
这个地方展开说的话,能说巨多的东西,但不方便细说,感兴趣的同学还是多读读各公司的技术报告吧。scaling_law 只是放缓了,不是死了,在没有新的技术指引的情况下,scaling_law 你不信也得信,它毕竟是用某种规则在做训练,按照自己的直觉来做训练基本等于“random”。
开弓没有回头箭,pretrain 的实验阶段一定要做的鲁棒一些。