#!/usr/bin/env python3 """Capability check: compare abliterated model against stock on MMLU via lm-eval-harness. Quick verification that abliteration surgery didn't lobotomize the model. Uses lm-evaluation-harness for proper log-probability scoring — the same methodology used by OrcaRouter, Coletti, and other abliteration releases. Requires: pip install lm-eval Usage: obliteratus capability-check \\ --abliterated outputs/my-abliterated-model \\ --stock Qwen/Qwen3.8-27B \\ --device mps # Quick mode (5 subjects, ~1 min per model): obliteratus capability-check --abliterated ... --stock ... --quick # Custom subjects: obliteratus capability-check --abliterated ... --stock ... \\ --subjects mmlu_abstract_algebra,mmlu_computer_security Lessons learned: - DO NOT use custom generate-and-extract for MMLU. Log-probability scoring (what lm-eval does) gives results comparable to published numbers. Custom generation + letter extraction underperforms by 20+ pp. - DO NOT use repetition_penalty for benchmarking. It interferes with reasoning chains and degrades scores. Only use it for long-form generation. - Stock Qwen3.8-27B scores ~87% on MMLU via lm-eval (0-shot). If your stock score is much lower, your test setup is broken. """ from __future__ import annotations import json import logging import subprocess import sys import tempfile from pathlib import Path logger = logging.getLogger(__name__) QUICK_SUBJECTS = [ "mmlu_abstract_algebra", "mmlu_computer_security", "mmlu_us_foreign_policy", "mmlu_high_school_biology", "mmlu_professional_medicine", ] DEFAULT_LIMIT = 5 # per subject; 57 subjects × 5 = 285 questions (comparable to OrcaRouter n=300) def _run_lm_eval(model_path: str, tasks: str, limit: int, device: str, output_dir: str, dtype: str = "bfloat16") -> dict: """Run lm-eval-harness and return parsed results.""" cmd = [ sys.executable, "-m", "lm_eval", "--model", "hf", "--model_args", f"pretrained={model_path},dtype={dtype},trust_remote_code=True", "--tasks", tasks, "--num_fewshot", "0", "--limit", str(limit), "--batch_size", "1", "--device", device, "--output_path", output_dir, ] logger.info("Running: %s", " ".join(cmd[-8:])) result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600) if result.returncode != 0: logger.error("lm-eval failed:\n%s", result.stderr[-1000:]) raise RuntimeError(f"lm-eval exited with code {result.returncode}") # Parse results from output directory results_files = list(Path(output_dir).rglob("results*.json")) if not results_files: raise FileNotFoundError(f"No results files in {output_dir}") with open(results_files[0]) as f: return json.load(f) def capability_check( abliterated_path: str, stock_path: str, device: str = "auto", dtype: str = "bfloat16", quick: bool = False, subjects: list[str] | None = None, limit: int = DEFAULT_LIMIT, output_dir: str | None = None, ) -> dict: """Compare abliterated vs stock model on MMLU. Args: abliterated_path: Path or HF repo for abliterated model. stock_path: Path or HF repo for stock model. device: Device (auto, cuda, mps, cpu). dtype: Model dtype. quick: Use 5 subjects instead of full MMLU. subjects: Custom subject list (overrides quick). limit: Questions per subject. output_dir: Where to save results. Returns: dict with abliterated_acc, stock_acc, delta_pp. """ if subjects: tasks = ",".join(subjects) elif quick: tasks = ",".join(QUICK_SUBJECTS) else: tasks = "mmlu" if output_dir is None: output_dir = tempfile.mkdtemp(prefix="obliteratus_capcheck_") out = Path(output_dir) # Run abliterated logger.info("=== ABLITERATED ===") abl_results = _run_lm_eval( abliterated_path, tasks, limit, device, str(out / "abliterated"), dtype ) # Run stock logger.info("=== STOCK ===") stock_results = _run_lm_eval( stock_path, tasks, limit, device, str(out / "stock"), dtype ) # Extract aggregate MMLU accuracy abl_acc = None stock_acc = None for key in ["mmlu", tasks.split(",")[0]]: if key in abl_results.get("results", {}): abl_acc = abl_results["results"][key].get("acc,none") break for key in ["mmlu", tasks.split(",")[0]]: if key in stock_results.get("results", {}): stock_acc = stock_results["results"][key].get("acc,none") break # If running individual subjects, compute mean if abl_acc is None: accs = [v["acc,none"] for k, v in abl_results["results"].items() if "acc,none" in v and not k.startswith("mmlu -")] abl_acc = sum(accs) / len(accs) if accs else 0 if stock_acc is None: accs = [v["acc,none"] for k, v in stock_results["results"].items() if "acc,none" in v and not k.startswith("mmlu -")] stock_acc = sum(accs) / len(accs) if accs else 0 delta = (abl_acc - stock_acc) * 100 summary = { "abliterated_acc": round(abl_acc, 4), "stock_acc": round(stock_acc, 4), "delta_pp": round(delta, 1), "tasks": tasks, "limit": limit, "method": "lm-eval-harness 0-shot log-likelihood", } # Save summary with open(out / "capability_summary.json", "w") as f: json.dump(summary, f, indent=2) return summary def main(): import argparse logging.basicConfig(level=logging.INFO, format="%(message)s") p = argparse.ArgumentParser( description="Compare abliterated vs stock model on MMLU via lm-eval-harness." ) p.add_argument("--abliterated", required=True, help="Abliterated model path or HF repo") p.add_argument("--stock", required=True, help="Stock model path or HF repo") p.add_argument("--device", default="auto") p.add_argument("--dtype", default="bfloat16") p.add_argument("--quick", action="store_true", help="5 subjects only (~1 min per model)") p.add_argument("--subjects", type=str, default=None, help="Comma-separated subject list") p.add_argument("--limit", type=int, default=DEFAULT_LIMIT, help="Questions per subject") p.add_argument("--output-dir", type=str, default=None) args = p.parse_args() subjects = args.subjects.split(",") if args.subjects else None result = capability_check( args.abliterated, args.stock, device=args.device, dtype=args.dtype, quick=args.quick, subjects=subjects, limit=args.limit, output_dir=args.output_dir, ) print(f"\n{'='*50}") print(f"CAPABILITY CHECK ({result['tasks']})") print(f"{'='*50}") print(f"Stock: {result['stock_acc']*100:.1f}%") print(f"Abliterated: {result['abliterated_acc']*100:.1f}%") print(f"Delta: {result['delta_pp']:+.1f}pp") print(f"Method: {result['method']}") if __name__ == "__main__": main()