File size: 4,559 Bytes
7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 d5ed50c 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 7f153d6 5840d20 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | """Summarise every run into figures ready for a resourcing argument.
py -m src.knowledge_parsing.report
py -m src.knowledge_parsing.report --scale 6800
`--scale` extrapolates to a given page count (e.g. BUMA scale, ~6,700-6,800
pages) using the MEASURED seconds/page — documents served from cache are left
out, because including them would make the figure look far faster than reality.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from .config import PipelineConfig
# Below this, the fixed cost of loading models dominates, so seconds/page comes
# out far higher than it would on a large document. Measured on this machine:
# 1 page -> 54.0 s/page, 5 pages -> 33.2, 20 pages -> 19.0.
MIN_PAGES_TO_EXTRAPOLATE = 20
def _duration(seconds: float) -> str:
if seconds < 90:
return f"{seconds:.0f} s"
if seconds < 5400:
return f"{seconds/60:.1f} min"
return f"{seconds/3600:.1f} h"
def collect(runs_dir: Path) -> list[dict]:
results = []
for m in sorted(runs_dir.glob("*/manifest.json")):
try:
results.append(json.loads(m.read_text(encoding="utf-8")))
except (OSError, json.JSONDecodeError) as e:
# Manifest corrupt or half-written (a run that died mid-way). Skipped,
# but reported — a report that silently loses a run gives a wrong
# seconds/page figure with nothing to show for it.
print(f" ! skipping unreadable manifest: {m} ({type(e).__name__})")
return results
def render(runs: list[dict], scale: int | None) -> None:
if not runs:
print("No runs yet. Try: py -m src.knowledge_parsing.run --input data/knowledge_docs/")
return
print(f"{'run':<17} {'backend':<9} {'ver':<8} {'docs':>4} {'pages':>5} "
f"{'s/page':>8} {'warnings':>9}")
print("-" * 66)
for r in runs:
s = r.get("summary", {})
cfg = r.get("config", {})
# the backend recorded in MinerU's output is trusted over the config
recorded = {d.get("backend_recorded") for d in r.get("documents", [])} - {None}
backend = "/".join(sorted(recorded)) if recorded else cfg.get("backend", "?")
spp = s.get("seconds_per_page")
print(f"{r.get('run_id',''):<17} {backend:<9} "
f"{str(r.get('mineru_version') or '?')[:7]:<8} "
f"{s.get('documents_ok',0):>4} {s.get('total_pages',0):>5} "
f"{(f'{spp:.2f}' if spp else '-'):>8} {s.get('total_warnings',0):>9}")
measured = [r for r in runs if (r.get("summary") or {}).get("seconds_per_page")]
if not measured:
print("\n(no run has actually parsed anything yet — all cache hits)")
return
print("\n--- extrapolation ---")
target = scale or 6800
doubtful = False
for r in measured:
s = r["summary"]
spp = s["seconds_per_page"]
pages = s.get("pages_measured", 0)
cfg = r.get("config", {})
note = ""
if pages < MIN_PAGES_TO_EXTRAPOLATE:
note = f" ⚠ only {pages} page" + ("" if pages == 1 else "s")
doubtful = True
print(f" {cfg.get('backend','?'):<9} {spp:>6.2f} s/page → "
f"{target} pages = {_duration(spp * target)}{note}")
if doubtful:
print(
f"\n ⚠ A run under {MIN_PAGES_TO_EXTRAPOLATE} pages is NOT fit to "
"extrapolate from.\n"
" Model loading is a fixed cost, so a small document looks far\n"
" slower per page. Measured earlier on this machine:\n"
" 1 page -> 54.0 s/page | 5 pages -> 33.2 | 20 pages -> 19.0\n"
" Use a document of >= 20 pages before the number leaves the room."
)
if len(measured) >= 2:
fastest = min(measured, key=lambda r: r["summary"]["seconds_per_page"])
slowest = max(measured, key=lambda r: r["summary"]["seconds_per_page"])
a = slowest["summary"]["seconds_per_page"]
b = fastest["summary"]["seconds_per_page"]
if b:
print(f"\n fastest vs slowest: {a/b:.1f}×")
def main() -> None:
p = argparse.ArgumentParser(description="Summary figures across every run")
p.add_argument("--runs-dir", type=Path, default=None)
p.add_argument("--scale", type=int, default=None,
help="extrapolate to this many pages (e.g. 6800)")
a = p.parse_args()
cfg = PipelineConfig()
render(collect(a.runs_dir or cfg.runs_dir), a.scale)
if __name__ == "__main__":
main()
|