Download scripts/analysis/image_checker_timing_report.py from Jack04810/agentic-rl-main: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/Jack04810/agentic-rl-main/resolve/main/scripts/analysis/image_checker_timing_report.py
- Command line
-
hf download hf://Jack04810/agentic-rl-main/scripts/analysis/image_checker_timing_report.py
-
curl -L -o image_checker_timing_report.py https://huggingface.co/Jack04810/agentic-rl-main/resolve/main/scripts/analysis/image_checker_timing_report.py
10.8 kB
| #!/usr/bin/env python | |
| """Summarize OPD image-checker smoke timing from logs and visual artifacts.""" | |
| from __future__ import annotations | |
| import argparse | |
| import ast | |
| import json | |
| import math | |
| import re | |
| from collections import Counter | |
| from pathlib import Path | |
| from typing import Iterable, Any | |
| SUMMARY_KEYS = ( | |
| "visual/ic_latency_ms", | |
| "visual/checker_latency_ms", | |
| "visual/refiner_latency_ms", | |
| "visual/ic_calls", | |
| "visual/checker_calls", | |
| "visual/refiner_calls", | |
| "visual/teacher_batch_calls", | |
| "visual/ic_batch_calls", | |
| "visual/checker_batch_calls", | |
| "visual/refiner_batch_calls", | |
| "visual/checker_high", | |
| "visual/checker_medium", | |
| "visual/checker_low", | |
| "visual/checker_parse_failure", | |
| "visual/checker_image_missing", | |
| "visual/checker_aux_evidence_used", | |
| "visual/fallback_checker", | |
| "visual/fallback_refiner", | |
| ) | |
| _STEP_RE = re.compile(r"step_(\d+)") | |
| _COMPLETION_MODE_COUNTS_RE = re.compile(r"completion_mode_counts=(\{.*?\})(?:\s*\||$)") | |
| def _as_float(value: Any, default: float = 0.0) -> float: | |
| try: | |
| out = float(value) | |
| except (TypeError, ValueError): | |
| return default | |
| if not math.isfinite(out): | |
| return default | |
| return out | |
| def _ratio(num: float, den: float) -> float: | |
| den = _as_float(den) | |
| if den <= 0.0: | |
| return 0.0 | |
| return round(_as_float(num) / den, 4) | |
| def _parse_visual_batch_line(line: str) -> dict[str, float] | None: | |
| if "[VISUAL-BATCH]" not in line or "generate_summary" not in line: | |
| return None | |
| fields: dict[str, float] = {} | |
| for part in line.split(" | ")[1:]: | |
| if "=" not in part: | |
| continue | |
| key, value = part.split("=", 1) | |
| key = key.strip() | |
| if key in SUMMARY_KEYS: | |
| fields[key] = _as_float(value.strip()) | |
| return fields | |
| def parse_log_files(log_files: Iterable[Path]) -> list[dict[str, float]]: | |
| batches: list[dict[str, float]] = [] | |
| for path in log_files: | |
| if not path or not path.exists(): | |
| continue | |
| with path.open("r", encoding="utf-8", errors="replace") as handle: | |
| for line in handle: | |
| parsed = _parse_visual_batch_line(line) | |
| if parsed is not None: | |
| batches.append(parsed) | |
| return batches | |
| def parse_trainer_completion_modes(log_files: Iterable[Path]) -> dict[str, int]: | |
| counts: Counter[str] = Counter() | |
| for path in log_files: | |
| if not path or not path.exists(): | |
| continue | |
| with path.open("r", encoding="utf-8", errors="replace") as handle: | |
| for line in handle: | |
| match = _COMPLETION_MODE_COUNTS_RE.search(line) | |
| if not match: | |
| continue | |
| raw = match.group(1) | |
| try: | |
| payload = json.loads(raw) | |
| except json.JSONDecodeError: | |
| try: | |
| payload = ast.literal_eval(raw) | |
| except (ValueError, SyntaxError): | |
| continue | |
| if not isinstance(payload, dict): | |
| continue | |
| for key, value in payload.items(): | |
| counts[str(key)] += int(_as_float(value)) | |
| return dict(sorted(counts.items())) | |
| def _step_from_path(path: Path) -> int: | |
| for parent in (path.parent, *path.parents): | |
| match = _STEP_RE.fullmatch(parent.name) | |
| if match: | |
| return int(match.group(1)) | |
| return -1 | |
| def _score_label(score: Any) -> str: | |
| value = _as_float(score) | |
| if value >= 0.75: | |
| return "high" | |
| if value >= 0.25: | |
| return "medium" | |
| return "low" | |
| def _artifact_files(output_dir: Path | None) -> list[Path]: | |
| if output_dir is None: | |
| return [] | |
| root = output_dir / "visual_supervision" | |
| if not root.exists(): | |
| return [] | |
| return sorted(root.glob("step_*/rank*.jsonl")) | |
| def parse_artifacts(output_dir: Path | None) -> dict[str, Any]: | |
| checker_labels: Counter[str] = Counter() | |
| routes: Counter[str] = Counter() | |
| route_x_checker: Counter[str] = Counter() | |
| checker_by_sample: dict[tuple[int, int], str] = {} | |
| false_high = 0 | |
| false_low = 0 | |
| checker_rows = 0 | |
| route_rows = 0 | |
| for path in _artifact_files(output_dir): | |
| step = _step_from_path(path) | |
| with path.open("r", encoding="utf-8", errors="replace") as handle: | |
| for raw in handle: | |
| try: | |
| row = json.loads(raw) | |
| except json.JSONDecodeError: | |
| continue | |
| kind = str(row.get("kind", "")) | |
| sample_idx = int(_as_float(row.get("sample_idx"), -1)) | |
| key = (step, sample_idx) | |
| if kind == "checker": | |
| label = str(row.get("label") or _score_label(row.get("score"))).lower() | |
| if label == "image_missing": | |
| label = "low" | |
| checker_by_sample[key] = label | |
| checker_labels[label] += 1 | |
| checker_rows += 1 | |
| elif kind == "route": | |
| route = str(row.get("route") or "unknown").lower() | |
| label = checker_by_sample.get(key) or _score_label(row.get("checker_score")) | |
| routes[route] += 1 | |
| route_x_checker[f"{route}:{label}"] += 1 | |
| route_rows += 1 | |
| answer = _as_float(row.get("answer_reward")) | |
| if label == "high" and answer <= 0.0: | |
| false_high += 1 | |
| if label == "low" and answer > 0.0: | |
| false_low += 1 | |
| return { | |
| "artifact_checker_rows": checker_rows, | |
| "artifact_route_rows": route_rows, | |
| "checker_labels": dict(sorted(checker_labels.items())), | |
| "routes": dict(sorted(routes.items())), | |
| "route_x_checker": dict(sorted(route_x_checker.items())), | |
| "false_high_count": false_high, | |
| "false_low_count": false_low, | |
| } | |
| def build_report(*, log_files: Iterable[Path], output_dir: Path | None = None) -> dict[str, Any]: | |
| log_file_list = [Path(path) for path in log_files] | |
| batches = parse_log_files(log_file_list) | |
| report: dict[str, Any] = {"log_batches": len(batches)} | |
| for key in SUMMARY_KEYS: | |
| report[key] = round(sum(_as_float(batch.get(key)) for batch in batches), 4) | |
| report["derived/checker_ms_per_call"] = _ratio( | |
| report["visual/checker_latency_ms"], | |
| report["visual/checker_calls"], | |
| ) | |
| report["derived/checker_ms_per_batch"] = _ratio( | |
| report["visual/checker_latency_ms"], | |
| report["visual/checker_batch_calls"], | |
| ) | |
| report["derived/ic_ms_per_call"] = _ratio( | |
| report["visual/ic_latency_ms"], | |
| report["visual/ic_calls"], | |
| ) | |
| report["derived/refiner_ms_per_call"] = _ratio( | |
| report["visual/refiner_latency_ms"], | |
| report["visual/refiner_calls"], | |
| ) | |
| report["trainer_completion_modes"] = parse_trainer_completion_modes(log_file_list) | |
| report.update(parse_artifacts(output_dir)) | |
| return report | |
| def _fmt_counter(mapping: dict[str, int]) -> str: | |
| if not mapping: | |
| return "none" | |
| return ",".join(f"{key}={value}" for key, value in sorted(mapping.items())) | |
| def format_report(report: dict[str, Any]) -> str: | |
| lines = [ | |
| ( | |
| "[IMAGE-CHECKER-TIMING] " | |
| f"log_batches={int(report.get('log_batches', 0))} " | |
| f"checker_latency_ms={report.get('visual/checker_latency_ms', 0.0)} " | |
| f"checker_calls={report.get('visual/checker_calls', 0.0)} " | |
| f"checker_batch_calls={report.get('visual/checker_batch_calls', 0.0)} " | |
| f"checker_ms_per_call={report.get('derived/checker_ms_per_call', 0.0)} " | |
| f"checker_ms_per_batch={report.get('derived/checker_ms_per_batch', 0.0)}" | |
| ), | |
| ( | |
| "[IMAGE-CHECKER-TIMING] " | |
| f"ic_latency_ms={report.get('visual/ic_latency_ms', 0.0)} " | |
| f"ic_calls={report.get('visual/ic_calls', 0.0)} " | |
| f"ic_batch_calls={report.get('visual/ic_batch_calls', 0.0)} " | |
| f"ic_ms_per_call={report.get('derived/ic_ms_per_call', 0.0)} " | |
| f"refiner_latency_ms={report.get('visual/refiner_latency_ms', 0.0)} " | |
| f"refiner_calls={report.get('visual/refiner_calls', 0.0)} " | |
| f"refiner_batch_calls={report.get('visual/refiner_batch_calls', 0.0)} " | |
| f"refiner_ms_per_call={report.get('derived/refiner_ms_per_call', 0.0)}" | |
| ), | |
| ( | |
| "[IMAGE-CHECKER-TIMING] " | |
| f"checker_labels={_fmt_counter(report.get('checker_labels', {}))} " | |
| f"routes={_fmt_counter(report.get('routes', {}))} " | |
| f"trainer_completion_modes={_fmt_counter(report.get('trainer_completion_modes', {}))} " | |
| f"route_x_checker={_fmt_counter(report.get('route_x_checker', {}))}" | |
| ), | |
| ( | |
| "[IMAGE-CHECKER-TIMING] " | |
| f"parse_failure={report.get('visual/checker_parse_failure', 0.0)} " | |
| f"image_missing={report.get('visual/checker_image_missing', 0.0)} " | |
| f"aux_used={report.get('visual/checker_aux_evidence_used', 0.0)} " | |
| f"fallback_checker={report.get('visual/fallback_checker', 0.0)} " | |
| f"false_high_count={report.get('false_high_count', 0)} " | |
| f"false_low_count={report.get('false_low_count', 0)}" | |
| ), | |
| ] | |
| return "\n".join(lines) | |
| def _discover_logs(log_dir: Path | None, pattern: str) -> list[Path]: | |
| if log_dir is None or not log_dir.exists(): | |
| return [] | |
| return sorted(log_dir.glob(pattern), key=lambda p: p.stat().st_mtime) | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--log-file", action="append", default=[], help="Training log file. Repeatable.") | |
| parser.add_argument("--log-dir", type=Path, default=None, help="Directory to scan when --log-file is absent.") | |
| parser.add_argument("--output-dir", type=Path, default=None, help="Training output_dir containing visual_supervision.") | |
| parser.add_argument( | |
| "--pattern", | |
| default="train_opd_7b_dyme_probe_image_checker*.log", | |
| help="Log glob used with --log-dir.", | |
| ) | |
| parser.add_argument("--json", action="store_true", help="Print JSON instead of text.") | |
| args = parser.parse_args() | |
| log_files = [Path(p) for p in args.log_file] | |
| if not log_files: | |
| log_files = _discover_logs(args.log_dir, args.pattern) | |
| report = build_report(log_files=log_files, output_dir=args.output_dir) | |
| if args.json: | |
| print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)) | |
| else: | |
| print(format_report(report)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |