620. LMSYS - Chatbot Arena Human Preference Predictions | lmsys-chatbot-arena
我要感谢 Kaggle 和 LMSYS 提供这次出色的竞赛。通过这次比赛,我们学到了很多关于大语言模型(LLM)LoRA 训练的技术。我也想感谢每一位团队成员,@distiller, @xxycbadpanda, @w1623843225,感谢他们的辛勤工作。如果没有他们的创造力、想法和工程技能,我们很难取得如此优异的成绩。
首先,我们使用了官方数据(55k)+ 33k 去重数据,设置 fold n_splits=20,并且只在一个 fold 上进行训练以确保有更多的训练数据。此外,我们为 ultrafeedback 数据集中的 30,000 个样本创建了伪标签作为补充数据。
我们设计了一个独特的提示词。这个提示词的优势在于,当对话长度超过 max_length 时,它可以合理地截断最后一轮对话。这确保了 prompt、response_a 和 response_b 都有一定的比例显示,避免了只有 prompt 或 response_a 在最后一轮被截断的情况。我们甚至设定了一个规则,如果最后一轮剩余的 token 数量少于 80 个,我们将完全丢弃该轮(及其之后的轮次)。这些阈值和比例是通过观察训练集确定的。
def tokenize_cls_p3(example, tokenizer, max_length, is_train):
input_ids = []
attention_mask = []
dot_tokens = tokenizer("......", add_special_tokens=False)["input_ids"]
final_p_tokens = tokenizer("\n\n---\nWhich response is better? [A or B or tie]\nAnswer: ", add_special_tokens=False)["input_ids"]
for ps, ras, rbs in zip(example['prompt'], example['response_a'], example['response_b']):
one_input_ids = [tokenizer.bos_token_id]
prev_tokens_num = 2 + len(final_p_tokens) # 2 for bos_token and eos_token
for idx, (p, ra, rb) in enumerate(zip(ps, ras, rbs)):
r_tokens = tokenizer(f'\n\n## Round {idx+1}:' if idx else f'## Round {idx+1}:', add_special_tokens=False)["input_ids"]
p_tokens = tokenizer(f'\n### Prompt:\n{p}', add_special_tokens=False)["input_ids"]
ra_tokens = tokenizer(f'\n\n### Response A:\n{ra}', add_special_tokens=False)["input_ids"]
rb_tokens = tokenizer(f'\n\n### Response B:\n{rb}', add_special_tokens=False)["input_ids"]
all_tokens_num = prev_tokens_num + len(r_tokens) + len(p_tokens) + len(ra_tokens) + len(rb_tokens)
if all_tokens_num > max_length:
remain_tokens_num = max_length - prev_tokens_num - len(r_tokens) - 3*len(dot_tokens)
if remain_tokens_num >= 80:
p_tokens = p_tokens[:int(remain_tokens_num*0.2)] + dot_tokens if len( p_tokens) > int(remain_tokens_num*0.2) else p_tokens
ra_tokens = ra_tokens[:int(remain_tokens_num*0.4)] + dot_tokens if len(ra_tokens) > int(remain_tokens_num*0.4) else ra_tokens
rb_tokens = rb_tokens[:int(remain_tokens_num*0.4)] + dot_tokens if len(rb_tokens) > int(remain_tokens_num*0.4) else rb_tokens
one_input_ids += r_tokens + p_tokens + ra_tokens + rb_tokens
break
else:
prev_tokens_num = all_tokens_num
one_input_ids += r_tokens + p_tokens + ra_tokens + rb_tokens
one_input_ids += final_p_tokens + [tokenizer.eos_token_id]
one_attention_mask = [1] * len(one_input_ids)
input_ids.append(one_input_ids)
attention_mask.append(one_attention_mask)
if is_train:
labels = [0 if a_win else 1 if b_win else 2 for a_win, b_win, tie in zip(example['winner_model_a'], example['winner_model_b'], example['winner_tie'])]
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels,
}
else:
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
}
我们选择了 gemma-2-9b-it 作为我们的起始模型,它的表现比我们测试的 Llama3 8b 和 Llama3.1 8b 都要好得多。
我们使用 Gemma2ForSequenceClassification 进行 3 分类,并使用 lora bf16 对模型进行微调。我们团队成员有不同的 GPU 设置,但得分最高的实验是在四个 A100 GPU 上进行的。
Max_length: 2048
freeze_layers: 0
lora_r: 64
lora_alpha: 16
lora_dropout: 0.05
lora_bias: "none"
lora_target_modules:
- "q_proj"
- "k_proj"
- "v_proj"
- "o_proj"
- "gate_proj"
- "up_proj"
- "down_proj"
fold n_splits=20,并且只在一个 fold 上进行训练。ultrafeedback 数据集中的 30,000 个样本创建伪标签。然后,我们将这些数据与第一阶段的数据相结合(总计超过 100k),从头训练一个新模型。第一阶段的每个实验大约需要 10 小时,第二阶段在 4*A100 40G GPU 上大约需要 15 小时。
推理代码大体上与训练代码相似。然而,一些区别包括在推理期间将 max_length 增加到 3072,并交换 response_a 和 response_b 进行 TTA(测试时增强)。最终结果是两者输出的平均值。
我们对两种情况应用了后处理(两者之间有一些重叠):
response_a 或 response_b 为空(例如 '[null]', '[]', '[ ]'),我们假设非空响应为获胜者。然而,考虑到对数损失对极端值的极端敏感性以及标签中的噪声,我们根据训练集的观察,将空、非空和平局的预测值固定为 [0.04, 0.88, 0.08]。response_a 和 response_b 相同,我们将其视为平局,并将预测值固定为 [0.06, 0.06, 0.88]。具体的后处理代码如下:
df2 = pd.read_csv('/kaggle/input/lmsys-chatbot-arena/test.csv')
df2['id'] = df2['id'].astype(str)
a_null_df = df2[(df2["response_a"]== '[null]') | (df2["response_a"]== '[]') | (df2["response_a"]== '[ ]') | (df2["response_a"]== '[ ]') | (df2["response_a"]== '[""]') | (df2["response_a"]== '["",""]')]
a_null_id_list = a_null_df["id"].tolist()
submission_df.loc[submission_df['id'].isin(a_null_id_list), ['winner_model_a', 'winner_model_b', 'winner_tie']] = [0.04, 0.88, 0.08]
b_null_df = df2[(df2["response_b"]== '[null]') | (df2["response_b"]== '[]') | (df2["response_b"]== '[ ]') | (df2["response_b"]== '[ ]') | (df2["response_b"]== '[""]') | (df2["response_b"]== '["",""]')]
b_null_id_list = b_null_df["id"].tolist()
submission_df.loc[submission_df['id'].isin(b_null_id_list), ['winner_model_a', 'winner_model_b', 'winner_tie']] = [0.88, 0.04, 0.08]
same_a_b_df2 = df2[(df2["response_a"]==df2["response_b"])]
same_a_b_id_list = same_a_b_df2["id"].tolist()
submission_df.loc[submission_df['id'].isin(same_a_b_id_list), ['winner_model_a', 'winner_model_b', 'winner_tie']] = [0.06, 0.06, 0.88]