Spaces:
Running on Zero
Running on Zero
| #!/usr/bin/env python3 | |
| """ | |
| Frox AI Morph 1.1 — Evaluation Harness | |
| Runs three checks any time you finish a training phase: | |
| 1. Perplexity on WikiText-2 (language modeling quality) | |
| 2. Sanity generations on a fixed prompt set (coherence spot-check) | |
| 3. Throughput benchmark (tokens/sec at a few sequence lengths) | |
| Usage: | |
| python scripts/evaluate.py --model ./frox-morph-1-1-output/sft_final | |
| python scripts/evaluate.py --model ./frox-morph-1-1-output/sft_final --skip-generation | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) | |
| import torch | |
| from inference.engine.morph_engine import MorphInferenceEngine | |
| from training.pipeline.trainer import evaluate_perplexity | |
| from utils.common import print_banner, timer | |
| SANITY_PROMPTS = [ | |
| "What is the capital of France?", | |
| "Write a haiku about the ocean.", | |
| "Explain what a neural network is in two sentences.", | |
| "def fibonacci(n):\n # Complete this function", | |
| "What's 17 times 23?", | |
| "Give me three tips for staying focused while studying.", | |
| ] | |
| def run_perplexity(engine: MorphInferenceEngine) -> float: | |
| print("\n📊 Perplexity (WikiText-2)") | |
| ppl = evaluate_perplexity( | |
| engine.model.language_model, engine.tokenizer, engine.device, | |
| max_samples=500, seq_len=512, | |
| use_amp=engine.device.type == "cuda", amp_dtype=engine.dtype, | |
| ) | |
| print(f" Perplexity: {ppl}") | |
| return ppl | |
| def run_sanity_generations(engine: MorphInferenceEngine) -> list: | |
| print("\n🧪 Sanity Generations") | |
| results = [] | |
| for prompt in SANITY_PROMPTS: | |
| response = engine.generate( | |
| [{"role": "user", "content": prompt}], | |
| max_new_tokens=150, temperature=0.7, | |
| ) | |
| has_content = len(response.strip()) > 5 | |
| has_repetition = _check_repetition(response) | |
| status = "✓" if has_content and not has_repetition else "⚠" | |
| print(f"\n {status} Q: {prompt}") | |
| print(f" A: {response[:200]}{'...' if len(response) > 200 else ''}") | |
| results.append({ | |
| "prompt": prompt, "response": response, | |
| "has_content": has_content, "has_repetition": has_repetition, | |
| }) | |
| return results | |
| def _check_repetition(text: str, min_repeat: int = 4) -> bool: | |
| """Flag degenerate repetition (a common failure mode of undertrained models).""" | |
| words = text.split() | |
| if len(words) < min_repeat * 2: | |
| return False | |
| for i in range(len(words) - min_repeat): | |
| window = tuple(words[i:i + min_repeat]) | |
| rest = words[i + min_repeat:i + min_repeat * 2] | |
| if tuple(rest[:min_repeat]) == window: | |
| return True | |
| return False | |
| def run_throughput_benchmark(engine: MorphInferenceEngine) -> dict: | |
| print("\n⚡ Throughput Benchmark") | |
| results = {} | |
| for max_tokens in (50, 200, 500): | |
| t0 = time.perf_counter() | |
| _ = engine.generate( | |
| [{"role": "user", "content": "Tell me a short story about a robot."}], | |
| max_new_tokens=max_tokens, temperature=0.7, | |
| ) | |
| elapsed = time.perf_counter() - t0 | |
| tok_s = max_tokens / elapsed | |
| results[f"{max_tokens}_tokens"] = {"elapsed_s": round(elapsed, 2), "tok_per_s": round(tok_s, 1)} | |
| print(f" {max_tokens:>4} tokens: {elapsed:.2f}s ({tok_s:.1f} tok/s)") | |
| return results | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Evaluate Frox AI Morph 1.1") | |
| parser.add_argument("--model", type=str, required=True) | |
| parser.add_argument("--skip-perplexity", action="store_true") | |
| parser.add_argument("--skip-generation", action="store_true") | |
| parser.add_argument("--skip-throughput", action="store_true") | |
| parser.add_argument("--output", type=str, default="./eval_results.json") | |
| args = parser.parse_args() | |
| print_banner() | |
| engine = MorphInferenceEngine.from_pretrained(args.model) | |
| report = {"model_path": args.model, "stats": engine.stats()} | |
| if not args.skip_perplexity: | |
| with timer("Perplexity eval"): | |
| report["perplexity"] = run_perplexity(engine) | |
| if not args.skip_generation: | |
| with timer("Sanity generations"): | |
| report["sanity_generations"] = run_sanity_generations(engine) | |
| n_ok = sum(1 for r in report["sanity_generations"] | |
| if r["has_content"] and not r["has_repetition"]) | |
| report["sanity_pass_rate"] = f"{n_ok}/{len(SANITY_PROMPTS)}" | |
| if not args.skip_throughput: | |
| with timer("Throughput benchmark"): | |
| report["throughput"] = run_throughput_benchmark(engine) | |
| Path(args.output).write_text(json.dumps(report, indent=2, default=str)) | |
| print(f"\n✅ Full report saved to {args.output}") | |
| if "perplexity" in report: | |
| print(f"\n{'='*50}") | |
| print(f"SUMMARY: perplexity={report['perplexity']} | " | |
| f"sanity={report.get('sanity_pass_rate', 'skipped')}") | |
| print(f"{'='*50}") | |
| if __name__ == "__main__": | |
| main() | |