Skip to content

Commit e1f10ff

Browse files
authored
Merge pull request #548 from tugot17/add-mbpp-benchmark
Add MBPP benchmark
2 parents 4dea914 + b165766 commit e1f10ff

2 files changed

Lines changed: 136 additions & 0 deletions

File tree

benchmarks/benchmarker/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .humaneval import HumanEvalBenchmarker
77
from .livecodebench import LCBBenchmarker
88
from .math500 import Math500Benchmarker
9+
from .mbpp import MBPPBenchmarker
910
from .mmlu import MMLUBenchmarker
1011
from .mmstar import MMStarBenchmarker
1112
from .mtbench import MTBenchBenchmarker
@@ -25,5 +26,6 @@
2526
"FinanceQABenchmarker",
2627
"MMLUBenchmarker",
2728
"LCBBenchmarker",
29+
"MBPPBenchmarker",
2830
"SimpleQABenchmarker",
2931
]

benchmarks/benchmarker/mbpp.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""
2+
MBPP benchmark evaluation script.
3+
"""
4+
5+
import re
6+
from typing import Any, Dict, List, Optional, Tuple
7+
8+
from datasets import load_dataset
9+
10+
from .base import Benchmarker
11+
from .registry import BENCHMARKS
12+
from .utils import create_simple_sgl_function
13+
14+
15+
def extract_code_from_output(output: str) -> Optional[str]:
16+
"""Extract Python code from model output (markdown block or `def ...:`)."""
17+
code_block_pattern = r"```(?:python)?\s*(.*?)\s*```"
18+
match = re.search(code_block_pattern, output, re.DOTALL)
19+
if match:
20+
return match.group(1).strip()
21+
def_pattern = r"(def\s+\w+\([^)]*\):.*?)(?=\n\ndef\s+|\Z)"
22+
match = re.search(def_pattern, output, re.DOTALL)
23+
if match:
24+
return match.group(1).strip()
25+
return output.strip() if output.strip() else None
26+
27+
28+
def check_code_passes_tests(code: str, test_code: str) -> bool:
29+
"""Run `code` then `test_code` (which contains assertions) in a fresh namespace.
30+
31+
Returns True iff no exception is raised. Simplified vs. the official MBPP
32+
evaluation framework — we just want a pass/fail signal.
33+
"""
34+
try:
35+
namespace: Dict[str, Any] = {}
36+
exec(code, namespace)
37+
exec(test_code, namespace)
38+
return True
39+
except Exception:
40+
return False
41+
42+
43+
def build_mbpp_prompt(text: str, test_list: List[str]) -> str:
44+
"""Standard MBPP prompt format used in the original paper."""
45+
tests = "\n".join(test_list)
46+
return (
47+
"You are an expert Python programmer, and here is your task: "
48+
f"{text} Your code should pass these tests:\n\n{tests}\n\n[BEGIN]\n"
49+
)
50+
51+
52+
@BENCHMARKS.register("mbpp")
53+
class MBPPBenchmarker(Benchmarker):
54+
"""MBPP benchmark implementation (sanitized split)."""
55+
56+
def __init__(self, num_samples: Optional[int] = None):
57+
super().__init__(num_samples, None)
58+
59+
def load_data(self) -> Tuple[List[Dict[str, Any]], List[Optional[Dict[str, Any]]]]:
60+
# Sanitized split is the standard one quoted in DFlash and most
61+
# other speculative-decoding benchmarks.
62+
dataset = load_dataset("google-research-datasets/mbpp", "sanitized")["test"]
63+
questions: List[Dict[str, Any]] = []
64+
labels: List[Optional[Dict[str, Any]]] = []
65+
66+
for idx, q in enumerate(dataset):
67+
if self.num_samples is not None and idx >= self.num_samples:
68+
break
69+
70+
# Sanitized split uses `prompt`; full split uses `text`.
71+
text = q.get("prompt") or q.get("text") or ""
72+
test_list = q.get("test_list", []) or []
73+
# Sanitized split exposes `test_imports` (List[str]); full split
74+
# exposes `test_setup_code` (single str). Combine both into one
75+
# block so accuracy checks can run imports the tests rely on.
76+
test_imports = q.get("test_imports", []) or []
77+
test_setup_code = q.get("test_setup_code", "") or ""
78+
test_setup = "\n".join([*test_imports, test_setup_code]).strip()
79+
80+
prompt = build_mbpp_prompt(text, test_list)
81+
questions.append({"question": prompt})
82+
labels.append(
83+
{
84+
"test_list": test_list,
85+
"test_setup": test_setup,
86+
"canonical_solution": q.get("code", ""),
87+
}
88+
)
89+
90+
return questions, labels
91+
92+
def extract_answer(self, output: str, label: Optional[Any] = None) -> Optional[str]:
93+
# MBPP responses sometimes wrap in [DONE]; strip that and any leading [BEGIN].
94+
if output is None:
95+
return None
96+
cleaned = output.strip()
97+
cleaned = cleaned.split("[DONE]")[0].strip()
98+
if cleaned.startswith("[BEGIN]"):
99+
cleaned = cleaned[len("[BEGIN]") :].strip()
100+
return extract_code_from_output(cleaned)
101+
102+
def compute_accuracy(
103+
self, predictions: List[Any], labels: List[Any]
104+
) -> Optional[float]:
105+
if not labels:
106+
return None
107+
if all(label is None for label in labels):
108+
return None
109+
110+
correct = 0
111+
valid = 0
112+
for pred, label in zip(predictions, labels):
113+
if label is None or not isinstance(label, dict):
114+
continue
115+
valid += 1
116+
if pred is None:
117+
continue
118+
test_setup = label.get("test_setup", "") or ""
119+
test_list = label.get("test_list", []) or []
120+
test_code = test_setup + "\n" + "\n".join(test_list)
121+
if check_code_passes_tests(str(pred), test_code):
122+
correct += 1
123+
return correct / valid if valid > 0 else 0.0
124+
125+
def create_sgl_function(self):
126+
return create_simple_sgl_function(
127+
function_name="get_mbpp_answer",
128+
answer_key="answer",
129+
max_tokens=self.get_max_new_tokens(),
130+
stop=["[DONE]"],
131+
)
132+
133+
def get_max_new_tokens(self) -> int:
134+
return 1024

0 commit comments

Comments
 (0)