单轮对话的数据集构造,就是一个input、一个output,比较好理解。但是多轮对话的数据集是什么样的呢,假设我们现在有一条多轮对话数据如下:
User1:你好。
Assistant1:欢迎一起探讨大模型。
User2:大模型有哪些技术呢?
Assistant2:目前主要有预训练+微调+RLHF、RAG、Agent、多模态等。
User3:谢谢你。
Assistant3:不客气!
第一种构建方法:
User1、Assistant1、User2、Assistant2、User3的文本都视为模型的输入部分,将Assistant3的文本视为模型的预测部分。
缺点在于:没有充分利用多轮对话的训练数据,中间Assitant回复部分的信息量更丰富详细,这些数据在训练时被浪费了。
第二种构建方法:
将一条多轮对话数据,拆分成多条数据。User1(输入)、Assistant1(输出),User1+Assistant1+User2(输入)、Assistant2(输出),构建3个这种的QA对。
缺点在于:需要将一个包含n轮对话的数据,拆分成n条数据,前几轮对话信息被反复利用,训练方法低效
第三种构建方法:
将一条多轮对话数据拼接之后输入模型,并行计算Assistant部分的loss。
数据如下所示:
为什么这种做法是可行的?
在于因果模型的attention mask结构是一个对角掩码矩阵,每个token在编码的时候,只能看到它之前的token,看不到它之后的token。
所以User1部分的编码输出,只能感知到User1的内容,无法感知到它之后的文本,可以用来预测Assistant1的内容。依此类推。对于整个序列,只需要输入模型一次,便可并行获得每个位置的logits,从而用来计算loss。
参考Firefly 项目,现在基本很多开源微调框架都是采样这种方式:
此外还有几个小trick需要补充一下:
1、只有decoder-only架构能够采用第三种
2、大部分构建方法是基于单轮的,不适用于第三种方法;instruct指令是为了通用模型设计的,特定业务不需要
3、第三种方法仅需要提供多轮对话数据,在内部通过target_mask
区分输出token