644. WSDM Cup - Multilingual Chatbot Arena | wsdm-cup-multilingual-chatbot-arena
🙇🙇
感谢社区朋友的真诚分享,特别感谢 @sayoulala 在上一届 LMSYS 竞赛中分享的 pipeline。向每一位充满热情的参赛者致敬,祝贺所有获得荣誉的赢家!我还要特别感谢香港科技大学(广州)数据科学与分析学域 KIMI 实验室提供的 40 张 A100 80GB GPU 支持! @HKUST-gz
我的解决方案可能相对简单,尤其是与其他杰出的参赛者相比。然而,这是我第一次获得个人金牌。与获得荣誉的喜悦相比,竞赛期间的挫折和挑战占了 90%。希望大家不吝赐教,谢谢!
(1) 使用 AutoModelForCasualLM + vllm 推理 替换 AutoModelForSequenceClassification + transformers 推理。
(2) 比较 Token A 和 Token B 的 logits 来决定输出。
(3) 使用思维链 (CoT) 作为初始提示。
(1) 数据集区别:ultrafeedback + C4AI-Community/multilingual-reward-bench (用于后预训练),lmsys (排除标记为 tie 的数据) + wsdm (用于微调)。
(2) 损失计算区别:预训练期间,使用 A 或 B 相对于整个词汇表的交叉熵损失;微调期间,使用 A 和 B 之间的交叉熵损失。
(3) 数据增强区别:预训练中,在同一批次内使用 responseA+B 和 responseB+A;微调中,仅使用 responseA+B。
(4) 输入长度区别:预训练使用 1024 tokens,微调使用 2048 tokens。
(1) 使用相同流程分别在微调数据集上训练 Athene-v2-chat + nvidia/Llama-3.1-Nemotron-70B-Instruct-HF + Qwen2.5-72B-Instruct 以生成软标签。
(2) 微调期间训练超过一个 epoch,具体为两个 epoch。交叉验证结果显示,在第二个 epoch 结束时会有显著提升(平均提升约 0.002)。
(1) 使用 vllm 获取特定 token logits,配合 allowed_token_ids=[a_tok_id,b_tok_id] 和 logprobs=N。
(2) 将 awq 替换为 gptq。
def create_rounds(query, answer_a, answer_b, tokenizer):
messages = [
{
"role": "system",
"content": '''You are a judge tasked with evaluating responses from two
language models. Select the response that best meets the user's needs based on their query.
**Input:**
<Query>User's original query.</Query>
<Response_A>First model's response.</Response_A>
<Response_B>Second model's response.</Response_B>
**Output:**Return only one letter:
- A for Response_A
- B for Response_B
**Guidelines:**
- Respond with only A or B.
- Do not provide explanations.'''
},
{
"role": "user",
"content": f'''Here is your input to process now-
<Query>{query}</Query>
{'---'*10}
<Response_A>{answer_a}</Response_A>
{'---'*10}
<Response_B>{answer_b}</Response_B>'''
}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
return text+' Choice: '
def get_tokenizer(path):
tokenizer = AutoTokenizer.from_pretrained(
path,
add_eos_token=False,)
tokenizer.padding_side = "left" # 使用左侧填充
return tokenizer
class WSDMRanker(nn.Module):
def __init__(self, base_model, tokenizer):
super().__init__()
self.model = base_model ## AutoModelForCasualLM
self.token_ids = []
for letter in ['A','B']:
token_id = tokenizer(letter, add_special_tokens=False)["input_ids"][-1]
self.tok_locations.append(token_id)
def encode(self, input_ids, attention_mask):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask, output_hidden_states=True)
scores = []
for token_id in self.tok_locations:
score = outputs.logits[:, -1, token_id]
scores.append(score)
logits = torch.stack(scores, 1)
return logits.contiguous()
def forward(self, input_ids, attention_mask, labels=None, **kwargs):
logits = self.encode(input_ids, attention_mask)
ce_loss = (self.loss_fn(logits, labels)).mean() # label = 0 for A ; 1 for B
class WSDMRanker(nn.Module):
def __init__(self, base_model, tokenizer):
super().__init__()
self.model = base_model ## AutoModelForCasualLM
self.token_ids = []
for letter in ['A','B']:
token_id = tokenizer(letter, add_special_tokens=False)["input_ids"][-1]
self.tok_locations.append(token_id)
def forward(self, input_ids, attention_mask, labels=None, **kwargs):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask, output_hidden_states=True)
logits = outputs.logits[:, -1, :]
ce_loss = (self.loss_fn(logits, labels)).mean() # label = A_token_id for A ; B_token_id for B
class qWenSFTDataset(Dataset):
def __init__(self, dataset, tokenizer, max_prompt_len, max_completion_len) -> None:
super().__init__()
......
self.tokenizer = tokenizer
self.a = tokenizer.encode('A')[0]
self.b = tokenizer.encode('B')[0]
def _process_single_entry(self, data_entry):
_, data = data_entry
text = data['text']# Question + res_A + res_B
text2 = data['text2']# Question + res_B + res_A
features = self.tokenizer(text,padding=False,add_special_tokens=False,return_length=True)
features2 = self.tokenizer(text2,padding=False,add_special_tokens=False,return_length=True)
labels = self.a if data['label']==0 else self.b
labels2 = self.a if data['label2']==0 else self.b
return features['input_ids'],features['attention_mask'],features['length'], labels,features2['input_ids'],features2['attention_mask'],labels2
......
在后预训练期间,使用整个词汇表计算 A 和 B 的多类交叉熵,而在微调期间,使用二元交叉熵。这种方法可以提高模型性能的上限,但需要更多的训练步骤。
| fold | epoch 1 最佳 CV | epoch 2 最佳 CV |
|---|---|---|
| 0 | 0.7184 | 0.7209 |
| 1 | 0.7108 | 0.7153 |
| 2 | 0.7134 | 0.7145 |
| 3 | 0.7044 | 0.7091 |
| 4 | 0.7085 | 0.7115 |

(如果仅使用 logprobs=N 来选择前 N 个 tokens,Token A 和 Token B 都可能不在前 N 个 tokens 之中)
a_tok_id = tokenizer("A", add_special_tokens=False)["input_ids"][-1]
b_tok_id = tokenizer("B", add_special_tokens=False)["input_ids"][-1]
llm = vllm.LLM(
cfg.model_dir,
quantization="gptq",#"awq",
tensor_parallel_size=2,
......)
sampling_params = vllm.SamplingParams(n=1, top_k=1, logprobs=10, max_tokens=1, temperature=0.0, skip_special_tokens=False, allowed_token_ids=[a_tok_id,b_tok_id])
responses = llm.generate(test['prompt_list'], sampling_params, use_tqdm=True)
float16 CV:0.714
使用 GPTQ 8-bit 量化:CV: 0.713, LB: 0.703, PB: 0.714
使用 AWQ 4-bit 量化:CV: 0.710, LB: 0.708, PB: 0.709
推理代码:点击此处