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:
Fu Dai
2026-06-17 10:20:02 +04:00
co-authored by Claude Opus 4.8
commit 1a585693be
147 changed files with 13827 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
"""Speed-vs-accuracy Pareto scatter and per-method bar charts."""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List
import matplotlib
matplotlib.use("Agg") # headless
import matplotlib.pyplot as plt # noqa: E402
def pareto_points(rows: List[Dict[str, Any]], x_key: str, y_key: str) -> List[Dict[str, Any]]:
points = []
for row in rows:
if row.get("status") != "ok":
continue
if row.get(x_key) is None or row.get(y_key) is None:
continue
points.append({
"method": row["method"],
"x": row[x_key],
"y": row[y_key],
"t4_deployable": row.get("t4_deployable"),
})
return points
def _scatter(points, x_label, y_label, title, out_path: Path) -> None:
fig, ax = plt.subplots(figsize=(7, 5))
for p in points:
marker = "o" if p["t4_deployable"] else "x"
ax.scatter(p["x"], p["y"], marker=marker, s=80)
ax.annotate(p["method"], (p["x"], p["y"]), textcoords="offset points", xytext=(5, 5))
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
ax.set_title(title + " (o = T4-deployable, x = A100-only)")
fig.tight_layout()
fig.savefig(out_path, dpi=120)
plt.close(fig)
def _bar(rows, metric_key, y_label, out_path: Path) -> None:
ok = [r for r in rows if r.get("status") == "ok" and r.get(metric_key) is not None]
fig, ax = plt.subplots(figsize=(7, 5))
ax.bar([r["method"] for r in ok], [r[metric_key] for r in ok])
ax.set_ylabel(y_label)
ax.set_title(metric_key)
fig.tight_layout()
fig.savefig(out_path, dpi=120)
plt.close(fig)
def render_all(rows: List[Dict[str, Any]], out_dir: Path) -> List[Path]:
out_dir = Path(out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
written: List[Path] = []
p1 = out_dir / "pareto_latency_cer.png"
_scatter(pareto_points(rows, "mean_latency_s", "mean_cer"),
"mean latency (s) [lower=faster]", "CER vs BF16 [lower=better]",
"Speed vs recognition accuracy", p1)
written.append(p1)
p2 = out_dir / "pareto_latency_iou.png"
_scatter(pareto_points(rows, "mean_latency_s", "mean_bbox_iou"),
"mean latency (s) [lower=faster]", "bbox IoU vs BF16 [higher=better]",
"Speed vs detection accuracy", p2)
written.append(p2)
for metric, label in [("mean_latency_s", "mean latency (s)"),
("mean_cer", "CER vs BF16"),
("mean_bbox_iou", "bbox IoU vs BF16")]:
bp = out_dir / f"bar_{metric}.png"
_bar(rows, metric, label, bp)
written.append(bp)
return written