File size: 6,838 Bytes
770f650
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Comparative Report Generator for Cham-OCR Multi-GPU Benchmarks.
Aggregates trials from benchmark_*.json and formats the final Markdown comparison.
"""

import os
import sys
import glob
import json
import argparse

if sys.stdout and hasattr(sys.stdout, 'reconfigure'):
    try:
        sys.stdout.reconfigure(encoding='utf-8')
    except Exception:
        pass
if sys.stderr and hasattr(sys.stderr, 'reconfigure'):
    try:
        sys.stderr.reconfigure(encoding='utf-8')
    except Exception:
        pass

def generate_comparative_report(results_dir="benchmark_results", output_md="benchmark_results/COMPARATIVE_REPORT.md"):
    json_files = glob.glob(os.path.join(results_dir, "benchmark_*.json"))
    if not json_files:
        print(f"No benchmark JSON files found in {results_dir}")
        return

    summaries = []
    for jf in json_files:
        try:
            with open(jf, "r", encoding="utf-8") as f:
                data = json.load(f)
                summaries.append(data)
        except Exception as e:
            print(f"Error reading {jf}: {e}")

    lines = []
    lines.append("# Báo Cáo Đối Soánh Hiệu Năng & Chi Phí Huấn Luyện Cham-OCR Đa GPU")
    lines.append("## (PP-OCRv4 SVTR_LCNet - Pipeline Huấn Luyện Thật)\n")

    lines.append("### 1. Bảng Tổng Hợp So Sánh Các Cấu Hình Tối Ưu (Best & Max Batch)")
    lines.append("| GPU | Phân Loại | Batch | Workers | IPS (mẫu/s) | GPU% | VRAM% | Reader Time | Batch Time | $/h | Est 40 Epoch | Est Cost | IPS/$ |")
    lines.append("|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|")

    rankings = []

    for s in summaries:
        gpu = s["gpu_name"]
        cost = s["cost_per_hour"]
        workers = s["optimal_workers"]
        best_b = s["best_batch"]
        max_b = s["max_safe_batch"]
        trials = { (t["batch_size"], t["num_workers"]): t for t in s["all_trials"] if t.get("status") == "SUCCESS" }

        # Best Trial
        best_t = trials.get((best_b, workers))
        if best_t:
            lines.append(
                f"| **{gpu}** | 🌟 **Best** | {best_b} | {workers} | "
                f"**{best_t['ips']:.1f}** | {best_t['avg_gpu_util']:.0f}% | {best_t['vram_pct']:.1f}% | "
                f"{best_t['avg_reader_cost']:.4f}s | {best_t['avg_batch_cost']:.4f}s | "
                f"${cost:.2f} | {best_t['est_40_epoch_hours']:.1f}h | "
                f"**${best_t['est_40_epoch_cost']:.2f}** | **{best_t['ips_per_dollar']:.1f}** |"
            )
            rankings.append({
                "gpu": gpu,
                "type": "Best",
                "batch": best_b,
                "workers": workers,
                "ips": best_t["ips"],
                "cost": cost,
                "est_hours": best_t["est_40_epoch_hours"],
                "est_cost": best_t["est_40_epoch_cost"],
                "ips_per_dollar": best_t["ips_per_dollar"],
                "bottleneck": s["primary_bottleneck"]
            })

        # Max Safe Trial (if different from Best)
        if max_b != best_b:
            max_t = trials.get((max_b, workers))
            if max_t:
                lines.append(
                    f"| {gpu} | 🛡️ Max Safe | {max_b} | {workers} | "
                    f"{max_t['ips']:.1f} | {max_t['avg_gpu_util']:.0f}% | {max_t['vram_pct']:.1f}% | "
                    f"{max_t['avg_reader_cost']:.4f}s | {max_t['avg_batch_cost']:.4f}s | "
                    f"${cost:.2f} | {max_t['est_40_epoch_hours']:.1f}h | "
                    f"${max_t['est_40_epoch_cost']:.2f} | {max_t['ips_per_dollar']:.1f} |"
                )

    lines.append("\n### 2. Kết Luận Chi Tiết Cho Từng Machine")
    for s in summaries:
        gpu = s["gpu_name"]
        cost = s["cost_per_hour"]
        workers = s["optimal_workers"]
        best_b = s["best_batch"]
        max_b = s["max_safe_batch"]
        bottleneck = s["primary_bottleneck"]
        best_t = s.get("best_trial")

        lines.append(f"\n#### 🔹 Machine: **{gpu}** (${cost:.2f}/h)")
        lines.append(f"- **Max Batch Chạy Được**: `{max_b}` (ngưỡng giới hạn phần cứng trước khi OOM).")
        lines.append(f"- **Best Batch Nên Dùng**: `{best_b}` (điểm ngọt cho throughput cao nhất trước khi bão hòa).")
        lines.append(f"- **Num Workers Tối Ưu**: `{workers}` worker.")
        lines.append(f"- **Điểm Nghẽn Chính (Bottleneck)**: `{bottleneck}`.")
        if best_t:
            lines.append(f"- **Thời Gian Huấn Luyện Full 40 Epoch (10,000,000 mẫu)**: `{best_t['est_40_epoch_hours']:.2f} giờ` (~`{best_t['est_40_epoch_hours']*60:.0f} phút`).")
            lines.append(f"- **Chi Phí Ước Tính Full 40 Epoch**: `${best_t['est_40_epoch_cost']:.2f}`.")
            lines.append(f"- **Hiệu Năng / Chi Phí (P/P)**: `{best_t['ips_per_dollar']:.1f} mẫu / $`.")

    # Sort rankings by IPS/$ descending
    rankings.sort(key=lambda x: x["ips_per_dollar"], reverse=True)
    lines.append("\n### 3. Xếp Hạng Hiệu Quả Đầu Tư (Performance / Price Ranking)")
    lines.append("| Hạng | GPU | Best Batch | Tốc Độ (IPS) | Thời Gian Full 40 Epoch | Tổng Chi Phí ($) | Hiệu Suất Kinh Tế (IPS/$) |")
    lines.append("|:---:|:---|:---:|:---:|:---:|:---:|:---:|")

    for i, r in enumerate(rankings, 1):
        medal = "🥇" if i == 1 else ("🥈" if i == 2 else ("🥉" if i == 3 else f"#{i}"))
        lines.append(f"| {medal} | **{r['gpu']}** | {r['batch']} | {r['ips']:.1f} mẫu/s | {r['est_hours']:.1f}h | **${r['est_cost']:.2f}** | **{r['ips_per_dollar']:.1f}** |")

    if rankings:
        top = rankings[0]
        lines.append(f"\n> 🏆 **LỰA CHỌN TỐI ƯU NHẤT (P/P)**: **{top['gpu']}** là cỗ máy kinh tế nhất với **{top['ips_per_dollar']:.1f} mẫu / $**, tổng chi phí train full 40 epoch chỉ **${top['est_cost']:.2f}** trong **{top['est_hours']:.1f} giờ**.")

    report_content = "\n".join(lines)
    os.makedirs(os.path.dirname(os.path.abspath(output_md)), exist_ok=True)
    with open(output_md, "w", encoding="utf-8") as f:
        f.write(report_content)

    print(f"\n✅ Comparative report written to: {output_md}")
    print("\n" + report_content)
    return report_content

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Generate Comparative Benchmark Report")
    parser.add_argument("-d", "--results_dir", type=str, default="ocr-training/benchmark_results", help="Directory with benchmark JSONs")
    parser.add_argument("-o", "--output_md", type=str, default="ocr-training/benchmark_results/COMPARATIVE_REPORT.md", help="Output Markdown report path")
    args = parser.parse_args()

    generate_comparative_report(args.results_dir, args.output_md)