├── image ├── image.png ├── image 1.png ├── image 2.png ├── image 3.png ├── image 4.png ├── image 5.png ├── image 6.png ├── image 7.png ├── image 8.png ├── image 9.png ├── Untitled 1.png ├── Untitled 2.png ├── Untitled 3.png ├── Untitled 4.png ├── Untitled 5.png ├── Untitled 6.png ├── Untitled 7.png ├── Untitled 8.png ├── Untitled 9.png ├── Untitled.png ├── image 10.png ├── image 11.png ├── image 12.png ├── image 13.png ├── image 14.png ├── image 15.png ├── image 16.png ├── image 17.png ├── image 18.png ├── image 19.png ├── image 20.png ├── image 21.png ├── image 22.png ├── image 23.png ├── image 24.png ├── image 25.png ├── image 26.png ├── image 27.png ├── image 28.png ├── image 29.png ├── image 30.png ├── image 31.png ├── image 32.png ├── image 33.png ├── image 34.png ├── image 35.png ├── image 36.png ├── image 37.png ├── Untitled 10.png ├── Untitled 11.png ├── Untitled 12.png ├── Untitled 13.png ├── 538587cc32782cf454cc3c37d9de045.jpg ├── 1723703038290_F572471B-367B-44b4-A2F9-A1205C8ABEF3.png └── 从0训练LLM🌤️ db3ed7d1963f4dc7944b6505f4048613.md └── README.md /image/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image.png -------------------------------------------------------------------------------- /image/image 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 1.png -------------------------------------------------------------------------------- /image/image 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 2.png -------------------------------------------------------------------------------- /image/image 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 3.png -------------------------------------------------------------------------------- /image/image 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 4.png -------------------------------------------------------------------------------- /image/image 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 5.png -------------------------------------------------------------------------------- /image/image 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 6.png -------------------------------------------------------------------------------- /image/image 7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 7.png -------------------------------------------------------------------------------- /image/image 8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 8.png -------------------------------------------------------------------------------- /image/image 9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 9.png -------------------------------------------------------------------------------- /image/Untitled 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 1.png -------------------------------------------------------------------------------- /image/Untitled 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 2.png -------------------------------------------------------------------------------- /image/Untitled 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 3.png -------------------------------------------------------------------------------- /image/Untitled 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 4.png -------------------------------------------------------------------------------- /image/Untitled 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 5.png -------------------------------------------------------------------------------- /image/Untitled 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 6.png -------------------------------------------------------------------------------- /image/Untitled 7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 7.png -------------------------------------------------------------------------------- /image/Untitled 8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 8.png -------------------------------------------------------------------------------- /image/Untitled 9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 9.png -------------------------------------------------------------------------------- /image/Untitled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled.png -------------------------------------------------------------------------------- /image/image 10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 10.png -------------------------------------------------------------------------------- /image/image 11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 11.png -------------------------------------------------------------------------------- /image/image 12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 12.png -------------------------------------------------------------------------------- /image/image 13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 13.png -------------------------------------------------------------------------------- /image/image 14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 14.png -------------------------------------------------------------------------------- /image/image 15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 15.png -------------------------------------------------------------------------------- /image/image 16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 16.png -------------------------------------------------------------------------------- /image/image 17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 17.png -------------------------------------------------------------------------------- /image/image 18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 18.png -------------------------------------------------------------------------------- /image/image 19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 19.png -------------------------------------------------------------------------------- /image/image 20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 20.png -------------------------------------------------------------------------------- /image/image 21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 21.png -------------------------------------------------------------------------------- /image/image 22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 22.png -------------------------------------------------------------------------------- /image/image 23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 23.png -------------------------------------------------------------------------------- /image/image 24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 24.png -------------------------------------------------------------------------------- /image/image 25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 25.png -------------------------------------------------------------------------------- /image/image 26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 26.png -------------------------------------------------------------------------------- /image/image 27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 27.png -------------------------------------------------------------------------------- /image/image 28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 28.png -------------------------------------------------------------------------------- /image/image 29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 29.png -------------------------------------------------------------------------------- /image/image 30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 30.png -------------------------------------------------------------------------------- /image/image 31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 31.png -------------------------------------------------------------------------------- /image/image 32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 32.png -------------------------------------------------------------------------------- /image/image 33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 33.png -------------------------------------------------------------------------------- /image/image 34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 34.png -------------------------------------------------------------------------------- /image/image 35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 35.png -------------------------------------------------------------------------------- /image/image 36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 36.png -------------------------------------------------------------------------------- /image/image 37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/image 37.png -------------------------------------------------------------------------------- /image/Untitled 10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 10.png -------------------------------------------------------------------------------- /image/Untitled 11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 11.png -------------------------------------------------------------------------------- /image/Untitled 12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 12.png -------------------------------------------------------------------------------- /image/Untitled 13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/Untitled 13.png -------------------------------------------------------------------------------- /image/538587cc32782cf454cc3c37d9de045.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/538587cc32782cf454cc3c37d9de045.jpg -------------------------------------------------------------------------------- /image/1723703038290_F572471B-367B-44b4-A2F9-A1205C8ABEF3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pihang/LLM_Learning_ph/HEAD/image/1723703038290_F572471B-367B-44b4-A2F9-A1205C8ABEF3.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 从零预训练LLM、SFT、RLHF、DPO笔记整理+面试问题 2 | 3 | # 从0训练LLM🌤️ 4 | 5 | ![Untitled](image/Untitled.png) 6 | 7 | 11 | 12 | 由于预训练任务的本质在于「续写」,而「续写」的方式并一定能够很好的回答用户的问题。既然模型知道这些知识,只是不符合我们人类的对话习惯,那么我们只要再去教会模型「如何对话」就好了。**这就是 Instruction Tuning 要做的事情,即指令对齐。** 13 | 14 | 18 | 19 | 参考网址: 20 | 21 | 1. 训练llama3-20M:https://github.com/Mxoder/LLM-from-scratch/tree/main 。 公众号记录:[https://mp.weixin.qq.com/s/Yf_NU3pgedLHl8dWAaMRfQ](https://mp.weixin.qq.com/s/Yf_NU3pgedLHl8dWAaMRfQ) 22 | 2. InternLM: A Multilingual Language Model with Progressively Enhanced Capabilities. 证明了训练数据可以分成多个阶段,进行课程学习也能提升效果。 23 | 3. Scaling Language Models: Methods, Analysis & Insights from Training Gopher. DeepMind证明了提升模型规模和提升数据质量同样重要,仅仅是大模型也做不好推理任务,但如果数据处理的好的话,模型的推理能力能大幅提升。 24 | 4. 数据处理的思考:[https://mp.weixin.qq.com/s/oKMLhw5hk0LP85dtRAzBDg](https://mp.weixin.qq.com/s/oKMLhw5hk0LP85dtRAzBDg) 25 | 5. karpathy大神训练1B的nanoGPT: [**https://github.com/karpathy/nanoGPT**](https://github.com/karpathy/nanoGPT) 26 | [https://mp.weixin.qq.com/s/d1ypjLwaJKEV8Edfz83tVw](https://mp.weixin.qq.com/s/d1ypjLwaJKEV8Edfz83tVw)【**从零训练的 1B 以下小模型汇总**牛逼整理】 27 | 6. TinyLLama-1.1B模型训练:https://github.com/jzhang38/TinyLlama/blob/main/README_zh-CN.md【A100 *16 ,3个月,3万亿token,后面优化】 28 | 7. 训练的比较小而美:https://github.com/EleutherAI/pythia,有好多M级别checkpoints 29 | 8. 阅读了Allen实验室发布的关于[**OLMO模型**的技术报告](Accelerating the Science of Language Models),大方地开源了整套大模型构建的代码,涵盖了数据处理、训练、评估等各个环节。**https://github.com/allenai/OLMo** 30 | 9. [MINI_LLM](https://github.com/jiahe7ay/MINI_LLM),面壁智能的一位员工分享。知乎:https://zhuanlan.zhihu.com/p/684946331 31 | 10. [baby-llama2-chinese](https://github.com/DLLXW/baby-llama2-chinese),教你从头训练以及SFT,证明数据和模型参数才是王道。 32 | 33 | ![Untitled](image/Untitled%201.png) 34 | 35 | 11. tokenizer到RLHF全流程,并支持下游任务sft微调,给出三元组信息抽取微调示例:https://github.com/charent/ChatLM-mini-Chinese/ 36 | 12. 小项目:https://github.com/AI-Study-Han/Zero-Chatgpt 37 | 13. 38 | 39 | ![Untitled](image/Untitled%202.png) 40 | 41 | ![image.png](image/image.png) 42 | 43 | 问题1:微软的工作 TinyStories小模型探索? 44 | 45 | 原工作用的是 GPT Neo 架构(可以看他们的 config),这个算是很老的模型了用来追踪复现gpt3,现在我改成llama3。像早期的一些语言模型如 GPT-2,即使在一些 Common Craw 这样的语料库上大量预训练后,也很难生成长的、连贯的文本。比如前几年有一种 AI 玩具类型是做文本续写,例如彩云小梦,可以写写作文、小说什么的,如果大家玩过就知道效果其实一言难尽,和今天的大模型完全没法比,其实这就是 GPT-2 level 的续写能力。 46 | 47 | 追问:那么为啥不行呢? 48 | 49 | 会不会是因为训练的语料库太多、太宽泛,需要学习各种语法元素、词汇、知识、推理等等,才导致小语言模型(SLM)没法有一个很好的表现。作者决定专注于一个任务——短篇故事续写,来探索一下 LM 的性能边界。 50 | 51 | ## 数据为王 52 | 53 | 问题1:数据痛点有哪些? 54 | 55 | 数据处理,**数据配比**,模型调优,评估方案透露很少。 56 | 57 | 但中文社区之前最大的数据集仍是Wudao-data,首先量级上和英文数据集完全不对等,Wudao-data仅包含大约53B的 token,而C4则是超过100B,RefinedWeb、ThePile、The Stack等数据集则是在500B token的量级。其次是质量,尽管Wudao-data经过了严格的清洗过滤,但我们还是发现有很多格式错乱、重复、数据质量低的问题。 58 | 59 | 按照现在开源模型与日俱增的训练数据量来看,后续开源的基础模型估计都得2T tokens起步了,否则效果难以比肩(按照scaling law来看) 60 | 61 | ![Untitled](image/Untitled%203.png) 62 | 63 | **启智AI协作平台** 64 | 65 | 1. Wudao**开源200G、53B tokens**(闭源5TB,采用20多种规则从100TB原始网页数据中清洗得出最终数据集):[https://openi.pcl.ac.cn/openihu/WuDaoCorpus2.0_base_200G/datasets](https://openi.pcl.ac.cn/openihu/WuDaoCorpus2.0_base_200G/datasets) 66 | 2. Falcon系列模型是在RefinedWeb数据集【纯英文】上训练的大语言模型。RefinedWeb数据集是基于CommonCrawl构建的高质量数据集,包含了经过去重和过滤后得到的包含上万亿tokens的数据集。**开源600G**(闭源5000G)-2.8TB -别人也就训练7B和40B 67 | 使用trafilatura进行文本提取,文档和行级别规则,NSFW URL黑名单 68 | 去重:精确和模糊匹配:精确子字符串 + MinHash(约50%被移除) 69 | 《The RefinedWeb Dataset for Falcon LLM:Outperforming Curated Corpora with Web Data, and Web Data Only》 70 | 3. **Skywork/Skypile-150B**数据集开源600G约150Btoken(闭源3.2万亿token),包含大约166M个单独网页,平均每篇文章中文字符超过1,000页面中包含的经过处理和清理的文本 (并使用了FastText和BERT等模型移除了黄暴、低质量的内容) ,数据格式为jsonl,每一行为一个文档,按行用json进行解析,文本存放在text字段中。他们说好于wudao而且**最坦诚**的technical report 71 | 闭源的用于**Skywork-13B**预训练:**详见https://github.com/SkyworkAI/Skywork** 预训练分为两个阶段:1、监督模型训练损失和CEVAL、MMLU等准确率变化。 2、第二阶段预训练在通用语料中额外加入STEM(数学,科学,工程,技术)相关数据继续训练。第二阶段训练大约130B token,两阶段总计训练3.2T,产生了我们最终的Skywork-13B-Base模型。 72 | 4. [**TigerBot](https://github.com/TigerResearch/TigerBot),** 基于 GPT3 的 pretrain 的数据分布,采集中文书籍,互联网,和百科类数据,并通过数据源质量分过滤和 tf-idf soft deduping,从 20TB 数据过滤到 2TB,保持语言和类目的比例,并在此基础上随机抽样 [**100G 数据开源**](https://github.com/TigerResearch/TigerBot?tab=readme-ov-file#%E9%A2%84%E8%AE%AD%E7%BB%83%E6%95%B0%E6%8D%AE) 73 | 74 | ![Untitled](image/Untitled%204.png) 75 | 76 | 5. [**书生·万卷](https://opendatalab.org.cn/OpenDataLab/WanJuan1_dot_0)多模态语料库,** 开源分为1,2版本。并且还包括图文数据集1.0和视频数据集。我们使用纯文本数据集1.0,来自网页、百科、书籍、专利、教材、考题等不同来源的清洗后预训练语料组成,数据总量超过5亿个文档,数据大小**超过1TB(中英混合)**。 77 | 78 | ![Untitled](image/Untitled%205.png) 79 | 80 | ![Untitled](image/Untitled%206.png) 81 | 82 | [WanJuan2.0](https://opendatalab.org.cn/OpenDataLab/WanJuanCC)参考是从CommonCrawl获取的一个 1T Tokens 的高质量英文网络文本数据集,在各种验证集上的PPL表现出竞争力,特别是在要求更高语言流畅性的tiny-storys等集上。 83 | • **数据量**:**约 100B Tokens(主要是英文为主)**; 84 | 85 | ![Untitled](image/Untitled%207.png) 86 | 87 | 88 | 问题2、预训练数据组成有哪些? 89 | 90 | 中英文各自:网页数据、社交媒体数据、百科全书、其他年报等,Github。当然构建领域数据评估模型好坏也很重要: 91 | 92 | | 技术文章 | 电影评论 | 政务报告 | 游戏 | 金融 | 通用领域 | 93 | | --- | --- | --- | --- | --- | --- | 94 | 95 | ### 1.1 数据经验 96 | 97 | 面壁科技minicpm作者关于预训练分享: 98 | 99 | 1、中英混合比例。逻辑推理比较强的样本,像代码,数学。这种就是**模型越大,混合的比例反而可以越高。** 100 | 101 | 2、导致ppl崩掉(困惑度异常高或不收敛)的,都要清洗掉,政治敏感数据清洗,去重等,肯定是一个很长的pipeline。 102 | 103 | 3、基于开源数据,做一些聚类的topic。然后基于这些topic,丢到更大的模型,来构建一批高质量的数据,是一个反而比较低成本的方案 104 | 105 | 问题1:不同训练阶段的训练样本有哪些方案? 106 | 107 | 1、**末期高质量样本(面壁科技minicpm),**快速收敛阶段和平稳阶段,都采用普通样本。 108 | 109 | 退火阶段【学习率或其他超参数逐渐降低,以帮助模型更好地收敛到稳定的最优解】,混入高质量样本来做教科书式的学习【提升模型的最终性能】。 110 | 111 | 2、初期高质量样本。先高质量为主让模型快速收敛后添加更多普通样本 112 | 113 | 3、**全程高质量样本(PHIL方式)。**PHIL就是探索小模型能不能在特定领域达到SOTA。**好处,特定榜单/领域效果会特别好。坏处,模型泛化能力会很差**(但PHIL从来没说要做世界模型。 114 | 115 | 问题2:数据配比太重要了,有哪些trick? 116 | 117 | 参考BloombergerGPT,就会发现模型的能力其实很差,比通用大模型会差很多。这里面犯的最大的错误就是数据配比,他们应该是用1:1的比例混合通用数据和金融数据。 118 | 119 | 1、预训练时,通用数据和领域数据绝对不能1:1。对于复现chatgpt3.5来说,**数据配比应该是OpenAI最核心的秘密**和比别人领先最后的地方。 120 | 121 | 2、二次预训练时,领域数据比例要在15%以下,一旦超过这个阈值,模型的通用能力会下降很明显。这个结果其实和ChatGPT大概用不到10%的中文数据就能得到一个很不错的中文模型的结果还挺相似的,每100B的领域数据,需要配上700B-1000B的通用数据,这比直接训练通用大模型要困难多了。 122 | 123 | 3、sft时,其实领域数据和通用数据比例在1:1的时候还是有不错的效果的。当然,如果sft的数据量少,混不混数据的差别就不太大了。(比预训练好多了🧋) 124 | 125 | ### 1.2 数据处理 126 | 127 | [https://mp.weixin.qq.com/s/oKMLhw5hk0LP85dtRAzBDg](https://mp.weixin.qq.com/s/oKMLhw5hk0LP85dtRAzBDg) 128 | 129 | 问题1:预训练的数据可以分为哪两类? 130 | 131 | 一类是网页数据(web data),比如非盈利性机构构建的CommonCrawl数据集(5000G过滤后)是一个海量的、非结构化的、多语言的网页数据集。它包含了超过 8 年的网络爬虫数据集,包含原始网页数据(WARC)、元数据(WAT)和文本提取(WET),包含数百亿网页,数据量级在PB级规模 132 | 133 | 二类称之为专有数据(curated high-quality corpora),为某一个领域、语言、行业的特有数据。比如对话、书籍、代码、技术报告、论文考试等数据。 134 | 135 | 问题2:专有数据有多难开源的? 136 | 137 | 就拿高质量的book书籍数据来说,在网上能直接获取到数据来自The pile中的Book3,量级也才85GB左右,和这些巨头所用数据量级相差数十倍(Books – 2TB)。 138 | 139 | 问题3:文本去重和存储推荐啥格式? 140 | 141 | 推荐使用`parquet`格式(列式存储格式,只读取相关的列, Hadoop 生态系统开发/həˈduːp/)存储数据,可以大大减小存储占用 142 | 143 | 推荐使用`Minhash`去重,效果优于Simhash,但时间消耗长!([参考](https://github.com/DLLXW/baby-llama2-chinese)) 144 | 145 | 问题4:数据处理过程? 146 | 147 | 根据分析得到的数据集特征,调整配置文件,再进行数据处理: 148 | · 数据处理30法则:若某个数据点超出均值±30的范围,通常被视为异常值 149 | · **先进行筛选,再过滤**,能减少数据处理的时间 150 | 151 | 好数据模板: 152 | 153 | ![538587cc32782cf454cc3c37d9de045.jpg](image/538587cc32782cf454cc3c37d9de045.jpg) 154 | 155 | > LLama,Falcon等模型已经证明,预训练中大部分使用网页数据可以训练出非常不错的大模型。 156 | > 157 | 158 | 智源社区:[https://hub.baai.ac.cn/view/30442](https://hub.baai.ac.cn/view/30442) 159 | 160 | ## scaling law 161 | 162 | 由OpenAI在2020年提出,经验公式: 163 | 164 | 168 | 169 | 根据scaling law,模型越大,高质量数据越多,效果越好。 170 | 171 | 但还有一个很直观的情况,随着预训练样本的质量不断提升,训练手段的优化。新的模型,往往效果能轻松反超参数量两倍于它的模型。 172 | 173 | 《**Scaling Laws for Neural Language Models**》 模型的最终性能**「主要与」**计算量,模型参数量和数据大小三者相关,而与模型的具体结构(层数/深度/宽度)基本无关。假定计算量整体放大10倍,OpenAI认为模型参数更重要,模型应放大 (5.32)倍,数据放大 (1.86)倍;后来DeepMind和Google认为模型参数量与数据同等重要,两者都应该分别放大 (3.16)倍。 174 | 175 | 不过LLama认为这个观点是针对**「训练阶段」**而言的,并不是**「推理阶段」。尽管根据Scaling Law,`10B模型只需要200B的数据( Chinchilla 定律),但是作者发现7B的模型性能在1T的数据后还能继续提升`。** 176 | 177 | 过拟合程度与模型大小和数据集大小的比例有关。每增加8倍模型参数,只需增加约5倍数据就能避免性能损失。 178 | 179 | 故事: 180 | 181 | > 但我们回到2020年,当大部分人都在基于bert做各种魔改的时候。OpenAI发现了这么一个规律。数据,训练参数一直增长下去,好像loss的确是在不断的下降哎?于是,他们拿着这个paper去问微软的CTO,你想不想看看这个loss下降到一定程度会发生什么?会发生什么 182 | chatgpt就出来了 183 | > 184 | 185 | ## **Tokenizer** 186 | 187 | LLaMA 2 的分词器,二代的词表比较小(32k),LLaMA 3 的词表太大了(128k),在 SLM 中会占用太多的参数比重,并且这只是个专有任务数据训练,没必要用太大的词表。[QWEN-1.8b] 188 | 189 | > **分词后一个中文大概1~3token占用,每一个token只需要两个字节(uint16存储)** 190 | > 191 | 192 | 问题1:自己构建分词器还是用别人的? 193 | 194 | 推荐直接用别人的,**glm2(词表大小=64793)**和qwen的词表中文都很好(llama官方所提供的词表中,中文的部分只有700个,这也是llama中文能力聊胜于无的原因),而且压缩率很高。baby进行过实验,小模型的分词器其实影响不大: 195 | 196 | ![Untitled](image/Untitled%208.png) 197 | 198 | 追问:如果自己构建tokenizer有哪些方法? 199 | 200 | tokenizer到词表构建有三种[token子词划分算法]:BPE(Byte-Pair Encoding)、WordPiece、SentencePiece 201 | 1、**BPE:统计每个连续或相邻`字节对`出现频率【传统的以字符为单个token而非字节,apple字符=[a,p,p,l.e]】,将最高频的连续字节对合并为新的子词**。 GPT2、LLaMA 202 | 2、WordPiece:将能够提升LM概率最大的相邻子词进行合并加入词表 。例如BERT 203 | 3、SentencePiece:把空格当成一种特殊字符处理。例如ChatGLM、PaLM 204 | 205 | 追问1:BPE在中文语料库构建分词器有什么局限? 206 | 207 | BPE提出在英文字符处理多字节的高频字节对合并,英文字符通常是单字节的,因此 BPE 分词器通过字节对的合并操作能够生成合理的子词。但是`中文字符~2-3个字节`,**BPE 可能会错误地将这些字节视为独立的字符或部分字符进行处理,导致在编码过程中将一个完整的汉字拆分成多个错误的字节组合**。如果编码不是UTF-8,解码时会乱码。 208 | 209 | 追问2:那针对中文的分词解决方案? 210 | 211 | 在训练 BPE 分词器时,可以考虑使用基于字符级别的分词器(例如 `BertTokenizer`)或使用专门为中文设计的分词方法,如 Jieba 或 `SentencePiece`。这些工具对中文的处理更友好。 212 | 213 | - `BBPE(Byte-level BPE`)通过将字节作为基本单元进行分词,克服了传统 BPE 在处理多字节字符时的局限性。它的语言无关性和对所有 Unicode 字符的支持,使其成为多语言模型分词的理想选择。 214 | - BBPE[Byte-level BPE]分词流程举例子【将单词拆分为字符序列并在末尾添加后缀“ ``”,统计单词频率】 215 | 216 | 在 BBPE 中,该句子的处理流程如下: 217 | 218 | 1. 将每个字符编码为字节序列: 219 | - “你” -> `E4 B8 8B` 220 | - “好” -> `E5 A5 BD` 221 | - “吗” -> `E5 90 97` 222 | - “?” -> `3F` 223 | 2. BBPE 根据频率统计合并高频字节组合,可能最终将 `E4 B8 8B`、`E5 A5 BD` 等组合为单独的子词。 224 | 3. 最终,编码器将输入句子编码为字节组合序列。 225 | 226 | ~~问题1:为什么分词批量填充时padding_side='left’?~~ 227 | 228 | 对于 decoder-only 的模型做生成任务是必要的,因为我们本质上做的是 next token prediction,如果 pad 挡在了生成序列的右边,会影响到模型生成。 229 | 230 | 问题2:自回归语言模型训练用的分词器处理是啥函数? 231 | 232 | 使用 `DataCollatorForLanguageModeling` 进行自回归语言模型训练时,通常会对输入序列进行填充,并生成相应的标签用于预测下一个token。【不做MLM完型填空任务其实就是token预测】 233 | 234 | - 分词预测代码 235 | 236 | ```python 237 | # DataCollatorForLanguageModeling 238 | dc = DataCollatorForLanguageModeling(tokenizer, mlm=False) 239 | data = ['南京', '南京市', '南京市长江'] 240 | 241 | raw_tokens = [tokenizer(text) for text in data] 242 | 243 | print(f'tokenizer.pad_token_id: {tokenizer.pad_token_id}\n') 244 | print(dc(raw_tokens)) 245 | 246 | ''' 247 | tokenizer.pad_token_id: 151643 248 | 249 | { 250 | 'input_ids': tensor([[151643, 151643, 102034], 251 | [151643, 151643, 112891], 252 | [102034, 102975, 69177]]), 253 | 'attention_mask': tensor([[0, 0, 1], 254 | [0, 0, 1], 255 | [1, 1, 1]]), 256 | 'labels': tensor([[ -100, -100, 102034], 257 | [ -100, -100, 112891], 258 | [102034, 102975, 69177]]) 259 | } 260 | ''' 261 | labels 确实是 input_ids 的原位复制,区别在于 input_ids 里用 pad_token_id 来填充,labels 里对应的是 -100、表示不计算 loss 262 | ``` 263 | 264 | 265 | 追问:DataCollatorForSeq2Seq 和 DataCollatorForLanguageModeling区别是啥? 266 | 267 | DataCollatorForSeq2Seq:用于序列到序列任务,处理输入和输出序列对,适用于机器翻译、文本摘要等任务。 268 | DataCollatorForLanguageModeling:用于语言模型任务,处理单个序列,适用于掩码语言模型和自回归语言模型(Causal Language Modeling, CLM)的训练。 269 | 270 | 问题3:为什么tokenizer压缩率高了好? 271 | 272 | 比如Qwen的分词器就很好,首先压缩率指的是tokenizer将文本转换为token序列时的效率和效果。压缩率越高,代表相同文本内容时,生成的token序列更短,每个token包含信息量更大。这样的好处使可以减少计算消耗,提高模型效率,降低数据冗余。 273 | 274 | 问题4:分词器处理数据如何存储的? 275 | 276 | 数据预处理采取GPT的通用做法,对语料进行提前分词,对一个样本做完分词后在末尾加上一个结束符号``,与下一个样本区分开。然后将所有的训练语料拼接成一个数组(`np.uint16`)**以.bin二进制格式存储到磁盘上**。如果语料过大,避免内存溢出,可以选择mmap格式。 277 | 278 | 追问:uint16是啥?mmap格式是啥? 279 | 280 | uint16是Unsigned 16-bit Integer,非负整数,占2字节,比传统int32(有负的)节约一半内存存储。 281 | 282 | **mmap(内存映射文件)**是一种用于将文件或文件的一部分映射到进程的地址空间的技术。这**使得文件的内容可以像在内存中一样进行访问,而不需要将整个文件加载到内存中—-多进程可以通过映射共享文件数据防止多次IO操作**。【以像操作内存数组一样操作文件内容。这种方式可以提高文件I/O操作的效率】 283 | 284 | 问题5:special_tokens在预训练中的作用? 285 | 286 | 假设tokenizer.special_tokens['']=2,这个和tokenizer本身SentencePiece 也将 定义为 2要进行明确,可以覆盖。然后在预训练阶段我们通过这个token_id=2也就是来分割文本之间。假设输入[30910, 56961, 2, 54640] →decode后为‘昭通’; 实际[30910, 56961, 54640] →decode后也为‘昭通’。**这个2在字符串是没有任何意思的,但是对模型输入是有很大用的,他占了一个token位置,并且会参与attention计算**,所以之前有学者**SoftMax-off-by-One做文章,防止(量化或流式部署时有问题),97% 以上的异常激活发生在空格和标点符号位置上** 287 | 288 | - 代码实现 289 | 290 | ```python 291 | import sentencepiece as spm 292 | import torch 293 | sp = spm.SentencePieceProcessor() 294 | sp.load('./chatglm_tokenizer/tokenizer.model') 295 | X = torch.tensor([[30910, 56961, 2, 54640]], device='cuda:0') 296 | tokens_list = X[0].tolist() 297 | print(sp.decode(tokens_list)) 298 | # 想查看tokenizer.special_tokens['']=2 也就是说位置 299 | print(((X[0] == 2).nonzero(as_tuple=True)[0]).tolist()) 300 | ``` 301 | 302 | 303 | 问题6:embedding层参数为啥不宜过大? 304 | 305 | 为了避免模型头重脚轻,所以词表和embedding层参数不能太大。如果embedding层的参数占比过高,导致过拟合模型可能会更容易记住训练数据而不是学习到通用的特征。并且浪费推理资源而且embedding层的影响可能会掩盖其他层的效果,使得调参过程复杂化。 306 | 307 | 问题7:如何训练自己的BPE分词器? 308 | 309 | **调用tokenizers库里的方法,初始化tokenizer,设置BPE训练器和设置词汇表大小3.2w,将文本yield逐句读取训练,同时设置字符级解码**。如下代码 310 | 311 | - tokenizer训练代码 312 | 313 | ```python 314 | # 初始化tokenizer 315 | tokenizer = Tokenizer(models.BPE()) 316 | tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) # 处理字节级文本的预处理器 317 | 318 | # 设置训练器 319 | trainer = trainers.BpeTrainer( 320 | vocab_size=32000, # 创建一个 BPE 训练器,设置词汇表大小为 32000 321 | show_progress=True, 322 | initial_alphabet=pre_tokenizers.ByteLevel.alphabet() # 初始化训练器的字母表 323 | ) 324 | 325 | # 读取文本数据 326 | texts = read_texts_from_json(data_path) # 逐个句子读取 327 | 328 | # 训练tokenizer 329 | tokenizer.train_from_iterator(texts, trainer=trainer) 330 | 331 | # 设置解码器 332 | tokenizer.decoder = decoders.ByteLevel() # 用于将分词后的字节级别标记转换回文本 333 | 334 | # 保存tokenizer 335 | tokenizer_dir = "./miaomiao_tokenizer" 336 | os.makedirs(tokenizer_dir, exist_ok=True) 337 | tokenizer.save(os.path.join(tokenizer_dir, "tokenizer.json")) 338 | tokenizer.model.save("./miaomiao_tokenizer") 339 | ``` 340 | 341 | 342 | 最主要、手动添加特殊标记。 343 | 344 | 1、这里添加`system`, `user`, `assistant,<|endoftext|>`等为词表外的特殊解码词, 345 | 346 | 2、另外指定`"additional_special_tokens": ["<|im_start|>", "<|im_end|>"]为特殊tokens【这个占了token_id,但解码后没有任何意义,GPT4输入这个都会当成空】` 347 | 348 | 3、最后`bos_token`, `eos_token`, `pad_token`, `unk_token` 是常用的特殊 token,分别表示句子的开始、结束、填充和未知 token。这里配置为空或 `None`。其中`"eos_token": "<|im_end|>”+"pad_token": "<|endoftext|>",` 349 | —-——-—chat_template配置也在分词器构建进行———- 350 | 351 | 模板中使用了 `Jinja2` 风格的语法 352 | 353 | "chat_template": "{% for message in messages %}{% if loop.first and messages[0]['role'] != 'system' %}{{ 'system\n你是一个由喵阿姨开发的喵喵小助手\n' }}{% endif %}{{'' + message['role'] + '\n' + message['content'] + '' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ 'assistant\n' }}{% endif %}” 354 | 355 | 问题8:BPE生成哪些分词信息文件? 356 | 357 | merges.txt(存储了 BPE 合并规则)和vocab.json( token 到 token ID 的映射)。这两个文件包含了BPE算法所需的核心信息。 358 | 359 | tokenizer_config.json(配置了分词器的相关参数,如 tokenizer 类、特殊标记等),tokenizer.json(通常包含完整的词表、BPE 合并规则) 360 | 361 | ![image.png](image/image%201.png) 362 | 363 | ## 混合精度 364 | 365 | 问题1:vLLM是啥? 366 | 367 | 答:vLLM 采用一种**集中式调度器**(scheduler)来协调分布式 GPU 工作器(worker)的执行。KV 缓存管理器由 PagedAttention 驱动,能以分页方式有效管理 KV 缓存。 368 | 369 | v100不支持bf16, 改用fp16吧。 370 | 371 | 问题2:**bf16和fp16区别?** 372 | 373 | 都是半精度,2字节。BF16可表示的整数范围(指数位)更广泛,但是尾数精度较小(有效精度尾数位);FP16表示整数范围较小,但是尾数精度较高。 374 | 375 | ![Untitled](image/Untitled%209.png) 376 | 377 | **问题3:显存占用有哪些?** 378 | 379 | 四部分:模型权重+优化器、梯度、动量等+前向激活值+cache 380 | 381 | ![Untitled](image/Untitled%2010.png) 382 | 383 | **问题4:激活显存(Activation Memory)是啥作用?如何节约显存占用** 384 | 385 | 激活显存(Activation Memory)指的是在深度学习模型的前向传播和反向传播过程中,存储中间激活值所需的显存。这些激活值是神经网络每一层的输出,它们在反向传播阶段用于计算梯度。 386 | 387 | **激活显存 = 每一层输出D × 批次B × 序列长度L × 2bytes (所以训练加载如果激活要预留一般显存)** 388 | 389 | 追问:所以如何管理显存占用呢? 390 | 391 | — **梯度检查点**(Gradient Checkpointing):通过在前向传播过程中有选择地存储激活值,在需要时重新计算未存储的激活值,以减少显存占用。 392 | 393 | — **混合精度训练**(Mixed Precision Training):使用FP16而不是FP32来存储激活值,可以显著减少显存占用。 394 | 395 | —**分布式训练**(Distributed Training):将模型和数据分布到多个GPU上,可以有效地分摊激活显存的需求。 396 | 397 | —**调小batch**,或者**累计梯度**模型大batch的训练效果 398 | 399 | 问题5:在反向传播时对梯度计算有做什么处理? 400 | 401 | 梯度剪裁,在1阈值进行基于梯度L2范数的裁剪,不过剪裁前需要先取消缩放(unscale)这些梯度【考虑到混合精度+累计梯度对loss、优化器的缩放】。在反向传播时防止梯度爆炸,从而确保训练过程的稳定性对深层网络结构友好。 402 | 403 | 实现:================= 404 | `scaler = torch.cuda.amp.GradScaler(enabled=(dtype == 'float16')) 405 | scaler.unscale_(optimizer) 406 | torch.nn.utils.clip_grad_norm_(model.parameters(), 1)` 407 | 408 | 追问5:混合精度训练通过什么实现的? 409 | 410 | 首先,混合精度上下文管理器 (`torch.cuda.amp.autocast()`), 411 | 大多数的计算会使用 `float16` 进行加速和节省显存,只有在需要较高精度的操作(如 `softmax`)时会自动回退到 `float32`. 412 | `torch.cuda.amp.**GradScaler`【**在反向传播之前,将损失放大,这样即使在 `fp16` 精度下计算,梯度值也足够大,避免因数值精度问题导致梯度消失**】** 负责在训练过程中动态调整缩放因子,以避免数值不稳定的情况。也就是**在正向计算保存为fp16,反向梯度更新要还原为fp32。** 413 | 414 | ## 优化器 415 | 416 | 问题1:**优化器和LR scheduler如何协同工作?** 417 | 418 | 首先,优化器负责更新模型参数以最小化损失函数(比如Adam会维护一些内部状态,比如动量、学习率调整影响 参数更新 的方式),而学习率调度器负责动态调整学习率,以便在训练过程中更好地控制参数更新的步长。 419 | 420 | 424 | 425 | 问题2:什么是**W-(C)-S-D策略<—学习率策略?** 426 | 427 | 改进:`Warm-Cosine-Stable-Decay` 【warmup预热+余弦退火策略+最小学习率收尾】 428 | 429 | - Warmup:与现有策略相同,初期阶段逐渐增加学习率。 430 | - Cosine:在训练的主要阶段,采用**余弦退火(Cosine Annealing)策略。学习率按照余弦函数变化,逐渐减小到一个最小值**。这种策略有助于在训练过程中跳出局部最优,因为学习率会周期性地增大和减小。 431 | - Stable:**在训练即将结束时**,将学习率升高到一个稳定值。这段时间内,模型以稳定的学习率进行训练,有助于模型在新的学习率下找到更优的解。 432 | - Decay:在训练的最后阶段,逐渐减小学习率,以帮助模型收敛到最终的解。 433 | - lr代码 434 | 435 | ```python 436 | def get_lr(it): 437 | # 1) 线性 Warmup从0开始增到0.1 438 | if it < warmup_iters: 439 | return learning_rate * it / warmup_iters 440 | # 2) if it > lr_decay_iters, return min learning rate 441 | if it > lr_decay_iters: 442 | return min_lr 443 | # 3) 余弦衰减to min learning rate 444 | decay_ratio = (it - warmup_iters) / (lr_decay_iters - warmup_iters) 445 | assert 0 <= decay_ratio <= 1 446 | coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio)) # coeff ranges 0..1 447 | return min_lr + coeff * (learning_rate - min_lr) 448 | ``` 449 | 450 | 451 | 追问:还有余弦退火策略的步长控制建议? 🉐 🉐 🉐 452 | 453 | 460 | 461 | 468 | 469 | ![在使用余弦退火策略时,合理设置周期长度的重要性。建议在目标步长的110%~120%之间,不要超过25%!!!](image/image%202.png) 470 | 471 | 在使用余弦退火策略时,合理设置周期长度的重要性。建议在目标步长的110%~120%之间,不要超过25%!!! 472 | 473 | > **lr高→探索,低→微调/稳定** 474 | > 475 | 476 | 问题3:配置优化器的部分为什么,大于或等于2D(维度>2D)的参数会被衰减,小于2D不会衰减? 477 | 478 | 神经网络参数的维度决定了其自由度。**对于大于或等于2D的参数(如权重矩阵),通常应用权重衰减以限制参数大小,减少过拟合风险**。对于小于2D的参数(如偏置向量),通常不应用权重衰减,因为其对模型复杂度影响较小。通过为不同参数组设置不同的衰减策略,可以更有效地控制模型复杂度。 479 | 480 | 问题4:自定义的学习率策略在代码如何实现参数更新? 481 | 482 | get_lr()指定策略动态计算当前迭代的学习率,再更新到torch自带`optimizer.param_groups`中优化器中各参数组的学习率[’lr’],从而控制模型训练过程中参数更新的步长。 483 | 484 | ## 评价指标 485 | 486 | [syk-benchmark评价指标] 487 | 488 | 问题1:预训练和微调评价指标不同? 489 | 490 | 比起预训练(Pretrain)环节里相对明确的评价指标(如PPL、NLL等), 491 | 492 | NLL 是指负对数似然(Negative Log-Likelihood),希望最小化(=最大化训练数据的似然)拟合最好。 493 | 494 | ![Untitled](image/Untitled%2011.png) 495 | 496 | Instruction 环节中的评价指标比较令人头疼。BLEU 和 ROUGH 这样的指标已经不再客观 × 497 | 498 | 像 [FastChat] 中一样,利用 GPT-4 为模型的生成结果打分 √ 人类打分 √ 499 | 500 | 问题2:如何对预训练阶段模型评估? 501 | 502 | 传统的评估主要看的是Training loss或者Benchmark上的指标。但是有缺陷: 503 | 504 | 1、Training loss因为数据配比的原因是一个整体的指标,并且不同模型不能相互比较。比如代码占比越高,其实loss会更低因为结构更容易预测。 505 | 506 | 2、Training loss第二个问题是可能会选择出一个更加过拟合的模型 507 | 508 | 3、Benchmark的问题就更加严重了,第一是因为benchmark是公开数据,非常容易针对。Benckmark评估具有一定的波动性&可操作性而且评估是准确率(不连续的指标),不同Prompt选择和答案提取方式会对结果产生很大的影响 509 | 510 | 因此还有一种方法,**构建了中文,英文,代码,arxiv文章等多个领域的验证并且没有出现在训练数据中。通过Cross Entropy损失函数,整体的损失函数为每个位置预测真实词损失的平均 ——来监控模型训练效果**(参考skywork预训练) 511 | 512 | 追问:grokking现象是啥? 513 | 514 | 在前几轮的训练中,模型的损失函数(loss)持续下降,但准确率没有显著提高。突然,在某一轮训练之后,模型的准确率大幅提升。这种现象可以被描述为模型“grokking”了任务。(也就是用准确率是不连续指标,它提醒我们模型性能的提升有时并不是线性的,而是可能在训练的某个阶段突然发生) 515 | 516 | 问题3:评估时为啥要让所有模型的文档长度一样,而不是让分词后token一样? 517 | 518 | 本质上领域困惑度是衡量不同模型生成高质量文档的概率,概率越大模型效果越好,因此我们需要保证所有模型看到的文档是一样的。此外,因为不同模型使用不同的分词器,分词后的token数目差异很大,以Llama为例,会将汉字切分为3个unicode编码,如果按照分词后token比较的话,那么Llama模型看到的文档长度会比其他模型短,而我们知道文档前面的token loss是更低的,后面token loss更高,因此按照分词后token比较的话会对Llama这种分词更细的模型会不公平。 519 | 520 | 问题4:评测数据的指标如何计算,比如常用的acc分数? 521 | 522 | 这里拿`MMLU综合语义理解`数据评测计算举例子,可以参考/home/ph/LLM/Skywork-main/data/。 523 | 524 | 首先介绍下MMLU测评数据4个文件夹组成(基本都是多选题,出的还挺难):1、Auxiliary Training(用于微调模型来源于多项选择数据集如MCTest、RACE、ARC、OBQA) 2、**Dev , 用于少样本学习few-shot** 3、**Test, 内容最多用于最终评估模型性能** 4、Val, 验证集通常用于在训练过程中监测模型的性能 525 | 526 | CMMLU中文测评集如下: 527 | 528 | ![image.png](image/image%203.png) 529 | 530 | - CMMLU子类69个领域: 531 | 532 | ["农学", "解剖学", "古汉语", "艺术学", "天文学", "商业伦理", "中国公务员考试", "中国驾驶规则", "中国饮食文化", "中国外交政策", "中国历史", "中国文学", "中国教师资格", "临床知识", "大学精算学", "大学教育学", "大学工程水文学", "大学法律", "大学数学", "大学医学统计", "大学医学", "计算机科学", "计算机安全", "概念物理学", "建设工程管理", "经济学", "教育学", "电气工程", "小学语文", "小学常识", "小学信息技术", "初等数学", "民族学", "食品科学", "遗传学", "全球事实", "高中生物", "高中化学", "高中地理", "高中数学", "高中物理学", "高中政治", "人类性行为", "国际法学", "新闻学", "法理学", "法律与道德基础", "逻辑学", "机器学习", "管理学", "市场营销", "马克思主义理论", "现代汉语", "营养学", "哲学", "专业会计", "专业法学", "专业医学", "专业心理学", "公共关系", "安全研究", "社会学", "体育学", "中医中药", "病毒学", "世界历史", "世界宗教"] 533 | 534 | 535 | 然后, 536 | 537 | 追问4:常见可以用来给中文评测的数据集除了CMMLU还有哪些经典的? 538 | 539 | 全面的中文基础模型评测数据集`C-Eval`,由13948道多选题组成涵盖了 540 | 52 个学科和四个难度的级别。 541 | 542 | ![image.png](image/image%204.png) 543 | 544 | 问题5:除了将测评做成选择题分类【大部分结合prompt都是做分类输出】计算acc,对于生成式回答需要啥其他指标? 545 | 546 | Rouge(用于摘要生成任务,特别是在评估覆盖率),BELU(也不合理,只看到了局部n-gram相似) 这两个都是基于浅层匹配的指标,对语义等价的多样化表达不敏感。 547 | 548 | ## 模型架构 549 | 550 | 问题1:LLama2的架构特点有哪些? 551 | 552 | 首先,相比llama1增加如下,llama3架构其实和2一样,只是数据更好更庞大了。 553 | 554 | 1、前置的RMSNorm,相比传统的LN减少了计算均值操作更快。为啥前置pre而不是post呢?因为输入前归一化有助于保持梯度在网络中更稳定地传播和深层叠加【如果在输出再归一,梯度在子层内已经过大变化,可能产生梯度消失或爆炸,不利于梯度稳定训练】GPT-3 和其他类似的模型在设计中都这样 555 | 556 | 2、在QK上使用ROPE旋转位置编码 557 | 558 | 3、使用causal mask保证每个位置只能看到前面的tokens 559 | 560 | 4、down(up(x)×Silu(gate(x))),里面都线性层。用SwiGLU (效果类似平滑版的ReLU)代替了传统的 ReLU 激活函数 561 | 562 | 5、使用GQA代替MHA 563 | 564 | ## 预训练tricks 565 | 566 | 问题1:多阶段渐进式预训练(Multi-phase Progressive Pretraining)是啥?【预训练策略】 567 | -比如在InternLM 568 | 569 | 根据前一阶段的学习情况和侧线实验的结果,调整各个阶段的数据组合和学习设置。每个阶段都有特定的能力发展目标,还要考虑数据配比和上采样,可能在不同阶段使用不同的数据组合,**可能涉及更复杂的课程学习策略**。 570 | 571 | 追问:它和增量预训练(Incremental Pretraining)的区别是啥? 572 | 573 | 增量预训练通常是在原有模型基础上添加新数据训,而非混合。(通常更关注于如何有效地整合新数据,当然可以做点小样本高质量配比) 574 | 575 | 问题2:阶段式训练是咋样的? 576 | 577 | 比如**TinyLLama-1.1B**放出来的所有中间checkpoints,可使用FSDP、flash attention 2、fused rotary positional embedding。(**24k tokens/秒/A100**) 578 | 579 | ![Untitled](image/Untitled%2012.png) 580 | 581 | 问题3:**预训练时有哪些坑?** 582 | 583 | 1.多卡的话最好设置ddp_find_unused_parameters=False,这样也能提升训练的速度 584 | 585 | 2.尽可能地把单个bs调大,因为我试过把单个bs没有调那么大,通过使用累积梯度步数来增大总的bs,但是收敛的没有把单个bs调大快,我在想这个和学习率调整有关。【数设置上有两个地方需要注意:一个是学习率需要略小,在e-5这个量级就可以,**其次是global_batch需要比较大,一般在2-4M tokens,这样训练起来会比较稳定**】 586 | 587 | 3、不说并行分布式计算的效率问题,听说国内某个top 3科技巨头用2000卡训练,中途有卡崩溃,他们花了2天才找到是哪张卡出故障 588 | 589 | 4、无法从收敛曲线去预测模型的实际效果 590 | 591 | 5、过拟合的问题:只用领域数据非常容易过拟合到领域数据上,对OOD的处理会表现的非常差。对各个环节的数据配比要求会很高,最好是在原来规模程度的数据上,增加额外的场景数据,重新走部分流程。**但是困难的是,原有数据保持分布的采样,你拿到的模型是个黑盒,别人并没有给你原始的数据分布,更何况海涉及到了惊细的清洗**。有可能整体要付出的成本不下于重新塑造一个通用大模型。 592 | 593 | 6、需要分布式训练系统的开发工程师,负责把训练框架给支起来,协调、运维和管理这么多机器。 594 | 595 | 追问:上面第5点过拟合,OOD问题如何避免? 596 | 597 | 首先OOD(Out-of-Distribution)由于如果分阶段训练简单数据和复杂数据在分布上有显著差异,模型可能难以适应新的数据分布。因此一定要有合理数据配比,混合使用简单和复杂的数据才能让模型减小过拟合可能。另外构造优秀的验证集评分(最好有个综合验证集减少偏差,当然也可以加点当前那阶段特定验证评估)可以定期监控模型的好坏实现早停或者说打断。 598 | 599 | 问题5:FP32/FP16/BF16的选择问题? 600 | 601 | 更倾向于BF16,因为看起来更好收敛 602 | 603 | 问题6:并行计算方案的选择? 604 | 605 | Megatron-DeepSpeed是现在比较SOTA的一个方案 606 | 607 | 问题7:断点续训要保存checkpoint哪些东西从中断出继续加载? 608 | 609 | --resume_only_model: 默认为False, 即为严格的断点续训, 这会**读取模型、优化器和lr_scheduler的权重和各个设备存储的随机种子, 并将从上次训练暂停的stpes后继续计数进行训练**. 如果设置为True, 则只读取模型的权重. 610 | 611 | 问题8: 预训练`X,Y`如何划分? 612 | 613 | 按照seq_len划分,例如1024,则X[:-1]为(batch, 2013 ) 去预测 Y[1:]为(batch,1023) 614 | 615 | `logits = self.output(h)` # [32, 1023, 64793] 616 | 617 | `self.last_loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)` 618 | 619 | **在一批次所有样本和所有有效位置【忽略token_id = -1,为处理填充标记(`padding tokens`)】上取平均交叉熵损失**。 620 | 621 | -但我好像没有填充标记过滤 622 | 623 | 问题9:ROPE是作用在哪一部分? 624 | 625 | 629 | 630 | 问题10: 权重初始化使用了什么策略? 631 | 632 | 一般使用基于正态分布的初始化。 633 | 634 | 比如**普通线性层 (nn.Linear) 和嵌入层 (nn.Embedding)** 的初始化使用均值为 0.0,标准差为 0.02 的正态分布,如果存在偏置,则使用零初始化 (torch.nn.init.zeros_)。 635 | 636 | 然后**残差连接中的投影层**(以 'w3.weight' 或 'wo.weight' 结尾的层)基于 GPT-2 论文中的方法,使用均值为 0.0,标准差为 0.02 / sqrt(2 * params.n_layers) 的正态分布进行初始化。 637 | 638 | 问题11:用了什么注意力机制代码? 639 | 640 | 实现:**`torch.nn.functional.scaled_dot_product_attention**(xq, xk, xv, attn_mask=None, dropout_p= 0.0, is_causal=True)` 641 | 642 | 专注于计算单个注意力头的**缩放点积注意力(SDPA)** ,~~这个参数不涉及多个头的并行计算和最终的线性变换~~。使用 PyTorch 的高效矩阵运算(如点积和转置)来加速计算,缩放因子其实就是`1 / sqrt(d_k)`。【默认使用flash attention2优化内存管理】 643 | 644 | 追问:新版torch.compile(model)有啥作用? 645 | 646 | `torch.compile(model)` 擅长消除与 PyTorch 相关的框架开销。当模型中包含大型、高效的 CUDA 内核时(例如 CausalSelfAttention),这种优化尤为明显 647 | 648 | torch.compile(m, backend="tensorrt"):使用 Torch-TensorRT 进行推理优化-**解决在 PyTorch 中准确捕获计算图的问题,也可以转成"onnxrt"** 649 | 650 | ### 关于算力部分 651 | 652 | - 在一个月内训完Llama2至少也需要4500张A100-80G; llama是2048块A100一个月 653 | - [nanoGPT](https://github.com/pihang/nanoGPT), DDP(还可使用FSDP进行多GPU和多节点分布式训练),reproduces GPT-2 (124M) on OpenWebText, running on a single 8XA100 40GB node in about 4 days of training(OpenAI 的 WebText 数据集55.21 GB训练集) 654 | - [**TinyLlama-1.1B](https://github.com/jzhang38/TinyLlama/blob/main/README_zh-CN.md),在3万亿tokens(开源也就1500亿)上进行预训练。经过精心优化,我们"仅"需16块A100-40G的GPU,便可在90天内完成这个任务。各种优化完后**达到**24k tokens/秒/A100**的训练速度[如果塞入A100-40G,16k tokens的per-gpu batch size] 655 | - Pythia的数字来自他们的论文。MPT的数字来自[这里](https://huggingface.co/mosaicml/mpt-1b-redpajama-200b),作者说MPT-1.3B"was trained on 440 台A100-40GBs for about half a day" on 200B tokens。[batch size 2200, sequence length 2048] 656 | - [Phi-1](https://zhuanlan.zhihu.com/p/690423105) 是微软出品的一个 350M 和 1.3B 的模型,使用由大模型合成的「教科书」级别数据,也就是说相比网页数据更优质一些的数据训练,具体可以参见他们的论文 Textbooks are all you need I/II。因为数据质量高,合成也困难,只用了 6B 的数据,在 8 个 A100 上训练了 4 天。 657 | - [baby-llama2-chinese](https://github.com/DLLXW/baby-llama2-chinese)说4张3090训练634亿Tokens的预训练语料+300M参数量的模型已是预训练的极限[注:没有使用DeepSpeed、Megatron等分布式训练架构] 658 | 659 | ## Llama2-chinese-92M~218M 训练流程笔记 660 | 661 | 训练参考: 662 | 663 | [GitHub - DLLXW/baby-llama2-chinese: 用于从头预训练+SFT一个小参数量的中文LLaMa2的仓库;24G单卡即可运行得到一个具备简单中文问答能力的chat-llama2.](https://github.com/DLLXW/baby-llama2-chinese/tree/main) 664 | 665 | - `torchrun --standalone --nproc_per_node=4 pretrain.py` 666 | - 查看tensorboard:`tensorboard --logdir /home/ph/LLM2/baby-llama2-chinese/out/seq1024_dim512_layers8_heads8/tensorboard` 667 | 668 | 0️⃣、准备预训练的数据集 669 | 670 | | **预训练语料** | **描述** | 规模 | 671 | | --- | --- | --- | 672 | | Wiki中文百科 | 中文Wikipedia的数据 | 254,547条,500M | 673 | | BaiduBaiKe | 中文BaiduBaiKe的数据 | 4.3G | 674 | | C4_zh | C4是可用的最大语言数据集之一,收集了来自互联网上超过3.65亿个域的超过1560亿个token。C4_zh是其中的一部分 | 25G,分成95个文件 | 675 | | WuDaoCorpora:智源研究院BAAI | 中文悟道开源的200G数据 | 200G | 676 | | shibing624/medical | 源自shibing624的一部分医学领域的预训练数据 | 分成pre,sft,reward, 600M | 677 | 678 | 共计**634亿(63B)Tokens**的数据量-中文为主。 →过滤后273w段,bin大小12G 679 | 680 | ~~:key~~ 681 | 682 | 2️⃣、低质量文本过滤暂时直接用别人过滤好的,这里主要讲短的过滤。 683 | 684 | 问题1:文本去重过程? 685 | 686 | **文本去重**: 687 | 688 | - 符号转换:中文和英文符号识别并正确转化,去掉回车,过滤连续的标点符号 689 | - 清洗数据:少于15清洗, 690 | - json处理:`rich.progress.open`进度条打开处理挺好用。 691 | - 对齐,将json都对齐为{‘response’:xxx}到list→存储为.parquet格式,可以有效减小存储占用 692 | - 整合:读取不同数据.parquet → 转化为Python 对象 → 转化pd.DataFrame(变成行0-1w,列为'response’) → 每1w行写入总的Parquet 693 | 694 | 一、Minhash【推荐】 695 | 696 | 首先,对文本去除 使用Jaccard 相似度【交集/并集】阈值过滤,先将字符串处理为显式地编码为字节序列在给哈希函数处理 697 | 698 | 追问1:什么是**Minhash签名**? 699 | 700 | ①对集合中的元素进行哈希,每个哈希函数会将一个元素映射为一个整数值。(n个哈希置换就是n个长度的签名) 701 | 702 | ②取最小哈希值组成一个序列 - 》MinHash签名 703 | 704 | ③过比较不同集合的 MinHash 签名,签名中相同位置的值相等的比例可以大致代表集合的相似性(`Jaccard` 相似度) 705 | 706 | ![Untitled](image/Untitled%2013.png) 707 | 708 | **[1,2,3],[3,2,1],那么相似性估计为多少 -》1/3** 709 | 710 | 追问2:什么是哈希置换数? 711 | 712 | **哈希置换数= 哈希函数** 。 这个参数决定了 MinHash 签名的长度,并影响计算的精度和效率。【越大越耗时但越精准】 713 | 714 | 追问3:为啥字符串分割后要`.encode('utf8')`? 715 | 716 | 大多数哈希函数(包括 MinHash)**期望输入是字节序列而不是字符串**。这样可以避免字符编码带来的不一致性。 717 | 718 | 比如"example" 是一个字符串,通过 encode('utf8') 转换为字节序列 b'example'。这个字节序列可以被安全地用于哈希计算。 719 | 720 | --- 721 | 722 | 流程: 723 | 724 | ①读取parquet -》f"{`response.as_py()`}” 725 | , 正则只保留中文和英文、下划线,不要标点符号 分词 726 | 727 | ②获取一段文本的mini hash,**通过生成签名来压缩集合数据,转变为一个256的array** 728 | 729 | ```python 730 | mini_hash = MinHash(num_perm=num_perm) 731 | # TODO 个人感觉用jieba【jieba.lcut(doc)】去分词更合理,而非n-gram和字符分 732 | for s in doc: # 字符迭代分割 733 | mini_hash.update(s.encode('utf-8')) # 哈希函数处理的是一致的字节形式,需要将字符串显式地编码为字节序列 734 | return mini_hash 735 | ``` 736 | 737 | ③上面minhash转换的签名放入lsh,然后用`lsh.query`方法查找相似文档阈值超过的索引(桶索引效率高) 738 | 739 | ④构建哈希表整合为去重set集合,重新遍历所有文档,去掉重复docs,写入parquet格式数据存储 740 | 741 | 问题4:对文档有什么分割方案用于相似度去重? 742 | 743 | 遍历字符、滑动窗口固定了窗口长度,[jieba分词](https://blog.csdn.net/qq_36488175/article/details/109788291#pythonjieba_38) 744 | 745 | 问题5:MinHash 和 MinHashLSH区别在哪? 746 | 747 | - MinHash哈希技术,用于估计两个集合之间的相似性,通常是 Jaccard 相似度. 748 | - MinHashLSH (Locality-Sensitive Hashing **局部敏感哈希**),用于快速查找相似项,旨在**将相似的输入映射到相同的桶中(而不需要对每一对数据进行直接比较)**。对于 MinHashLSH,它使用 MinHash 签名来实现这一点。上面minhash转换的签名放入lsh,然后用lsh.query方法查找相似文档阈值超过的索引 749 | 750 | 追问:MinHashLSH(局部敏感哈希)为什么相似查找非常高效性? 751 | 752 | 高效有两点核心: 753 | 754 | 759 | 760 | - 举个例子【经典-一看就懂】: 761 | 762 | 假设我们有以下 4 个文档,每个文档的 MinHash 签名长度为 6。为了简化,我们将签名分成 2 段,每段包含 3 个哈希值。 763 | 764 | 文档 MinHash 签名 765 | 文档 A: [1, 3, 5, 2, 4, 6] 766 | 文档 B: [2, 3, 4, 1, 5, 6] 767 | 文档 C: [1, 3, 5, 2, 4, 7] 768 | 文档 D: [2, 4, 6, 1, 3, 5] 769 | 770 | - 分段:第一段:包含签名的前 3 个哈希值 第二段:后3个哈希值 771 | - 用哈希函数[例如H(x) = sum(x) % 10]计算段哈希值: 772 | 产生 4个文档*2段 = 8个哈希值 773 | - **将每个段的哈希值和对应的文档 ID 存储在两个不同的哈希表中:** 774 | 775 | 哈希表 1(段 1 哈希值): 776 | 777 | 哈希值 9: [A, B, C] - 》 桶 也避免了哈希冲突 778 | 哈希值 2: [D] 779 | 哈希表 2(段 2 哈希值): 780 | 781 | 哈希值 2: [A, B] 782 | 哈希值 3: [C] 783 | 哈希值 9: [D] 784 | 785 | 假设我们要查询一个新的文档 Q,其 MinHash 签名为 [1, 3, 4, 2, 5, 6]。-》段哈希值计算为8,3 786 | 787 | 通过哈希表查找,候选集合为 [C]。 788 | 789 | 790 | 二、Simhash 791 | 792 | 主要思想是降维, 将高维的特征向量映射成低维的特征向量,通过两个向量的Hamming Distance(`汉明距离`-**将一个字符串变换成另外一个字符串所需要替换的字符个数**)来确定文章是否重复或者高度近似。 793 | 794 | 流程: 795 | 796 | ①文本预处理(正则、分词、滑窗) 滑窗的目的是增加上下文 797 | 特征提取:n-gram或滑动窗口 798 | 799 | ②每个特征通常会被赋予一个权重。一般默认为1,也可以用TF-IDF 800 | 801 | ③对每个特征计算一个固定长度的哈希值(比如 64 位)`hash(feature) & ((1 << self.f) - 1)` 802 | 803 | ④对累加器中的每个位进行判断,如果某个位的值为正,则该位设为 1;否则为 0。 804 | 这样就对一整个字符串 生成了一个固定长度`(如 64 位)的 Simhash 值`。 805 | 806 | ⑤和上面的minhash一样采用分段桶管理,将64位分成4部分转化哈希表查询,加快效率 807 | 808 | > 汉明距离越大,去重率通常会越高 809 | > 810 | 811 | 问题6:Minhash和Simhash对比有啥区别? 812 | 813 | **Minhash用Jaccard相似度;Simhash用汉明距离(适合网页去重变动小的去重)** 814 | 815 | ![从563w段话过滤到273w段](image/image%205.png) 816 | 817 | 从563w段话过滤到273w段 818 | 819 | 问题4:如何存储? 820 | 821 | `parquet`/ˈpɑːkeɪ/格式(列式存储格式,只读取相关的列, Hadoop生态系统开发)存储数据,可以大大减小存储占用。 822 | 823 | 3️⃣、分词器选用chatGLM2的,词表大小64793。以uint16的二进制存储。 824 | 825 | 问题5:那么**如何将文本存储为token?** 826 | 827 | 数据预处理采取GPT的通用做法,对语料进行提前分词,对一个样本做完分词后在末尾加上一个结束符号``对应token_id=2,与下一个样本区分开。然后将所有的训练语料拼接成一个数组(np.uint16)一直叠加老长了,然后以.bin二进制格式存储到磁盘上。 828 | 829 | 问题6:代码中应用了哪些 special_token? 830 | 831 | 文本之间都加了'``’分割-token_id=2【decoder后没啥变化】; 开始符``隔开,填充用到``, 832 | 833 | 4️⃣bin的分词数据集进行内存映射,加载预训练 834 | 835 | 问题1:什么是mmap内存映射? 836 | 837 | **mmap(内存映射文件)**是一种用于将文件或文件的一部分映射到进程的地址空间的技术。这**使得文件的内容可以像在内存中一样进行访问,而不需要将整个文件加载到内存中—-多进程可以通过映射共享文件数据防止多次IO操作**。【以像操作内存数组一样操作文件内容。这种方式可以提高文件I/O操作的效率】 838 | 839 | 代码实现: 840 | 841 | ```python 842 | with open(data_path_lst[0],'r') as f: 843 | nbytes = f.seek(0,2) 844 | flen = f.tell() // np.dtype('uint16').itemsize 845 | self.data = np.memmap(data_path_lst[0],dtype=np.dtype('uint16'),shape=(flen//max_length,max_length)) 846 | ``` 847 | 848 | 追问:为啥内存映射(防止内存溢出)是单个大文件而不能多个? 849 | 850 | 文件连续性: **内存映射需要文件在磁盘上是连续的**,以便能够将其内容直接映射到内存地址空间中。多个小文件在磁盘上的物理位置可能不连续,这会使得内存映射的实现变得复杂。 851 | 852 | 5️⃣模型设置 853 | 854 | ```python 855 | class ModelArgs: 856 | dim: int = 4096 512 857 | n_layers: int = 32 8 858 | n_heads: int = 32 8 859 | n_kv_heads: Optional[int] = None 8 860 | vocab_size: int = -1 # defined later by tokenizer 64793 861 | multiple_of: int = 256 # make SwiGLU hidden layer size multiple of large power of 2 32 862 | norm_eps: float = 1e-5 863 | max_seq_len: int = 2048 512 864 | dropout: float = 0.0 865 | ``` 866 | 867 | - 小模型-baby-llama2 868 | 869 | ```python 870 | Transformer( 871 | (tok_embeddings): Embedding(64793, 512) 872 | (dropout): Dropout(p=0.0, inplace=False) 873 | (layers): ModuleList( 874 | (0-7): 8 x TransformerBlock( 875 | (attention): Attention( 876 | (wq): Linear(in_features=512, out_features=512, bias=False) 877 | (wk): Linear(in_features=512, out_features=512, bias=False) 878 | (wv): Linear(in_features=512, out_features=512, bias=False) 879 | (wo): Linear(in_features=512, out_features=512, bias=False) 880 | (attn_dropout): Dropout(p=0.0, inplace=False) 881 | (resid_dropout): Dropout(p=0.0, inplace=False) 882 | ) 883 | (feed_forward): FeedForward( 884 | (w1): Linear(in_features=512, out_features=1376, bias=False) 885 | (w2): Linear(in_features=1376, out_features=512, bias=False) 886 | (w3): Linear(in_features=512, out_features=1376, bias=False) 887 | (dropout): Dropout(p=0.0, inplace=False) 888 | ) 889 | (attention_norm): RMSNorm() # 归一化,稳定训练 890 | (ffn_norm): RMSNorm() # 更好的梯度流动 891 | ) 892 | ) 893 | (norm): RMSNorm() 894 | (output): Linear(in_features=512, out_features=64793, bias=False) 895 | ) 896 | ``` 897 | 898 | 899 | 6️⃣分布式训练 900 | 901 | - `torchrun --standalone --nproc_per_node=4 pretrain.py` (在代码内指定多卡) 902 | 903 | 193w步steps,4卡 904 | 905 | - 实验记录(用于追踪实验) 906 | 907 | 1、4卡配置,卡显存消耗13G,193w步steps,loss大概收敛2.4 908 | 909 | ```python 910 | gradient_accumulation_steps = 2 911 | batch_size = 16 912 | max_seq_len = 512 913 | dim = 512 914 | n_layers = 12 915 | n_heads = 8 916 | multiple_of = 32 # 前馈层FFN的隐藏维度 917 | dropout = 0.0 918 | min_lr = 1e-5 919 | learning_rate = 3e-4 # max learning rate 920 | weight_decay = 1e-1 921 | ``` 922 | 923 | 2、不管几卡,这个配置直接爆显存(30G一卡会够用) 924 | 925 | ```python 926 | gradient_accumulation_steps = 2 927 | batch_size = 16 928 | max_seq_len = 1024 929 | dim = 1024 930 | n_layers = 12 931 | n_heads = 8 932 | multiple_of = 32 933 | ``` 934 | 935 | 3、**6卡train_loader 迭代129w次,总的129*16*6~12392w个样本 936 | —花了1.5d,占用13G,loss在2.0波动,不过收敛太早了提前进入decay学习率阶段稳定在0.0001训练(要增加余弦退火的过程,增加模型规模但要考虑模型并行)。** 937 | 938 | ```python 939 | gradient_accumulation_steps = 2 940 | batch_size = 16 ---占13G 941 | max_seq_len = 512 942 | dim = 512 943 | n_layers = 12 944 | n_heads = 8 945 | multiple_of = 32 946 | ``` 947 | 948 | ![1723703038290_F572471B-367B-44b4-A2F9-A1205C8ABEF3.png](image/1723703038290_F572471B-367B-44b4-A2F9-A1205C8ABEF3.png) 949 | 950 | 951 | 952 | 953 | To run with DDP on 4 gpus across 2 nodes, example: 954 | 955 | - Run on the first (master) node with example IP 123.456.123.456: 956 | $ torchrun --nproc_per_node=8 --nnodes=2 --node_rank=0 --master_addr=123.456.123.456 --master_port=1234 train.py 957 | - Run on the worker node: 958 | $ torchrun --nproc_per_node=8 --nnodes=2 --node_rank=1 --master_addr=123.456.123.456 --master_port=1234 train.py 959 | (If your cluster does not have Infiniband interconnect prepend NCCL_IB_DISABLE=1) 960 | 961 | 问题1:**DistributedDataParallel(DDP)**在多GPU下高效分布式训练原理? 962 | 963 | 首先,DDP会将模型的一个副本会被复制到每个 GPU 上,并在自己的数据子集上进行前向和反向传播。 964 | 965 | 其次,数据并行,每个 GPU 处理输入数据的一个子集。在每个训练步骤中,所有 GPU 同时进行前向和反向传播计算。 966 | 967 | 然后,梯度在反向传播完成后,DDP 会自动同步所有 GPU 上的梯度,all-reduce操作通信实现。 968 | 969 | 最后,all-reduce 操作会收集每个 GPU 上的梯度,将它们相加,然后将结果分发回每个 GPU,所有GPU模型相同梯度更新。 970 | 971 | 问题2:**`NCCL(NVIDIA Collective Communications Library)`**操作 BROADCAST 超时? 972 | 973 | NVIDIA 集体通信库 (NCCL) 实现了**针对 NVIDIA GPU 和网络进行优化的多 GPU 和多节点通信原语**。提供了all-gather, all-reduce, broadcast, reduce, reduce-scatter, p2p, 接收等例程,这些例程经过优化,可在 PCIe (4090)和 NVLink(A100) 高速互连上实现高带宽和低延迟。一个节点以及跨节点的 NVIDIA Mellanox 网络。 974 | 975 | > **NCCL 会在所有进程之间进行数据的聚合和分发** 976 | > 977 | 978 | 分布式每个参与的进程需要在同一个时刻参与到 NCCL 的集合操作(如 broadcast、all_reduce 等)中。这意味着所有进程必须在相同的代码位置调用相同的 NCCL 操作。 979 | 980 | 报错可能原因 981 | 982 | 1、如果使用多个节点进行分布式训练,网络延迟或带宽不足可能导致超时。带宽要高 983 | 984 | 2、如果 GPU 负载过高,可能导致 NCCL 操作无法及时完成,确保没有其他进程占用过多资源 985 | 986 | 3、需要启动调试显示os.environ['NCCL_DEBUG'] = 'INFO’ 987 | 988 | 4、设置 NCCL_P2P_DISABLE=1 或 NCCL_IB_DISABLE=1,以排除可能的网络接口问题 989 | 990 | 5、如果某个进程在 NCCL 操作之前卡住,比如在数据加载或模型计算中遇到瓶颈,其他进程会在 NCCL 操作中等待(不均衡工作负载),训练效率很低可能导致超时错误。 991 | 992 | 6、大量使用 swap 可能会影响到系统上运行的其他进程,导致它们的性能也受到影响 993 | 994 | 7、**如果 ALLREDUCE 操作涉及的数据量过大,可能需要很长时间来完成,尤其是在带宽有限的情况下** 995 | 996 | 追问:NCCL在RTX40系报错NotImplementedError: Using RTX 4000 series doesn't support faster communication broadband via P2P or IB. ? 997 | 998 | 默认开启了 NCCL P2P(Peer-to-Peer)和 IB(InfiniBand)通信优化,而这些优化在你的环境中并不被支持。所以需要 999 | 1000 | ```json 1001 | "env": { 1002 | "NCCL_P2P_DISABLE": "1", # export导入环境变量 1003 | "NCCL_IB_DISABLE": "1", 1004 | "CUDA_DEVICE_MAX_CONNECTIONS": "0,1", 1005 | "CUDA_VISIBLE_DEVICES": "2" 1006 | } 1007 | ``` 1008 | 1009 | 问题3:master_process变化是啥? 1010 | 1011 | DDP的话除了一个是主进程,其他都是false 1012 | 1013 | 问题4:**为什么预训练显存要预留一半以上?** 💛💛💛 1014 | 1015 | 1021 | 1022 | 问题5: Deepspeed框架中`ZeRO2` 比DDP优势在哪? 1023 | 1024 | DDP是使用 `all-reduce` 来同步梯度。每个 GPU 在计算完自己的梯度后,通过 all-reduce **将所有 GPU 的梯度求和并分发**。这样确保每个 GPU 的梯度一致,用于参数更新。 1025 | 1026 | 而Zero2会分割模型的优化器状态和梯度,减少了需要同步的数据量。在参数更新时,可能需要使用 `all-reduce`【分布式计算中用于聚合和分发数据的高效操作】 来收集分割的参数,以便进行完整的参数更新。智能的分割和聚合策略 1027 | 1028 | 问题6:`all-gather`分布式通信操作用于哪些场合? 1029 | 1030 | 1037 | 1038 | 问题7:gradient_accumulation_steps的用处? 1039 | 1040 | 用于控制梯度累积,目的是在显存有限的情况下,**通过累积多个小批次的梯度来模拟更大的批次大小的效果**。也就是在每个小批次中,计算损失并进行反向传播,但不立即更新模型参数【进行梯度平均保持尺度一致】🦠🦠🦠 1041 | 1042 | -减少了每次更新的频率,这可能在某些情况下提供更稳定的训练过程- 1043 | 1044 | 追问1:gradient_accumulation_steps 和 batch_size 大小影响显存比例 1045 | 1046 | `gradient_accumulation_steps = 2` *batch_size = 16 * 4GPU 1047 | 1048 | 总的有效批次更新参数:4*16=64样本【`节约一半显存`】 1049 | 1050 | 等同于 gradient_accumulation_steps = 1 * batch_size = 32 1051 | 1052 | 追问2:gradient_accumulation_steps=2,batch_size=16 和gradient_accumulation_steps=1,batch_size=32对模型训练效果一样吗? 1053 | 1054 | 首先batch_size肯定越大训练越快,但我们显存不够所以用了梯度累计,这在效果上会有啥影响吗,如果用的**fp16会导致浮点数精度有限而导致的误差积累和不稳定行为**。 1055 | 1056 | 问题8:DDP时画loss收敛图时会有几个进程画几个图是为啥? 1057 | 1058 | 首先loss数组收集的数据其实是当前进程的GPU中没步长的loss,如果用了4卡也就是4个进程同步训练,那么会各自维护自己的loss数组。进程中变量不共享,所以画图会画4张。 1059 | 1060 | 如果想避免,只需要画`master_process`的loss收敛即可。 1061 | 1062 | 如果想要过程中追踪收敛,用 `TensorBoard`组件的SummaryWriter 函数。 1063 | 1064 | 问题9:那么DDP为什么不同步loss去计算梯度再分发呢? 1065 | 1066 | 首先,DDP实际步骤是每个 GPU 在计算完自己的梯度后,通过 `all-reduce` 将所有 GPU 的梯度求和并分发。这样确保每个 GPU 的梯度一致,用于参数更新(**设计核心是最大化并行计算并最小化通信开销**)。 1067 | 1068 | 1072 | 1073 | ## [**Zero-Chatgpt](https://github.com/AI-Study-Han/Zero-Chatgpt)训练流程** 1074 | 1075 | 用了规范的Deepspeed,**也加入了强化学习(rlhf,ppo)的代码** 1076 | 1077 | 有个不错的参考:[https://github.com/AI-Study-Han/Mini-Llama2-Chinese/tree/main/code2/pretrain_code](https://github.com/AI-Study-Han/Zero-Chatgpt) 1078 | 1079 | - `sh Zero-Chatgpt-main/pretrain/pretrain.sh` 1080 | 1081 | 0️⃣数据集 1082 | 1083 | 一共收集了10B左右的中文训练语料,包括[中文维基百科](https://huggingface.co/datasets/pleisto/wikipedia-cn-20230720-filtered/blob/main/wikipedia-cn-20230720-filtered.json),[中文百度百科](https://huggingface.co/datasets/xuqinyang/BaiduBaike-5.63M/blob/main/563w_baidubaike.json)和[SkyPile-150B](https://huggingface.co/datasets/Skywork/SkyPile-150B)随机抽取了部分数据 1084 | 中文维基百科和SkyPile-150B数据【[下载链接](https://www.modelscope.cn/datasets/modelscope/SkyPile-150B/files),非常多,包含大约2.33亿个独特的网页,每个网页平均包含超过1,000个中文字符。该数据集总共包括大约 1500 亿个令牌和 620 GB 的纯文本数据。】比较干净 1085 | 1086 | 问题1:和之前baby数据量对比? 1087 | 1088 | 数据采样配比后数据量大了些。 1089 | 1090 | →下面处理完后10B token,剩余bin大小19G。baby之前200多w,bin 12G。 1091 | 1092 | - baby,**train_loader 迭代129w次,总的129*16*6~12392w个样本 。一个样本512token** 1093 | - zero,**train_loader 迭代48355次,总的4.8*24*6~696w个样本 。一个样本1024toke** 1094 | 1095 | 1️⃣数据处理 1096 | 1097 | 之前baby项目,数据是用`parquet` 格式+分词后bin的uint16存储【token之间隔着区分,先分词在构建非常快,因为是bin】。 1098 | 1099 | 这里对不同数据集json处理后(过滤清洗去重,采样分配)+`{’text’:text , ‘source’, ‘baidubaike’}` 存储进json。【json区分上下文,老慢了,因为都是json→json,然后再去训练tokenizer】 1100 | —tmd要跑3天都处理不完[处理json的去重非常慢2-ngram+minhashlsh]— 1101 | 1102 | 最终563w条数据只剩下140多w条数据 1103 | 1104 | 2️⃣分词器构建 1105 | 1106 | 用BPE构建了一个3.2w的分词器,手动添加特殊token:`"system"`、`"user"` 、 `"assistant"` 、`<|endoftext|>`、`<|im_start|>`、`<|im_end|>`(顺序id依次叠加)。 1107 | 1108 | 总计 1109 | 1110 | ``` 1111 | "bos_token_id": 32005, 1112 | "eos_token_id": 32005, ----<|im_end|>对应一个token_id 1113 | "vocab_size": 32006 1114 | ``` 1115 | 1116 | 3️⃣Deepspeed框架 1117 | 1118 | ds_config设置: 1119 | 1120 | zero2优化器、梯度拆分 1121 | 1122 | ```python 1123 | "zero_optimization": { 1124 | "stage": 2, 1125 | "allgather_partitions": true, 1126 | "allgather_bucket_size": 2e8, 1127 | "overlap_comm": true, 1128 | "reduce_scatter": true, 1129 | "reduce_bucket_size": 2e8, 1130 | "contiguous_gradients": true 1131 | }, 1132 | ``` 1133 | 1134 | zero3权重、梯度、优化器完全参数拆分 1135 | 1136 | ```python 1137 | "zero_optimization": { 1138 | "stage": 3, 1139 | "allgather_partitions": true, 1140 | "allgather_bucket_size": 1e8, 1141 | "overlap_comm": false, 1142 | "reduce_scatter": true, 1143 | "reduce_bucket_size": 1e8, 1144 | "contiguous_gradients": true, 1145 | "sub_group_size": 5e4, 1146 | "memory_efficient_linear": true 1147 | }, 1148 | ``` 1149 | 1150 | 问题1:`sub_group_size`代表啥,为啥是zero3的特有参数? 1151 | 1152 | 首先zero-2权重只在优化器阶段进行分片,梯度同步后仍需要在所有设备间保存完整的模型副本。但zero-3则是权重、优化器状态、梯度都被分片存储,任何时刻每个设备只保留模型的部分参数。 1153 | 1154 | 所以sub_group_size**控制在模型并行过程中,权重分片和通信时划分的小组规模**。sub_group_size越大通信会在更大规模的子组中进行,这可能会增加通信开销,但能减少全局同步次数。 1155 | 1156 | 当然默认情况下,`sub_group_size` 会自动调整,但在一些场景中,手动设置可以更好地适应具体硬件和任务需求。 1157 | 1158 | 问题2:deepspeed分布式训练在代码哪里体现? 1159 | 1160 | ```python 1161 | from transformers import Trainer, TrainingArguments 1162 | 1163 | training_args = TrainingArguments( 1164 | ..., 1165 | deepspeed="path/to/deepspeed_config.json" 1166 | ) 1167 | trainer = Trainer( 1168 | model=model, 1169 | args=**training_args**, 1170 | ... 1171 | ) 1172 | ``` 1173 | 1174 | 基于 Hugging Face 的 `Trainer`,通常会在 `training_args` 中启用 DeepSpeed. 1175 | 通常会使用一个 `deepspeed_config.json` 文件来定义**优化策略、内存管理、混合精度**等设置。 1176 | 1177 | 问题3:使用zero-3的问题有哪些? 1178 | 1179 | 一、**OOM** 1180 | 1181 | 虽然显著优化了显存使用,但它在前向和反向传播过程中有更频繁的通信操作,导致有时会意外触发 OOM,尤其是在全局通信不平衡或显存碎片化严重的情况下。 1182 | 1183 | 解决: 1184 | 1185 | 1、动态显存分配PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True 1186 | 1187 | 2、sub_group_size=1e5调小一点 1188 | 1189 | 3、减小per_device_train_batch_size 1190 | 1191 | 二、**速度慢的要死** 1192 | 在前向和反向传播过程中需要频繁进行 **`all-gather`(模型参数收集) 和 `reduce-scatter` (梯度聚合并分发)操作**。这些操作需要跨设备进行大量的数据通信,因此对通信带宽和延迟有更高的要求。 1193 | **如果增大每次通信传输数据量,会减少训练时间,但是显存会增加溢出。**🤬🤬🤬🤬 1194 | 1195 | 追问:zero3预训练代码的显存问题 1196 | 1197 | 问题4:使用梯度检查点(`gradient_checkpointing`)和混合精度训练(FP16/BF16)发送CUDA内存分配器断言错误? 1198 | 1199 | 因为我在启动程序时export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,max_split_size_mb:32指定了更小的分配单元可以减少内存碎片化的问题来减小显存占用。这样不能同时开启梯度检查点。 1200 | 1201 | 问题5:显存一直超出,显存限制是个难以突破的瓶颈。【一直OOM】 1202 | 1203 | 修改ds_config.py设置:-调小了内存分配也会溢出-。。。。。。。。 1204 | 1205 | ``` 1206 | "zero_optimization": { 1207 | "stage": 3, 1208 | "offload_optimizer": { # 优化器卸载到内存 1209 | "device": "cpu", 1210 | "pin_memory": true 1211 | }, 1212 | "offload_param": { # 将模型参数卸载到内存 1213 | "device": "cpu", 1214 | "pin_memory": true 1215 | }, 1216 | "allgather_partitions": true, 1217 | "allgather_bucket_size": 5e7, 1218 | "overlap_comm": false, 1219 | "reduce_scatter": true, 1220 | "reduce_bucket_size": 5e7, 1221 | "contiguous_gradients": false, 1222 | "sub_group_size": 5e4 1223 | }, 1224 | ``` 1225 | 1226 | ```python 1227 | "zero_optimization": { 1228 | "stage": 3, 1229 | "offload_optimizer": { 1230 | "device": "cpu", 1231 | "pin_memory": true 1232 | }, 1233 | "offload_param": { 1234 | "device": "cpu", 1235 | "pin_memory": true 1236 | }, 1237 | "overlap_comm": true, 1238 | "contiguous_gradients": true, 1239 | "sub_group_size": 1000000000.0, 1240 | "reduce_bucket_size": "auto", 1241 | "stage3_prefetch_bucket_size": "auto", 1242 | "stage3_param_persistence_threshold": "auto", 1243 | "stage3_max_live_parameters": 1000000000.0, 1244 | "stage3_max_reuse_distance": 1000000000.0, 1245 | "stage3_gather_16bit_weights_on_model_save": true 1246 | }, 1247 | ``` 1248 | 1249 | 追问:在shift_logits = logits[..., :-1, :].contiguous()报错 1250 | RuntimeError: !block->expandable_segment_ INTERNAL ASSERT FAILED at "../c10/cuda/CUDACachingAllocator.cpp":2525, please report a bug to PyTorch. 1251 | 1252 | 因为`.contiguous()` 是一个常见的操作,它将张量重新排列成内存中连续的数据布局,CUDA 内存分配器在试图扩展内存块时遇到了碎片化或内存不足的问题。 1253 | 但是必须要用。因为如果你直接用 `.view()` 替换 `.contiguous()`,在某些情况下可能会触发错误,尤其是当张量在内存中不是连续存储的时。具体来说,如果你对 `logits` 或 `labels` 进行了切片或维度交换操作,直接使用 `.view()` 可能会导致错误。 1254 | 解决办法: 1255 | 1256 | 1、export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:16,expandable_segments:False 1257 | 1258 | 2、降低cuda11.3和降低torch版本 1259 | 1260 | 问题6:有个代码问题你觉得这些DDP,deepspeed的参数注入是在哪里实现? 1261 | 1262 | 在 DDP 和 DeepSpeed 的参数注入中,`@dataclass` 装饰器简化了配置类的定义和初始化过程。一般直接作用在class TrainingArguments类上面,然后启动时的—参数快速注入,deepspeed一般通过json配置文件导入。 1263 | 1264 | 问题7:deepspeed启动和torchrun启动有啥区别? 1265 | 1266 | - deepspeed step2.py --deepspeed:直接使用 `deepspeed` 命令来启动训练脚本会自动处理了很多底层的配置 1267 | - torchrun --nnodes 1 --nproc_per_node 1 sft.py --deepspeed ds_config.json : 是 `PyTorch` 提供的分布式启动工具,更灵活可以直接控制 `--nnodes` 和 `--nproc_per_node`,并且不仅限于 DeepSpeed,还可以与其他分布式策略如DDP结合。 1268 | 1269 | 问题8:accelerate launch和torchrun启动有啥区别? 1270 | 1271 | accelerate launch --multi_gpu --num_processes 2 dpo_train.py: 来自Hugging Face的`Accelerate`库,适用于 Hugging Face 的模型和分布式训练框架 1272 | 1273 | 4️⃣ 预训练 1274 | 1275 | 1、这里采用 Hugging Face内置的transormers包和对应PretrainedConfig配置 1276 | 1277 | 2、我们自定义model初始化 = **MiaomiaoForCausalLM**继承(PreTrainedModel )以及 **MiaomiaoPreTrainedModel**参数配置。 1278 | 3、主要结构包括: 1279 | 1280 | - `MiaomiaoModel`:实现具体的 Transformer 架构。 1281 | - `MiaomiaoDecoderLayer`:单层解码器,包含注意力机制和 MLP。 1282 | - `MiaomiaoAttention`:实现不同注意力机制的类。 1283 | - `MiaomiaoRMSNorm`:归一化层。 1284 | 1285 | 4、这里Attention有三种选择: 1286 | 1287 | ```python 1288 | Miaomiao_ATTENTION_CLASSES = { 1289 | "eager": MiaomiaoAttention, 1290 | "flash_attention_2": MiaomiaoFlashAttention2, 1291 | "sdpa": MiaomiaoSdpaAttention, 1292 | } 1293 | ``` 1294 | 1295 | `from flash_attn import flash_attn_func, flash_attn_varlen_func` FlashAtte加速调用 1296 | 1297 | 问题2:如何在训练过程中追踪可视化? 1298 | 1299 | Hugging Face 的 Trainer 默认支持 `WandB`(report_to="wandb”),配合deepspeed追踪可视化 1300 | 1301 | - 小模型-miaomiao+追踪记录,大概160M多, 24层,不过词表就3.2w,lens=1024 1302 | 1303 | ```python 1304 | MiaomiaoForCausalLM( 1305 | (model): MiaomiaoModel( 1306 | (embed_tokens): Embedding(32006, 512) 1307 | (layers): ModuleList( 1308 | (0-23): 24 x MiaomiaoDecoderLayer( 1309 | (self_attn): MiaomiaoSdpaAttention( 1310 | (q_proj): Linear(in_features=512, out_features=512, bias=False) 1311 | (k_proj): Linear(in_features=512, out_features=512, bias=False) 1312 | (v_proj): Linear(in_features=512, out_features=512, bias=False) 1313 | (o_proj): Linear(in_features=512, out_features=512, bias=False) 1314 | (rotary_emb): MiaomiaoRotaryEmbedding() 1315 | ) 1316 | (mlp): MiaomiaoMLP( 1317 | (gate_proj): Linear(in_features=512, out_features=2752, bias=False) 1318 | (up_proj): Linear(in_features=512, out_features=2752, bias=False) 1319 | (down_proj): Linear(in_features=2752, out_features=512, bias=False) 1320 | (act_fn): SiLU() 1321 | ) 1322 | (input_layernorm): MiaomiaoRMSNorm() 1323 | (post_attention_layernorm): MiaomiaoRMSNorm() 1324 | ) 1325 | ) 1326 | (norm): MiaomiaoRMSNorm() 1327 | ) 1328 | (lm_head): Linear(in_features=512, out_features=32006, bias=False) 1329 | ) 1330 | ``` 1331 | 1332 | wandb记录:https://wandb.ai/1274168976ph/huggingface/runs/aimial6r/workspace?nw=nwuser1274168976ph 1333 | 1334 | loss收敛到3.2,我觉得是分词器太小效果没那么好 1335 | 1336 | ![预训练后会不停说胡话](image/image%206.png) 1337 | 1338 | 预训练后会不停说胡话 1339 | 1340 | 1341 | 5️⃣自定义model推理 1342 | 1343 | 问题4:如何自定义model模块加载并推理? 1344 | 1345 | 用的huggingface自带的transformers加载模型,所需文件: 1346 | 1347 | ![image.png](image/image%207.png) 1348 | 1349 | **这里config.json很重要,指定了`AutoModelForCausalLM.from_pretrained()`加载模型的配置(`AutoConfig`、`AutoModel`[模型架构]、`AutoModelForCausalLM`[主要最外层包括从输入到输出]三大件)** 1350 | 1351 | ```json 1352 | "model_type": "miaomiao", 1353 | "architectures": [ 1354 | "MiaomiaoModel" 1355 | ], 1356 | "auto_map": { 1357 | "AutoConfig": "configuration_miaomiao.MiaomiaoConfig", 1358 | "AutoModel": "modeling_miaomiao.MiaomiaoModel", 1359 | "AutoModelForCausalLM": "modeling_miaomiao.MiaomiaoForCausalLM" 1360 | }, 1361 | ``` 1362 | 1363 | 追问:如何加载自定义tokenizer? 1364 | 1365 | 首先明确分词器文件里面有哪些: 1366 | 1367 | ![image.png](image/image%208.png) 1368 | 1369 | 加载用huggingface中transformers库 1370 | 1371 | ```python 1372 | from transformers import AutoModelForCausalLM, AutoTokenizer,AutoConfig 1373 | tokenizer = AutoTokenizer.from_pretrained('./Zero-Chatgpt-main/train_tokenizer/miaomiao_tokenizer/', trust_remote_code=True) 1374 | ``` 1375 | 1376 | --- 1377 | 1378 | ## ChatLM-mini-Chinese项目全流程 1379 | 1380 | 项目地址:https://github.com/charent/ChatLM-mini-Chinese 1381 | 1382 | 中文对话小模型,模型参数只有0.2B(算共享权重约210M) 1383 | 1384 | - 使用`Huggingface`NLP框架,包括`transformers`、`accelerate`、`trl`、`peft`等 1385 | - 自实现`trainer`,支持单机单卡、单机多卡进行预训练、SFT微调。 1386 | - 使用DPO进行全量偏好优化。也支持使用`peft lora`进行偏好优化;支持模型合并,可将`Lora adapter`合并到原始模型中。 1387 | 1388 | 0️⃣数据集 1389 | 1390 | | 社区问答json版webtext2019zh-大规模高质量数据集 | ‣ | 共410万,清洗后剩余260万 | 1391 | | --- | --- | --- | 1392 | | baike_qa2019百科类问答 | https://aistudio.baidu.com/datasetdetail/107726 | 共140万,清洗后剩余130万 | 1393 | | 中国医药领域问答 | ‣ | 共79万 | 1394 | | 知乎问答数据 | [https://huggingface.co/datasets/wangrui6/Zhihu-KOL?row=5](https://huggingface.co/datasets/wangrui6/Zhihu-KOL?row=5) | 共100万行,清洗后剩余97万行 | 1395 | | belle开源的指令训练数据 | [https://github.com/LianjiaTech/BELLE/tree/main](https://github.com/LianjiaTech/BELLE/tree/main) | 共370万行 | 1396 | | rlhf | | | 1397 | | alpaca-gpt4-data-zh | [https://huggingface.co/datasets/c-s-ale/alpaca-gpt4-data-zh](https://huggingface.co/datasets/c-s-ale/alpaca-gpt4-data-zh) | | 1398 | | [huozi_rlhf_data_json](https://huggingface.co/datasets/Skepsun/huozi_rlhf_data_json) | | | 1399 | | [rlhf-reward-single-round-trans_chinese](https://huggingface.co/datasets/beyond/rlhf-reward-single-round-trans_chinese) | | | 1400 | | | | | 1401 | 1402 | 问题3:如何自己构建RLHF的数据集? 1403 | 1404 | 这里rejected我们用model.generate生成,注意这里输入`batch_prompt.append(f"{item['prompt']}[EOS]")`,输出后结合不同的rlhf开源数据集整合形成 1405 | .append({"prompt", "chosen", "rejected"}) 1406 | 1407 | 如果两个答案长度有超过max_len,或者reject.strip() == chosen.strip(),这两个相同的也不要。 1408 | 输出的train_dataset: 1409 | 1410 | ![image.png](image/image%209.png) 1411 | 1412 | --- 1413 | 1414 | ## SFT 1415 | 1416 | 可参考好的项目: 1417 | 1418 | 1、llama3-Chinese-chat微调:https://github.com/CrazyBoyM/llama3-Chinese-chat 1419 | 1420 | 2、 1421 | 1422 | SFT样本构建 1423 | 1424 | - prompt和answer之间一定要有一个开始符``隔开,然后answer后需要一个结束符``。 1425 | - 计算loss的时候,对prompt部分的loss进行mask,只计算answer部分的loss即可。 1426 | 1427 | 1️⃣sft数据处理 1428 | 1429 | 首先参考[zero-chatgpt](https://github.com/AI-Study-Han/Zero-Chatgpt/tree/main/data_process) , 采用[firefly-train-1.1M](https://huggingface.co/datasets/YeungNLP/firefly-train-1.1M/blob/main/firefly-train-1.1M.jsonl),[ruozhiout_qa_cn.jsonl](https://www.modelscope.cn/datasets/baicai003/Llama3-Chinese-dataset/files)。根据问题长度对数据集进行了清洗和去重,最后剩余40多w条数据。 1430 | 1431 | 问题1:模型尺寸小对微调数据有啥要求? 1432 | 1433 | 因为模型尺寸比较小,只想训练单论对话能力。之前尝试使用50B token训练了1.5B的模型,2w条训练数据就有比较好的对话能力,这里0.1B的模型2w条sft数据训练后对话能力还是比较差,需要更多的sft数据训练,这里用了30w条。 1434 | 1435 | ![image.png](image/image%2010.png) 1436 | 1437 | 问题2:对不同数据如何处理成统一格式? 🀄(可以参考)🀄 1438 | 1439 | 1、首先去读jsonl数据变成统一格式,其中一条数据为: 1440 | 1441 | `'{"messages": [{'from': 'user', 'value': '自然语言推理:\n前提:家里人心甘情愿地养...是被家里人收养的孤儿'}, {'from': 'assistant', 'value': '中立'}]}\n'` 1442 | 1443 | **其中firefly数据处理165w条,所有数据过滤完一共30w** 1444 | 1445 | 2.1、我们对处理完统一格式数据后,拉取其中的value问答拼接,用MinHashLSH进行过滤阈值超过0.4重复。 1446 | 1447 | 2.2、~~在Minhashlsh去重后,还可以利用现有的tokenizer、model加载来计算两种回答困惑:~~ 1448 | 1449 | ~~不合理勿看~~ 1450 | 1451 | ~~例如:~~ 1452 | 1453 | ~~user_input用qwen2的分词器`tokenizer.apply_chat_template`生成'<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n自然语言推理:\n前提:家里人心甘情愿地养他,还有几家想让他做女婿的\n假设:他是被家里人收养的孤儿<|im_end|>\n<|im_start|>assistant\n’~~ 1454 | 1455 | 最后统一转化为: 1456 | **`sft_data.append({'prompt': prompt, 'answer': answer})`** 1457 | 1458 | 有个不明所以的操作,他划分25%作为rlhf训练数据,强化学习的数据是根据sft没有使用的数据进行生成的,sft数据原有的回答为"chosen",使用之前指令微调后的模型生成的回答作为"rejected",一共生成了10w条数据。 1459 | 1460 | 2️⃣多轮对话 1461 | 1462 | 训练的时候,需要在每个Assistant的回复后面都添加``(有些分词器是),作为此轮对话生成结束的标识符。否则推理的时候,模型很难采样到,从而无法结束生成。 1463 | 1464 | ![image.png](image/image%2011.png) 1465 | 1466 | 问题1:将一条多轮对话数据,拆分成多条数据去预测assistant的loss效率过低,有什么多轮对话高效训练方法? 1467 | 1468 | 参考Firefly项目训练多轮对话模型时,我们**将一条多轮对话数据拼接之后,输入模型,并行计算每个位置的loss,只有Assistant部分的loss参与权重更新**。 1469 | 1470 | ![ ](image/image%2012.png) 1471 | 1472 | 1473 | 1474 | 追问:为什么这种做法是可行的? 1475 | 1476 | 在于因果语言模型的attention mask。以GPT为代表的Causal Language Model(因果语言模型),这种模型的attention mask是一个对角掩码矩阵,每个token在编码的时候,只能看到它之前的token,看不到它之后的token。 1477 | User2部分的编码输出,只能看到User1、Assistant1、User2的内容,可以用来预测Assistant2的内容,依此类推。对于整个序列,**只需要输入模型一次,便可并行获得每个位置的logits,从而用来计算loss**。 1478 | 1479 | 问题2:类似GLM这种Prefix LM而非causal LM训练的模型怎么使用mask? 1480 | 1481 | 存在prefix attention mask的设计。**对于prefix而言,它的attention是双向的,而预测部分的attention是单向的[也是就prompt可以双向不用mask]**。 1482 | 1483 | ![image.png](image/image%2013.png) 1484 | 1485 | 问题3: 1486 | 1487 | 3️⃣开始微调 1488 | 1489 | 问题1:模型输入如何处理,格式是咋样的? 1490 | 1491 | - 单样本代码处理示例 1492 | 1493 | ```python 1494 | features = Features({ 1495 | 'prompt': Value('string'), 1496 | 'answer': Value('string') 1497 | }) 1498 | sft_dataset = load_dataset('json', data_files=data_path, features=features) 1499 | data = [] 1500 | # 遍历数据集并取出每个元素 1501 | for example in sft_dataset['train']: 1502 | prompt = example['prompt'] 1503 | answer = example['answer'] 1504 | messages = [ 1505 | {"role": "user", "content": prompt} 1506 | ] 1507 | prompt_text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) 1508 | answer_text = answer + tokenizer.eos_token # eos=32003 1509 | 1510 | prompt_id = self.tokenizer.encode(prompt_text) 1511 | if (len(prompt_id) > self.prompt_max_len): 1512 | prompt_id = prompt_id[:self.prompt_max_len] 1513 | 1514 | answer_id = tokenizer.encode(answer_text) 1515 | if (len(answer_id) > self.answer_max_len): 1516 | answer_id = prompt_id[:self.prompt_max_len] 1517 | input_id = prompt_id + answer_id 1518 | labels = [self.tokenizer.pad_token_id] * len(prompt_id) + answer_id 1519 | pad_len = self.max_length - len(input_id) # 1024长度向右pad 1520 | input_id = input_id + [self.tokenizer.pad_token_id] * pad_len 1521 | labels = labels + [self.tokenizer.pad_token_id] * pad_len 1522 | labels = [(l if l != self.tokenizer.pad_token_id else IGNORE_INDEX ) for l in labels] # 不计算loss = -100 1523 | input_id = torch.LongTensor(input_id) 1524 | labels = torch.LongTensor(labels) 1525 | attention_mask = input_id.ne(self.tokenizer.pad_token_id) # pad位置false 1526 | data.append({ 1527 | "input_ids": input_id, 1528 | "labels": labels, 1529 | "attention_mask": attention_mask 1530 | }) 1531 | ``` 1532 | 1533 | 1534 | 单个QA样本格式处理为: 1535 | 1536 | ```python 1537 | data.append({ 1538 | "input_ids": input_id, 1539 | "labels": labels, 1540 | "attention_mask": attention_mask 1541 | }) 1542 | 1543 | ``` 1544 | 1545 | 1、假设设置最长1024 lens, 讲input按照apply_chat_template处理例如 1546 | 1547 | `<|im_start|>system\n你是一个sb<|im_end|>\n<|im_start|>user\n input <|im_end|>\n<|im_start|>assistant\n` 1548 | 1549 | 2、结合答案`answer+` . 通过分词器编码为token_id,然后向右补齐1024 1550 | 1551 | 3、最终**input_id为1024长度的tensor; 1552 | labes在input和为-100,只有answer位置为对应的token_id; 1553 | attention_mask只有位置为False,也就是不可见** 1554 | 1555 | # RLHF+DPO 1556 | 1557 | - 有个超级好的个人源码项目:[https://github.com/chunhuizhang/personal_chatgpt/tree/main](https://github.com/chunhuizhang/personal_chatgpt/tree/main) 1558 | - huggingface官方参考:[https://github.com/huggingface/trl](https://github.com/huggingface/trl) 1559 | - 强化学习纸质书:[https://github.com/datawhalechina/easy-rl](https://github.com/datawhalechina/easy-rl) 1560 | - 实现可以参考:https://github.com/charent/ChatLM-mini-Chinese/ 1561 | 1562 | 强化学习搞清概念: 1563 | 1564 | 1573 | 1574 | ![**状态价值函数都有,动作价值函数再加个给定state**](image/image%2014.png) 1575 | **状态价值函数都有,动作价值函数再加个给定state** 1576 | 1577 | 问题2:扩展下强化学习在棋牌游戏怎么应用? 1578 | 1579 | **state表示手牌等信息;action表示当前的出牌动作;reward表示一系列未来的奖励总和,能够赢; 环境是规则,可以更新状态; agent就是要训练的网络。** 1580 | 1581 | 使用DQN算法,目标是最大化[{s1,a1,r1},{s2,a2,r2},…{sn,an,rn}]一系列的期望总的reward奖励,这里DQN思想是通过{s1,a1}会输出Q值(**它将返回在该状态下执行该动作的未来奖励期望**),采样尽可能多的action对来选择最大回报的action。 1582 | 1583 | ### RLHF理论 1584 | 1585 | ![image.png](image/image%2015.png) 1586 | 1587 | 问题1:RLHF原始训练公式是啥? 1588 | 1589 | 也即是policy objective: 1590 | 1591 | ![左边是训练好的奖励模型](image/image%2016.png) 1592 | 1593 | 左边是训练好的奖励模型 1594 | 1595 | 非常简单,目标最大化LLM输出y的偏好奖励值,同时避免离原始LLM策略偏差过大。 1596 | 1597 | 问题2:RLHF训练麻烦点在哪? 1598 | 1599 | 1、PPO很多超参,训练不稳定 1600 | 1601 | 2、需要同时操作三个模型(Actor、Ref、Reward),增加了复杂性。 1602 | 1603 | - **Actor Model**:负责生成动作的策略模型 *πθ*。 1604 | - **Reference Model (Ref Model)**:提供参考策略 *π*ref,用于计算KL惩罚。(预训练策略参考或人类专家参考) 1605 | - **Reward Model (RM)**:评估动作和状态对的奖励 *rϕ*(*x*,*y*)。 1606 | 1607 | 问题3:什么是reward hacking? 1608 | 1609 | 指的是在给定奖励机制下,个体通过**非预期的方式**最大化奖励的行为。 1610 | 1611 | 为了防止奖励黑客,通常会引入额外的约束或惩罚项(如KL散度),以确保代理的行为与期望的策略保持一致。 1612 | 1613 | 问题4:reward模型怎么来的? 1614 | 1615 | ![image.png](image/image%2017.png) 1616 | 1617 | ### DPO理论 1618 | 1619 | **Direct Preference Optimization** 1620 | 1621 | 问题1:如何从RLHF推导改进过来? 1622 | 1623 | 就是将reward model的损失目标过程将r(x,y)消掉,将KL散度展开对数并提出期望,期望变换。推导后得到奖励函数与最优策略之间的直接映射。 1624 | 1625 | 最终抵消r无需奖励模型: 1626 | 1627 | ![想要的数据概率-不要的数据概率](image/image%2018.png) 1628 | 1629 | 想要的数据概率-不要的数据概率 1630 | 1631 | 其中为当前策略生成回答的累计概率【每个token的概率求和】 1632 | 1633 | 数学推导参考:[https://github.com/chunhuizhang/personal_chatgpt/blob/main/tutorials/trl_hf/trl_dpo.ipynb](https://github.com/chunhuizhang/personal_chatgpt/blob/main/tutorials/trl_hf/trl_dpo.ipynb) 1634 | 1635 | 问题2:DPO的改进? 1636 | 1637 | 加载2个模型,其中一个推理,另外一个训练。直接在偏好数据上进行训练即可开始训练时,reference model和policy model都是同一个模型,只不过在训练过程中reference model不会更新权重。 1638 | 1639 | 1. RLHF算法包含奖励模型(reward model)和策略模型(policy model,也称为actor model),基于偏好数据以及强化学习不断迭代优化策略模型的过程。 1640 | 2. DPO算法不包含奖励模型和强化学习过程,直接通过偏好数据进行微调,将强化学习过程直接转化换为SFT过程,因此整个训练过程简单、高效,主要的改进之处体现在于损失函数。 1641 | 1642 | 追问:损失函数如何控制偏好程度以及KL散度的分布体现? 1643 | 1644 | 首先,公式核心思想希望调整模型的参数Π⊖,使得在给定输入x情况,生成yw的概率大于yl。 1645 | 1646 | 通过控制β来调整偏好程度(一般在0.1~0.5),β越大模型将更快的趋向于产生优选输出。 1647 | 1648 | 公式设计对数概率比率,其实很KL散度衡量两个分布差异相似,隐含了这种行为差异。 1649 | 1650 | **也就是 一个train, 一个ref用于计算KL散度,帮助训练保持稳定** 1651 | 1652 | 问题3:代码如何训练DPO? 1653 | 1654 | ```python 1655 | from trl import DPOTrainer 1656 | training_args = TrainingArguments() # 初始化训练参数 1657 | # 初始化 DPO trainer 1658 | dpo_trainer = DPOTrainer( 1659 | model_train, 1660 | model_ref, 1661 | peft_config=peft_config, 1662 | args=training_args, 1663 | beta=config.beta, 1664 | train_dataset=train_dataset, 1665 | eval_dataset=eval_dataset, 1666 | tokenizer=tokenizer,...) 1667 | # 训练 1668 | dpo_trainer.train( 1669 | # resume_from_checkpoint=True # 断点续训练 1670 | ) 1671 | # 9. save log 1672 | loss_log = pd.DataFrame(dpo_trainer.state.log_history) 1673 | loss_log.to_csv(f"{log_dir}/dpo_train_log_{time.strftime('%Y%m%d-%H%M')}.csv") 1674 | 1675 | # 10. 保存模型/lora 1676 | suffixe = '/lora/' if peft_config is not None else '/dpo' 1677 | model_save_dir = '/'.join(config.sft_model_file.split('/')[0: -1]) + suffixe 1678 | dpo_trainer.save_model(model_save_dir) 1679 | ``` 1680 | 1681 | 问题4:model_train和model_ref的关系? 1682 | 1683 | 一开始加载是同样的模型。随着训练的进行,`model_train` 会逐渐偏离 `model_ref`,以适应特定的偏好数据。这意味着 `model_train` 学习到了偏好数据中体现的“偏好”,从而在相同输入上倾向于生成与偏好一致的输出。 1684 | 1685 | ## 数据采集 1686 | 1687 |  在获得SFT模型的基础上,无需训练奖励模型,取得**正向回答(chosen)和负向回答(rejected)**即可开始微调。微调的`chosen`文本来自原数据集[alpaca-gpt4-data-zh](https://huggingface.co/datasets/c-s-ale/alpaca-gpt4-data-zh),拒绝文本`rejected`来自SFT微调1个epoch后的模型输出,另外两个数据集:[huozi_rlhf_data_json](https://huggingface.co/datasets/Skepsun/huozi_rlhf_data_json)和[rlhf-reward-single-round-trans_chinese](https://huggingface.co/datasets/beyond/rlhf-reward-single-round-trans_chinese),合并后共8万条dpo数据。 1688 | 1689 | DPO偏好优化数据集示例: 1690 | 1691 | ``` 1692 | { 1693 | "prompt": "为给定的产品创建一个创意标语。,输入:可重复使用的水瓶。", 1694 | "chosen": "\"保护地球,从拥有可重复使用的水瓶开始!\"", 1695 | "rejected": "\"让你的水瓶成为你的生活伴侣,让你的水瓶成为你的伙伴\"" 1696 | } 1697 | ``` 1698 | 1699 | 问题1:RLHF的训练集测试集单个样本示例? 1700 | 1701 | 实际用自己的chat_template处理chat: 1702 | 1703 | `{**"prompt"**: "<|im_start|>system\n你是一个由喵阿姨开发的喵喵小助手<|im_end|>\n<|im_start|>user\n请将下面的xx<|im_end|>\n 1704 | <|im_start|>assistant\n", "response": "Mary不喜欢这个颜色<|im_end|>", 1705 | **"chosen"**: "但John提出选择这个颜色用于新房间的壁纸。<|im_end|>", 1706 | **"rejected"**: "因为我不喜欢这个颜色。<|im_end|>"}` 1707 | 1708 | 问题2:实际输入模型的chosen_dataset和reject_dataset是咋样? 1709 | 1710 | 首先有chosen_sentence和reject_sentence,应用到chat_template的prompt如下,当然两个偏好样本红色回答处不一样 1711 | 1712 | ![image.png](image/image%2019.png) 1713 | 1714 | 然后通过分词器转化为token_id,并且max_seq_len向右补齐PAD,分词器输出包括["input_ids"],["attention_mask"]。例如: 1715 | 1716 | ![image.png](image/image%2020.png) 1717 | 1718 | ## RLHF代码过程 1719 | 1720 | **问题1:首先如何构建及加载奖励模型(RewardModel或者critic_model)?** 1721 | 1722 | 1726 | 1727 | 1、首先基于base模型和分词器加载,有些模型在输入序列开头会有填充标记。 1728 | 1729 | 2、计算奖励的关键通过`v_head`线性层将隐藏状态映射为奖励分数rewards = self.v_head(hidden_states).squeeze(-1)。【表示模型对每个token的打分】 1730 | 1731 | 3、将输入序列分成选择(chosen)和拒绝(rejected)两个部分。 1732 | 1733 | - 对于每对选择和拒绝的序列,找到它们首次不同的地方(称为 `divergence`)。 1734 | - 计算从 `divergence` 开始到序列结束(PAD位置)的奖励差异,并通过 `logsigmoid` 函数计算损失。 1735 | 1736 | 4、输出chosen_id ,rejected_id -》(1024) [输出的选择和拒绝token_id序列,多的PAD补齐] 1737 | 以及输出chosen_reward,rejected_reward -》(1024)【v_head的对应token奖励值,一般reward -1~1】 1738 | 索引`[div: end_ind]`的区间reward来计算**对比损失函数** 1739 | 1740 | ![image.png](image/image%2021.png) 1741 | 1742 | **输出一个y让他 max(chosen回答的奖励-reject回答的奖励)** 1743 | 1744 | 5、最后输出: 1745 | 1746 | ``` 1747 | { 1748 | "loss": loss, 1749 | "chosen_mean_scores": chosen_mean_scores, 1750 | "rejected_mean_scores": rejected_mean_scores, 1751 | } 1752 | ``` 1753 | 1754 | 其中chosen_mean_scores和rejected_mean_scores为**最后一个有效的token所对应的模型输出往往能够总结整个序列的语义或贡献**。而loss是整个一对偏好回答之间的loss 1755 | 1756 | 问题2:损失函数为啥不用标准的交叉熵损失? 1757 | 1758 | 任务是对两个序列进行排序或优劣比较,交叉熵损失并不适用。交叉熵损失是基于离散的标签(真实答案)进行监督学习,而在排序任务中,并没有明确的“正确答案”标签,只有“选择更好序列”的相对偏好Ranking Task。因此,选择**对比损失函数【logsigmoid】**,会被鼓励尽量增大两者的差异,从而提升预测的准确性。这种损失函数通常用于排序任务或强化学习中的策略优化。 1759 | 1760 | ```python 1761 | loss += -torch.nn.functional.**logsigmoid**(c_truncated_reward -r_truncated_reward).mean() 1762 | ``` 1763 | 1764 | 问题3:奖励模型是怎么设计的? 1765 | 1766 | 奖励模型的架构通常类似于一个分类器或回归模型,它接受输入和一个候选回答,并输出一个分数。 1767 | 这里就是在base模型上加一个线性层v_head转化为奖励值标量,通过比较“选择的序列”和“拒绝的序列”来进行优化的。每次前向传播后,根据计算的损失,梯度会被反向传播到 `v_head` 和 `base_model` 的参数中,从而更新它们的权重,使得模型的奖励输出更加符合人类偏好。: 1768 | 1769 | 问题4:如何评估奖励模型的效果? 1770 | 1771 | 有三个评价的指标:选择序列得分、拒绝序列得分和准确率。 1772 | 1773 | 其中acc= (选择的序列得分>拒绝的序列)的数量/总量 1774 | 1775 | 问题5:实验中的奖励模型好像不是事先训练好的分类器,这里具体采用了什么方法来RLHF的? 1776 | 1777 | 按照常规方法应该先通过人工标注数据独立训练奖励模型,然后用这个奖励模型的输出微调策略模型(也即是LLM), 用对比损失 PPO使其输出更符合人类的预期。 1778 | 1779 | **But,实际代码实现为了方便,通常奖励模型 和策略模型是一起训练的,奖励模型在base模型上加一个v_head线性层,负责状态映射为奖励值(本质上奖励和策略模型是一个)。采用边训练边打分,就是模型在自我监督的过程中不断调整自身的生成策略,使得输出更加符合“高奖励”的标准。 1780 | 在每个训练步骤中,模型生成“选择的序列”(`chosen_ids`)和“拒绝的序列”(`rejected_ids`),然后通过`v_head`层计算这两个序列的奖励值(`chosen_rewards`和`rejected_rewards`)。随后,这些奖励值被用于计算损失函数。** 1781 | 1782 | ————————代码实现了一个自监督的训练过程,使用单一模型同时进行生成和奖励评估,而没有典型RLHF中的两个分离模型———————— 1783 | 1784 | 代码体现很明显:`rm_model.backward(loss)` 1785 | 1786 | 追问:那这个奖励模型长啥样呢? 1787 | 1788 | - rm_model 1789 | 1790 | ```python 1791 | DeepSpeedEngine( 1792 | (module): RewardModel( 1793 | (v_head): Linear(in_features=512, out_features=1, bias=False) 1794 | (rwtransformer): MiaomiaoModel( 1795 | (embed_tokens): Embedding(32008, 512) 1796 | (layers): ModuleList( 1797 | (0-23): 24 x MiaomiaoDecoderLayer( 1798 | (self_attn): MiaomiaoSdpaAttention( 1799 | (q_proj): Linear(in_features=512, out_features=512, bias=False) 1800 | (k_proj): Linear(in_features=512, out_features=512, bias=False) 1801 | (v_proj): Linear(in_features=512, out_features=512, bias=False) 1802 | (o_proj): Linear(in_features=512, out_features=512, bias=False) 1803 | (rotary_emb): MiaomiaoRotaryEmbedding() 1804 | ) 1805 | (mlp): MiaomiaoMLP( 1806 | (gate_proj): Linear(in_features=512, out_features=2752, bias=False) 1807 | (up_proj): Linear(in_features=512, out_features=2752, bias=False) 1808 | (down_proj): Linear(in_features=2752, out_features=512, bias=False) 1809 | (act_fn): SiLU() 1810 | ) 1811 | (input_layernorm): MiaomiaoRMSNorm() 1812 | (post_attention_layernorm): MiaomiaoRMSNorm() 1813 | ) 1814 | ) 1815 | (norm): MiaomiaoRMSNorm() 1816 | ) 1817 | ) 1818 | ) 1819 | ``` 1820 | 1821 | 1822 | 问题6:训练时优化器和lr等选择? 1823 | 1824 | 优化器选择Deepspeed库提供的融合版FusedAdam,适合大规模分布式训练。将模型参数分为权重衰减和权重不衰减,方便Lora对应。 1825 | 1826 | 学习率调度器从零线性增加到初始设定的步数。 1827 | 1828 | | | A100 | RTX4090 | 影响FSDP | 1829 | | --- | --- | --- | --- | 1830 | | 内存带宽 | 1,555 GB/s | 1,008 GB/s | 1.5倍 | 1831 | | 通信带宽 | NVLink600 GB/s(双向) | PCIe 4 32 GB/s(单向) | >>18倍 | 1832 | 1833 | --- 1834 | 1835 | - 腾讯元宝论文精度:[https://yuanbao.tencent.com/chat/naQivTmsDa/8fe43035-69b8-436d-8ef6-9221c41db072](https://yuanbao.tencent.com/chat/naQivTmsDa/8fe43035-69b8-436d-8ef6-9221c41db072) 1836 | 1837 | 查询prompt: 1838 | 1839 | 1、有没有经典论文实验证明了课程学习预训练大模型策略对模型效果的影响?给我查询下论文,英文也行,就是提到LLM、课程学习,多阶段渐进预训练. 1840 | 2、这个论文讲了什么,重点帮我提取出论文提到的关于课程学习或者多阶段渐进预训练数据的策略相关对模型效果影响的内容或实验。 1841 | 1842 | 3、通读论文,概括下课程学习策略对模型的提升主要在哪,它的不足点在哪 1843 | 1844 | 4、这些帮我在原文中找到对应证明这些观点的句子或者图片表格。 1845 | 1846 | 1847 | ## 可行方向 1848 | 1849 | 1、实验mamba<7b 1850 | 1851 | 2、Moe多任务 1852 | 1853 | 3、Clip去看看多模态的 1854 | 1855 | 4、DPO非常重要,llama3.1技术报告有说,未来的资源会像这方面倾斜 -------------------------------------------------------------------------------- /image/从0训练LLM🌤️ db3ed7d1963f4dc7944b6505f4048613.md: -------------------------------------------------------------------------------- 1 | # 从0训练LLM🌤️ 2 | 3 | ![Untitled](Untitled.png) 4 | 5 | 9 | 10 | 由于预训练任务的本质在于「续写」,而「续写」的方式并一定能够很好的回答用户的问题。既然模型知道这些知识,只是不符合我们人类的对话习惯,那么我们只要再去教会模型「如何对话」就好了。**这就是 Instruction Tuning 要做的事情,即指令对齐。** 11 | 12 | 16 | 17 | 参考网址: 18 | 19 | 1. 训练llama3-20M:https://github.com/Mxoder/LLM-from-scratch/tree/main 。 公众号记录:[https://mp.weixin.qq.com/s/Yf_NU3pgedLHl8dWAaMRfQ](https://mp.weixin.qq.com/s/Yf_NU3pgedLHl8dWAaMRfQ) 20 | 2. InternLM: A Multilingual Language Model with Progressively Enhanced Capabilities. 证明了训练数据可以分成多个阶段,进行课程学习也能提升效果。 21 | 3. Scaling Language Models: Methods, Analysis & Insights from Training Gopher. DeepMind证明了提升模型规模和提升数据质量同样重要,仅仅是大模型也做不好推理任务,但如果数据处理的好的话,模型的推理能力能大幅提升。 22 | 4. 数据处理的思考:[https://mp.weixin.qq.com/s/oKMLhw5hk0LP85dtRAzBDg](https://mp.weixin.qq.com/s/oKMLhw5hk0LP85dtRAzBDg) 23 | 5. karpathy大神训练1B的nanoGPT: [**https://github.com/karpathy/nanoGPT**](https://github.com/karpathy/nanoGPT) 24 | [https://mp.weixin.qq.com/s/d1ypjLwaJKEV8Edfz83tVw](https://mp.weixin.qq.com/s/d1ypjLwaJKEV8Edfz83tVw)【**从零训练的 1B 以下小模型汇总**牛逼整理】 25 | 6. TinyLLama-1.1B模型训练:https://github.com/jzhang38/TinyLlama/blob/main/README_zh-CN.md【A100 *16 ,3个月,3万亿token,后面优化】 26 | 7. 训练的比较小而美:https://github.com/EleutherAI/pythia,有好多M级别checkpoints 27 | 8. 阅读了Allen实验室发布的关于[**OLMO模型**的技术报告](Accelerating the Science of Language Models),大方地开源了整套大模型构建的代码,涵盖了数据处理、训练、评估等各个环节。**https://github.com/allenai/OLMo** 28 | 9. [MINI_LLM](https://github.com/jiahe7ay/MINI_LLM),面壁智能的一位员工分享。知乎:https://zhuanlan.zhihu.com/p/684946331 29 | 10. [baby-llama2-chinese](https://github.com/DLLXW/baby-llama2-chinese),教你从头训练以及SFT,证明数据和模型参数才是王道。 30 | 31 | ![Untitled](Untitled%201.png) 32 | 33 | 11. tokenizer到RLHF全流程,并支持下游任务sft微调,给出三元组信息抽取微调示例:https://github.com/charent/ChatLM-mini-Chinese/ 34 | 12. 小项目:https://github.com/AI-Study-Han/Zero-Chatgpt 35 | 13. 36 | 37 | ![Untitled](Untitled%202.png) 38 | 39 | ![image.png](image.png) 40 | 41 | 问题1:微软的工作 TinyStories小模型探索? 42 | 43 | 原工作用的是 GPT Neo 架构(可以看他们的 config),这个算是很老的模型了用来追踪复现gpt3,现在我改成llama3。像早期的一些语言模型如 GPT-2,即使在一些 Common Craw 这样的语料库上大量预训练后,也很难生成长的、连贯的文本。比如前几年有一种 AI 玩具类型是做文本续写,例如彩云小梦,可以写写作文、小说什么的,如果大家玩过就知道效果其实一言难尽,和今天的大模型完全没法比,其实这就是 GPT-2 level 的续写能力。 44 | 45 | 追问:那么为啥不行呢? 46 | 47 | 会不会是因为训练的语料库太多、太宽泛,需要学习各种语法元素、词汇、知识、推理等等,才导致小语言模型(SLM)没法有一个很好的表现。作者决定专注于一个任务——短篇故事续写,来探索一下 LM 的性能边界。 48 | 49 | ## 数据为王 50 | 51 | 问题1:数据痛点有哪些? 52 | 53 | 数据处理,**数据配比**,模型调优,评估方案透露很少。 54 | 55 | 但中文社区之前最大的数据集仍是Wudao-data,首先量级上和英文数据集完全不对等,Wudao-data仅包含大约53B的 token,而C4则是超过100B,RefinedWeb、ThePile、The Stack等数据集则是在500B token的量级。其次是质量,尽管Wudao-data经过了严格的清洗过滤,但我们还是发现有很多格式错乱、重复、数据质量低的问题。 56 | 57 | 按照现在开源模型与日俱增的训练数据量来看,后续开源的基础模型估计都得2T tokens起步了,否则效果难以比肩(按照scaling law来看) 58 | 59 | ![Untitled](Untitled%203.png) 60 | 61 | **启智AI协作平台** 62 | 63 | 1. Wudao**开源200G、53B tokens**(闭源5TB,采用20多种规则从100TB原始网页数据中清洗得出最终数据集):[https://openi.pcl.ac.cn/openihu/WuDaoCorpus2.0_base_200G/datasets](https://openi.pcl.ac.cn/openihu/WuDaoCorpus2.0_base_200G/datasets) 64 | 2. Falcon系列模型是在RefinedWeb数据集【纯英文】上训练的大语言模型。RefinedWeb数据集是基于CommonCrawl构建的高质量数据集,包含了经过去重和过滤后得到的包含上万亿tokens的数据集。**开源600G**(闭源5000G)-2.8TB -别人也就训练7B和40B 65 | 使用trafilatura进行文本提取,文档和行级别规则,NSFW URL黑名单 66 | 去重:精确和模糊匹配:精确子字符串 + MinHash(约50%被移除) 67 | 《The RefinedWeb Dataset for Falcon LLM:Outperforming Curated Corpora with Web Data, and Web Data Only》 68 | 3. **Skywork/Skypile-150B**数据集开源600G约150Btoken(闭源3.2万亿token),包含大约166M个单独网页,平均每篇文章中文字符超过1,000页面中包含的经过处理和清理的文本 (并使用了FastText和BERT等模型移除了黄暴、低质量的内容) ,数据格式为jsonl,每一行为一个文档,按行用json进行解析,文本存放在text字段中。他们说好于wudao而且**最坦诚**的technical report 69 | 闭源的用于**Skywork-13B**预训练:**详见https://github.com/SkyworkAI/Skywork** 预训练分为两个阶段:1、监督模型训练损失和CEVAL、MMLU等准确率变化。 2、第二阶段预训练在通用语料中额外加入STEM(数学,科学,工程,技术)相关数据继续训练。第二阶段训练大约130B token,两阶段总计训练3.2T,产生了我们最终的Skywork-13B-Base模型。 70 | 4. [**TigerBot](https://github.com/TigerResearch/TigerBot),** 基于 GPT3 的 pretrain 的数据分布,采集中文书籍,互联网,和百科类数据,并通过数据源质量分过滤和 tf-idf soft deduping,从 20TB 数据过滤到 2TB,保持语言和类目的比例,并在此基础上随机抽样 [**100G 数据开源**](https://github.com/TigerResearch/TigerBot?tab=readme-ov-file#%E9%A2%84%E8%AE%AD%E7%BB%83%E6%95%B0%E6%8D%AE) 71 | 72 | ![Untitled](Untitled%204.png) 73 | 74 | 5. [**书生·万卷](https://opendatalab.org.cn/OpenDataLab/WanJuan1_dot_0)多模态语料库,** 开源分为1,2版本。并且还包括图文数据集1.0和视频数据集。我们使用纯文本数据集1.0,来自网页、百科、书籍、专利、教材、考题等不同来源的清洗后预训练语料组成,数据总量超过5亿个文档,数据大小**超过1TB(中英混合)**。 75 | 76 | ![Untitled](Untitled%205.png) 77 | 78 | ![Untitled](Untitled%206.png) 79 | 80 | [WanJuan2.0](https://opendatalab.org.cn/OpenDataLab/WanJuanCC)参考是从CommonCrawl获取的一个 1T Tokens 的高质量英文网络文本数据集,在各种验证集上的PPL表现出竞争力,特别是在要求更高语言流畅性的tiny-storys等集上。 81 | • **数据量**:**约 100B Tokens(主要是英文为主)**; 82 | 83 | ![Untitled](Untitled%207.png) 84 | 85 | 1. 86 | 87 | 人话-初级checkpoints 88 | 89 | 1-2- 90 | 91 | 问题2、预训练数据组成有哪些? 92 | 93 | 中英文各自:网页数据、社交媒体数据、百科全书、其他年报等,Github。当然构建领域数据评估模型好坏也很重要: 94 | 95 | | 技术文章 | 电影评论 | 政务报告 | 游戏 | 金融 | 通用领域 | 96 | | --- | --- | --- | --- | --- | --- | 97 | 98 | ### 1.1 数据经验 99 | 100 | 面壁科技minicpm作者关于预训练分享: 101 | 102 | 1、中英混合比例。逻辑推理比较强的样本,像代码,数学。这种就是**模型越大,混合的比例反而可以越高。** 103 | 104 | 2、导致ppl崩掉(困惑度异常高或不收敛)的,都要清洗掉,政治敏感数据清洗,去重等,肯定是一个很长的pipeline。 105 | 106 | 3、基于开源数据,做一些聚类的topic。然后基于这些topic,丢到更大的模型,来构建一批高质量的数据,是一个反而比较低成本的方案 107 | 108 | 问题1:不同训练阶段的训练样本有哪些方案? 109 | 110 | 1、**末期高质量样本(面壁科技minicpm),**快速收敛阶段和平稳阶段,都采用普通样本。 111 | 112 | 退火阶段【学习率或其他超参数逐渐降低,以帮助模型更好地收敛到稳定的最优解】,混入高质量样本来做教科书式的学习【提升模型的最终性能】。 113 | 114 | 2、初期高质量样本。先高质量为主让模型快速收敛后添加更多普通样本 115 | 116 | 3、**全程高质量样本(PHIL方式)。**PHIL就是探索小模型能不能在特定领域达到SOTA。**好处,特定榜单/领域效果会特别好。坏处,模型泛化能力会很差**(但PHIL从来没说要做世界模型。 117 | 118 | 问题2:数据配比太重要了,有哪些trick? 119 | 120 | 参考BloombergerGPT,就会发现模型的能力其实很差,比通用大模型会差很多。这里面犯的最大的错误就是数据配比,他们应该是用1:1的比例混合通用数据和金融数据。 121 | 122 | 1、预训练时,通用数据和领域数据绝对不能1:1。对于复现chatgpt3.5来说,**数据配比应该是OpenAI最核心的秘密**和比别人领先最后的地方。 123 | 124 | 2、二次预训练时,领域数据比例要在15%以下,一旦超过这个阈值,模型的通用能力会下降很明显。这个结果其实和ChatGPT大概用不到10%的中文数据就能得到一个很不错的中文模型的结果还挺相似的,每100B的领域数据,需要配上700B-1000B的通用数据,这比直接训练通用大模型要困难多了。 125 | 126 | 3、sft时,其实领域数据和通用数据比例在1:1的时候还是有不错的效果的。当然,如果sft的数据量少,混不混数据的差别就不太大了。(比预训练好多了🧋) 127 | 128 | ### 1.2 数据处理 129 | 130 | [https://mp.weixin.qq.com/s/oKMLhw5hk0LP85dtRAzBDg](https://mp.weixin.qq.com/s/oKMLhw5hk0LP85dtRAzBDg) 131 | 132 | 问题1:预训练的数据可以分为哪两类? 133 | 134 | 一类是网页数据(web data),比如非盈利性机构构建的CommonCrawl数据集(5000G过滤后)是一个海量的、非结构化的、多语言的网页数据集。它包含了超过 8 年的网络爬虫数据集,包含原始网页数据(WARC)、元数据(WAT)和文本提取(WET),包含数百亿网页,数据量级在PB级规模 135 | 136 | 二类称之为专有数据(curated high-quality corpora),为某一个领域、语言、行业的特有数据。比如对话、书籍、代码、技术报告、论文考试等数据。 137 | 138 | 问题2:专有数据有多难开源的? 139 | 140 | 就拿高质量的book书籍数据来说,在网上能直接获取到数据来自The pile中的Book3,量级也才85GB左右,和这些巨头所用数据量级相差数十倍(Books – 2TB)。 141 | 142 | 问题3:文本去重和存储推荐啥格式? 143 | 144 | 推荐使用`parquet`格式(列式存储格式,只读取相关的列, Hadoop 生态系统开发/həˈduːp/)存储数据,可以大大减小存储占用 145 | 146 | 推荐使用`Minhash`去重,效果优于Simhash,但时间消耗长!([参考](https://github.com/DLLXW/baby-llama2-chinese)) 147 | 148 | 问题4:数据处理过程? 149 | 150 | 根据分析得到的数据集特征,调整配置文件,再进行数据处理: 151 | · 数据处理30法则:若某个数据点超出均值±30的范围,通常被视为异常值 152 | · **先进行筛选,再过滤**,能减少数据处理的时间 153 | 154 | 好数据模板: 155 | 156 | ![538587cc32782cf454cc3c37d9de045.jpg](538587cc32782cf454cc3c37d9de045.jpg) 157 | 158 | > LLama,Falcon等模型已经证明,预训练中大部分使用网页数据可以训练出非常不错的大模型。 159 | > 160 | 161 | 智源社区:[https://hub.baai.ac.cn/view/30442](https://hub.baai.ac.cn/view/30442) 162 | 163 | ## scaling law 164 | 165 | 由OpenAI在2020年提出,经验公式: 166 | 167 | 171 | 172 | 根据scaling law,模型越大,高质量数据越多,效果越好。 173 | 174 | 但还有一个很直观的情况,随着预训练样本的质量不断提升,训练手段的优化。新的模型,往往效果能轻松反超参数量两倍于它的模型。 175 | 176 | 《**Scaling Laws for Neural Language Models**》 模型的最终性能**「主要与」**计算量,模型参数量和数据大小三者相关,而与模型的具体结构(层数/深度/宽度)基本无关。假定计算量整体放大10倍,OpenAI认为模型参数更重要,模型应放大 (5.32)倍,数据放大 (1.86)倍;后来DeepMind和Google认为模型参数量与数据同等重要,两者都应该分别放大 (3.16)倍。 177 | 178 | 不过LLama认为这个观点是针对**「训练阶段」**而言的,并不是**「推理阶段」。尽管根据Scaling Law,`10B模型只需要200B的数据( Chinchilla 定律),但是作者发现7B的模型性能在1T的数据后还能继续提升`。** 179 | 180 | 过拟合程度与模型大小和数据集大小的比例有关。每增加8倍模型参数,只需增加约5倍数据就能避免性能损失。 181 | 182 | 故事: 183 | 184 | > 但我们回到2020年,当大部分人都在基于bert做各种魔改的时候。OpenAI发现了这么一个规律。数据,训练参数一直增长下去,好像loss的确是在不断的下降哎?于是,他们拿着这个paper去问微软的CTO,你想不想看看这个loss下降到一定程度会发生什么?会发生什么 185 | chatgpt就出来了 186 | > 187 | 188 | ## **Tokenizer** 189 | 190 | LLaMA 2 的分词器,二代的词表比较小(32k),LLaMA 3 的词表太大了(128k),在 SLM 中会占用太多的参数比重,并且这只是个专有任务数据训练,没必要用太大的词表。[QWEN-1.8b] 191 | 192 | > **分词后一个中文大概1~3token占用,每一个token只需要两个字节(uint16存储)** 193 | > 194 | 195 | 问题1:自己构建分词器还是用别人的? 196 | 197 | 推荐直接用别人的,**glm2(词表大小=64793)**和qwen的词表中文都很好(llama官方所提供的词表中,中文的部分只有700个,这也是llama中文能力聊胜于无的原因),而且压缩率很高。baby进行过实验,小模型的分词器其实影响不大: 198 | 199 | ![Untitled](Untitled%208.png) 200 | 201 | 追问:如果自己构建tokenizer有哪些方法? 202 | 203 | tokenizer到词表构建有三种[token子词划分算法]:BPE(Byte-Pair Encoding)、WordPiece、SentencePiece 204 | 1、**BPE:统计每个连续或相邻`字节对`出现频率【传统的以字符为单个token而非字节,apple字符=[a,p,p,l.e]】,将最高频的连续字节对合并为新的子词**。 GPT2、LLaMA 205 | 2、WordPiece:将能够提升LM概率最大的相邻子词进行合并加入词表 。例如BERT 206 | 3、SentencePiece:把空格当成一种特殊字符处理。例如ChatGLM、PaLM 207 | 208 | 追问1:BPE在中文语料库构建分词器有什么局限? 209 | 210 | BPE提出在英文字符处理多字节的高频字节对合并,英文字符通常是单字节的,因此 BPE 分词器通过字节对的合并操作能够生成合理的子词。但是`中文字符~2-3个字节`,**BPE 可能会错误地将这些字节视为独立的字符或部分字符进行处理,导致在编码过程中将一个完整的汉字拆分成多个错误的字节组合**。如果编码不是UTF-8,解码时会乱码。 211 | 212 | 追问2:那针对中文的分词解决方案? 213 | 214 | 在训练 BPE 分词器时,可以考虑使用基于字符级别的分词器(例如 `BertTokenizer`)或使用专门为中文设计的分词方法,如 Jieba 或 `SentencePiece`。这些工具对中文的处理更友好。 215 | 216 | - `BBPE(Byte-level BPE`)通过将字节作为基本单元进行分词,克服了传统 BPE 在处理多字节字符时的局限性。它的语言无关性和对所有 Unicode 字符的支持,使其成为多语言模型分词的理想选择。 217 | - BBPE[Byte-level BPE]分词流程举例子【将单词拆分为字符序列并在末尾添加后缀“ ``”,统计单词频率】 218 | 219 | 在 BBPE 中,该句子的处理流程如下: 220 | 221 | 1. 将每个字符编码为字节序列: 222 | - “你” -> `E4 B8 8B` 223 | - “好” -> `E5 A5 BD` 224 | - “吗” -> `E5 90 97` 225 | - “?” -> `3F` 226 | 2. BBPE 根据频率统计合并高频字节组合,可能最终将 `E4 B8 8B`、`E5 A5 BD` 等组合为单独的子词。 227 | 3. 最终,编码器将输入句子编码为字节组合序列。 228 | 229 | ~~问题1:为什么分词批量填充时padding_side='left’?~~ 230 | 231 | 对于 decoder-only 的模型做生成任务是必要的,因为我们本质上做的是 next token prediction,如果 pad 挡在了生成序列的右边,会影响到模型生成。 232 | 233 | 问题2:自回归语言模型训练用的分词器处理是啥函数? 234 | 235 | 使用 `DataCollatorForLanguageModeling` 进行自回归语言模型训练时,通常会对输入序列进行填充,并生成相应的标签用于预测下一个token。【不做MLM完型填空任务其实就是token预测】 236 | 237 | - 分词预测代码 238 | 239 | ```python 240 | # DataCollatorForLanguageModeling 241 | dc = DataCollatorForLanguageModeling(tokenizer, mlm=False) 242 | data = ['南京', '南京市', '南京市长江'] 243 | 244 | raw_tokens = [tokenizer(text) for text in data] 245 | 246 | print(f'tokenizer.pad_token_id: {tokenizer.pad_token_id}\n') 247 | print(dc(raw_tokens)) 248 | 249 | ''' 250 | tokenizer.pad_token_id: 151643 251 | 252 | { 253 | 'input_ids': tensor([[151643, 151643, 102034], 254 | [151643, 151643, 112891], 255 | [102034, 102975, 69177]]), 256 | 'attention_mask': tensor([[0, 0, 1], 257 | [0, 0, 1], 258 | [1, 1, 1]]), 259 | 'labels': tensor([[ -100, -100, 102034], 260 | [ -100, -100, 112891], 261 | [102034, 102975, 69177]]) 262 | } 263 | ''' 264 | labels 确实是 input_ids 的原位复制,区别在于 input_ids 里用 pad_token_id 来填充,labels 里对应的是 -100、表示不计算 loss 265 | ``` 266 | 267 | 268 | 追问:DataCollatorForSeq2Seq 和 DataCollatorForLanguageModeling区别是啥? 269 | 270 | DataCollatorForSeq2Seq:用于序列到序列任务,处理输入和输出序列对,适用于机器翻译、文本摘要等任务。 271 | DataCollatorForLanguageModeling:用于语言模型任务,处理单个序列,适用于掩码语言模型和自回归语言模型(Causal Language Modeling, CLM)的训练。 272 | 273 | 问题3:为什么tokenizer压缩率高了好? 274 | 275 | 比如Qwen的分词器就很好,首先压缩率指的是tokenizer将文本转换为token序列时的效率和效果。压缩率越高,代表相同文本内容时,生成的token序列更短,每个token包含信息量更大。这样的好处使可以减少计算消耗,提高模型效率,降低数据冗余。 276 | 277 | 问题4:分词器处理数据如何存储的? 278 | 279 | 数据预处理采取GPT的通用做法,对语料进行提前分词,对一个样本做完分词后在末尾加上一个结束符号``,与下一个样本区分开。然后将所有的训练语料拼接成一个数组(`np.uint16`)**以.bin二进制格式存储到磁盘上**。如果语料过大,避免内存溢出,可以选择mmap格式。 280 | 281 | 追问:uint16是啥?mmap格式是啥? 282 | 283 | uint16是Unsigned 16-bit Integer,非负整数,占2字节,比传统int32(有负的)节约一半内存存储。 284 | 285 | **mmap(内存映射文件)**是一种用于将文件或文件的一部分映射到进程的地址空间的技术。这**使得文件的内容可以像在内存中一样进行访问,而不需要将整个文件加载到内存中—-多进程可以通过映射共享文件数据防止多次IO操作**。【以像操作内存数组一样操作文件内容。这种方式可以提高文件I/O操作的效率】 286 | 287 | 问题5:special_tokens在预训练中的作用? 288 | 289 | 假设tokenizer.special_tokens['']=2,这个和tokenizer本身SentencePiece 也将 定义为 2要进行明确,可以覆盖。然后在预训练阶段我们通过这个token_id=2也就是来分割文本之间。假设输入[30910, 56961, 2, 54640] →decode后为‘昭通’; 实际[30910, 56961, 54640] →decode后也为‘昭通’。**这个2在字符串是没有任何意思的,但是对模型输入是有很大用的,他占了一个token位置,并且会参与attention计算**,所以之前有学者**SoftMax-off-by-One做文章,防止(量化或流式部署时有问题),97% 以上的异常激活发生在空格和标点符号位置上** 290 | 291 | - 代码实现 292 | 293 | ```python 294 | import sentencepiece as spm 295 | import torch 296 | sp = spm.SentencePieceProcessor() 297 | sp.load('./chatglm_tokenizer/tokenizer.model') 298 | X = torch.tensor([[30910, 56961, 2, 54640]], device='cuda:0') 299 | tokens_list = X[0].tolist() 300 | print(sp.decode(tokens_list)) 301 | # 想查看tokenizer.special_tokens['']=2 也就是说位置 302 | print(((X[0] == 2).nonzero(as_tuple=True)[0]).tolist()) 303 | ``` 304 | 305 | 306 | 问题6:embedding层参数为啥不宜过大? 307 | 308 | 为了避免模型头重脚轻,所以词表和embedding层参数不能太大。如果embedding层的参数占比过高,导致过拟合模型可能会更容易记住训练数据而不是学习到通用的特征。并且浪费推理资源而且embedding层的影响可能会掩盖其他层的效果,使得调参过程复杂化。 309 | 310 | 问题7:如何训练自己的BPE分词器? 311 | 312 | **调用tokenizers库里的方法,初始化tokenizer,设置BPE训练器和设置词汇表大小3.2w,将文本yield逐句读取训练,同时设置字符级解码**。如下代码 313 | 314 | - tokenizer训练代码 315 | 316 | ```python 317 | # 初始化tokenizer 318 | tokenizer = Tokenizer(models.BPE()) 319 | tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) # 处理字节级文本的预处理器 320 | 321 | # 设置训练器 322 | trainer = trainers.BpeTrainer( 323 | vocab_size=32000, # 创建一个 BPE 训练器,设置词汇表大小为 32000 324 | show_progress=True, 325 | initial_alphabet=pre_tokenizers.ByteLevel.alphabet() # 初始化训练器的字母表 326 | ) 327 | 328 | # 读取文本数据 329 | texts = read_texts_from_json(data_path) # 逐个句子读取 330 | 331 | # 训练tokenizer 332 | tokenizer.train_from_iterator(texts, trainer=trainer) 333 | 334 | # 设置解码器 335 | tokenizer.decoder = decoders.ByteLevel() # 用于将分词后的字节级别标记转换回文本 336 | 337 | # 保存tokenizer 338 | tokenizer_dir = "./miaomiao_tokenizer" 339 | os.makedirs(tokenizer_dir, exist_ok=True) 340 | tokenizer.save(os.path.join(tokenizer_dir, "tokenizer.json")) 341 | tokenizer.model.save("./miaomiao_tokenizer") 342 | ``` 343 | 344 | 345 | 最主要、手动添加特殊标记。 346 | 347 | 1、这里添加`system`, `user`, `assistant,<|endoftext|>`等为词表外的特殊解码词, 348 | 349 | 2、另外指定`"additional_special_tokens": ["<|im_start|>", "<|im_end|>"]为特殊tokens【这个占了token_id,但解码后没有任何意义,GPT4输入这个都会当成空】` 350 | 351 | 3、最后`bos_token`, `eos_token`, `pad_token`, `unk_token` 是常用的特殊 token,分别表示句子的开始、结束、填充和未知 token。这里配置为空或 `None`。其中`"eos_token": "<|im_end|>”+"pad_token": "<|endoftext|>",` 352 | —-——-—chat_template配置也在分词器构建进行———- 353 | 354 | 模板中使用了 `Jinja2` 风格的语法 355 | 356 | "chat_template": "{% for message in messages %}{% if loop.first and messages[0]['role'] != 'system' %}{{ 'system\n你是一个由喵阿姨开发的喵喵小助手\n' }}{% endif %}{{'' + message['role'] + '\n' + message['content'] + '' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ 'assistant\n' }}{% endif %}” 357 | 358 | 问题8:BPE生成哪些分词信息文件? 359 | 360 | merges.txt(存储了 BPE 合并规则)和vocab.json( token 到 token ID 的映射)。这两个文件包含了BPE算法所需的核心信息。 361 | 362 | tokenizer_config.json(配置了分词器的相关参数,如 tokenizer 类、特殊标记等),tokenizer.json(通常包含完整的词表、BPE 合并规则) 363 | 364 | ![image.png](image%201.png) 365 | 366 | ## 混合精度 367 | 368 | 问题1:vLLM是啥? 369 | 370 | 答:vLLM 采用一种**集中式调度器**(scheduler)来协调分布式 GPU 工作器(worker)的执行。KV 缓存管理器由 PagedAttention 驱动,能以分页方式有效管理 KV 缓存。 371 | 372 | v100不支持bf16, 改用fp16吧。 373 | 374 | 问题2:**bf16和fp16区别?** 375 | 376 | 都是半精度,2字节。BF16可表示的整数范围(指数位)更广泛,但是尾数精度较小(有效精度尾数位);FP16表示整数范围较小,但是尾数精度较高。 377 | 378 | ![Untitled](Untitled%209.png) 379 | 380 | **问题3:显存占用有哪些?** 381 | 382 | 四部分:模型权重+优化器、梯度、动量等+前向激活值+cache 383 | 384 | ![Untitled](Untitled%2010.png) 385 | 386 | **问题4:激活显存(Activation Memory)是啥作用?如何节约显存占用** 387 | 388 | 激活显存(Activation Memory)指的是在深度学习模型的前向传播和反向传播过程中,存储中间激活值所需的显存。这些激活值是神经网络每一层的输出,它们在反向传播阶段用于计算梯度。 389 | 390 | **激活显存 = 每一层输出D × 批次B × 序列长度L × 2bytes (所以训练加载如果激活要预留一般显存)** 391 | 392 | 追问:所以如何管理显存占用呢? 393 | 394 | — **梯度检查点**(Gradient Checkpointing):通过在前向传播过程中有选择地存储激活值,在需要时重新计算未存储的激活值,以减少显存占用。 395 | 396 | — **混合精度训练**(Mixed Precision Training):使用FP16而不是FP32来存储激活值,可以显著减少显存占用。 397 | 398 | —**分布式训练**(Distributed Training):将模型和数据分布到多个GPU上,可以有效地分摊激活显存的需求。 399 | 400 | —**调小batch**,或者**累计梯度**模型大batch的训练效果 401 | 402 | 问题5:在反向传播时对梯度计算有做什么处理? 403 | 404 | 梯度剪裁,在1阈值进行基于梯度L2范数的裁剪,不过剪裁前需要先取消缩放(unscale)这些梯度【考虑到混合精度+累计梯度对loss、优化器的缩放】。在反向传播时防止梯度爆炸,从而确保训练过程的稳定性对深层网络结构友好。 405 | 406 | 实现:================= 407 | `scaler = torch.cuda.amp.GradScaler(enabled=(dtype == 'float16')) 408 | scaler.unscale_(optimizer) 409 | torch.nn.utils.clip_grad_norm_(model.parameters(), 1)` 410 | 411 | 追问5:混合精度训练通过什么实现的? 412 | 413 | 首先,混合精度上下文管理器 (`torch.cuda.amp.autocast()`), 414 | 大多数的计算会使用 `float16` 进行加速和节省显存,只有在需要较高精度的操作(如 `softmax`)时会自动回退到 `float32`. 415 | `torch.cuda.amp.**GradScaler`【**在反向传播之前,将损失放大,这样即使在 `fp16` 精度下计算,梯度值也足够大,避免因数值精度问题导致梯度消失**】** 负责在训练过程中动态调整缩放因子,以避免数值不稳定的情况。也就是**在正向计算保存为fp16,反向梯度更新要还原为fp32。** 416 | 417 | ## 优化器 418 | 419 | 问题1:**优化器和LR scheduler如何协同工作?** 420 | 421 | 首先,优化器负责更新模型参数以最小化损失函数(比如Adam会维护一些内部状态,比如动量、学习率调整影响 参数更新 的方式),而学习率调度器负责动态调整学习率,以便在训练过程中更好地控制参数更新的步长。 422 | 423 | 427 | 428 | 问题2:什么是**W-(C)-S-D策略<—学习率策略?** 429 | 430 | 改进:`Warm-Cosine-Stable-Decay` 【warmup预热+余弦退火策略+最小学习率收尾】 431 | 432 | - Warmup:与现有策略相同,初期阶段逐渐增加学习率。 433 | - Cosine:在训练的主要阶段,采用**余弦退火(Cosine Annealing)策略。学习率按照余弦函数变化,逐渐减小到一个最小值**。这种策略有助于在训练过程中跳出局部最优,因为学习率会周期性地增大和减小。 434 | - Stable:**在训练即将结束时**,将学习率升高到一个稳定值。这段时间内,模型以稳定的学习率进行训练,有助于模型在新的学习率下找到更优的解。 435 | - Decay:在训练的最后阶段,逐渐减小学习率,以帮助模型收敛到最终的解。 436 | - lr代码 437 | 438 | ```python 439 | def get_lr(it): 440 | # 1) 线性 Warmup从0开始增到0.1 441 | if it < warmup_iters: 442 | return learning_rate * it / warmup_iters 443 | # 2) if it > lr_decay_iters, return min learning rate 444 | if it > lr_decay_iters: 445 | return min_lr 446 | # 3) 余弦衰减to min learning rate 447 | decay_ratio = (it - warmup_iters) / (lr_decay_iters - warmup_iters) 448 | assert 0 <= decay_ratio <= 1 449 | coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio)) # coeff ranges 0..1 450 | return min_lr + coeff * (learning_rate - min_lr) 451 | ``` 452 | 453 | 454 | 追问:还有余弦退火策略的步长控制建议? 🉐 🉐 🉐 455 | 456 | 463 | 464 | 471 | 472 | ![在使用余弦退火策略时,合理设置周期长度的重要性。建议在目标步长的110%~120%之间,不要超过25%!!!](image%202.png) 473 | 474 | 在使用余弦退火策略时,合理设置周期长度的重要性。建议在目标步长的110%~120%之间,不要超过25%!!! 475 | 476 | > **lr高→探索,低→微调/稳定** 477 | > 478 | 479 | 问题3:配置优化器的部分为什么,大于或等于2D(维度>2D)的参数会被衰减,小于2D不会衰减? 480 | 481 | 神经网络参数的维度决定了其自由度。**对于大于或等于2D的参数(如权重矩阵),通常应用权重衰减以限制参数大小,减少过拟合风险**。对于小于2D的参数(如偏置向量),通常不应用权重衰减,因为其对模型复杂度影响较小。通过为不同参数组设置不同的衰减策略,可以更有效地控制模型复杂度。 482 | 483 | 问题4:自定义的学习率策略在代码如何实现参数更新? 484 | 485 | get_lr()指定策略动态计算当前迭代的学习率,再更新到torch自带`optimizer.param_groups`中优化器中各参数组的学习率[’lr’],从而控制模型训练过程中参数更新的步长。 486 | 487 | ## 评价指标 488 | 489 | [syk-benchmark评价指标] 490 | 491 | 问题1:预训练和微调评价指标不同? 492 | 493 | 比起预训练(Pretrain)环节里相对明确的评价指标(如PPL、NLL等), 494 | 495 | NLL 是指负对数似然(Negative Log-Likelihood),希望最小化(=最大化训练数据的似然)拟合最好。 496 | 497 | ![Untitled](Untitled%2011.png) 498 | 499 | Instruction 环节中的评价指标比较令人头疼。BLEU 和 ROUGH 这样的指标已经不再客观 × 500 | 501 | 像 [FastChat] 中一样,利用 GPT-4 为模型的生成结果打分 √ 人类打分 √ 502 | 503 | 问题2:如何对预训练阶段模型评估? 504 | 505 | 传统的评估主要看的是Training loss或者Benchmark上的指标。但是有缺陷: 506 | 507 | 1、Training loss因为数据配比的原因是一个整体的指标,并且不同模型不能相互比较。比如代码占比越高,其实loss会更低因为结构更容易预测。 508 | 509 | 2、Training loss第二个问题是可能会选择出一个更加过拟合的模型 510 | 511 | 3、Benchmark的问题就更加严重了,第一是因为benchmark是公开数据,非常容易针对。Benckmark评估具有一定的波动性&可操作性而且评估是准确率(不连续的指标),不同Prompt选择和答案提取方式会对结果产生很大的影响 512 | 513 | 因此还有一种方法,**构建了中文,英文,代码,arxiv文章等多个领域的验证并且没有出现在训练数据中。通过Cross Entropy损失函数,整体的损失函数为每个位置预测真实词损失的平均 ——来监控模型训练效果**(参考skywork预训练) 514 | 515 | 追问:grokking现象是啥? 516 | 517 | 在前几轮的训练中,模型的损失函数(loss)持续下降,但准确率没有显著提高。突然,在某一轮训练之后,模型的准确率大幅提升。这种现象可以被描述为模型“grokking”了任务。(也就是用准确率是不连续指标,它提醒我们模型性能的提升有时并不是线性的,而是可能在训练的某个阶段突然发生) 518 | 519 | 问题3:评估时为啥要让所有模型的文档长度一样,而不是让分词后token一样? 520 | 521 | 本质上领域困惑度是衡量不同模型生成高质量文档的概率,概率越大模型效果越好,因此我们需要保证所有模型看到的文档是一样的。此外,因为不同模型使用不同的分词器,分词后的token数目差异很大,以Llama为例,会将汉字切分为3个unicode编码,如果按照分词后token比较的话,那么Llama模型看到的文档长度会比其他模型短,而我们知道文档前面的token loss是更低的,后面token loss更高,因此按照分词后token比较的话会对Llama这种分词更细的模型会不公平。 522 | 523 | 问题4:评测数据的指标如何计算,比如常用的acc分数? 524 | 525 | 这里拿`MMLU综合语义理解`数据评测计算举例子,可以参考/home/ph/LLM/Skywork-main/data/。 526 | 527 | 首先介绍下MMLU测评数据4个文件夹组成(基本都是多选题,出的还挺难):1、Auxiliary Training(用于微调模型来源于多项选择数据集如MCTest、RACE、ARC、OBQA) 2、**Dev , 用于少样本学习few-shot** 3、**Test, 内容最多用于最终评估模型性能** 4、Val, 验证集通常用于在训练过程中监测模型的性能 528 | 529 | CMMLU中文测评集如下: 530 | 531 | ![image.png](image%203.png) 532 | 533 | - CMMLU子类69个领域: 534 | 535 | ["农学", "解剖学", "古汉语", "艺术学", "天文学", "商业伦理", "中国公务员考试", "中国驾驶规则", "中国饮食文化", "中国外交政策", "中国历史", "中国文学", "中国教师资格", "临床知识", "大学精算学", "大学教育学", "大学工程水文学", "大学法律", "大学数学", "大学医学统计", "大学医学", "计算机科学", "计算机安全", "概念物理学", "建设工程管理", "经济学", "教育学", "电气工程", "小学语文", "小学常识", "小学信息技术", "初等数学", "民族学", "食品科学", "遗传学", "全球事实", "高中生物", "高中化学", "高中地理", "高中数学", "高中物理学", "高中政治", "人类性行为", "国际法学", "新闻学", "法理学", "法律与道德基础", "逻辑学", "机器学习", "管理学", "市场营销", "马克思主义理论", "现代汉语", "营养学", "哲学", "专业会计", "专业法学", "专业医学", "专业心理学", "公共关系", "安全研究", "社会学", "体育学", "中医中药", "病毒学", "世界历史", "世界宗教"] 536 | 537 | 538 | 然后, 539 | 540 | 追问4:常见可以用来给中文评测的数据集除了CMMLU还有哪些经典的? 541 | 542 | 全面的中文基础模型评测数据集`C-Eval`,由13948道多选题组成涵盖了 543 | 52 个学科和四个难度的级别。 544 | 545 | ![image.png](image%204.png) 546 | 547 | 问题5:除了将测评做成选择题分类【大部分结合prompt都是做分类输出】计算acc,对于生成式回答需要啥其他指标? 548 | 549 | Rouge(用于摘要生成任务,特别是在评估覆盖率),BELU(也不合理,只看到了局部n-gram相似) 这两个都是基于浅层匹配的指标,对语义等价的多样化表达不敏感。 550 | 551 | ## 模型架构 552 | 553 | 问题1:LLama2的架构特点有哪些? 554 | 555 | 首先,相比llama1增加如下,llama3架构其实和2一样,只是数据更好更庞大了。 556 | 557 | 1、前置的RMSNorm,相比传统的LN减少了计算均值操作更快。为啥前置pre而不是post呢?因为输入前归一化有助于保持梯度在网络中更稳定地传播和深层叠加【如果在输出再归一,梯度在子层内已经过大变化,可能产生梯度消失或爆炸,不利于梯度稳定训练】GPT-3 和其他类似的模型在设计中都这样 558 | 559 | 2、在QK上使用ROPE旋转位置编码 560 | 561 | 3、使用causal mask保证每个位置只能看到前面的tokens 562 | 563 | 4、down(up(x)×Silu(gate(x))),里面都线性层。用SwiGLU (效果类似平滑版的ReLU)代替了传统的 ReLU 激活函数 564 | 565 | 5、使用GQA代替MHA 566 | 567 | ## 预训练tricks 568 | 569 | 问题1:多阶段渐进式预训练(Multi-phase Progressive Pretraining)是啥?【预训练策略】 570 | -比如在InternLM 571 | 572 | 根据前一阶段的学习情况和侧线实验的结果,调整各个阶段的数据组合和学习设置。每个阶段都有特定的能力发展目标,还要考虑数据配比和上采样,可能在不同阶段使用不同的数据组合,**可能涉及更复杂的课程学习策略**。 573 | 574 | 追问:它和增量预训练(Incremental Pretraining)的区别是啥? 575 | 576 | 增量预训练通常是在原有模型基础上添加新数据训,而非混合。(通常更关注于如何有效地整合新数据,当然可以做点小样本高质量配比) 577 | 578 | 问题2:阶段式训练是咋样的? 579 | 580 | 比如**TinyLLama-1.1B**放出来的所有中间checkpoints,可使用FSDP、flash attention 2、fused rotary positional embedding。(**24k tokens/秒/A100**) 581 | 582 | ![Untitled](Untitled%2012.png) 583 | 584 | 问题3:**预训练时有哪些坑?** 585 | 586 | 1.多卡的话最好设置ddp_find_unused_parameters=False,这样也能提升训练的速度 587 | 588 | 2.尽可能地把单个bs调大,因为我试过把单个bs没有调那么大,通过使用累积梯度步数来增大总的bs,但是收敛的没有把单个bs调大快,我在想这个和学习率调整有关。【数设置上有两个地方需要注意:一个是学习率需要略小,在e-5这个量级就可以,**其次是global_batch需要比较大,一般在2-4M tokens,这样训练起来会比较稳定**】 589 | 590 | 3、不说并行分布式计算的效率问题,听说国内某个top 3科技巨头用2000卡训练,中途有卡崩溃,他们花了2天才找到是哪张卡出故障 591 | 592 | 4、无法从收敛曲线去预测模型的实际效果 593 | 594 | 5、过拟合的问题:只用领域数据非常容易过拟合到领域数据上,对OOD的处理会表现的非常差。对各个环节的数据配比要求会很高,最好是在原来规模程度的数据上,增加额外的场景数据,重新走部分流程。**但是困难的是,原有数据保持分布的采样,你拿到的模型是个黑盒,别人并没有给你原始的数据分布,更何况海涉及到了惊细的清洗**。有可能整体要付出的成本不下于重新塑造一个通用大模型。 595 | 596 | 6、需要分布式训练系统的开发工程师,负责把训练框架给支起来,协调、运维和管理这么多机器。 597 | 598 | 追问:上面第5点过拟合,OOD问题如何避免? 599 | 600 | 首先OOD(Out-of-Distribution)由于如果分阶段训练简单数据和复杂数据在分布上有显著差异,模型可能难以适应新的数据分布。因此一定要有合理数据配比,混合使用简单和复杂的数据才能让模型减小过拟合可能。另外构造优秀的验证集评分(最好有个综合验证集减少偏差,当然也可以加点当前那阶段特定验证评估)可以定期监控模型的好坏实现早停或者说打断。 601 | 602 | 问题5:FP32/FP16/BF16的选择问题? 603 | 604 | 更倾向于BF16,因为看起来更好收敛 605 | 606 | 问题6:并行计算方案的选择? 607 | 608 | Megatron-DeepSpeed是现在比较SOTA的一个方案 609 | 610 | 问题7:断点续训要保存checkpoint哪些东西从中断出继续加载? 611 | 612 | --resume_only_model: 默认为False, 即为严格的断点续训, 这会**读取模型、优化器和lr_scheduler的权重和各个设备存储的随机种子, 并将从上次训练暂停的stpes后继续计数进行训练**. 如果设置为True, 则只读取模型的权重. 613 | 614 | 问题8: 预训练`X,Y`如何划分? 615 | 616 | 按照seq_len划分,例如1024,则X[:-1]为(batch, 2013 ) 去预测 Y[1:]为(batch,1023) 617 | 618 | `logits = self.output(h)` # [32, 1023, 64793] 619 | 620 | `self.last_loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)` 621 | 622 | **在一批次所有样本和所有有效位置【忽略token_id = -1,为处理填充标记(`padding tokens`)】上取平均交叉熵损失**。 623 | 624 | -但我好像没有填充标记过滤 625 | 626 | 问题9:ROPE是作用在哪一部分? 627 | 628 | 632 | 633 | 问题10: 权重初始化使用了什么策略? 634 | 635 | 一般使用基于正态分布的初始化。 636 | 637 | 比如**普通线性层 (nn.Linear) 和嵌入层 (nn.Embedding)** 的初始化使用均值为 0.0,标准差为 0.02 的正态分布,如果存在偏置,则使用零初始化 (torch.nn.init.zeros_)。 638 | 639 | 然后**残差连接中的投影层**(以 'w3.weight' 或 'wo.weight' 结尾的层)基于 GPT-2 论文中的方法,使用均值为 0.0,标准差为 0.02 / sqrt(2 * params.n_layers) 的正态分布进行初始化。 640 | 641 | 问题11:用了什么注意力机制代码? 642 | 643 | 实现:**`torch.nn.functional.scaled_dot_product_attention**(xq, xk, xv, attn_mask=None, dropout_p= 0.0, is_causal=True)` 644 | 645 | 专注于计算单个注意力头的**缩放点积注意力(SDPA)** ,~~这个参数不涉及多个头的并行计算和最终的线性变换~~。使用 PyTorch 的高效矩阵运算(如点积和转置)来加速计算,缩放因子其实就是`1 / sqrt(d_k)`。【默认使用flash attention2优化内存管理】 646 | 647 | 追问:新版torch.compile(model)有啥作用? 648 | 649 | `torch.compile(model)` 擅长消除与 PyTorch 相关的框架开销。当模型中包含大型、高效的 CUDA 内核时(例如 CausalSelfAttention),这种优化尤为明显 650 | 651 | torch.compile(m, backend="tensorrt"):使用 Torch-TensorRT 进行推理优化-**解决在 PyTorch 中准确捕获计算图的问题,也可以转成"onnxrt"** 652 | 653 | ### 关于算力部分 654 | 655 | - 在一个月内训完Llama2至少也需要4500张A100-80G; llama是2048块A100一个月 656 | - [nanoGPT](https://github.com/pihang/nanoGPT), DDP(还可使用FSDP进行多GPU和多节点分布式训练),reproduces GPT-2 (124M) on OpenWebText, running on a single 8XA100 40GB node in about 4 days of training(OpenAI 的 WebText 数据集55.21 GB训练集) 657 | - [**TinyLlama-1.1B](https://github.com/jzhang38/TinyLlama/blob/main/README_zh-CN.md),在3万亿tokens(开源也就1500亿)上进行预训练。经过精心优化,我们"仅"需16块A100-40G的GPU,便可在90天内完成这个任务。各种优化完后**达到**24k tokens/秒/A100**的训练速度[如果塞入A100-40G,16k tokens的per-gpu batch size] 658 | - Pythia的数字来自他们的论文。MPT的数字来自[这里](https://huggingface.co/mosaicml/mpt-1b-redpajama-200b),作者说MPT-1.3B"was trained on 440 台A100-40GBs for about half a day" on 200B tokens。[batch size 2200, sequence length 2048] 659 | - [Phi-1](https://zhuanlan.zhihu.com/p/690423105) 是微软出品的一个 350M 和 1.3B 的模型,使用由大模型合成的「教科书」级别数据,也就是说相比网页数据更优质一些的数据训练,具体可以参见他们的论文 Textbooks are all you need I/II。因为数据质量高,合成也困难,只用了 6B 的数据,在 8 个 A100 上训练了 4 天。 660 | - [baby-llama2-chinese](https://github.com/DLLXW/baby-llama2-chinese)说4张3090训练634亿Tokens的预训练语料+300M参数量的模型已是预训练的极限[注:没有使用DeepSpeed、Megatron等分布式训练架构] 661 | 662 | ## Llama2-chinese-92M~218M 训练流程笔记 663 | 664 | 训练参考: 665 | 666 | [GitHub - DLLXW/baby-llama2-chinese: 用于从头预训练+SFT一个小参数量的中文LLaMa2的仓库;24G单卡即可运行得到一个具备简单中文问答能力的chat-llama2.](https://github.com/DLLXW/baby-llama2-chinese/tree/main) 667 | 668 | - `torchrun --standalone --nproc_per_node=4 pretrain.py` 669 | - 查看tensorboard:`tensorboard --logdir /home/ph/LLM2/baby-llama2-chinese/out/seq1024_dim512_layers8_heads8/tensorboard` 670 | 671 | 0️⃣、准备预训练的数据集 672 | 673 | | **预训练语料** | **描述** | 规模 | 674 | | --- | --- | --- | 675 | | Wiki中文百科 | 中文Wikipedia的数据 | 254,547条,500M | 676 | | BaiduBaiKe | 中文BaiduBaiKe的数据 | 4.3G | 677 | | C4_zh | C4是可用的最大语言数据集之一,收集了来自互联网上超过3.65亿个域的超过1560亿个token。C4_zh是其中的一部分 | 25G,分成95个文件 | 678 | | WuDaoCorpora:智源研究院BAAI | 中文悟道开源的200G数据 | 200G | 679 | | shibing624/medical | 源自shibing624的一部分医学领域的预训练数据 | 分成pre,sft,reward, 600M | 680 | 681 | 共计**634亿(63B)Tokens**的数据量-中文为主。 →过滤后273w段,bin大小12G 682 | 683 | ~~:key~~ 684 | 685 | 2️⃣、低质量文本过滤暂时直接用别人过滤好的,这里主要讲短的过滤。 686 | 687 | 问题1:文本去重过程? 688 | 689 | **文本去重**: 690 | 691 | - 符号转换:中文和英文符号识别并正确转化,去掉回车,过滤连续的标点符号 692 | - 清洗数据:少于15清洗, 693 | - json处理:`rich.progress.open`进度条打开处理挺好用。 694 | - 对齐,将json都对齐为{‘response’:xxx}到list→存储为.parquet格式,可以有效减小存储占用 695 | - 整合:读取不同数据.parquet → 转化为Python 对象 → 转化pd.DataFrame(变成行0-1w,列为'response’) → 每1w行写入总的Parquet 696 | 697 | 一、Minhash【推荐】 698 | 699 | 首先,对文本去除 使用Jaccard 相似度【交集/并集】阈值过滤,先将字符串处理为显式地编码为字节序列在给哈希函数处理 700 | 701 | 追问1:什么是**Minhash签名**? 702 | 703 | ①对集合中的元素进行哈希,每个哈希函数会将一个元素映射为一个整数值。(n个哈希置换就是n个长度的签名) 704 | 705 | ②取最小哈希值组成一个序列 - 》MinHash签名 706 | 707 | ③过比较不同集合的 MinHash 签名,签名中相同位置的值相等的比例可以大致代表集合的相似性(`Jaccard` 相似度) 708 | 709 | ![Untitled](Untitled%2013.png) 710 | 711 | **[1,2,3],[3,2,1],那么相似性估计为多少 -》1/3** 712 | 713 | 追问2:什么是哈希置换数? 714 | 715 | **哈希置换数= 哈希函数** 。 这个参数决定了 MinHash 签名的长度,并影响计算的精度和效率。【越大越耗时但越精准】 716 | 717 | 追问3:为啥字符串分割后要`.encode('utf8')`? 718 | 719 | 大多数哈希函数(包括 MinHash)**期望输入是字节序列而不是字符串**。这样可以避免字符编码带来的不一致性。 720 | 721 | 比如"example" 是一个字符串,通过 encode('utf8') 转换为字节序列 b'example'。这个字节序列可以被安全地用于哈希计算。 722 | 723 | --- 724 | 725 | 流程: 726 | 727 | ①读取parquet -》f"{`response.as_py()`}” 728 | , 正则只保留中文和英文、下划线,不要标点符号 分词 729 | 730 | ②获取一段文本的mini hash,**通过生成签名来压缩集合数据,转变为一个256的array** 731 | 732 | ```python 733 | mini_hash = MinHash(num_perm=num_perm) 734 | # TODO 个人感觉用jieba【jieba.lcut(doc)】去分词更合理,而非n-gram和字符分 735 | for s in doc: # 字符迭代分割 736 | mini_hash.update(s.encode('utf-8')) # 哈希函数处理的是一致的字节形式,需要将字符串显式地编码为字节序列 737 | return mini_hash 738 | ``` 739 | 740 | ③上面minhash转换的签名放入lsh,然后用`lsh.query`方法查找相似文档阈值超过的索引(桶索引效率高) 741 | 742 | ④构建哈希表整合为去重set集合,重新遍历所有文档,去掉重复docs,写入parquet格式数据存储 743 | 744 | 问题4:对文档有什么分割方案用于相似度去重? 745 | 746 | 遍历字符、滑动窗口固定了窗口长度,[jieba分词](https://blog.csdn.net/qq_36488175/article/details/109788291#pythonjieba_38) 747 | 748 | 问题5:MinHash 和 MinHashLSH区别在哪? 749 | 750 | - MinHash哈希技术,用于估计两个集合之间的相似性,通常是 Jaccard 相似度. 751 | - MinHashLSH (Locality-Sensitive Hashing **局部敏感哈希**),用于快速查找相似项,旨在**将相似的输入映射到相同的桶中(而不需要对每一对数据进行直接比较)**。对于 MinHashLSH,它使用 MinHash 签名来实现这一点。上面minhash转换的签名放入lsh,然后用lsh.query方法查找相似文档阈值超过的索引 752 | 753 | 追问:MinHashLSH(局部敏感哈希)为什么相似查找非常高效性? 754 | 755 | 高效有两点核心: 756 | 757 | 762 | 763 | - 举个例子【经典-一看就懂】: 764 | 765 | 假设我们有以下 4 个文档,每个文档的 MinHash 签名长度为 6。为了简化,我们将签名分成 2 段,每段包含 3 个哈希值。 766 | 767 | 文档 MinHash 签名 768 | 文档 A: [1, 3, 5, 2, 4, 6] 769 | 文档 B: [2, 3, 4, 1, 5, 6] 770 | 文档 C: [1, 3, 5, 2, 4, 7] 771 | 文档 D: [2, 4, 6, 1, 3, 5] 772 | 773 | - 分段:第一段:包含签名的前 3 个哈希值 第二段:后3个哈希值 774 | - 用哈希函数[例如H(x) = sum(x) % 10]计算段哈希值: 775 | 产生 4个文档*2段 = 8个哈希值 776 | - **将每个段的哈希值和对应的文档 ID 存储在两个不同的哈希表中:** 777 | 778 | 哈希表 1(段 1 哈希值): 779 | 780 | 哈希值 9: [A, B, C] - 》 桶 也避免了哈希冲突 781 | 哈希值 2: [D] 782 | 哈希表 2(段 2 哈希值): 783 | 784 | 哈希值 2: [A, B] 785 | 哈希值 3: [C] 786 | 哈希值 9: [D] 787 | 788 | 假设我们要查询一个新的文档 Q,其 MinHash 签名为 [1, 3, 4, 2, 5, 6]。-》段哈希值计算为8,3 789 | 790 | 通过哈希表查找,候选集合为 [C]。 791 | 792 | 793 | 二、Simhash 794 | 795 | 主要思想是降维, 将高维的特征向量映射成低维的特征向量,通过两个向量的Hamming Distance(`汉明距离`-**将一个字符串变换成另外一个字符串所需要替换的字符个数**)来确定文章是否重复或者高度近似。 796 | 797 | 流程: 798 | 799 | ①文本预处理(正则、分词、滑窗) 滑窗的目的是增加上下文 800 | 特征提取:n-gram或滑动窗口 801 | 802 | ②每个特征通常会被赋予一个权重。一般默认为1,也可以用TF-IDF 803 | 804 | ③对每个特征计算一个固定长度的哈希值(比如 64 位)`hash(feature) & ((1 << self.f) - 1)` 805 | 806 | ④对累加器中的每个位进行判断,如果某个位的值为正,则该位设为 1;否则为 0。 807 | 这样就对一整个字符串 生成了一个固定长度`(如 64 位)的 Simhash 值`。 808 | 809 | ⑤和上面的minhash一样采用分段桶管理,将64位分成4部分转化哈希表查询,加快效率 810 | 811 | > 汉明距离越大,去重率通常会越高 812 | > 813 | 814 | 问题6:Minhash和Simhash对比有啥区别? 815 | 816 | **Minhash用Jaccard相似度;Simhash用汉明距离(适合网页去重变动小的去重)** 817 | 818 | ![从563w段话过滤到273w段](image%205.png) 819 | 820 | 从563w段话过滤到273w段 821 | 822 | 问题4:如何存储? 823 | 824 | `parquet`/ˈpɑːkeɪ/格式(列式存储格式,只读取相关的列, Hadoop生态系统开发)存储数据,可以大大减小存储占用。 825 | 826 | 3️⃣、分词器选用chatGLM2的,词表大小64793。以uint16的二进制存储。 827 | 828 | 问题5:那么**如何将文本存储为token?** 829 | 830 | 数据预处理采取GPT的通用做法,对语料进行提前分词,对一个样本做完分词后在末尾加上一个结束符号``对应token_id=2,与下一个样本区分开。然后将所有的训练语料拼接成一个数组(np.uint16)一直叠加老长了,然后以.bin二进制格式存储到磁盘上。 831 | 832 | 问题6:代码中应用了哪些 special_token? 833 | 834 | 文本之间都加了'``’分割-token_id=2【decoder后没啥变化】; 开始符``隔开,填充用到``, 835 | 836 | 4️⃣bin的分词数据集进行内存映射,加载预训练 837 | 838 | 问题1:什么是mmap内存映射? 839 | 840 | **mmap(内存映射文件)**是一种用于将文件或文件的一部分映射到进程的地址空间的技术。这**使得文件的内容可以像在内存中一样进行访问,而不需要将整个文件加载到内存中—-多进程可以通过映射共享文件数据防止多次IO操作**。【以像操作内存数组一样操作文件内容。这种方式可以提高文件I/O操作的效率】 841 | 842 | 代码实现: 843 | 844 | ```python 845 | with open(data_path_lst[0],'r') as f: 846 | nbytes = f.seek(0,2) 847 | flen = f.tell() // np.dtype('uint16').itemsize 848 | self.data = np.memmap(data_path_lst[0],dtype=np.dtype('uint16'),shape=(flen//max_length,max_length)) 849 | ``` 850 | 851 | 追问:为啥内存映射(防止内存溢出)是单个大文件而不能多个? 852 | 853 | 文件连续性: **内存映射需要文件在磁盘上是连续的**,以便能够将其内容直接映射到内存地址空间中。多个小文件在磁盘上的物理位置可能不连续,这会使得内存映射的实现变得复杂。 854 | 855 | 5️⃣模型设置 856 | 857 | ```python 858 | class ModelArgs: 859 | dim: int = 4096 512 860 | n_layers: int = 32 8 861 | n_heads: int = 32 8 862 | n_kv_heads: Optional[int] = None 8 863 | vocab_size: int = -1 # defined later by tokenizer 64793 864 | multiple_of: int = 256 # make SwiGLU hidden layer size multiple of large power of 2 32 865 | norm_eps: float = 1e-5 866 | max_seq_len: int = 2048 512 867 | dropout: float = 0.0 868 | ``` 869 | 870 | - 小模型-baby-llama2 871 | 872 | ```python 873 | Transformer( 874 | (tok_embeddings): Embedding(64793, 512) 875 | (dropout): Dropout(p=0.0, inplace=False) 876 | (layers): ModuleList( 877 | (0-7): 8 x TransformerBlock( 878 | (attention): Attention( 879 | (wq): Linear(in_features=512, out_features=512, bias=False) 880 | (wk): Linear(in_features=512, out_features=512, bias=False) 881 | (wv): Linear(in_features=512, out_features=512, bias=False) 882 | (wo): Linear(in_features=512, out_features=512, bias=False) 883 | (attn_dropout): Dropout(p=0.0, inplace=False) 884 | (resid_dropout): Dropout(p=0.0, inplace=False) 885 | ) 886 | (feed_forward): FeedForward( 887 | (w1): Linear(in_features=512, out_features=1376, bias=False) 888 | (w2): Linear(in_features=1376, out_features=512, bias=False) 889 | (w3): Linear(in_features=512, out_features=1376, bias=False) 890 | (dropout): Dropout(p=0.0, inplace=False) 891 | ) 892 | (attention_norm): RMSNorm() # 归一化,稳定训练 893 | (ffn_norm): RMSNorm() # 更好的梯度流动 894 | ) 895 | ) 896 | (norm): RMSNorm() 897 | (output): Linear(in_features=512, out_features=64793, bias=False) 898 | ) 899 | ``` 900 | 901 | 902 | 6️⃣分布式训练 903 | 904 | - `torchrun --standalone --nproc_per_node=4 pretrain.py` (在代码内指定多卡) 905 | 906 | 193w步steps,4卡 907 | 908 | - 实验记录(用于追踪实验) 909 | 910 | 1、4卡配置,卡显存消耗13G,193w步steps,loss大概收敛2.4 911 | 912 | ```python 913 | gradient_accumulation_steps = 2 914 | batch_size = 16 915 | max_seq_len = 512 916 | dim = 512 917 | n_layers = 12 918 | n_heads = 8 919 | multiple_of = 32 # 前馈层FFN的隐藏维度 920 | dropout = 0.0 921 | min_lr = 1e-5 922 | learning_rate = 3e-4 # max learning rate 923 | weight_decay = 1e-1 924 | ``` 925 | 926 | 2、不管几卡,这个配置直接爆显存(30G一卡会够用) 927 | 928 | ```python 929 | gradient_accumulation_steps = 2 930 | batch_size = 16 931 | max_seq_len = 1024 932 | dim = 1024 933 | n_layers = 12 934 | n_heads = 8 935 | multiple_of = 32 936 | ``` 937 | 938 | 3、**6卡train_loader 迭代129w次,总的129*16*6~12392w个样本 939 | —花了1.5d,占用13G,loss在2.0波动,不过收敛太早了提前进入decay学习率阶段稳定在0.0001训练(要增加余弦退火的过程,增加模型规模但要考虑模型并行)。** 940 | 941 | ```python 942 | gradient_accumulation_steps = 2 943 | batch_size = 16 ---占13G 944 | max_seq_len = 512 945 | dim = 512 946 | n_layers = 12 947 | n_heads = 8 948 | multiple_of = 32 949 | ``` 950 | 951 | ![1723703038290_F572471B-367B-44b4-A2F9-A1205C8ABEF3.png](1723703038290_F572471B-367B-44b4-A2F9-A1205C8ABEF3.png) 952 | 953 | 哒哒哒哒哒哒多多多多多多多多多多多多多多多多多多多多多多多多多多多多多 954 | 955 | 956 | To run with DDP on 4 gpus across 2 nodes, example: 957 | 958 | - Run on the first (master) node with example IP 123.456.123.456: 959 | $ torchrun --nproc_per_node=8 --nnodes=2 --node_rank=0 --master_addr=123.456.123.456 --master_port=1234 train.py 960 | - Run on the worker node: 961 | $ torchrun --nproc_per_node=8 --nnodes=2 --node_rank=1 --master_addr=123.456.123.456 --master_port=1234 train.py 962 | (If your cluster does not have Infiniband interconnect prepend NCCL_IB_DISABLE=1) 963 | 964 | 问题1:**DistributedDataParallel(DDP)**在多GPU下高效分布式训练原理? 965 | 966 | 首先,DDP会将模型的一个副本会被复制到每个 GPU 上,并在自己的数据子集上进行前向和反向传播。 967 | 968 | 其次,数据并行,每个 GPU 处理输入数据的一个子集。在每个训练步骤中,所有 GPU 同时进行前向和反向传播计算。 969 | 970 | 然后,梯度在反向传播完成后,DDP 会自动同步所有 GPU 上的梯度,all-reduce操作通信实现。 971 | 972 | 最后,all-reduce 操作会收集每个 GPU 上的梯度,将它们相加,然后将结果分发回每个 GPU,所有GPU模型相同梯度更新。 973 | 974 | 问题2:**`NCCL(NVIDIA Collective Communications Library)`**操作 BROADCAST 超时? 975 | 976 | NVIDIA 集体通信库 (NCCL) 实现了**针对 NVIDIA GPU 和网络进行优化的多 GPU 和多节点通信原语**。提供了all-gather, all-reduce, broadcast, reduce, reduce-scatter, p2p, 接收等例程,这些例程经过优化,可在 PCIe (4090)和 NVLink(A100) 高速互连上实现高带宽和低延迟。一个节点以及跨节点的 NVIDIA Mellanox 网络。 977 | 978 | > **NCCL 会在所有进程之间进行数据的聚合和分发** 979 | > 980 | 981 | 分布式每个参与的进程需要在同一个时刻参与到 NCCL 的集合操作(如 broadcast、all_reduce 等)中。这意味着所有进程必须在相同的代码位置调用相同的 NCCL 操作。 982 | 983 | 报错可能原因 984 | 985 | 1、如果使用多个节点进行分布式训练,网络延迟或带宽不足可能导致超时。带宽要高 986 | 987 | 2、如果 GPU 负载过高,可能导致 NCCL 操作无法及时完成,确保没有其他进程占用过多资源 988 | 989 | 3、需要启动调试显示os.environ['NCCL_DEBUG'] = 'INFO’ 990 | 991 | 4、设置 NCCL_P2P_DISABLE=1 或 NCCL_IB_DISABLE=1,以排除可能的网络接口问题 992 | 993 | 5、如果某个进程在 NCCL 操作之前卡住,比如在数据加载或模型计算中遇到瓶颈,其他进程会在 NCCL 操作中等待(不均衡工作负载),训练效率很低可能导致超时错误。 994 | 995 | 6、大量使用 swap 可能会影响到系统上运行的其他进程,导致它们的性能也受到影响 996 | 997 | 7、**如果 ALLREDUCE 操作涉及的数据量过大,可能需要很长时间来完成,尤其是在带宽有限的情况下** 998 | 999 | 追问:NCCL在RTX40系报错NotImplementedError: Using RTX 4000 series doesn't support faster communication broadband via P2P or IB. ? 1000 | 1001 | 默认开启了 NCCL P2P(Peer-to-Peer)和 IB(InfiniBand)通信优化,而这些优化在你的环境中并不被支持。所以需要 1002 | 1003 | ```json 1004 | "env": { 1005 | "NCCL_P2P_DISABLE": "1", # export导入环境变量 1006 | "NCCL_IB_DISABLE": "1", 1007 | "CUDA_DEVICE_MAX_CONNECTIONS": "0,1", 1008 | "CUDA_VISIBLE_DEVICES": "2" 1009 | } 1010 | ``` 1011 | 1012 | 问题3:master_process变化是啥? 1013 | 1014 | DDP的话除了一个是主进程,其他都是false 1015 | 1016 | 问题4:**为什么预训练显存要预留一半以上?** 💛💛💛 1017 | 1018 | 1024 | 1025 | 问题5: Deepspeed框架中`ZeRO2` 比DDP优势在哪? 1026 | 1027 | DDP是使用 `all-reduce` 来同步梯度。每个 GPU 在计算完自己的梯度后,通过 all-reduce **将所有 GPU 的梯度求和并分发**。这样确保每个 GPU 的梯度一致,用于参数更新。 1028 | 1029 | 而Zero2会分割模型的优化器状态和梯度,减少了需要同步的数据量。在参数更新时,可能需要使用 `all-reduce`【分布式计算中用于聚合和分发数据的高效操作】 来收集分割的参数,以便进行完整的参数更新。智能的分割和聚合策略 1030 | 1031 | 问题6:`all-gather`分布式通信操作用于哪些场合? 1032 | 1033 | 1040 | 1041 | 问题7:gradient_accumulation_steps的用处? 1042 | 1043 | 用于控制梯度累积,目的是在显存有限的情况下,**通过累积多个小批次的梯度来模拟更大的批次大小的效果**。也就是在每个小批次中,计算损失并进行反向传播,但不立即更新模型参数【进行梯度平均保持尺度一致】🦠🦠🦠 1044 | 1045 | -减少了每次更新的频率,这可能在某些情况下提供更稳定的训练过程- 1046 | 1047 | 追问1:gradient_accumulation_steps 和 batch_size 大小影响显存比例 1048 | 1049 | `gradient_accumulation_steps = 2` *batch_size = 16 * 4GPU 1050 | 1051 | 总的有效批次更新参数:4*16=64样本【`节约一半显存`】 1052 | 1053 | 等同于 gradient_accumulation_steps = 1 * batch_size = 32 1054 | 1055 | 追问2:gradient_accumulation_steps=2,batch_size=16 和gradient_accumulation_steps=1,batch_size=32对模型训练效果一样吗? 1056 | 1057 | 首先batch_size肯定越大训练越快,但我们显存不够所以用了梯度累计,这在效果上会有啥影响吗,如果用的**fp16会导致浮点数精度有限而导致的误差积累和不稳定行为**。 1058 | 1059 | 问题8:DDP时画loss收敛图时会有几个进程画几个图是为啥? 1060 | 1061 | 首先loss数组收集的数据其实是当前进程的GPU中没步长的loss,如果用了4卡也就是4个进程同步训练,那么会各自维护自己的loss数组。进程中变量不共享,所以画图会画4张。 1062 | 1063 | 如果想避免,只需要画`master_process`的loss收敛即可。 1064 | 1065 | 如果想要过程中追踪收敛,用 `TensorBoard`组件的SummaryWriter 函数。 1066 | 1067 | 问题9:那么DDP为什么不同步loss去计算梯度再分发呢? 1068 | 1069 | 首先,DDP实际步骤是每个 GPU 在计算完自己的梯度后,通过 `all-reduce` 将所有 GPU 的梯度求和并分发。这样确保每个 GPU 的梯度一致,用于参数更新(**设计核心是最大化并行计算并最小化通信开销**)。 1070 | 1071 | 1075 | 1076 | ## [**Zero-Chatgpt](https://github.com/AI-Study-Han/Zero-Chatgpt)训练流程** 1077 | 1078 | 用了规范的Deepspeed,**也加入了强化学习(rlhf,ppo)的代码** 1079 | 1080 | 有个不错的参考:[https://github.com/AI-Study-Han/Mini-Llama2-Chinese/tree/main/code2/pretrain_code](https://github.com/AI-Study-Han/Zero-Chatgpt) 1081 | 1082 | - `sh Zero-Chatgpt-main/pretrain/pretrain.sh` 1083 | 1084 | 0️⃣数据集 1085 | 1086 | 一共收集了10B左右的中文训练语料,包括[中文维基百科](https://huggingface.co/datasets/pleisto/wikipedia-cn-20230720-filtered/blob/main/wikipedia-cn-20230720-filtered.json),[中文百度百科](https://huggingface.co/datasets/xuqinyang/BaiduBaike-5.63M/blob/main/563w_baidubaike.json)和[SkyPile-150B](https://huggingface.co/datasets/Skywork/SkyPile-150B)随机抽取了部分数据 1087 | 中文维基百科和SkyPile-150B数据【[下载链接](https://www.modelscope.cn/datasets/modelscope/SkyPile-150B/files),非常多,包含大约2.33亿个独特的网页,每个网页平均包含超过1,000个中文字符。该数据集总共包括大约 1500 亿个令牌和 620 GB 的纯文本数据。】比较干净 1088 | 1089 | 问题1:和之前baby数据量对比? 1090 | 1091 | 数据采样配比后数据量大了些。 1092 | 1093 | →下面处理完后10B token,剩余bin大小19G。baby之前200多w,bin 12G。 1094 | 1095 | - baby,**train_loader 迭代129w次,总的129*16*6~12392w个样本 。一个样本512token** 1096 | - zero,**train_loader 迭代48355次,总的4.8*24*6~696w个样本 。一个样本1024toke** 1097 | 1098 | 1️⃣数据处理 1099 | 1100 | 之前baby项目,数据是用`parquet` 格式+分词后bin的uint16存储【token之间隔着区分,先分词在构建非常快,因为是bin】。 1101 | 1102 | 这里对不同数据集json处理后(过滤清洗去重,采样分配)+`{’text’:text , ‘source’, ‘baidubaike’}` 存储进json。【json区分上下文,老慢了,因为都是json→json,然后再去训练tokenizer】 1103 | —tmd要跑3天都处理不完[处理json的去重非常慢2-ngram+minhashlsh]— 1104 | 1105 | 最终563w条数据只剩下140多w条数据 1106 | 1107 | 2️⃣分词器构建 1108 | 1109 | 用BPE构建了一个3.2w的分词器,手动添加特殊token:`"system"`、`"user"` 、 `"assistant"` 、`<|endoftext|>`、`<|im_start|>`、`<|im_end|>`(顺序id依次叠加)。 1110 | 1111 | 总计 1112 | 1113 | ``` 1114 | "bos_token_id": 32005, 1115 | "eos_token_id": 32005, ----<|im_end|>对应一个token_id 1116 | "vocab_size": 32006 1117 | ``` 1118 | 1119 | 3️⃣Deepspeed框架 1120 | 1121 | ds_config设置: 1122 | 1123 | zero2优化器、梯度拆分 1124 | 1125 | ```python 1126 | "zero_optimization": { 1127 | "stage": 2, 1128 | "allgather_partitions": true, 1129 | "allgather_bucket_size": 2e8, 1130 | "overlap_comm": true, 1131 | "reduce_scatter": true, 1132 | "reduce_bucket_size": 2e8, 1133 | "contiguous_gradients": true 1134 | }, 1135 | ``` 1136 | 1137 | zero3权重、梯度、优化器完全参数拆分 1138 | 1139 | ```python 1140 | "zero_optimization": { 1141 | "stage": 3, 1142 | "allgather_partitions": true, 1143 | "allgather_bucket_size": 1e8, 1144 | "overlap_comm": false, 1145 | "reduce_scatter": true, 1146 | "reduce_bucket_size": 1e8, 1147 | "contiguous_gradients": true, 1148 | "sub_group_size": 5e4, 1149 | "memory_efficient_linear": true 1150 | }, 1151 | ``` 1152 | 1153 | 问题1:`sub_group_size`代表啥,为啥是zero3的特有参数? 1154 | 1155 | 首先zero-2权重只在优化器阶段进行分片,梯度同步后仍需要在所有设备间保存完整的模型副本。但zero-3则是权重、优化器状态、梯度都被分片存储,任何时刻每个设备只保留模型的部分参数。 1156 | 1157 | 所以sub_group_size**控制在模型并行过程中,权重分片和通信时划分的小组规模**。sub_group_size越大通信会在更大规模的子组中进行,这可能会增加通信开销,但能减少全局同步次数。 1158 | 1159 | 当然默认情况下,`sub_group_size` 会自动调整,但在一些场景中,手动设置可以更好地适应具体硬件和任务需求。 1160 | 1161 | 问题2:deepspeed分布式训练在代码哪里体现? 1162 | 1163 | ```python 1164 | from transformers import Trainer, TrainingArguments 1165 | 1166 | training_args = TrainingArguments( 1167 | ..., 1168 | deepspeed="path/to/deepspeed_config.json" 1169 | ) 1170 | trainer = Trainer( 1171 | model=model, 1172 | args=**training_args**, 1173 | ... 1174 | ) 1175 | ``` 1176 | 1177 | 基于 Hugging Face 的 `Trainer`,通常会在 `training_args` 中启用 DeepSpeed. 1178 | 通常会使用一个 `deepspeed_config.json` 文件来定义**优化策略、内存管理、混合精度**等设置。 1179 | 1180 | 问题3:使用zero-3的问题有哪些? 1181 | 1182 | 一、**OOM** 1183 | 1184 | 虽然显著优化了显存使用,但它在前向和反向传播过程中有更频繁的通信操作,导致有时会意外触发 OOM,尤其是在全局通信不平衡或显存碎片化严重的情况下。 1185 | 1186 | 解决: 1187 | 1188 | 1、动态显存分配PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True 1189 | 1190 | 2、sub_group_size=1e5调小一点 1191 | 1192 | 3、减小per_device_train_batch_size 1193 | 1194 | 二、**速度慢的要死** 1195 | 在前向和反向传播过程中需要频繁进行 **`all-gather`(模型参数收集) 和 `reduce-scatter` (梯度聚合并分发)操作**。这些操作需要跨设备进行大量的数据通信,因此对通信带宽和延迟有更高的要求。 1196 | **如果增大每次通信传输数据量,会减少训练时间,但是显存会增加溢出。**🤬🤬🤬🤬 1197 | 1198 | 追问:zero3预训练代码的显存问题 1199 | 1200 | 问题4:使用梯度检查点(`gradient_checkpointing`)和混合精度训练(FP16/BF16)发送CUDA内存分配器断言错误? 1201 | 1202 | 因为我在启动程序时export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,max_split_size_mb:32指定了更小的分配单元可以减少内存碎片化的问题来减小显存占用。这样不能同时开启梯度检查点。 1203 | 1204 | 问题5:显存一直超出,显存限制是个难以突破的瓶颈。【一直OOM】 1205 | 1206 | 修改ds_config.py设置:-调小了内存分配也会溢出-。。。。。。。。 1207 | 1208 | ``` 1209 | "zero_optimization": { 1210 | "stage": 3, 1211 | "offload_optimizer": { # 优化器卸载到内存 1212 | "device": "cpu", 1213 | "pin_memory": true 1214 | }, 1215 | "offload_param": { # 将模型参数卸载到内存 1216 | "device": "cpu", 1217 | "pin_memory": true 1218 | }, 1219 | "allgather_partitions": true, 1220 | "allgather_bucket_size": 5e7, 1221 | "overlap_comm": false, 1222 | "reduce_scatter": true, 1223 | "reduce_bucket_size": 5e7, 1224 | "contiguous_gradients": false, 1225 | "sub_group_size": 5e4 1226 | }, 1227 | ``` 1228 | 1229 | ```python 1230 | "zero_optimization": { 1231 | "stage": 3, 1232 | "offload_optimizer": { 1233 | "device": "cpu", 1234 | "pin_memory": true 1235 | }, 1236 | "offload_param": { 1237 | "device": "cpu", 1238 | "pin_memory": true 1239 | }, 1240 | "overlap_comm": true, 1241 | "contiguous_gradients": true, 1242 | "sub_group_size": 1000000000.0, 1243 | "reduce_bucket_size": "auto", 1244 | "stage3_prefetch_bucket_size": "auto", 1245 | "stage3_param_persistence_threshold": "auto", 1246 | "stage3_max_live_parameters": 1000000000.0, 1247 | "stage3_max_reuse_distance": 1000000000.0, 1248 | "stage3_gather_16bit_weights_on_model_save": true 1249 | }, 1250 | ``` 1251 | 1252 | 追问:在shift_logits = logits[..., :-1, :].contiguous()报错 1253 | RuntimeError: !block->expandable_segment_ INTERNAL ASSERT FAILED at "../c10/cuda/CUDACachingAllocator.cpp":2525, please report a bug to PyTorch. 1254 | 1255 | 因为`.contiguous()` 是一个常见的操作,它将张量重新排列成内存中连续的数据布局,CUDA 内存分配器在试图扩展内存块时遇到了碎片化或内存不足的问题。 1256 | 但是必须要用。因为如果你直接用 `.view()` 替换 `.contiguous()`,在某些情况下可能会触发错误,尤其是当张量在内存中不是连续存储的时。具体来说,如果你对 `logits` 或 `labels` 进行了切片或维度交换操作,直接使用 `.view()` 可能会导致错误。 1257 | 解决办法: 1258 | 1259 | 1、export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:16,expandable_segments:False 1260 | 1261 | 2、降低cuda11.3和降低torch版本 1262 | 1263 | 问题6:有个代码问题你觉得这些DDP,deepspeed的参数注入是在哪里实现? 1264 | 1265 | 在 DDP 和 DeepSpeed 的参数注入中,`@dataclass` 装饰器简化了配置类的定义和初始化过程。一般直接作用在class TrainingArguments类上面,然后启动时的—参数快速注入,deepspeed一般通过json配置文件导入。 1266 | 1267 | 问题7:deepspeed启动和torchrun启动有啥区别? 1268 | 1269 | - deepspeed step2.py --deepspeed:直接使用 `deepspeed` 命令来启动训练脚本会自动处理了很多底层的配置 1270 | - torchrun --nnodes 1 --nproc_per_node 1 sft.py --deepspeed ds_config.json : 是 `PyTorch` 提供的分布式启动工具,更灵活可以直接控制 `--nnodes` 和 `--nproc_per_node`,并且不仅限于 DeepSpeed,还可以与其他分布式策略如DDP结合。 1271 | 1272 | 问题8:accelerate launch和torchrun启动有啥区别? 1273 | 1274 | accelerate launch --multi_gpu --num_processes 2 dpo_train.py: 来自Hugging Face的`Accelerate`库,适用于 Hugging Face 的模型和分布式训练框架 1275 | 1276 | 4️⃣ 预训练 1277 | 1278 | 1、这里采用 Hugging Face内置的transormers包和对应PretrainedConfig配置 1279 | 1280 | 2、我们自定义model初始化 = **MiaomiaoForCausalLM**继承(PreTrainedModel )以及 **MiaomiaoPreTrainedModel**参数配置。 1281 | 3、主要结构包括: 1282 | 1283 | - `MiaomiaoModel`:实现具体的 Transformer 架构。 1284 | - `MiaomiaoDecoderLayer`:单层解码器,包含注意力机制和 MLP。 1285 | - `MiaomiaoAttention`:实现不同注意力机制的类。 1286 | - `MiaomiaoRMSNorm`:归一化层。 1287 | 1288 | 4、这里Attention有三种选择: 1289 | 1290 | ```python 1291 | Miaomiao_ATTENTION_CLASSES = { 1292 | "eager": MiaomiaoAttention, 1293 | "flash_attention_2": MiaomiaoFlashAttention2, 1294 | "sdpa": MiaomiaoSdpaAttention, 1295 | } 1296 | ``` 1297 | 1298 | `from flash_attn import flash_attn_func, flash_attn_varlen_func` FlashAtte加速调用 1299 | 1300 | 问题2:如何在训练过程中追踪可视化? 1301 | 1302 | Hugging Face 的 Trainer 默认支持 `WandB`(report_to="wandb”),配合deepspeed追踪可视化 1303 | 1304 | - 小模型-miaomiao+追踪记录,大概160M多, 24层,不过词表就3.2w,lens=1024 1305 | 1306 | ```python 1307 | MiaomiaoForCausalLM( 1308 | (model): MiaomiaoModel( 1309 | (embed_tokens): Embedding(32006, 512) 1310 | (layers): ModuleList( 1311 | (0-23): 24 x MiaomiaoDecoderLayer( 1312 | (self_attn): MiaomiaoSdpaAttention( 1313 | (q_proj): Linear(in_features=512, out_features=512, bias=False) 1314 | (k_proj): Linear(in_features=512, out_features=512, bias=False) 1315 | (v_proj): Linear(in_features=512, out_features=512, bias=False) 1316 | (o_proj): Linear(in_features=512, out_features=512, bias=False) 1317 | (rotary_emb): MiaomiaoRotaryEmbedding() 1318 | ) 1319 | (mlp): MiaomiaoMLP( 1320 | (gate_proj): Linear(in_features=512, out_features=2752, bias=False) 1321 | (up_proj): Linear(in_features=512, out_features=2752, bias=False) 1322 | (down_proj): Linear(in_features=2752, out_features=512, bias=False) 1323 | (act_fn): SiLU() 1324 | ) 1325 | (input_layernorm): MiaomiaoRMSNorm() 1326 | (post_attention_layernorm): MiaomiaoRMSNorm() 1327 | ) 1328 | ) 1329 | (norm): MiaomiaoRMSNorm() 1330 | ) 1331 | (lm_head): Linear(in_features=512, out_features=32006, bias=False) 1332 | ) 1333 | ``` 1334 | 1335 | wandb记录:https://wandb.ai/1274168976ph/huggingface/runs/aimial6r/workspace?nw=nwuser1274168976ph 1336 | 1337 | loss收敛到3.2,我觉得是分词器太小效果没那么好 1338 | 1339 | ![预训练后会不停说胡话](image%206.png) 1340 | 1341 | 预训练后会不停说胡话 1342 | 1343 | 1344 | 5️⃣自定义model推理 1345 | 1346 | 问题4:如何自定义model模块加载并推理? 1347 | 1348 | 用的huggingface自带的transformers加载模型,所需文件: 1349 | 1350 | ![image.png](image%207.png) 1351 | 1352 | **这里config.json很重要,指定了`AutoModelForCausalLM.from_pretrained()`加载模型的配置(`AutoConfig`、`AutoModel`[模型架构]、`AutoModelForCausalLM`[主要最外层包括从输入到输出]三大件)** 1353 | 1354 | ```json 1355 | "model_type": "miaomiao", 1356 | "architectures": [ 1357 | "MiaomiaoModel" 1358 | ], 1359 | "auto_map": { 1360 | "AutoConfig": "configuration_miaomiao.MiaomiaoConfig", 1361 | "AutoModel": "modeling_miaomiao.MiaomiaoModel", 1362 | "AutoModelForCausalLM": "modeling_miaomiao.MiaomiaoForCausalLM" 1363 | }, 1364 | ``` 1365 | 1366 | 追问:如何加载自定义tokenizer? 1367 | 1368 | 首先明确分词器文件里面有哪些: 1369 | 1370 | ![image.png](image%208.png) 1371 | 1372 | 加载用huggingface中transformers库 1373 | 1374 | ```python 1375 | from transformers import AutoModelForCausalLM, AutoTokenizer,AutoConfig 1376 | tokenizer = AutoTokenizer.from_pretrained('./Zero-Chatgpt-main/train_tokenizer/miaomiao_tokenizer/', trust_remote_code=True) 1377 | ``` 1378 | 1379 | --- 1380 | 1381 | ## ChatLM-mini-Chinese项目全流程 1382 | 1383 | 项目地址:https://github.com/charent/ChatLM-mini-Chinese 1384 | 1385 | 中文对话小模型,模型参数只有0.2B(算共享权重约210M) 1386 | 1387 | - 使用`Huggingface`NLP框架,包括`transformers`、`accelerate`、`trl`、`peft`等 1388 | - 自实现`trainer`,支持单机单卡、单机多卡进行预训练、SFT微调。 1389 | - 使用DPO进行全量偏好优化。也支持使用`peft lora`进行偏好优化;支持模型合并,可将`Lora adapter`合并到原始模型中。 1390 | 1391 | 0️⃣数据集 1392 | 1393 | | 社区问答json版webtext2019zh-大规模高质量数据集 | ‣ | 共410万,清洗后剩余260万 | 1394 | | --- | --- | --- | 1395 | | baike_qa2019百科类问答 | https://aistudio.baidu.com/datasetdetail/107726 | 共140万,清洗后剩余130万 | 1396 | | 中国医药领域问答 | ‣ | 共79万 | 1397 | | 知乎问答数据 | [https://huggingface.co/datasets/wangrui6/Zhihu-KOL?row=5](https://huggingface.co/datasets/wangrui6/Zhihu-KOL?row=5) | 共100万行,清洗后剩余97万行 | 1398 | | belle开源的指令训练数据 | [https://github.com/LianjiaTech/BELLE/tree/main](https://github.com/LianjiaTech/BELLE/tree/main) | 共370万行 | 1399 | | rlhf | | | 1400 | | alpaca-gpt4-data-zh | [https://huggingface.co/datasets/c-s-ale/alpaca-gpt4-data-zh](https://huggingface.co/datasets/c-s-ale/alpaca-gpt4-data-zh) | | 1401 | | [huozi_rlhf_data_json](https://huggingface.co/datasets/Skepsun/huozi_rlhf_data_json) | | | 1402 | | [rlhf-reward-single-round-trans_chinese](https://huggingface.co/datasets/beyond/rlhf-reward-single-round-trans_chinese) | | | 1403 | | | | | 1404 | 1405 | 问题3:如何自己构建RLHF的数据集? 1406 | 1407 | 这里rejected我们用model.generate生成,注意这里输入`batch_prompt.append(f"{item['prompt']}[EOS]")`,输出后结合不同的rlhf开源数据集整合形成 1408 | .append({"prompt", "chosen", "rejected"}) 1409 | 1410 | 如果两个答案长度有超过max_len,或者reject.strip() == chosen.strip(),这两个相同的也不要。 1411 | 输出的train_dataset: 1412 | 1413 | ![image.png](image%209.png) 1414 | 1415 | --- 1416 | 1417 | ## SFT 1418 | 1419 | 可参考好的项目: 1420 | 1421 | 1、llama3-Chinese-chat微调:https://github.com/CrazyBoyM/llama3-Chinese-chat 1422 | 1423 | 2、 1424 | 1425 | SFT样本构建 1426 | 1427 | - prompt和answer之间一定要有一个开始符``隔开,然后answer后需要一个结束符``。 1428 | - 计算loss的时候,对prompt部分的loss进行mask,只计算answer部分的loss即可。 1429 | 1430 | 1️⃣sft数据处理 1431 | 1432 | 首先参考[zero-chatgpt](https://github.com/AI-Study-Han/Zero-Chatgpt/tree/main/data_process) , 采用[firefly-train-1.1M](https://huggingface.co/datasets/YeungNLP/firefly-train-1.1M/blob/main/firefly-train-1.1M.jsonl),[ruozhiout_qa_cn.jsonl](https://www.modelscope.cn/datasets/baicai003/Llama3-Chinese-dataset/files)。根据问题长度对数据集进行了清洗和去重,最后剩余40多w条数据。 1433 | 1434 | 问题1:模型尺寸小对微调数据有啥要求? 1435 | 1436 | 因为模型尺寸比较小,只想训练单论对话能力。之前尝试使用50B token训练了1.5B的模型,2w条训练数据就有比较好的对话能力,这里0.1B的模型2w条sft数据训练后对话能力还是比较差,需要更多的sft数据训练,这里用了30w条。 1437 | 1438 | ![image.png](image%2010.png) 1439 | 1440 | 问题2:对不同数据如何处理成统一格式? 🀄(可以参考)🀄 1441 | 1442 | 1、首先去读jsonl数据变成统一格式,其中一条数据为: 1443 | 1444 | `'{"messages": [{'from': 'user', 'value': '自然语言推理:\n前提:家里人心甘情愿地养...是被家里人收养的孤儿'}, {'from': 'assistant', 'value': '中立'}]}\n'` 1445 | 1446 | **其中firefly数据处理165w条,所有数据过滤完一共30w** 1447 | 1448 | 2.1、我们对处理完统一格式数据后,拉取其中的value问答拼接,用MinHashLSH进行过滤阈值超过0.4重复。 1449 | 1450 | 2.2、~~在Minhashlsh去重后,还可以利用现有的tokenizer、model加载来计算两种回答困惑:~~ 1451 | 1452 | ~~不合理勿看~~ 1453 | 1454 | ~~例如:~~ 1455 | 1456 | ~~user_input用qwen2的分词器`tokenizer.apply_chat_template`生成'<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n自然语言推理:\n前提:家里人心甘情愿地养他,还有几家想让他做女婿的\n假设:他是被家里人收养的孤儿<|im_end|>\n<|im_start|>assistant\n’~~ 1457 | 1458 | 最后统一转化为: 1459 | **`sft_data.append({'prompt': prompt, 'answer': answer})`** 1460 | 1461 | 有个不明所以的操作,他划分25%作为rlhf训练数据,强化学习的数据是根据sft没有使用的数据进行生成的,sft数据原有的回答为"chosen",使用之前指令微调后的模型生成的回答作为"rejected",一共生成了10w条数据。 1462 | 1463 | 2️⃣多轮对话 1464 | 1465 | 训练的时候,需要在每个Assistant的回复后面都添加``(有些分词器是),作为此轮对话生成结束的标识符。否则推理的时候,模型很难采样到,从而无法结束生成。 1466 | 1467 | ![image.png](image%2011.png) 1468 | 1469 | 问题1:将一条多轮对话数据,拆分成多条数据去预测assistant的loss效率过低,有什么多轮对话高效训练方法? 1470 | 1471 | 参考Firefly项目训练多轮对话模型时,我们**将一条多轮对话数据拼接之后,输入模型,并行计算每个位置的loss,只有Assistant部分的loss参与权重更新**。 1472 | 1473 | ![ ](image%2012.png) 1474 | 1475 | 1476 | 1477 | 追问:为什么这种做法是可行的? 1478 | 1479 | 在于因果语言模型的attention mask。以GPT为代表的Causal Language Model(因果语言模型),这种模型的attention mask是一个对角掩码矩阵,每个token在编码的时候,只能看到它之前的token,看不到它之后的token。 1480 | User2部分的编码输出,只能看到User1、Assistant1、User2的内容,可以用来预测Assistant2的内容,依此类推。对于整个序列,**只需要输入模型一次,便可并行获得每个位置的logits,从而用来计算loss**。 1481 | 1482 | 问题2:类似GLM这种Prefix LM而非causal LM训练的模型怎么使用mask? 1483 | 1484 | 存在prefix attention mask的设计。**对于prefix而言,它的attention是双向的,而预测部分的attention是单向的[也是就prompt可以双向不用mask]**。 1485 | 1486 | ![image.png](image%2013.png) 1487 | 1488 | 问题3: 1489 | 1490 | 3️⃣开始微调 1491 | 1492 | 问题1:模型输入如何处理,格式是咋样的? 1493 | 1494 | - 单样本代码处理示例 1495 | 1496 | ```python 1497 | features = Features({ 1498 | 'prompt': Value('string'), 1499 | 'answer': Value('string') 1500 | }) 1501 | sft_dataset = load_dataset('json', data_files=data_path, features=features) 1502 | data = [] 1503 | # 遍历数据集并取出每个元素 1504 | for example in sft_dataset['train']: 1505 | prompt = example['prompt'] 1506 | answer = example['answer'] 1507 | messages = [ 1508 | {"role": "user", "content": prompt} 1509 | ] 1510 | prompt_text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) 1511 | answer_text = answer + tokenizer.eos_token # eos=32003 1512 | 1513 | prompt_id = self.tokenizer.encode(prompt_text) 1514 | if (len(prompt_id) > self.prompt_max_len): 1515 | prompt_id = prompt_id[:self.prompt_max_len] 1516 | 1517 | answer_id = tokenizer.encode(answer_text) 1518 | if (len(answer_id) > self.answer_max_len): 1519 | answer_id = prompt_id[:self.prompt_max_len] 1520 | input_id = prompt_id + answer_id 1521 | labels = [self.tokenizer.pad_token_id] * len(prompt_id) + answer_id 1522 | pad_len = self.max_length - len(input_id) # 1024长度向右pad 1523 | input_id = input_id + [self.tokenizer.pad_token_id] * pad_len 1524 | labels = labels + [self.tokenizer.pad_token_id] * pad_len 1525 | labels = [(l if l != self.tokenizer.pad_token_id else IGNORE_INDEX ) for l in labels] # 不计算loss = -100 1526 | input_id = torch.LongTensor(input_id) 1527 | labels = torch.LongTensor(labels) 1528 | attention_mask = input_id.ne(self.tokenizer.pad_token_id) # pad位置false 1529 | data.append({ 1530 | "input_ids": input_id, 1531 | "labels": labels, 1532 | "attention_mask": attention_mask 1533 | }) 1534 | ``` 1535 | 1536 | 1537 | 单个QA样本格式处理为: 1538 | 1539 | ```python 1540 | data.append({ 1541 | "input_ids": input_id, 1542 | "labels": labels, 1543 | "attention_mask": attention_mask 1544 | }) 1545 | 1546 | ``` 1547 | 1548 | 1、假设设置最长1024 lens, 讲input按照apply_chat_template处理例如 1549 | 1550 | `<|im_start|>system\n你是一个sb<|im_end|>\n<|im_start|>user\n input <|im_end|>\n<|im_start|>assistant\n` 1551 | 1552 | 2、结合答案`answer+` . 通过分词器编码为token_id,然后向右补齐1024 1553 | 1554 | 3、最终**input_id为1024长度的tensor; 1555 | labes在input和为-100,只有answer位置为对应的token_id; 1556 | attention_mask只有位置为False,也就是不可见** 1557 | 1558 | # RLHF+DPO 1559 | 1560 | - 有个超级好的个人源码项目:[https://github.com/chunhuizhang/personal_chatgpt/tree/main](https://github.com/chunhuizhang/personal_chatgpt/tree/main) 1561 | - huggingface官方参考:[https://github.com/huggingface/trl](https://github.com/huggingface/trl) 1562 | - 强化学习纸质书:[https://github.com/datawhalechina/easy-rl](https://github.com/datawhalechina/easy-rl) 1563 | - 实现可以参考:https://github.com/charent/ChatLM-mini-Chinese/ 1564 | 1565 | 强化学习搞清概念: 1566 | 1567 | 1576 | 1577 | ![**状态价值函数都有,动作价值函数再加个给定state**](image%2014.png) 1578 | 1579 | **状态价值函数都有,动作价值函数再加个给定state** 1580 | 1581 | 问题2:扩展下强化学习在棋牌游戏怎么应用? 1582 | 1583 | **state表示手牌等信息;action表示当前的出牌动作;reward表示一系列未来的奖励总和,能够赢; 环境是规则,可以更新状态; agent就是要训练的网络。** 1584 | 1585 | 使用DQN算法,目标是最大化[{s1,a1,r1},{s2,a2,r2},…{sn,an,rn}]一系列的期望总的reward奖励,这里DQN思想是通过{s1,a1}会输出Q值(**它将返回在该状态下执行该动作的未来奖励期望**),采样尽可能多的action对来选择最大回报的action。 1586 | 1587 | ### RLHF理论 1588 | 1589 | ![image.png](image%2015.png) 1590 | 1591 | 问题1:RLHF原始训练公式是啥? 1592 | 1593 | 也即是policy objective: 1594 | 1595 | ![左边是训练好的奖励模型](image%2016.png) 1596 | 1597 | 左边是训练好的奖励模型 1598 | 1599 | 非常简单,目标最大化LLM输出y的偏好奖励值,同时避免离原始LLM策略偏差过大。 1600 | 1601 | 问题2:RLHF训练麻烦点在哪? 1602 | 1603 | 1、PPO很多超参,训练不稳定 1604 | 1605 | 2、需要同时操作三个模型(Actor、Ref、Reward),增加了复杂性。 1606 | 1607 | - **Actor Model**:负责生成动作的策略模型 *πθ*。 1608 | - **Reference Model (Ref Model)**:提供参考策略 *π*ref,用于计算KL惩罚。(预训练策略参考或人类专家参考) 1609 | - **Reward Model (RM)**:评估动作和状态对的奖励 *rϕ*(*x*,*y*)。 1610 | 1611 | 问题3:什么是reward hacking? 1612 | 1613 | 指的是在给定奖励机制下,个体通过**非预期的方式**最大化奖励的行为。 1614 | 1615 | 为了防止奖励黑客,通常会引入额外的约束或惩罚项(如KL散度),以确保代理的行为与期望的策略保持一致。 1616 | 1617 | 问题4:reward模型怎么来的? 1618 | 1619 | ![image.png](image%2017.png) 1620 | 1621 | ### DPO理论 1622 | 1623 | **Direct Preference Optimization** 1624 | 1625 | 问题1:如何从RLHF推导改进过来? 1626 | 1627 | 就是将reward model的损失目标过程将r(x,y)消掉,将KL散度展开对数并提出期望,期望变换。推导后得到奖励函数与最优策略之间的直接映射。 1628 | 1629 | 最终抵消r无需奖励模型: 1630 | 1631 | ![想要的数据概率-不要的数据概率](image%2018.png) 1632 | 1633 | 想要的数据概率-不要的数据概率 1634 | 1635 | 其中为当前策略生成回答的累计概率【每个token的概率求和】 1636 | 1637 | 数学推导参考:[https://github.com/chunhuizhang/personal_chatgpt/blob/main/tutorials/trl_hf/trl_dpo.ipynb](https://github.com/chunhuizhang/personal_chatgpt/blob/main/tutorials/trl_hf/trl_dpo.ipynb) 1638 | 1639 | 问题2:DPO的改进? 1640 | 1641 | 加载2个模型,其中一个推理,另外一个训练。直接在偏好数据上进行训练即可开始训练时,reference model和policy model都是同一个模型,只不过在训练过程中reference model不会更新权重。 1642 | 1643 | 1. RLHF算法包含奖励模型(reward model)和策略模型(policy model,也称为actor model),基于偏好数据以及强化学习不断迭代优化策略模型的过程。 1644 | 2. DPO算法不包含奖励模型和强化学习过程,直接通过偏好数据进行微调,将强化学习过程直接转化换为SFT过程,因此整个训练过程简单、高效,主要的改进之处体现在于损失函数。 1645 | 1646 | 追问:损失函数如何控制偏好程度以及KL散度的分布体现? 1647 | 1648 | 首先,公式核心思想希望调整模型的参数Π⊖,使得在给定输入x情况,生成yw的概率大于yl。 1649 | 1650 | 通过控制β来调整偏好程度(一般在0.1~0.5),β越大模型将更快的趋向于产生优选输出。 1651 | 1652 | 公式设计对数概率比率,其实很KL散度衡量两个分布差异相似,隐含了这种行为差异。 1653 | 1654 | **也就是 一个train, 一个ref用于计算KL散度,帮助训练保持稳定** 1655 | 1656 | 问题3:代码如何训练DPO? 1657 | 1658 | ```python 1659 | from trl import DPOTrainer 1660 | training_args = TrainingArguments() # 初始化训练参数 1661 | # 初始化 DPO trainer 1662 | dpo_trainer = DPOTrainer( 1663 | model_train, 1664 | model_ref, 1665 | peft_config=peft_config, 1666 | args=training_args, 1667 | beta=config.beta, 1668 | train_dataset=train_dataset, 1669 | eval_dataset=eval_dataset, 1670 | tokenizer=tokenizer,...) 1671 | # 训练 1672 | dpo_trainer.train( 1673 | # resume_from_checkpoint=True # 断点续训练 1674 | ) 1675 | # 9. save log 1676 | loss_log = pd.DataFrame(dpo_trainer.state.log_history) 1677 | loss_log.to_csv(f"{log_dir}/dpo_train_log_{time.strftime('%Y%m%d-%H%M')}.csv") 1678 | 1679 | # 10. 保存模型/lora 1680 | suffixe = '/lora/' if peft_config is not None else '/dpo' 1681 | model_save_dir = '/'.join(config.sft_model_file.split('/')[0: -1]) + suffixe 1682 | dpo_trainer.save_model(model_save_dir) 1683 | ``` 1684 | 1685 | 问题4:model_train和model_ref的关系? 1686 | 1687 | 一开始加载是同样的模型。随着训练的进行,`model_train` 会逐渐偏离 `model_ref`,以适应特定的偏好数据。这意味着 `model_train` 学习到了偏好数据中体现的“偏好”,从而在相同输入上倾向于生成与偏好一致的输出。 1688 | 1689 | ## 数据采集 1690 | 1691 |  在获得SFT模型的基础上,无需训练奖励模型,取得**正向回答(chosen)和负向回答(rejected)**即可开始微调。微调的`chosen`文本来自原数据集[alpaca-gpt4-data-zh](https://huggingface.co/datasets/c-s-ale/alpaca-gpt4-data-zh),拒绝文本`rejected`来自SFT微调1个epoch后的模型输出,另外两个数据集:[huozi_rlhf_data_json](https://huggingface.co/datasets/Skepsun/huozi_rlhf_data_json)和[rlhf-reward-single-round-trans_chinese](https://huggingface.co/datasets/beyond/rlhf-reward-single-round-trans_chinese),合并后共8万条dpo数据。 1692 | 1693 | DPO偏好优化数据集示例: 1694 | 1695 | ``` 1696 | { 1697 | "prompt": "为给定的产品创建一个创意标语。,输入:可重复使用的水瓶。", 1698 | "chosen": "\"保护地球,从拥有可重复使用的水瓶开始!\"", 1699 | "rejected": "\"让你的水瓶成为你的生活伴侣,让你的水瓶成为你的伙伴\"" 1700 | } 1701 | ``` 1702 | 1703 | 问题1:RLHF的训练集测试集单个样本示例? 1704 | 1705 | 实际用自己的chat_template处理chat: 1706 | 1707 | `{**"prompt"**: "<|im_start|>system\n你是一个由喵阿姨开发的喵喵小助手<|im_end|>\n<|im_start|>user\n请将下面的xx<|im_end|>\n 1708 | <|im_start|>assistant\n", "response": "Mary不喜欢这个颜色<|im_end|>", 1709 | **"chosen"**: "但John提出选择这个颜色用于新房间的壁纸。<|im_end|>", 1710 | **"rejected"**: "因为我不喜欢这个颜色。<|im_end|>"}` 1711 | 1712 | 问题2:实际输入模型的chosen_dataset和reject_dataset是咋样? 1713 | 1714 | 首先有chosen_sentence和reject_sentence,应用到chat_template的prompt如下,当然两个偏好样本红色回答处不一样 1715 | 1716 | ![image.png](image%2019.png) 1717 | 1718 | 然后通过分词器转化为token_id,并且max_seq_len向右补齐PAD,分词器输出包括["input_ids"],["attention_mask"]。例如: 1719 | 1720 | ![image.png](image%2020.png) 1721 | 1722 | ## RLHF代码过程 1723 | 1724 | **问题1:首先如何构建及加载奖励模型(RewardModel或者critic_model)?** 1725 | 1726 | 1730 | 1731 | 1、首先基于base模型和分词器加载,有些模型在输入序列开头会有填充标记。 1732 | 1733 | 2、计算奖励的关键通过`v_head`线性层将隐藏状态映射为奖励分数rewards = self.v_head(hidden_states).squeeze(-1)。【表示模型对每个token的打分】 1734 | 1735 | 3、将输入序列分成选择(chosen)和拒绝(rejected)两个部分。 1736 | 1737 | - 对于每对选择和拒绝的序列,找到它们首次不同的地方(称为 `divergence`)。 1738 | - 计算从 `divergence` 开始到序列结束(PAD位置)的奖励差异,并通过 `logsigmoid` 函数计算损失。 1739 | 1740 | 4、输出chosen_id ,rejected_id -》(1024) [输出的选择和拒绝token_id序列,多的PAD补齐] 1741 | 以及输出chosen_reward,rejected_reward -》(1024)【v_head的对应token奖励值,一般reward -1~1】 1742 | 索引`[div: end_ind]`的区间reward来计算**对比损失函数** 1743 | 1744 | ![image.png](image%2021.png) 1745 | 1746 | **输出一个y让他 max(chosen回答的奖励-reject回答的奖励)** 1747 | 1748 | 5、最后输出: 1749 | 1750 | ``` 1751 | { 1752 | "loss": loss, 1753 | "chosen_mean_scores": chosen_mean_scores, 1754 | "rejected_mean_scores": rejected_mean_scores, 1755 | } 1756 | ``` 1757 | 1758 | 其中chosen_mean_scores和rejected_mean_scores为**最后一个有效的token所对应的模型输出往往能够总结整个序列的语义或贡献**。而loss是整个一对偏好回答之间的loss 1759 | 1760 | 问题2:损失函数为啥不用标准的交叉熵损失? 1761 | 1762 | 任务是对两个序列进行排序或优劣比较,交叉熵损失并不适用。交叉熵损失是基于离散的标签(真实答案)进行监督学习,而在排序任务中,并没有明确的“正确答案”标签,只有“选择更好序列”的相对偏好Ranking Task。因此,选择**对比损失函数【logsigmoid】**,会被鼓励尽量增大两者的差异,从而提升预测的准确性。这种损失函数通常用于排序任务或强化学习中的策略优化。 1763 | 1764 | ```python 1765 | loss += -torch.nn.functional.**logsigmoid**(c_truncated_reward -r_truncated_reward).mean() 1766 | ``` 1767 | 1768 | 问题3:奖励模型是怎么设计的? 1769 | 1770 | 奖励模型的架构通常类似于一个分类器或回归模型,它接受输入和一个候选回答,并输出一个分数。 1771 | 这里就是在base模型上加一个线性层v_head转化为奖励值标量,通过比较“选择的序列”和“拒绝的序列”来进行优化的。每次前向传播后,根据计算的损失,梯度会被反向传播到 `v_head` 和 `base_model` 的参数中,从而更新它们的权重,使得模型的奖励输出更加符合人类偏好。: 1772 | 1773 | 问题4:如何评估奖励模型的效果? 1774 | 1775 | 有三个评价的指标:选择序列得分、拒绝序列得分和准确率。 1776 | 1777 | 其中acc= (选择的序列得分>拒绝的序列)的数量/总量 1778 | 1779 | 问题5:实验中的奖励模型好像不是事先训练好的分类器,这里具体采用了什么方法来RLHF的? 1780 | 1781 | 按照常规方法应该先通过人工标注数据独立训练奖励模型,然后用这个奖励模型的输出微调策略模型(也即是LLM), 用对比损失 PPO使其输出更符合人类的预期。 1782 | 1783 | **But,实际代码实现为了方便,通常奖励模型 和策略模型是一起训练的,奖励模型在base模型上加一个v_head线性层,负责状态映射为奖励值(本质上奖励和策略模型是一个)。采用边训练边打分,就是模型在自我监督的过程中不断调整自身的生成策略,使得输出更加符合“高奖励”的标准。 1784 | 在每个训练步骤中,模型生成“选择的序列”(`chosen_ids`)和“拒绝的序列”(`rejected_ids`),然后通过`v_head`层计算这两个序列的奖励值(`chosen_rewards`和`rejected_rewards`)。随后,这些奖励值被用于计算损失函数。** 1785 | 1786 | ————————代码实现了一个自监督的训练过程,使用单一模型同时进行生成和奖励评估,而没有典型RLHF中的两个分离模型———————— 1787 | 1788 | 代码体现很明显:`rm_model.backward(loss)` 1789 | 1790 | 追问:那这个奖励模型长啥样呢? 1791 | 1792 | - rm_model 1793 | 1794 | ```python 1795 | DeepSpeedEngine( 1796 | (module): RewardModel( 1797 | (v_head): Linear(in_features=512, out_features=1, bias=False) 1798 | (rwtransformer): MiaomiaoModel( 1799 | (embed_tokens): Embedding(32008, 512) 1800 | (layers): ModuleList( 1801 | (0-23): 24 x MiaomiaoDecoderLayer( 1802 | (self_attn): MiaomiaoSdpaAttention( 1803 | (q_proj): Linear(in_features=512, out_features=512, bias=False) 1804 | (k_proj): Linear(in_features=512, out_features=512, bias=False) 1805 | (v_proj): Linear(in_features=512, out_features=512, bias=False) 1806 | (o_proj): Linear(in_features=512, out_features=512, bias=False) 1807 | (rotary_emb): MiaomiaoRotaryEmbedding() 1808 | ) 1809 | (mlp): MiaomiaoMLP( 1810 | (gate_proj): Linear(in_features=512, out_features=2752, bias=False) 1811 | (up_proj): Linear(in_features=512, out_features=2752, bias=False) 1812 | (down_proj): Linear(in_features=2752, out_features=512, bias=False) 1813 | (act_fn): SiLU() 1814 | ) 1815 | (input_layernorm): MiaomiaoRMSNorm() 1816 | (post_attention_layernorm): MiaomiaoRMSNorm() 1817 | ) 1818 | ) 1819 | (norm): MiaomiaoRMSNorm() 1820 | ) 1821 | ) 1822 | ) 1823 | ``` 1824 | 1825 | 1826 | 问题6:训练时优化器和lr等选择? 1827 | 1828 | 优化器选择Deepspeed库提供的融合版FusedAdam,适合大规模分布式训练。将模型参数分为权重衰减和权重不衰减,方便Lora对应。 1829 | 1830 | 学习率调度器从零线性增加到初始设定的步数。 1831 | 1832 | | | A100 | RTX4090 | 影响FSDP | 1833 | | --- | --- | --- | --- | 1834 | | 内存带宽 | 1,555 GB/s | 1,008 GB/s | 1.5倍 | 1835 | | 通信带宽 | NVLink600 GB/s(双向) | PCIe 4 32 GB/s(单向) | >>18倍 | 1836 | 1837 | --- 1838 | 1839 | - 腾讯元宝论文精度:[https://yuanbao.tencent.com/chat/naQivTmsDa/8fe43035-69b8-436d-8ef6-9221c41db072](https://yuanbao.tencent.com/chat/naQivTmsDa/8fe43035-69b8-436d-8ef6-9221c41db072) 1840 | 1841 | 查询prompt: 1842 | 1843 | 1、有没有经典论文实验证明了课程学习预训练大模型策略对模型效果的影响?给我查询下论文,英文也行,就是提到LLM、课程学习,多阶段渐进预训练. 1844 | 2、这个论文讲了什么,重点帮我提取出论文提到的关于课程学习或者多阶段渐进预训练数据的策略相关对模型效果影响的内容或实验。 1845 | 1846 | 3、通读论文,概括下课程学习策略对模型的提升主要在哪,它的不足点在哪 1847 | 1848 | 4、这些帮我在原文中找到对应证明这些观点的句子或者图片表格。 1849 | 1850 | ## 论文阅读 1851 | 1852 | monica搜索+gpt4o网页聊天+腾讯元宝精度 1853 | 1854 | 课程学习在预训练大模型中的应用,尤其是多阶段渐进预训练策略,已被多项研究证实能够有效提升模型效果。 1855 | 1856 | Curriculum [kəˈrɪkjələm] 1857 | 1858 | 1️⃣《Automatic Pair Construction for Contrastive Post-training》2023 1859 | 1860 | 要解决的问题是如何自动构建对比数据来对大型语言模型(LLMs)进行**对比后训练(技术有 序列似然校准(SLiC)和直接偏好优化(DPO))**,以提高其与人类偏好的对齐。 1861 | 1862 | ![image.png](image%2022.png) 1863 | 1864 | **课程学习策略用在对比后训练中**。具体实现方法是根据模型在不同对比对上的表现,动态调整训练数据中对比对的比例。例如,初期训练使用较容易的对比对(如GPT-4 vs InstructGPT),随着训练的进行,逐渐增加难度更高的对比对(如ChatGPT vs InstructGPT)。当然要通过线性函数f(t)=kt来控制EasyPair和HardPair在训练中的比例,混合比例阶段式训练策略确保模型能够逐步适应任务理解对比差异。 1865 | 1866 | ![image.png](image%2023.png) 1867 | 1868 | 不足: 1869 | 1870 | 作者提出课程学习的效果可能高度依赖于课程设计的质量和顺序,不当的课程设计可能导致次优结果。可能导致模型过度适应特定的学习路径,影响其在未见场景中的表现 1871 | 1872 | 2️⃣《CURRICULUM LEARNING FOR LANGUAGE MODELING》 1873 | 1874 | 课程学习在计算机视觉和机器翻译等领域已被证明可以提高模型训练速度和性能,所以作者使用基于能力的课程学习(Competence Based Curriculum, CBC)方法,探索了8种不同的样本难度度量方式,在WikiText-2和WikiText-103数据集上训练ELMo模型。 1875 | 1876 | we believe that structuring the pre-training methodology can lead to improved model performance. To introduce structure into LM training we leverage Platanios et al.’s competence based curriculum (CBC)[ 6 ] as shown in Algorithm 1。 -(对预训练方法进行结构化可以提高模型性能,但后面证明方法不如参数和数据) 1877 | 1878 | 在预训练后,使用GLUE基准测试评估模型的迁移学习能力。 1879 | 1880 | ![是在Bert上进行的](image%2024.png) 1881 | 1882 | 是在Bert上进行的 1883 | 1884 | 结论: 1885 | 1886 | 1、**在小规模预训练语料上,CBC方法可以在下游任务上略微优于随机采样**。 1887 | 2、**随着预训练语料规模增大,CBC方法的优势消失**。 1888 | 3、预训练困惑度与下游任务性能之间没有明显相关性。 1889 | 4、不同难度度量方法之间没有显著差异。 1890 | 1891 | 问题1:怎么实现数据的课程划分? 1892 | 1893 | 语料库X是一个样本集合S,其中每个样本si是一个词序列si = wio, wi1, ..., win,使用诸如句子长度或一元词频稀有度等启发式方法按难度排序,并被分配一个难度值εsi = [0, 1]。 1894 | 1895 | 给定一个处理过的语料库,模型被分配一个初始能力λ0和一个能力增量λincrement。模型的能力分数代表了模型在训练过程中的进展程度。在每个训练步骤中,模型从难度低于其当前能力的数据中采样,更新其权重,并提高其能力。模型只能训练难度εsi <= λt的样本。 1896 | 1897 | **探索了8种样本难度的代理指标:无课程、随机、样本长度、一元词频样本概率,二元词频样本概率、三元词频样本概率、词性多样性(POS)和样本依存句法复杂度( cumulative density function,DEP)我们为语料库X中的每个样本si计算一个难度值,然后根据这个难度分数对数据集进行排序。使用排序后的数据集,我们计算累积密度函数 cumulative density function(CDF),使每个样本的难度分数εsi ∈ [0, 1]。无课程方法将λ0设为1** 1898 | 1899 | ![image.png](image%2025.png) 1900 | 1901 | 追问2:n-gram词频样本如何区分样本难度? 1902 | 1903 | 注意这里**计算的不是给定前N个词的每个词的条件概率,而是给定文本语料库的N元语法的概率** 1904 | 1905 | 比如样本"The cat sat on the mat. The dog sat on the rug.” 1906 | 1907 | 文中使用的方法(整个3-gram的概率): 1908 | P(The cat sat) = 次数(The cat sat) / 所有语料库3-gram的总数 1909 | 1910 | 太多就考虑使用采样技术估计 1911 | 1912 | 追问3:Dependency Tree如何区分样本难度?(听拉垮这个不用管) 1913 | 1914 | **依存树**,将句子建模为树结构,模拟文本样本中词与词组之间的交。SPACY.IO来为每个样本生成解析树,并测量每棵树的深度。然后,这些信息被用来计算难度—和随机差不多(因为深度很少唯一性) 1915 | 1916 | 追问4:Part of Speech Diversity 词性多样性? 1917 | 1918 | 更难的句子具有更高的词性(POS)多样性。我们使用SPACY.IO的词性标注器为每个样本生成一组词性标签 1919 | 1920 | ![image.png](image%2026.png) 1921 | 1922 | 其中,pos(si)表示样本si中的词性标签集合,len(set())计算集合中唯一元素的数量。 1923 | 1924 | 数据: 1925 | 1926 | wikitext-2包含约200万个标记 wikitext-103包含约1.03亿个标记,**使用困惑度指标,评估模型在GLUE(通用语言理解评估基准)上的迁移能力** 1927 | 1928 | ![image.png](image%2027.png) 1929 | 1930 | ![image.png](image%2028.png) 1931 | 1932 | 3️⃣《Curriculum Learning with Quality-Driven Data Selection》2024 1933 | 1934 | 提出了一种新的**多模态**大语言模型(MLLM)**指令微调数据选择和课程学习**方法 1935 | 1936 | ![image.png](image%2029.png) 1937 | 1938 | 这个表格强调了课程学习策略的有效性,同时也显示了在不同场景下,数据质量和数量的平衡对模型性能的重要影响。它证明了适当的数据选择和课程学习可以显著提高模型在多个任务上的表现。 1939 | 1940 | 问题1:表格中为啥只有5%,10%,不是全部数据微调? 1941 | 1942 | DIL、DIS和DIQ方法都是为了选择最有价值的数据子集。通过只使用一小部分精心选择的数据,研究者希望证明数据质量比数据数量更重要。使用较小的数据子集允许研究者实施多阶段的课程学习策略,逐步增加数据的复杂性和数量。 1943 | 1944 | 问题2:作者对课程学习局限分析以及未来展望 1945 | 1946 | 课程学习的效果可能对超参数(如每个阶段的数据量、阶段数量等)敏感,需要仔细调整。 1947 | 1948 | 研究者提出可以设计更复杂和动态的课程学习策略,可能根据模型的实时学习进度来调整数据的难度和类型。 1949 | 1950 | 4️⃣《Language Model Pre-training with Linguistically Motivated Curriculum Learning》 1951 | 1952 | 数据会影响预训练的质量,而且在序列长度方面也已经探索了课程学习。我们**从语言学(linguistic perspective)的角度考虑课程设计,先学习常用词,后学习罕见词。这是通过用其组成成分标签替换包含不常用词的层级短语来实现的。通过这种语法替换,可以通过逐步引入频率递减的词来制定课程**。 1953 | 实验: 1954 | 1955 | 在bert上对比 1956 | 1957 | 5️⃣《CLIMB – Curriculum Learning for Infant-inspired Model Building》2023 看star啥也不是 1958 | 1959 | 直接否认课程学习策略带来收益 1960 | 1961 | 关于这个**Baby**LM Challenge  1962 | 参考儿童语言习得过程去做课程学习训练 1963 | 1964 | ![image.png](image%2030.png) 1965 | 1966 | 剑桥大学与合作者提交的 "婴儿语言模型挑战赛"(严格赛道)参赛作品。Baby LM 挑战赛要求参赛团队在儿童可能看到的相同数据量(大约 1,000 万字)上训练语言模型。基于婴儿启发的模型构建的课程学习 1967 | 1968 | 摘要作者自己的结论: 1969 | 1970 | ![课程实验证明了特定任务和设置组合的关键要点,仔细选择模型架构和训练超参数](image%2031.png) 1971 | 1972 | 课程实验证明了特定任务和设置组合的关键要点,仔细选择模型架构和训练超参数 1973 | 1974 | 使用Roberta 语言模型的变体 1975 | 1976 | 问题1:课程学习如何划分课程类型? 1977 | 1978 | 作者分了3种:词汇课程、数据课程、目标课程 1979 | 1980 | 1、**词汇课程(Vocabulary Curriculum)** 1981 | 1982 | 策略: a) 基于词频选择词汇 b) 基于词类选择词汇 c) 结合词频和词类的混合策略 1983 | 1984 | 结果:不同表示难度的方法对不同任务有不同影响。例如,基于词频的方法在BLiMP任务(评估语法知识)上表现较好,而基于词类的方法在SuperGLUE任务(涵盖问答、常识推理、词义消歧、句子接受性-**通用语言理解**)上表现更佳。 1985 | 1986 | ![linear、log是词汇引入速度,MSGS Ambig是否能正确理解和处理句子中的语法模糊性](image%2032.png) 1987 | 1988 | linear、log是词汇引入速度,MSGS Ambig是否能正确理解和处理句子中的语法模糊性 1989 | 1990 | 追问2:词频怎么选择词汇? 1991 | 1992 | 引入具有较低Token ID 的词(即更常见的词),然后逐步引入具有较高Token ID 的词(即较少见的词)。 1993 | 1994 | 追问3:词性POS(Part of Speech)怎么去划分课程词汇学习? 1995 | 1996 | 词性顺序: 1997 | 根据Bergelson和Swingley(2015)的研究,设定了一个从实词到虚词的词性顺序: 1998 | NOUN(名词)> VERB(动词)> ADJ(形容词)> PRON(代词)> DET(限定词)> ADP(介词)> NUM(数词)> CONJ(连词)> PRT(小品词)> PNCT(标点符号),用线性或对数步进函数来控制词汇引入的速度。[试图模仿儿童语言习得的自然顺序] 1999 | 2000 | > 课程在当前阶段超纲词汇其他词性的词会被替换为`UNK`(未知)标记 2001 | > 2002 | 2003 | 2、**数据课程(Data Curriculum)** 2004 | 2005 | 策略: a) 基于数据源难度排序 b) 使用一元语言模型计算困惑度 c) 使用模型自评估困惑度 2006 | 2007 | 结果:在多语料库数据集中,按难度排序训练数据可能有益。特别是,**对数据源进行难度排序的方法在某些任务上表现优于基线模型(也只是某些特定任务)**。 2008 | 2009 | ![image.png](image%2033.png) 2010 | 2011 | ![image.png](image%2034.png) 2012 | 2013 | 问题4:如何将数据与人类儿童学习语言的方式进行课程学习? 2014 | 2015 | 理论驱动和模型驱动的方法来确定某个样例的"相对难度",并在难度逐步增加的实例上训练模型。 2016 | 2017 | ![image.png](image%2035.png) 2018 | 2019 | 从简单的口语到复杂的书面语言的渐进过程 2020 | 2021 | 追问5:基于数据源来确定实例的难度过于片面,如何改进难度区分?❤️‍🔥❤️‍🔥❤️‍🔥 2022 | 2023 | 1)使用单独训练的一元模型unigram来初始评估困惑度,或2)最初随机抽样训练实例。在25,000个训练步骤之后,我们切换到使用当前模型进行困惑度评估【在线动态评估实例】。 2024 | 2025 | 一元模型计算困惑度:PP(W) = [(1/P(w₁)) * (1/P(w₂)) * ... * (1/P(wₙ))]^(1/n) 2026 | 2027 | 模型怎么去评估每个实例的困惑度,类似上面公式,然后困惑度的范围是从1(预测完全正确)到正无穷 2028 | 2029 | ![**CLIMB-data-split对比**](image%2036.png) 2030 | 2031 | **CLIMB-data-split对比** 2032 | 2033 | 3、**目标课程(Objective Curriculum)** 2034 | 2035 | 策略: a) 从词类预测任务开始,逐步过渡到掩码语言模型(MLM)任务 b) 多任务学习方法,同时进行词类预测和MLM 2036 | 结果:多任务学习方法在目标课程中表现较好,特别是在BLiMP和SuperGLUE任务上有所改善 2037 | 2038 | 问题6:如何设计目标课程划分? 2039 | 2040 | 他们提出使用无监督词性标注器来估计词类,并尝试不同数量的词性标签分类: 2041 | 2042 | 三分类:动词、名词、其他 ;十分类:使用10个通用词性标签 2043 | 研究中考虑了两种任务激活方式:顺序激活:先进行词类预测,然后进行MLM 2044 | 并行优化:类似于多任务学习 2045 | 对每个目标函数,都使用独立的任务头、线性分类器和优化器。 2046 | 2047 | ![image.png](image%2037.png) 2048 | 2049 | 6️⃣ 2050 | 2051 | ## 可行方向 2052 | 2053 | 1、实验mamba<7b 2054 | 2055 | 2、Moe多任务 2056 | 2057 | 3、Clip去看看多模态的 2058 | 2059 | 4、DPO非常重要,llama3.1技术报告有说,未来的资源会像这方面倾斜 --------------------------------------------------------------------------------