|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import argparse |
| 4 | +import json |
| 5 | +import time |
| 6 | +from pathlib import Path |
| 7 | +from typing import Any |
| 8 | + |
| 9 | +import requests |
| 10 | + |
| 11 | +from benchmarks.mineru_diffusion.harness import ( |
| 12 | + BenchmarkResult, |
| 13 | + STOP_STRINGS, |
| 14 | + read_cases, |
| 15 | + summarize_results, |
| 16 | + write_default_cases, |
| 17 | +) |
| 18 | + |
| 19 | + |
| 20 | +def _extract_output(payload: dict[str, Any]) -> str: |
| 21 | + return payload["choices"][0]["message"]["content"] |
| 22 | + |
| 23 | + |
| 24 | +def create_session() -> requests.Session: |
| 25 | + session = requests.Session() |
| 26 | + session.trust_env = False |
| 27 | + return session |
| 28 | + |
| 29 | + |
| 30 | +def build_request_payload(case: dict[str, Any]) -> dict[str, Any]: |
| 31 | + block_size = case.get("block_size", 32) |
| 32 | + dynamic_threshold = case.get("dynamic_threshold", 0.95) |
| 33 | + vllm_xargs = { |
| 34 | + "block_size": block_size, |
| 35 | + "dynamic_threshold": dynamic_threshold, |
| 36 | + } |
| 37 | + payload = { |
| 38 | + "model": case.get("model", "mineru-diffusion"), |
| 39 | + "messages": case["messages"], |
| 40 | + "max_tokens": case.get("max_tokens", 1024), |
| 41 | + "temperature": case.get("temperature", 1.0), |
| 42 | + "stop": case.get("stop", list(STOP_STRINGS)), |
| 43 | + "block_size": block_size, |
| 44 | + "dynamic_threshold": dynamic_threshold, |
| 45 | + "vllm_xargs": vllm_xargs, |
| 46 | + } |
| 47 | + if "max_denoising_steps" in case: |
| 48 | + max_denoising_steps = int(case["max_denoising_steps"]) |
| 49 | + payload["max_denoising_steps"] = max_denoising_steps |
| 50 | + vllm_xargs["max_denoising_steps"] = max_denoising_steps |
| 51 | + return payload |
| 52 | + |
| 53 | + |
| 54 | +def parse_task_max_tokens(value: str) -> dict[str, int]: |
| 55 | + if not value: |
| 56 | + return {} |
| 57 | + parsed: dict[str, int] = {} |
| 58 | + for part in value.split(","): |
| 59 | + if not part: |
| 60 | + continue |
| 61 | + task, sep, raw_tokens = part.partition("=") |
| 62 | + if not sep or not task: |
| 63 | + raise ValueError( |
| 64 | + "--task-max-tokens entries must use task=tokens format" |
| 65 | + ) |
| 66 | + parsed[task] = int(raw_tokens) |
| 67 | + return parsed |
| 68 | + |
| 69 | + |
| 70 | +def run_case(endpoint: str, case: dict[str, Any], timeout: float) -> BenchmarkResult: |
| 71 | + case_id = str(case.get("case_id", "unknown")) |
| 72 | + request_payload = build_request_payload(case) |
| 73 | + started = time.perf_counter() |
| 74 | + try: |
| 75 | + response = create_session().post(endpoint, json=request_payload, timeout=timeout) |
| 76 | + latency = time.perf_counter() - started |
| 77 | + response.raise_for_status() |
| 78 | + return BenchmarkResult( |
| 79 | + case_id=case_id, |
| 80 | + ok=True, |
| 81 | + latency_s=latency, |
| 82 | + output_text=_extract_output(response.json()), |
| 83 | + error=None, |
| 84 | + ) |
| 85 | + except Exception as exc: |
| 86 | + return BenchmarkResult( |
| 87 | + case_id=case_id, |
| 88 | + ok=False, |
| 89 | + latency_s=time.perf_counter() - started, |
| 90 | + output_text="", |
| 91 | + error=str(exc), |
| 92 | + ) |
| 93 | + |
| 94 | + |
| 95 | +def parse_args() -> argparse.Namespace: |
| 96 | + parser = argparse.ArgumentParser() |
| 97 | + parser.add_argument("--endpoint", default="http://127.0.0.1:18082/v1/chat/completions") |
| 98 | + parser.add_argument("--cases-jsonl", type=Path, default=None) |
| 99 | + parser.add_argument("--write-default-cases", type=Path, default=None) |
| 100 | + parser.add_argument( |
| 101 | + "--image", |
| 102 | + default=None, |
| 103 | + help="image URL used when generating default text/table/formula/layout cases", |
| 104 | + ) |
| 105 | + parser.add_argument("--output-dir", type=Path, default=Path("benchmark_results/mineru_diffusion")) |
| 106 | + parser.add_argument("--timeout", type=float, default=600.0) |
| 107 | + parser.add_argument( |
| 108 | + "--task-max-tokens", |
| 109 | + default="", |
| 110 | + help="comma-separated task=tokens overrides for generated default cases", |
| 111 | + ) |
| 112 | + return parser.parse_args() |
| 113 | + |
| 114 | + |
| 115 | +def main() -> None: |
| 116 | + args = parse_args() |
| 117 | + if ( |
| 118 | + args.cases_jsonl is None |
| 119 | + or args.write_default_cases is not None |
| 120 | + ) and args.image is None: |
| 121 | + raise SystemExit("--image is required when generating default cases") |
| 122 | + |
| 123 | + if args.write_default_cases is not None: |
| 124 | + write_default_cases( |
| 125 | + args.write_default_cases, |
| 126 | + args.image, |
| 127 | + task_max_tokens=parse_task_max_tokens(args.task_max_tokens), |
| 128 | + ) |
| 129 | + print(f"wrote {args.write_default_cases}") |
| 130 | + return |
| 131 | + |
| 132 | + cases_path = args.cases_jsonl |
| 133 | + if cases_path is None: |
| 134 | + cases_path = args.output_dir / "default_cases.jsonl" |
| 135 | + write_default_cases( |
| 136 | + cases_path, |
| 137 | + args.image, |
| 138 | + task_max_tokens=parse_task_max_tokens(args.task_max_tokens), |
| 139 | + ) |
| 140 | + |
| 141 | + args.output_dir.mkdir(parents=True, exist_ok=True) |
| 142 | + cases = read_cases(cases_path) |
| 143 | + results = [run_case(args.endpoint, case, args.timeout) for case in cases] |
| 144 | + summary = summarize_results(results) |
| 145 | + |
| 146 | + results_path = args.output_dir / f"results_{int(time.time())}.jsonl" |
| 147 | + summary_path = args.output_dir / "latest_summary.json" |
| 148 | + results_path.write_text( |
| 149 | + "".join(result.to_json() + "\n" for result in results), |
| 150 | + encoding="utf-8", |
| 151 | + ) |
| 152 | + summary_path.write_text( |
| 153 | + json.dumps(summary, indent=2, ensure_ascii=False) + "\n", |
| 154 | + encoding="utf-8", |
| 155 | + ) |
| 156 | + print(json.dumps(summary, indent=2, ensure_ascii=False)) |
| 157 | + print(f"results: {results_path}") |
| 158 | + |
| 159 | + |
| 160 | +if __name__ == "__main__": |
| 161 | + main() |
0 commit comments