Spaces:
Running
Running
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| from pathlib import Path | |
| from typing import Any | |
| from model_release.blum_finance.inference import _extract_json_object | |
| from model_release.evaluation.evaluate_candidate import load_examples | |
| from model_release.evaluation.tasks.blum_finance_eval import evaluate_predictions | |
| def artifact_revision(path: Path) -> str: | |
| """Return a stable 40-character revision for a local adapter artifact.""" | |
| digest = hashlib.sha1() # nosec B324 - compatibility identifier, not security | |
| for item in sorted(candidate_files(path)): | |
| digest.update(item.relative_to(path).as_posix().encode("utf-8")) | |
| digest.update(b"\0") | |
| with item.open("rb") as handle: | |
| for chunk in iter(lambda: handle.read(1024 * 1024), b""): | |
| digest.update(chunk) | |
| digest.update(b"\0") | |
| return digest.hexdigest() | |
| def candidate_files(path: Path) -> list[Path]: | |
| return [ | |
| item | |
| for item in path.rglob("*") | |
| if item.is_file() and not item.name.startswith(".") | |
| ] | |
| def build_generation_prompt(tokenizer: Any, messages: list[dict[str, str]]) -> str: | |
| return tokenizer.apply_chat_template( | |
| messages[:-1], | |
| tokenize=False, | |
| add_generation_prompt=True, | |
| enable_thinking=False, | |
| ) | |
| def generate_predictions( | |
| *, | |
| model_path: Path, | |
| adapter_path: Path | None, | |
| examples: list[dict[str, Any]], | |
| max_tokens: int, | |
| ) -> tuple[list[dict[str, Any] | None], list[dict[str, Any]]]: | |
| from mlx_lm import generate, load | |
| from mlx_lm.sample_utils import make_sampler | |
| model, tokenizer = load( | |
| str(model_path), | |
| adapter_path=str(adapter_path) if adapter_path else None, | |
| tokenizer_config={"trust_remote_code": True}, | |
| ) | |
| sampler = make_sampler(temp=0.0) | |
| predictions: list[dict[str, Any] | None] = [] | |
| generations: list[dict[str, Any]] = [] | |
| for index, example in enumerate(examples): | |
| prompt = build_generation_prompt(tokenizer, example["messages"]) | |
| generated = generate( | |
| model, | |
| tokenizer, | |
| prompt=prompt, | |
| max_tokens=max_tokens, | |
| sampler=sampler, | |
| verbose=False, | |
| ) | |
| try: | |
| parsed = _extract_json_object(generated) | |
| error = None | |
| except (ValueError, json.JSONDecodeError) as exc: | |
| parsed = None | |
| error = str(exc) | |
| predictions.append(parsed) | |
| generations.append( | |
| { | |
| "example_id": example.get("example_id"), | |
| "index": index, | |
| "generated_text": generated, | |
| "parse_error": error, | |
| } | |
| ) | |
| return predictions, generations | |
| def write_evaluation( | |
| *, | |
| output_dir: Path, | |
| model_revision: str, | |
| examples: list[dict[str, Any]], | |
| predictions: list[dict[str, Any] | None], | |
| generations: list[dict[str, Any]], | |
| ) -> dict[str, Any]: | |
| metrics, traces = evaluate_predictions( | |
| model_revision=model_revision, | |
| examples=examples, | |
| predictions=predictions, | |
| ) | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| payload = metrics.model_dump(mode="json") | |
| (output_dir / "evaluation_summary.json").write_text( | |
| json.dumps(payload, indent=2, sort_keys=True) + "\n", | |
| encoding="utf-8", | |
| ) | |
| write_jsonl(output_dir / "evaluation_traces.jsonl", traces) | |
| write_jsonl(output_dir / "generations.jsonl", generations) | |
| return payload | |
| def write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None: | |
| with path.open("w", encoding="utf-8") as handle: | |
| for row in rows: | |
| handle.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n") | |
| def main() -> None: | |
| parser = argparse.ArgumentParser( | |
| description="Evaluate a local MLX BLUM Finance base model or LoRA adapter." | |
| ) | |
| parser.add_argument("--model-path", type=Path, required=True) | |
| parser.add_argument("--adapter-path", type=Path) | |
| parser.add_argument("--revision") | |
| parser.add_argument("--test-file", type=Path, required=True) | |
| parser.add_argument("--output-dir", type=Path, required=True) | |
| parser.add_argument("--max-tokens", type=int, default=512) | |
| args = parser.parse_args() | |
| examples = load_examples(args.test_file) | |
| revision = args.revision or ( | |
| artifact_revision(args.adapter_path) | |
| if args.adapter_path | |
| else artifact_revision(args.model_path) | |
| ) | |
| predictions, generations = generate_predictions( | |
| model_path=args.model_path, | |
| adapter_path=args.adapter_path, | |
| examples=examples, | |
| max_tokens=args.max_tokens, | |
| ) | |
| payload = write_evaluation( | |
| output_dir=args.output_dir, | |
| model_revision=revision, | |
| examples=examples, | |
| predictions=predictions, | |
| generations=generations, | |
| ) | |
| print(json.dumps(payload, indent=2, sort_keys=True)) | |
| if __name__ == "__main__": | |
| main() | |