Files
2026-07-13 13:24:13 +08:00

279 lines
9.6 KiB
Python

import os
import json
import time
from arch.model import create_kv_cache
from utils.math_utils import *
import random
random.seed(42)
import torch.distributed as dist
class MathArgs:
prompt_type: str = 'direct'
adapt_few_shot: bool = False
num_shots: int = 0
generate_length: int = 1024
save_freq: int = 128
def get_rank():
return int(os.environ['RANK'])
def first_print(*args):
if get_rank() == 0:
print(*args)
def load_data(data_name, split, data_dir):
data_file = f"{data_dir}/{data_name}/{split}.jsonl"
assert os.path.exists(data_file), f"File not found: {data_file}"
examples = list(load_jsonl(data_file))
# add 'idx' in the first column
if "idx" not in examples[0]:
examples = [{"idx": i, **example} for i, example in enumerate(examples)]
# dedepulicate & sort
examples = sorted(examples, key=lambda x: x["idx"])
return examples
def prepare_data(data_name, args, limit):
examples = load_data(data_name, 'test', '../math_data')
examples = examples[:limit]
if len(examples) < limit:
repeat_times = (limit + len(examples) - 1) // len(examples)
print(f"Warning: {data_name} has only {len(examples)} examples, repeat {repeat_times} times")
examples = examples * repeat_times
out_folder = os.path.join(args.output_folder, data_name)
out_file = os.path.join(args.output_folder, data_name, os.path.basename(args.checkpoint_dir) + "_" + args.save_feature + ".jsonl")
os.makedirs(f"{out_folder}", exist_ok=True)
return examples, out_file
def model_generation(model, prompts, max_length, math_args):
outputs = []
for i in range(0, len(prompts), model._batch_size):
prompt = prompts[i:i + model._batch_size]
net_input = model.tok_batch_encode(prompt)[0].cuda()
generate_length = math_args.generate_length
if net_input.size(1) + generate_length > max_length:
print("Warning: input too long, reduce generation length to", max_length - net_input.size(1))
generate_length = max_length - net_input.size(1)
if generate_length <= 0:
print("Warning: input too long, skip sample")
continue
kv_cache = create_kv_cache(model.model.args, model._batch_size)
output = model._model_generate(net_input, net_input.size(1) + generate_length, kv_cache=kv_cache)
prediction = [model.tok_decode(o) for o in output.cpu().tolist()]
prediction = [pred[len(prpt):] for prpt, pred in zip(prompt, prediction)]
outputs.extend(prediction)
return outputs
def evaluate(args, model, limit):
math_args = MathArgs()
if 'DeepSeek-R1-Distill' in args.checkpoint_dir:
math_args.prompt_type = 'r1'
math_args.generate_length = 16384
math_args.save_freq = 128
else:
raise ValueError("Please use math fine-tuned model for evaluation.")
# 1024 for prompt
max_length = 1024 + math_args.generate_length
# here we set max_seq_len to max_length, to create less kv cache for an original 64k model
model._model.args.max_seq_len = 32768
# model._model.args.max_seq_len = max_length
data_names = "minerva_math,gaokao2023en,olympiadbench,aime24,amc23"
# data_names = "gsm8k,math,svamp,asdiv,mawps,carp_en,tabmwp,minerva_math,gaokao2023en,olympiadbench,college_math,aime24,amc23"
data_list = data_names.split(",")
for data_name in data_list:
first_print("Start eval on", data_name)
examples, out_file = prepare_data(data_name, args, limit)
first_print("Total samples:", len(examples))
finished_examples = []
finished_examples_num = 0
if os.path.exists(out_file):
finished_examples = list(load_jsonl(out_file))
finished_examples_num = len(finished_examples)
examples = examples[finished_examples_num:]
first_print("Left samples:", len(examples))
new_samples = []
for i in range(0, len(examples), math_args.save_freq):
first_print("Current samples:", i)
out_example = eval_math_save_part(model, data_name,
examples[i:i+math_args.save_freq], math_args,
max_length)
new_samples.extend(out_example)
if get_rank() == 0:
save_jsonl(finished_examples + new_samples, out_file)
first_print("saved to", out_file)
def eval_math_save_part(model, data_name, examples, math_args, max_length):
idx_span = (len(examples) + dist.get_world_size() - 1) // dist.get_world_size()
idx_start, idx_end = idx_span * get_rank(), idx_span * (get_rank() + 1)
examples = examples[idx_start:idx_end]
executor = PythonExecutor(get_answer_from_stdout=True)
samples = []
for example in examples:
idx = example["idx"]
# parse question and answer
example["question"] = parse_question(example, data_name)
if example["question"] == "":
continue
gt_cot, gt_ans = parse_ground_truth(example, data_name)
example["gt_ans"] = gt_ans
full_prompt = construct_prompt(example, data_name, math_args)
sample = {
"idx": idx,
"question": example["question"],
"gt_cot": gt_cot,
"gt": gt_ans,
"prompt": full_prompt,
}
# add remain fields
for key in [
"level",
"type",
"unit",
"solution_type",
"choices",
"solution",
"ques_type",
"ans_type",
"answer_type",
"dataset",
"subfield",
"filed",
"theorem",
"answer",
]:
if key in example:
sample[key] = example[key]
samples.append(sample)
# repeat n times
input_prompts = [sample["prompt"] for sample in samples]
remain_prompts = input_prompts
remain_prompts = [(i, prompt) for i, prompt in enumerate(remain_prompts)]
end_prompts = []
max_func_call = 1 if math_args.prompt_type in ["cot", "pal"] else 4
# start inference
# measure time use
start_time = time.time()
for epoch in range(max_func_call):
# self.first_print("-" * 20, "Epoch", epoch)
current_prompts = remain_prompts
if len(current_prompts) == 0:
break
# get all outputs
prompts = [item[1] for item in current_prompts]
outputs = model_generation(model, prompts, max_length, math_args)
# mean_generate_length /= len(prompts)
# first_print("Mean generate length:", mean_generate_length)
assert len(outputs) == len(current_prompts)
# process all outputs
remain_prompts = []
remain_codes = []
for (i, query), output in zip(current_prompts, outputs):
output = output.rstrip()
query += output
if "boxed" not in output and output.endswith("```"):
program = extract_program(query)
remain_prompts.append((i, query))
remain_codes.append(program)
else:
end_prompts.append((i, query))
# execute the remain prompts
remain_results = executor.batch_apply(remain_codes)
for k in range(len(remain_prompts)):
i, query = remain_prompts[k]
res, report = remain_results[k]
exec_result = res if res else report
exec_result = f"\n```output\n{exec_result}\n```\n"
query += exec_result
# not end
if epoch == max_func_call - 1:
query += "\nReach max function call limit."
remain_prompts[k] = (i, query)
# unsolved samples
# first_print("Unsolved samples:", len(remain_prompts))
end_prompts.extend(remain_prompts)
# sort by idx
end_prompts = sorted(end_prompts, key=lambda x: x[0])
# remove input_prompt from end_prompt
codes = []
assert len(input_prompts) == len(end_prompts)
for i in range(len(input_prompts)):
_, end_prompt = end_prompts[i]
code = end_prompt.split(input_prompts[i])[-1].strip()
for stop_word in ["</s>", "<|im_end|>", "<|endoftext|>"]:
if stop_word in code:
code = code.split(stop_word)[0].strip()
codes.append(code)
# extract preds
results = [
run_execute(executor, code, math_args.prompt_type, data_name) for code in codes
]
time_use = time.time() - start_time
# put results back to examples
all_samples = []
for i, sample in enumerate(samples):
code = codes[i : (i + 1)]
result = results[i : (i + 1) ]
preds = [item[0] for item in result]
reports = [item[1] for item in result]
for j in range(len(preds)):
if sample["gt"] in ["A", "B", "C", "D", "E"] and preds[j] not in [
"A",
"B",
"C",
"D",
"E",
]:
preds[j] = choice_answer_clean(code[j])
elif is_multi_choice(sample["gt"]) and not is_multi_choice(preds[j]):
# remove any non-choice char
preds[j] = "".join(
[c for c in preds[j] if c in ["A", "B", "C", "D", "E"]]
)
sample.pop("prompt")
sample.update({"code": code, "pred": preds, "report": reports})
all_samples.append(sample)
all_samples_gather = [None for _ in range(dist.get_world_size())]
dist.all_gather_object(all_samples_gather, all_samples)
all_samples_gather = sum(all_samples_gather, [])
return all_samples_gather