|
| 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