Datawhale干货
作者:ybq,来源:知乎
知乎:ybq;编辑:机器学习算法与自然语言处理
背景篇
Special Token
耗时问题
与 pretrain 的区别
pretrain 的每条数据都是满编 4K / 8K,sft 的每条数据原本多长就是多长; sft 会引入 pretrain 阶段未见过的 special_token,来让它们学习全新的语义; sft 会让模型见到最重要的 eos_token,pretrain 模型因为没见过该 token 而无法停止生成; 借助 special_token,sft 会把语料切分成不同的角色,标配的有 system、user、assistant,根据业务需求也可以有“背景”、“旁白”、“事件”等等; sft 的 prompt 不做 loss,但这并不是说它不能做 loss。主要原因是 prompt 的同质化比较严重,不做 loss_mask 的话,同样的一句话会被翻来覆去的学,但如果你能保证你的每条 prompt 都是独一无二的,就完全可以省去 prompt 的 loss_mask 环节。对了,session 数据一定要想清楚是每一个 answer 都算 loss,还是只对最后一轮的 answer 算 loss。
幻觉问题
User:日本的首都是哪里?Assistant:日本的首都是东京(幻觉) User:日本的首都是哪里?Assistant:日本的首都是北京(正确)
广义幻觉和狭义幻觉在降低用户的交互体验时并无明显区别,做通用 AI 助手并不需要区分这两种情况,而现有技术范式下,广义幻觉只能靠外挂 RAG、function_call 的方式来解决; 狭义幻觉的缓解方式其实还是调参数,那些魔改 GPT 的工作,并不会比调参带来更大的收益。这些工作在不同的基座模型上的收益也完全不一样,还是太 trick 了,多少有点旁门左道的感觉; 目前,工业界的 AI 助手是一个全链路系统,裸模型的的安全问题与幻觉问题,会被上下游的各种小模型和词典配置进行拦截或者改写,并不会直接暴露出来。
数据篇
数据多样性
OpenAI 官网列出了 ChatGPT 擅长的所有任务项,诸如翻译、emoji 聊天……之类的。我们就每个任务项都想办法来一点数据,照着尖子生的作业抄; LLM 毕竟是个语言模型,传统的每个 NLP 模型它都应该能胜任,那就把什么 NER、机器阅读理解、意图识别等传统的 NLP 任务也给模型补充一点,如果已有类似任务就不补充了。训练数据也很好搞,传统 NLP 数据集质量都很高,直接拿来用就行; 参考业务需求,下游业务需要某个特殊场景的任务,那就让 sft 阶段提前见一见,这种数据的典型代表就是过年前给模型灌一些对春联、猜灯谜的的数据。只要数据质量没问题,一般都不会破坏模型能力; ……
prompt 表达方式多样性,不要千篇一律的“把中文句子 A 翻译成英文”,也要适当有一些“我在英国旅游,我现在需要向路人问路,我想表达 A 的意思,该怎么说”,“我是一个英文老师,我需要向我的学生讲解句子 A 用英文怎么写,请你用最正宗的表达方式帮我完成。”这么做的目的是防止模型只认识 prompt 中的几个关键 token,进而导致训练过拟合或者泛化性变差; prompt 长度均衡,既要有短数据,也要有长数据,避免模型的 attention 退化到无法聚焦长 prompt。长数据还不能是字面意思的长,要有那种关键信息藏在 开头 / 中间 / 结尾 的各种数据场景,避免模型在训练时偷懒,只对 prompt 的起始 token 或结束 token 有 attention; answer 长度均衡,不能让模型没出输几个 token 就停止,适当的有一些语料让它学会输出尽量长的 answer,否则模型会很难 follow “不少于2000字” 这种指令; 多轮聊天的切换 topic 能力,也就是说,有的数据当前 query 是和 session 有关系的,有的数据则是当前 query 和 session 毫无关系,要让模型自己学会判断 query 是否和 session 有关。类似的数据还要有 system 是否生效,有些数据 system 是个摆设,有些数据的 answer 则和 system 直接相关; answer 分布的多样性,这最重要,千万别总共一万条训练数据,一千条数据的 answer 都说同一句话,answer 可是算 loss 的,太单一的话会严重让模型过拟合; ……
数据生产
prompt1 :请设计一个重生故事的大纲,大纲包含“父母重男轻女,女主高考状元,弟弟彩礼”等要素; prompt2 :请基于给定的故事大纲,扩充内容,生成一篇不少于多少字的文章。
不在乎成本,就选 GPT4 / Claude 3,用过的人都说好; 在乎成本,就在自己的机器上部署 Qwen_72B / deepseek_MOE,部署过的人都说好; llama 系列的模型就算了,它的中文能力,体验过的人都说不好; 文心 / 豆包 等效果不如 GPT4 的闭源模型,属于品味之选,为国产大模型助力,点赞。
数据飞轮
专项数据
知识 / 聊天二分类模型,用于判别该不该做 rag。不要纠结说自己的模型知道世界最高山是什么,这个知识不用做 rag。你根本没办法测出来哪些知识是模型具备且正确的,所以是知识问题就必须做 rag; 传统的 IR 模型,快速从库里面进行检索出候选候选文档,没太多说的,老 NLP 技能了。
检索内容为空的时候模型会怎么回复,别让它自由发挥出一些奇怪的结果; 检索内容相互矛盾的情况,别让他只盯着第一条 / 最后一条的内容回复; 检索内容和 query 完全无关的情况,也是需要让模型见过,防止出奇怪的结果; 检索内容错了。那就让模型照着错的答案念,千万别想着让模型自己判断 rag 的知识和自己的知识谁更正确。我们做 rag 的大前提就是默认“数据库知识准确率高于模型自己具备的知识”。这种取巧心理很容易把模型搞迷糊,到时候模型不 follow rag 内容就麻烦了。
简单的长文本任务就是“背密码”,随机把密码插入到 200K 文本的任意一个位置让模型来复述; 复杂点的长文本任务就可以是让模型概括 paper 的 instruction 内容,让模型列举出所有林黛玉出场的章节; 挑战性的任务则直接让模型去算林黛玉的出场次数。
训练篇
训练框架
alignment 的很多开源工作和开源代码都是基于 deepspeed 实现的,复现起来省事; 利用 AutoModelForCausalLM 可以直接训起来大多数开源模型,而不需要每次都 trans_hf_to_megatron; 训出来的模型可以直接起 tgi 服务,vllm推理等,用 megatron 的话还得 trans_megatron_to_hf。
epoch gradient_accumulation_steps global_batch_size ( megatron 的参数,deepspeed 同学可无视) learning_rate lr_scheduler_type dropout
zero_stage max_seq_len offload gradient_checkpointing seq_parallel_size
weight_decay per_device_train_batch_size num_warmup_steps
炼丹技巧
不同 task_type 要有不同的 channel_loss,分别观察; special_token 的 loss 一开始会有点高,但是下降也是很快; 创作类任务的 loss 会比其他任务的 loss 更高一点,这个现象很 make sense,答案越固定,搜索结果越单一的语料,loss 越低,反之亦然; 只要训练语料是通用数据,且数据进行了 sample,那么模型的初始 loss 就不会特别高,7B / 13B 可能在 2 左右,数据难了也有可能到 3,72B 则大多在 1 ~ 2 之间这个水平状态;最终 loss 则大概在 0.5 左右,根据语言模型定义,如果 loss 更低,那基本上模型只会说这一句话了,别的 token 都没概率了; 如果 loss 持续升高,不要对自己的训练数据产生任何质疑,想着是不是数据太难了不好学之类,这就是训练代码有问题。next_token_prediction 的训练方式就是在背书,它不存在学不会的情况,只存在学会了但不会泛化的情况。你就算是一堆随机乱码,模型啥也学不到,它也应该是 loss 持平,而不是 loss 升高啊。
answer1: ```json…… answer2 : 好的,这就为你用 json 输出结果 ```json……
拟合问题
首先,通过让 base 模型续写,判断是不是 pretrain 阶段训错了(通常情况下都不是),如果是的话,那没辙了,强行在 sft 阶段做知识注入来扭转 pretrain 的错误知识吧,一两条语料影响应该不会很大; 然后,判断 sft 模型对哪个 pattern 过拟合了,对 answer 里面的核心关键词进行测试,也就是“日本”,“首都”,“北京”。对着我们的模型发出一连串的提问,美国的首都是哪里?日本最大的城市是哪里?北京是谁的首都?日本的首都是北京吗?日本的首都是东京吗?……目的很简单,测试出来模型到底对哪个 token,哪个 pattern 过拟合了,到底是把日本的所有城市都回答成北京,还是把所有国家的首都都回答成北京。进而 grep,大概率类似 pattern 的语料都过多了。这时候或删除该 pattern 的语料,或改造该 pattern 的语料,都无所谓了。
夹逼准则
评估篇
评估方式
评估分析
有可能是 A 能力项的训练数据被更新了,质量不如上一版数据,需要重新 review; 也有可能是整体的训练数据变多了,导致 A 能力项的数据占比变小了,有点欠拟合了; 还有可能是这次训练的某些参数没配好; 当然,最有可能的一种情况是“波动”,这时候,你就重点挑两个 sft_v2 回答很差劲的 case,然后用 do_sample 的方式让 sft_v2 与 sft_v1 都预测个十次。我们看一下,是不是 path@10 也变差了,如果不是,那就不用很在意了。波动经常会影响 path@1 的结果,但对 path@N 的结果往往影响不大,训练方式才会让后者发生较大的改变。
总结篇
结语
我:这工作(某个方向的 sft)交给我合适吗,我能胜任吗? 前辈:这工作谁都能做。 我:那你为啥选我来做? 前辈:这不是因为我认识你,跟你熟悉嘛。 我:…… 前辈:你做不做,不做有的是人想做,不行我招个实习生来做。 我:我做我做,我当然做。
一起点赞三连↓