Spaces:
Sleeping
Sleeping
| """Local CPU probe to test the hypothesis that the sparse module_summary | |
| is what's killing Layer 6's no_regression_gate. | |
| Runs ep0 (bloom_filter_lite) twice against the env: | |
| A) prompt with the current sparse summary (`class BloomFilter: ...`) | |
| B) prompt with the actual module source as the summary | |
| Reports reward + components for each. If A gives gate=0 and B gives gate=1 | |
| (or any non-zero reward), hypothesis is confirmed. | |
| Uses Qwen2.5-Coder-0.5B-Instruct (cached locally), greedy decoding, | |
| max_new_tokens=512. The 0.5B is strictly weaker than the 1.5B that's been | |
| failing on the Space, so a positive result here is a strong signal. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| import sys | |
| import time | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| SRC = ROOT / "src" | |
| sys.path.insert(0, str(SRC)) | |
| sys.path.insert(0, str(ROOT)) | |
| from mutant_hunter.models import Action # noqa: E402 | |
| from mutant_hunter.server.mutant_hunter_environment import MutantHunterEnvironment # noqa: E402 | |
| from training.prompts import SYSTEM_PROMPT, render_few_shot # noqa: E402 | |
| MODEL_ID = "Qwen/Qwen2.5-Coder-0.5B-Instruct" | |
| MAX_NEW_TOKENS = 512 | |
| def _strip_markdown_fences(text: str) -> str: | |
| fence_re = re.compile(r"```(?:python|py)?\s*\n(.*?)```", re.DOTALL) | |
| m = fence_re.search(text) | |
| if m: | |
| return m.group(1).strip("\n") | |
| lines = text.splitlines() | |
| for i, ln in enumerate(lines): | |
| if ( | |
| ln.startswith("import ") | |
| or ln.startswith("from ") | |
| or ln.startswith("def test_") | |
| or ln.startswith("class Test") | |
| ): | |
| return "\n".join(lines[i:]) | |
| return text | |
| def build_sparse_prompt(obs) -> str: | |
| parts = [ | |
| SYSTEM_PROMPT, "", | |
| "## Few-shot examples", render_few_shot(), "", | |
| "## Live task", | |
| f"Module: {obs.module_path}", | |
| f"Repo: {obs.repo_name}", | |
| f"Baseline mutation score: {obs.baseline_mutation_score:.3f}", | |
| "", | |
| "## Module summary", | |
| obs.module_summary or "(no summary)", | |
| "", | |
| "## Existing tests", | |
| ] | |
| parts.extend(f" - {n}" for n in (obs.existing_tests or [])) | |
| parts.append("") | |
| parts.append("## Output the pytest file content now:") | |
| return "\n".join(parts) | |
| def build_zeroshot_rich_prompt(obs, source_text: str) -> str: | |
| """Rich source, NO few-shots. Tests whether few-shots are poisoning output.""" | |
| parts = [ | |
| SYSTEM_PROMPT, "", | |
| "## Live task", | |
| f"Module: {obs.module_path}", | |
| f"Repo: {obs.repo_name}", | |
| f"Baseline mutation score: {obs.baseline_mutation_score:.3f}", | |
| "", | |
| "## Module source (full)", | |
| "```python", | |
| source_text.rstrip(), | |
| "```", | |
| "", | |
| "## Existing tests", | |
| ] | |
| parts.extend(f" - {n}" for n in (obs.existing_tests or [])) | |
| parts.append("") | |
| parts.append("## Output the pytest file content now:") | |
| return "\n".join(parts) | |
| def build_rich_prompt(obs, source_text: str) -> str: | |
| parts = [ | |
| SYSTEM_PROMPT, "", | |
| "## Few-shot examples", render_few_shot(), "", | |
| "## Live task", | |
| f"Module: {obs.module_path}", | |
| f"Repo: {obs.repo_name}", | |
| f"Baseline mutation score: {obs.baseline_mutation_score:.3f}", | |
| "", | |
| "## Module source (full)", | |
| "```python", | |
| source_text.rstrip(), | |
| "```", | |
| "", | |
| "## Existing tests", | |
| ] | |
| parts.extend(f" - {n}" for n in (obs.existing_tests or [])) | |
| parts.append("") | |
| parts.append("## Output the pytest file content now:") | |
| return "\n".join(parts) | |
| def generate(model, tokenizer, prompt: str, label: str) -> str: | |
| import torch | |
| messages = [{"role": "user", "content": prompt}] | |
| templated = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) | |
| inputs = tokenizer(templated, return_tensors="pt", truncation=True, max_length=4096) | |
| print(f" [{label}] prompt tokens: {inputs['input_ids'].shape[1]}", flush=True) | |
| t0 = time.time() | |
| with torch.no_grad(): | |
| out = model.generate( | |
| **inputs, | |
| max_new_tokens=MAX_NEW_TOKENS, | |
| do_sample=False, | |
| pad_token_id=tokenizer.pad_token_id, | |
| ) | |
| full = tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True) | |
| print(f" [{label}] gen={time.time()-t0:.1f}s, raw_chars={len(full)}", flush=True) | |
| return _strip_markdown_fences(full) | |
| def submit_and_score(env: MutantHunterEnvironment, seed: int, test_code: str, label: str) -> dict: | |
| env.reset(seed=seed) | |
| try: | |
| action = Action(kind="submit_tests", test_code=test_code) | |
| except Exception as exc: | |
| return {"label": label, "rejected": True, "err": str(exc), "reward": 0.0} | |
| obs2 = env.step(action) | |
| md = obs2.metadata or {} | |
| return { | |
| "label": label, | |
| "reward": float(obs2.reward or 0.0), | |
| "components": dict(md.get("components") or {}), | |
| "no_regression_gate": md.get("no_regression_gate"), | |
| "killed_by_new_only": md.get("killed_by_new_only"), | |
| "new_tests_pass_clean": md.get("new_tests_pass_clean"), | |
| "status": md.get("status"), | |
| } | |
| def main() -> int: | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| env = MutantHunterEnvironment() | |
| obs = env.reset(seed=0) | |
| print(f"=== ep0 / {obs.repo_name} / {obs.module_path} ===", flush=True) | |
| source_path = ROOT / "src" / "mutant_hunter" / "corpus" / "_local" / "bloom_filter_lite" / "bloom.py" | |
| source_text = source_path.read_text(encoding="utf-8") | |
| p_sparse = build_sparse_prompt(obs) | |
| p_rich = build_rich_prompt(obs, source_text) | |
| p_zero = build_zeroshot_rich_prompt(obs, source_text) | |
| print(f"sparse: {len(p_sparse)} chars, rich: {len(p_rich)} chars, zeroshot-rich: {len(p_zero)} chars", flush=True) | |
| print(f"loading {MODEL_ID} on cpu (this takes ~30-60s) ...", flush=True) | |
| t0 = time.time() | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) | |
| if tokenizer.pad_token is None: | |
| tokenizer.pad_token = tokenizer.eos_token | |
| model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32) | |
| model.eval() | |
| print(f"model loaded in {time.time()-t0:.1f}s", flush=True) | |
| print(f"\n--- generating SPARSE (current env summary + new few-shots) ---", flush=True) | |
| code_sparse = generate(model, tokenizer, p_sparse, "sparse") | |
| print(f"\n--- generating RICH (full source + new few-shots) ---", flush=True) | |
| code_rich = generate(model, tokenizer, p_rich, "rich") | |
| out_dir = ROOT / "evaluation" / "_results" | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| (out_dir / "probe_sparse.py").write_text(code_sparse, encoding="utf-8") | |
| (out_dir / "probe_rich.py").write_text(code_rich, encoding="utf-8") | |
| print(f"\nwrote generated tests to {out_dir}/probe_{{sparse,rich}}.py", flush=True) | |
| print(f"\n--- scoring SPARSE in env ---", flush=True) | |
| s_sparse = submit_and_score(env, 0, code_sparse, "sparse") | |
| print(f"\n--- scoring RICH in env ---", flush=True) | |
| s_rich = submit_and_score(env, 0, code_rich, "rich") | |
| s_zero = None | |
| env.close() | |
| def show(s: dict) -> None: | |
| print(f"\n[{s['label']}] reward={s.get('reward'):.4f}") | |
| print(f" components={s.get('components')}") | |
| print(f" no_regression_gate={s.get('no_regression_gate')}") | |
| print(f" killed_by_new_only={s.get('killed_by_new_only')}") | |
| print(f" new_tests_pass_clean={s.get('new_tests_pass_clean')}") | |
| print(f" status={s.get('status')}") | |
| show(s_sparse) | |
| show(s_rich) | |
| print("\n=== verdict ===") | |
| g_sparse = s_sparse.get("no_regression_gate", 0.0) or 0.0 | |
| g_rich = s_rich.get("no_regression_gate", 0.0) or 0.0 | |
| if g_sparse > 0.0 or g_rich > 0.0: | |
| print("FIX WORKS: at least one prompt cleared the gate on 0.5B with new prompts.py.") | |
| else: | |
| print("STILL BROKEN: 0.5B couldn't pass gate even with new prompts. May need env-side summary fix or larger model.") | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |