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

268 lines
11 KiB
Python

import collections
import io
import json
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from functools import partial
from typing import Dict, Any, List, Callable
import vllm
from omegaconf import ListConfig
from tqdm import tqdm
from general_util.logger import get_child_logger
from scripts.apps.utils_execute import check_correctness as apps_check_correctness
logger = get_child_logger(__name__)
eval_func: Callable = None
def _mp_init_(_eval_func: Callable):
global eval_func
eval_func = _eval_func
def _eval_worker(_input):
i, test_cases, response = _input
if response is None:
return i, [[False] * len(test_cases["inputs"]) if test_cases else 1], False
full_res = eval_func(test_cases, response)
# full_res = [bool(tmp) if (not isinstance(tmp, bool)) and (not isinstance(tmp, int)) else tmp for tmp in full_res]
new_res = []
for tmp in full_res:
try:
if (not isinstance(tmp, bool)) and (not isinstance(tmp, int)):
new_res.append(bool(tmp))
else:
new_res.append(tmp)
except Exception as e:
print(e)
new_res.append(False)
full_res = new_res
res = all(item is True for item in full_res) is True
return i, full_res, res
class APPsEvaluator:
def __init__(self, ):
pass
def __call__(self, predictions, num_workers: int = 16):
success = 0
success_at_k = 0
if "difficulty" in predictions[0]:
all_difficulties = list(set([item["difficulty"] for item in predictions]))
successes_at_difficulty = {difficulty: 0 for difficulty in all_difficulties}
successes_at_k_at_difficulty = {difficulty: 0 for difficulty in all_difficulties}
all_difficulties = {difficulty: 0 for difficulty in all_difficulties}
else:
successes_at_difficulty = None
successes_at_k_at_difficulty = None
all_difficulties = None
evaluator = partial(apps_check_correctness, timeout=10, debug=False)
# Multiprocessing
_mp_inputs = []
for i, item in enumerate(predictions):
if item["test_cases"]:
if isinstance(item["pred"], list):
preds = item["pred"]
else:
preds = [item["pred"]]
item["full_res"] = [[] for _ in range(len(preds))]
item["res"] = [False for _ in range(len(preds))]
for j, pred in enumerate(preds):
_mp_inputs.append(((i, j), item["test_cases"], pred))
pbar = tqdm(_mp_inputs, total=len(_mp_inputs), desc="Evaluating", dynamic_ncols=True)
if len(_mp_inputs) > 0:
# _cache_fw = open(self.output_file.replace(".json", ".cache.jsonl"), "w")
outputs = collections.defaultdict(dict)
with ThreadPoolExecutor(max_workers=num_workers, initializer=_mp_init_, initargs=(evaluator,)) as executor:
futures = []
for _input in pbar:
future = executor.submit(_eval_worker, _input)
futures.append(future)
pbar.update()
for future in tqdm(as_completed(futures), total=len(futures), desc="Collecting results"):
idx, full_res, res = future.result()
outputs[idx[0]][idx[1]] = {
"res": res,
"full_res": full_res
}
for i, item in enumerate(predictions):
if item["test_cases"]:
if isinstance(item["pred"], list):
preds = item["pred"]
else:
preds = [item["pred"]]
item["full_res"] = []
item["res"] = []
for j, pred in enumerate(preds):
item["full_res"].append(outputs[i][j]["full_res"])
item["res"].append(outputs[i][j]["res"])
if all_difficulties is not None:
all_difficulties[item["difficulty"]] += 1
if any(item["res"]):
success_at_k += 1
if all_difficulties is not None:
successes_at_k_at_difficulty[item["difficulty"]] += 1
if item["res"][0]:
success += 1
if all_difficulties is not None:
successes_at_difficulty[item["difficulty"]] += 1
if len(item["res"]) == 1:
item["res"] = item["res"][0]
item["full_res"] = item["full_res"][0]
else:
item["res"] = []
item["full_res"] = []
# _cache_fw.write(json.dumps(item, ensure_ascii=False) + "\n")
# _cache_fw.close()
if len(predictions) == 0:
metrics = {"acc": 0, "pass@k": 0, "correct": 0, "total": 0}
else:
metrics = {"acc": success / len(predictions), "pass@k": success_at_k / len(predictions), "correct": success,
"total": len(predictions)}
if all_difficulties is not None:
for difficulty in all_difficulties:
if all_difficulties[difficulty] > 0:
metrics[f"acc_{difficulty}"] = successes_at_difficulty[difficulty] / all_difficulties[difficulty]
metrics[f"pass@k_{difficulty}"] = successes_at_k_at_difficulty[difficulty] / all_difficulties[difficulty]
else:
metrics[f"acc_{difficulty}"] = 0.
metrics[f"pass@k_{difficulty}"] = 0.
metrics[f"correct_{difficulty}"] = successes_at_difficulty[difficulty]
metrics[f"total_{difficulty}"] = all_difficulties[difficulty]
return predictions, metrics
class CodeExtractor:
def __init__(self, output_file: str, answer_clean: Callable, resume: bool = False,
index_field: str = "index", test_case_field: str = "input_output", evaluator: Callable = None, num_workers: int = 8,
saved_keys: List[str] = None, completion_separator: str = None):
self.predictions = []
self.output_file = output_file
self.answer_clean = answer_clean
self.index_field = index_field
self.test_case_field = test_case_field
self.evaluator = evaluator
self.num_workers = num_workers
self.saved_keys = saved_keys
if isinstance(self.saved_keys, ListConfig):
self.saved_keys = list(self.saved_keys)
self.completion_separator = completion_separator
logging_file = output_file.replace(".json", ".jsonl")
save_dir = os.path.dirname(logging_file)
if not os.path.exists(save_dir):
os.makedirs(save_dir, exist_ok=True)
if os.path.exists(logging_file):
if resume:
with open(logging_file, "r", encoding="utf-8") as f:
for line in f.readlines():
item = json.loads(line)
if isinstance(item["response"], str):
if item["response"].strip() == "":
continue
elif isinstance(item["response"], list):
if any([tmp.strip() == "" for tmp in item["response"]]):
continue
self.predictions.append(item)
logger.info(f"Load {len(self.predictions)} from {logging_file}")
self.logging_file = logging_file
def __call__(self, meta_data: Dict[str, Any], batch_model_outputs: Dict[str, Any], fw: io = None, **kwargs):
text = meta_data["text"]
if self.test_case_field in meta_data:
test_cases = meta_data[self.test_case_field]
else:
test_cases = None
index = meta_data[self.index_field]
response = batch_model_outputs["response"]
if isinstance(response, vllm.RequestOutput):
if response.finished:
response = [o.text for o in response.outputs]
if len(response) == 1:
response = response[0]
else:
response = ""
if isinstance(response, str):
if self.completion_separator:
pred_clean = self.answer_clean((text + response).split(self.completion_separator)[1])
else:
pred_clean = self.answer_clean(response)
elif isinstance(response, list):
if self.completion_separator:
pred_clean = [self.answer_clean((text + item).split(self.completion_separator)[1]) for item in response]
else:
pred_clean = [self.answer_clean(item) for item in response]
else:
raise ValueError(f"Unknown type of response: {type(response)}")
out_item = {
"text": text,
"test_cases": test_cases,
"response": response,
"pred": pred_clean,
"id": index,
}
if self.saved_keys is not None:
for key in self.saved_keys:
if key in meta_data:
out_item[key] = meta_data[key]
self.predictions.append(out_item)
if fw is not None:
fw.write(json.dumps(self.predictions[-1]) + "\n")
else:
with open(self.logging_file, "a") as f:
f.write(json.dumps(self.predictions[-1]) + "\n")
def batch_call(self, meta_data: List[Dict[str, Any]], batch_model_outputs: List[Dict[str, Any]], **kwargs):
with open(self.logging_file, "a") as f:
for m, b in zip(meta_data, batch_model_outputs):
self(m, b, fw=f, **kwargs)
def eval_single_response(self, response: str, test_cases):
if response is None:
return [[False] * len(test_cases["inputs"]) if test_cases else 1], False
full_res = self.evaluator(test_cases, response)
res = all(item is True for item in full_res) is True
return full_res, res
def get_results(self):
save_dir = os.path.dirname(self.output_file)
if not os.path.exists(save_dir):
os.makedirs(save_dir, exist_ok=True)
# self.fw.close()
# Remove duplicated ids to satisfy the submission requirements of ReClor.
outputs = sorted(self.predictions, key=lambda x: x["id"])
id_set = set()
new_outputs = []
for item in outputs:
if item["id"] not in id_set:
new_outputs.append(item)
id_set.add(item["id"])
self.predictions = new_outputs
self.predictions, metrics = self.evaluator(self.predictions, self.num_workers)
json.dump(self.predictions, open(self.output_file, "w", encoding="utf-8"), ensure_ascii=False)
json.dump(metrics, open(self.output_file.replace(".json", ".metrics.json"), "w"), indent=2)
return metrics, []