Suya OCR API — vLLM-backed, OpenAI-compatible OCR service
FastAPI service wrapping the Surya-OCR-2 model (datalab-to) served through vLLM: legacy /v1/api/ai/* endpoints, an OpenAI-compatible /v1/chat/completions endpoint, a coalescing request batcher, a local OCR CLI, Docker packaging, multilingual example outputs, and quantization/concurrency benchmarks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""Parse surya_timing_summary lines from logs/info.log and aggregate spans.
|
||||
|
||||
Usage:
|
||||
python scripts/parse_timing.py logs/info.log
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
_BATCH_RE = re.compile(r"batch_size=(\d+)")
|
||||
_REQID_RE = re.compile(r"request_id=(\S+)")
|
||||
|
||||
|
||||
def parse_line(line: str) -> Optional[Dict[str, Any]]:
|
||||
if "surya_timing_summary" not in line or "events=" not in line:
|
||||
return None
|
||||
events_str = line.split("events=", 1)[1].strip()
|
||||
try:
|
||||
events = ast.literal_eval(events_str)
|
||||
except (ValueError, SyntaxError):
|
||||
return None
|
||||
batch_match = _BATCH_RE.search(line)
|
||||
reqid_match = _REQID_RE.search(line)
|
||||
return {
|
||||
"request_id": reqid_match.group(1) if reqid_match else "-",
|
||||
"batch_size": int(batch_match.group(1)) if batch_match else 0,
|
||||
"events": events,
|
||||
}
|
||||
|
||||
|
||||
def aggregate(records: List[Dict[str, Any]]) -> Dict[str, Dict[str, float]]:
|
||||
stats: Dict[str, Dict[str, float]] = defaultdict(
|
||||
lambda: {"count": 0, "total_ms": 0.0, "mean_ms": 0.0, "total_tokens": 0}
|
||||
)
|
||||
for rec in records:
|
||||
for event in rec["events"]:
|
||||
s = stats[event["name"]]
|
||||
s["count"] += 1
|
||||
s["total_ms"] += event.get("duration_ms", 0.0)
|
||||
tokens = (event.get("metadata") or {}).get("token_count")
|
||||
if tokens:
|
||||
s["total_tokens"] += tokens
|
||||
for s in stats.values():
|
||||
s["mean_ms"] = round(s["total_ms"] / s["count"], 2) if s["count"] else 0.0
|
||||
s["total_ms"] = round(s["total_ms"], 2)
|
||||
return dict(stats)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Aggregate surya timing spans from a log file.")
|
||||
parser.add_argument("logfile", type=Path)
|
||||
args = parser.parse_args()
|
||||
records = [
|
||||
rec
|
||||
for rec in (parse_line(line) for line in args.logfile.read_text(encoding="utf-8").splitlines())
|
||||
if rec is not None
|
||||
]
|
||||
print(json.dumps({"records": len(records), "spans": aggregate(records)}, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user