import argparse import json from numbers import Real from pathlib import Path import pandas as pd from tabulate import tabulate from vlmeval.smp import collect_run_benchmark_report, load_run_status def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--work-dir', type=Path, action='append', required=True) parser.add_argument('--data', nargs='+', default=None) parser.add_argument('--verbose', action='store_true') args = parser.parse_args() if args.verbose and len(args.work_dir) != 1: parser.error('--verbose only supports a single --work-dir') if args.data is not None: args.data = list(dict.fromkeys(args.data)) return args def resolve_run_dir(work_dir: Path) -> Path: work_dir = work_dir.resolve() if (work_dir / 'status.json').exists(): return work_dir candidates = sorted( path for path in work_dir.iterdir() if path.is_dir() and (path / 'status.json').exists() ) if not candidates: raise FileNotFoundError(f'No status.json found in {work_dir} or its direct child directories.') return candidates[-1] def format_sigfig(value): if value is None or isinstance(value, bool): return '-' if isinstance(value, Real): try: if pd.isna(value): return '-' except Exception: pass return f'{float(value):.4g}' return str(value) def format_fail_rate(failed, total): if failed is None or total is None or total <= 0: return '-' return f'{failed / total * 100:.2f}% ({failed}/{total})' def format_metric_field(value): if value is None: return '-' if isinstance(value, Real) and not isinstance(value, bool): return format_sigfig(value) if isinstance(value, (list, tuple, dict)): return json.dumps(value, ensure_ascii=False) return str(value) def truncate_error_message(value, max_length=120): value = value or '-' if value != '-' and len(str(value)) > max_length: return f'{str(value)[:max_length]}...' return value def iter_primary_metric_rows(row): primary_metric = row.get('primary_metric') primary_metric_value = row.get('primary_metric_value') if isinstance(primary_metric, (list, tuple)): if not primary_metric: return [(None, None)] value_map = primary_metric_value if isinstance(primary_metric_value, dict) else {} return [(metric_name, value_map.get(metric_name)) for metric_name in primary_metric] return [(primary_metric, primary_metric_value)] def resolve_model_name(run_dir: Path) -> str: run_status = load_run_status(run_dir) return str(run_status.get('model_name') or run_dir.parent.name) def dedupe_column_names(names): counts = {} deduped_names = [] for name in names: name = str(name) counts[name] = counts.get(name, 0) + 1 deduped_names.append(name if counts[name] == 1 else f'{name}#{counts[name]}') return deduped_names def build_verbose_report_rows(rows): report_rows = [] for row in rows: metric_rows = iter_primary_metric_rows(row) eval_error = truncate_error_message(row['eval_error']) for idx, (primary_metric, primary_metric_value) in enumerate(metric_rows): report_rows.append({ 'benchmark': row['benchmark'] if idx == 0 else '', 'infer_fail_rate': format_fail_rate(row['infer_failed'], row['infer_total']) if idx == 0 else '', 'judge_fail_rate': format_fail_rate(row['judge_failed'], row['judge_total']) if idx == 0 else '', 'primary_metric': format_metric_field(primary_metric), 'primary_metric_value': format_metric_field(primary_metric_value), 'skip_reason': (row['skip_reason'] or '-') if idx == 0 else '', 'eval_error': eval_error if idx == 0 else '', }) return report_rows def build_summary_rows(run_reports, benchmark_order=None): model_columns = dedupe_column_names([report['model_name'] for report in run_reports]) merged_rows = {} benchmark_filter = set(benchmark_order) if benchmark_order is not None else None for model_column, report in zip(model_columns, run_reports): for row in report['rows']: benchmark = row['benchmark'] if benchmark_filter is not None and benchmark not in benchmark_filter: continue for primary_metric, primary_metric_value in iter_primary_metric_rows(row): row_key = (benchmark, format_metric_field(primary_metric)) if row_key not in merged_rows: merged_rows[row_key] = { 'benchmark': benchmark, 'primary_metric': format_metric_field(primary_metric), } merged_rows[row_key][model_column] = format_metric_field(primary_metric_value) benchmark_to_keys = {} for row_key in merged_rows: benchmark_to_keys.setdefault(row_key[0], []).append(row_key) if benchmark_order is None: ordered_benchmarks = list(benchmark_to_keys.keys()) else: ordered_benchmarks = [benchmark for benchmark in benchmark_order if benchmark in benchmark_to_keys] ordered_columns = ['benchmark', 'primary_metric', *model_columns] summary_rows = [] for benchmark in ordered_benchmarks: row_keys = benchmark_to_keys[benchmark] has_primary_metric = any(primary_metric != '-' for _, primary_metric in row_keys) if has_primary_metric: row_keys = [row_key for row_key in row_keys if row_key[1] != '-'] for row_key in row_keys: row = merged_rows[row_key] summary_rows.append({column: row.get(column, '-') for column in ordered_columns}) return summary_rows, ordered_columns def print_csv_and_table(rows, columns=None): if columns is None: columns = list(rows[0].keys()) df = pd.DataFrame(rows, columns=columns).fillna('-') print(df.to_csv(index=False, lineterminator='\n'), end='') print() print(tabulate(df, headers='keys', showindex=False)) def main(): args = parse_args() run_reports = [] for work_dir in args.work_dir: run_dir = resolve_run_dir(work_dir) run_reports.append({ 'run_dir': run_dir, 'model_name': resolve_model_name(run_dir), 'rows': collect_run_benchmark_report(run_dir), }) if args.verbose: rows = run_reports[0]['rows'] if not rows: return print_csv_and_table(build_verbose_report_rows(rows)) return summary_rows, columns = build_summary_rows(run_reports, benchmark_order=args.data) if not summary_rows: return print_csv_and_table(summary_rows, columns=columns) if __name__ == '__main__': main()