begumcig commited on
Commit
5fc63f1
·
1 Parent(s): 0e5bb7f

add: p-image-2-ideogram-benchmarking-results

Browse files
app.py CHANGED
@@ -1,8 +1,17 @@
 
 
1
  from pathlib import Path
2
 
3
  import gradio as gr
4
  import pandas as pd
5
- from gradio_leaderboard import Leaderboard
 
 
 
 
 
 
 
6
 
7
  custom_css = """
8
  .logo {
@@ -16,11 +25,470 @@ custom_css = """
16
  .text {
17
  font-size: 16px !important;
18
  }
19
- .tabs button {
20
- font-size: 20px;
21
- }
22
  .subtabs button {
23
  font-size: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
  h1, h2 {
26
  margin: 0;
@@ -28,207 +496,425 @@ h1, h2 {
28
  }
29
  """
30
 
31
- # override method to avoid bugg
32
- Leaderboard.raise_error_if_incorrect_config = lambda self: None
 
 
 
 
 
 
 
 
 
 
33
 
34
- abs_path = Path(__file__).parent / "data"
 
 
 
 
 
35
 
36
- # Load the JSONL file into a pandas DataFrame using the json library
37
- df = pd.read_json(abs_path / "text_to_image.jsonl", lines=True)
38
 
39
- # Format URL column, handling None/empty URLs
40
- df["URL"] = df.apply(
41
- lambda row: f'<a target="_blank" href="{row["URL"]}" style="color: var(--link-text-color); text-decoration: underline;text-decoration-style: dotted;">link</a>'
42
- if pd.notna(row["URL"]) and row["URL"]
43
- else "",
44
- axis=1,
45
  )
46
 
47
- # Compute aggregate GenEval score if individual GenEval columns exist but "GenEval" doesn't
48
- if "GenEval" not in df.columns:
49
- # Find all GenEval-related columns (e.g., "GenEval (Single Object) (VQA)")
50
- geneval_cols = [col for col in df.columns if col.startswith("GenEval")]
51
- if geneval_cols:
52
- # Compute mean of all GenEval columns, ignoring NaN values
53
- df["GenEval"] = df[geneval_cols].mean(axis=1, skipna=True)
54
- # If all values are NaN, set to NaN
55
- df["GenEval"] = df["GenEval"].where(df[geneval_cols].notna().any(axis=1), None)
56
-
57
- df = df[
58
- [
59
- "URL",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  "Platform",
61
- "Owner",
62
  "Device",
63
  "Model",
64
- "Optimization",
 
 
 
 
 
 
 
65
  "Median Inference Time",
 
 
 
 
66
  "Price per Image",
 
 
 
67
  ]
68
- + [
69
- col
70
- for col in df.columns.tolist()
71
- if col
72
- not in [
73
- "URL",
74
- "Model",
75
- "Median Inference Time",
76
- "Price per Image",
77
- "Platform",
78
- "Owner",
79
- "Device",
80
- "Optimization",
81
- ]
82
  ]
83
- ]
84
 
85
- # Sort by GenEval if it exists, otherwise try other common metrics
86
- sort_column = None
87
- if "GenEval" in df.columns:
88
- sort_column = "GenEval"
89
- elif "HPS (v2.1)" in df.columns:
90
- sort_column = "HPS (v2.1)"
91
- elif "GenAI-Bench (VQA)" in df.columns:
92
- sort_column = "GenAI-Bench (VQA)"
93
- elif len(df.columns) > 0:
94
- # Sort by first numeric column if available
95
  numeric_cols = df.select_dtypes(include=[float, int]).columns.tolist()
96
- if numeric_cols:
97
- sort_column = numeric_cols[0]
98
-
99
- if sort_column:
100
- df = df.sort_values(by=sort_column, ascending=False, na_position="last")
101
- else:
102
- # If no sort column found, just keep original order
103
- pass
104
-
105
- # Format numeric columns to have at most 4 decimal places
106
- numeric_cols = df.select_dtypes(include=[float, int]).columns.tolist()
107
- for col in numeric_cols:
108
- df[col] = df[col].apply(lambda x: round(x, 4) if pd.notna(x) else x)
109
-
110
- with gr.Blocks("ParityError/Interstellar", fill_width=True, css=custom_css) as demo:
111
- gr.HTML(
112
- """
113
- <div style="text-align: center;">
114
- <img src="https://huggingface.co/datasets/PrunaAI/documentation-images/resolve/main/inferbench/logo2-cropped.png" style="width: 200px; height: auto; max-width: 100%; margin: 0 auto;">
115
- <h1>🏋️ InferBench 🏋️</h1>
116
- <h2>A cost/quality/speed Leaderboard for Inference Providers!</h2>
117
- </div>
118
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  )
120
- with gr.Tabs():
121
- with gr.TabItem("Text-to-Image Leaderboard [WIP]"):
122
- Leaderboard(
123
- value=df,
124
- select_columns=df.columns.tolist(),
125
- datatype=[
126
- "markdown",
127
- "markdown",
128
- "markdown",
129
- "markdown",
130
- "markdown",
131
- "markdown",
132
- ]
133
- + ["number"] * (len(df.columns.tolist()) - 6),
134
- filter_columns=[
135
- "Platform",
136
- "Owner",
137
- "Device",
138
- "Model",
139
- "Optimization",
140
- ],
141
- )
142
- gr.Markdown(
143
- """
144
- > **💡 Note:** Each efficiency metric and quality metric captures only one dimension of model capacity. Rankings may vary when considering other metrics. This leaderboard is a work in progress and will be updated regularly. For now, some metrics are not computed on the entire benchmark.
145
- """
146
- )
147
- with gr.TabItem("About"):
148
- with gr.Row():
149
- with gr.Column():
150
- gr.Markdown(
151
- """
152
- # 📊 Text-to-Image Leaderboard
153
-
154
- This leaderboard compares the performance of different text-to-image providers.
155
 
156
- We started with a comprehensive benchmark comparing our very own FLUX-juiced with the “FLUX.1 [dev]” endpoints offered by:
157
-
158
- - Replicate: https://replicate.com/black-forest-labs/flux-dev
159
- - Fal: https://fal.ai/models/fal-ai/flux/dev
160
- - Fireworks AI: https://fireworks.ai/models/fireworks/flux-1-dev-fp8
161
- - Together AI: https://www.together.ai/models/flux-1-dev
162
-
163
- We also included the following non-FLUX providers:
164
-
165
- - AWS Nova Canvas: https://aws.amazon.com/ai/generative-ai/nova/creative/
166
-
167
- All of these inference providers offer implementations but they don’t always communicate about the optimisation methods used in the background, and most endpoint have different response times and performance measures.
168
-
169
- For comparison purposes we used the same generation set-up for all the providers.
170
-
171
- - 28 inference steps
172
- - 1024×1024 resolution
173
- - Guidance scale of 3.5
174
- - H100 GPU (80GB)—only reported by Replicate
175
-
176
- Although we did test with this specific Pruna configuration and hardware, the applied compression methods work with different config and hardware too!
177
-
178
- > We published a full blog post on [the creation of our FLUX-juiced endpoint](https://www.pruna.ai/blog/flux-juiced-the-fastest-image-generation-endpoint).
179
- """
180
- )
181
- with gr.Column():
182
- gr.Markdown(
183
- """
184
- # 🧃 FLUX.1-dev (juiced)
185
-
186
- FLUX.1-dev (juiced) is our optimized version of FLUX.1-dev, delivering up to **2.6x faster inference** than the official Replicate API, **without sacrificing image quality**.
187
 
188
- Under the hood, it uses a custom combination of:
189
 
190
- - **Graph compilation** for optimized execution paths
191
- - **Inference-time caching** for repeated operations
192
 
193
- We won’t go deep into the internals here, but here’s the gist:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
- > We combine compiler-level execution graph optimization with selective caching of heavy operations (like attention layers), allowing inference to skip redundant computations without any loss in fidelity.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
- These techniques are generalized and plug-and-play via the **Pruna Pro** pipeline, and can be applied to nearly any diffusion-based image model—not just FLUX. For a free but still very juicy model you can use our open source solution.
 
 
 
 
 
 
 
 
 
 
 
198
 
199
- > 🧪 Try FLUX-juiced now → [replicate.com/prunaai/flux.1-juiced](https://replicate.com/prunaai/flux.1-juiced)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
- ## Sample Images
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
- The prompts were randomly sampled from the [parti-prompts dataset](https://github.com/google-research/parti). The reported times represent the full duration of each API call.
204
 
205
- > **For samples, check out the [Pruna Notion page](https://pruna.notion.site/FLUX-1-dev-vs-Pruna-s-FLUX-juiced-1d270a039e5f80c6a2a3c00fc0d75ef0)**
206
- """
207
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
- with gr.Accordion("🌍 Join the Pruna AI community!", open=False):
210
- gr.HTML(
211
- """
212
- <a rel="nofollow" href="https://twitter.com/PrunaAI"><img alt="Twitter" src="https://img.shields.io/twitter/follow/PrunaAI?style=social"></a>
213
- <a rel="nofollow" href="https://github.com/PrunaAI/pruna"><img alt="GitHub" src="https://img.shields.io/github/stars/prunaai/pruna"></a>
214
- <a rel="nofollow" href="https://www.linkedin.com/company/93832878/admin/feed/posts/?feedType=following"><img alt="LinkedIn" src="https://img.shields.io/badge/LinkedIn-Connect-blue"></a>
215
- <a rel="nofollow" href="https://discord.com/invite/rskEr4BZJx"><img alt="Discord" src="https://img.shields.io/badge/Discord-Join%20Us-blue?style=social&amp;logo=discord"></a>
216
- <a rel="nofollow" href="https://www.reddit.com/r/PrunaAI/"><img alt="Reddit" src="https://img.shields.io/reddit/subreddit-subscribers/PrunaAI?style=social"></a>
217
- """
218
- )
219
- with gr.Accordion("Citation", open=True):
220
- gr.Markdown(
221
- """
222
- ```bibtex
223
- @article{InferBench,
224
- title={InferBench: A Leaderboard for Inference Providers},
225
- author={PrunaAI},
226
- year={2025},
227
- howpublished={\\url{https://huggingface.co/spaces/PrunaAI/InferBench}}
228
- }
229
- ```
230
- """
231
  )
 
 
 
232
 
233
 
234
  if __name__ == "__main__":
 
1
+ import json
2
+ from collections import defaultdict
3
  from pathlib import Path
4
 
5
  import gradio as gr
6
  import pandas as pd
7
+
8
+ from ui import (
9
+ render_about,
10
+ render_benchmarks,
11
+ render_footer,
12
+ render_header,
13
+ render_home,
14
+ )
15
 
16
  custom_css = """
17
  .logo {
 
25
  .text {
26
  font-size: 16px !important;
27
  }
28
+ .tabs button,
29
+ .tab-nav button,
 
30
  .subtabs button {
31
  font-size: 20px;
32
+ color: #581c87 !important;
33
+ }
34
+ .tabs button.selected,
35
+ .tabs button[aria-selected="true"],
36
+ .tab-nav button.selected,
37
+ .tab-nav button[aria-selected="true"],
38
+ .subtabs button.selected,
39
+ .subtabs button[aria-selected="true"] {
40
+ color: #ffffff !important;
41
+ background: #7c3aed !important;
42
+ border-color: #6d28d9 !important;
43
+ }
44
+ .dark .tabs button,
45
+ .dark .tab-nav button,
46
+ .dark .subtabs button {
47
+ color: #e9d5ff !important;
48
+ }
49
+ .dark .tabs button.selected,
50
+ .dark .tabs button[aria-selected="true"],
51
+ .dark .tab-nav button.selected,
52
+ .dark .tab-nav button[aria-selected="true"],
53
+ .dark .subtabs button.selected,
54
+ .dark .subtabs button[aria-selected="true"] {
55
+ color: #ffffff !important;
56
+ background: #6d28d9 !important;
57
+ }
58
+ .benchmark-catalogue-row {
59
+ width: 100%;
60
+ gap: 16px;
61
+ }
62
+ .benchmark-card {
63
+ width: 100%;
64
+ max-width: none;
65
+ min-height: 220px;
66
+ border: 1px solid rgba(124, 58, 237, 0.45);
67
+ border-left: 4px solid #7c3aed;
68
+ border-radius: 12px;
69
+ background: rgba(139, 92, 246, 0.14);
70
+ padding: 16px 18px;
71
+ box-shadow: 0 4px 12px rgba(76, 29, 149, 0.12);
72
+ }
73
+ .benchmark-card:hover {
74
+ border-color: #5b21b6;
75
+ box-shadow: 0 6px 18px rgba(76, 29, 149, 0.22);
76
+ }
77
+ .community-footer {
78
+ margin-top: 24px;
79
+ padding: 18px 20px;
80
+ border: 1px solid #c4b5fd;
81
+ border-radius: 12px;
82
+ background: rgba(139, 92, 246, 0.16);
83
+ }
84
+ .community-footer h3 {
85
+ margin: 0 0 10px;
86
+ color: #581c87;
87
+ font-size: 1.05rem;
88
+ }
89
+ .community-footer-links {
90
+ display: flex;
91
+ flex-wrap: wrap;
92
+ gap: 10px 14px;
93
+ align-items: center;
94
+ }
95
+ .dark .community-footer {
96
+ border-color: #6d28d9;
97
+ background: rgba(76, 29, 149, 0.35);
98
+ }
99
+ .dark .community-footer h3 {
100
+ color: #f5f3ff;
101
+ }
102
+ .home-callouts {
103
+ display: grid;
104
+ grid-template-columns: repeat(3, minmax(0, 1fr));
105
+ gap: 12px;
106
+ margin: 8px 0 20px;
107
+ }
108
+ .home-callouts > div {
109
+ padding: 14px 16px;
110
+ border: 1px solid #a78bfa;
111
+ border-radius: 10px;
112
+ background: #f5f3ff;
113
+ }
114
+ .home-callouts span {
115
+ color: #5b21b6;
116
+ font-size: 11px;
117
+ font-weight: 700;
118
+ letter-spacing: 0.05em;
119
+ }
120
+ .home-callouts strong {
121
+ display: block;
122
+ margin-top: 6px;
123
+ color: #1e1b4b;
124
+ font-size: 18px;
125
+ word-break: break-word;
126
+ }
127
+ .home-callouts em {
128
+ display: block;
129
+ margin-top: 4px;
130
+ color: #4c1d95;
131
+ font-style: normal;
132
+ font-size: 13px;
133
+ }
134
+ .home-benchmark-card {
135
+ height: 100%;
136
+ padding: 18px 18px 16px;
137
+ border: 1px solid #a78bfa;
138
+ border-left: 4px solid #7c3aed;
139
+ border-radius: 12px;
140
+ background: #f5f3ff;
141
+ box-shadow: 0 4px 12px rgba(76, 29, 149, 0.12);
142
+ }
143
+ .home-benchmark-title {
144
+ color: #1e1b4b;
145
+ font-size: 1.35rem;
146
+ font-weight: 700;
147
+ }
148
+ .home-benchmark-blurb {
149
+ margin: 8px 0 14px;
150
+ color: #312e81;
151
+ font-size: 0.95rem;
152
+ line-height: 1.45;
153
+ }
154
+ .home-top-label {
155
+ color: #5b21b6;
156
+ font-size: 11px;
157
+ font-weight: 700;
158
+ letter-spacing: 0.05em;
159
+ text-transform: uppercase;
160
+ }
161
+ .home-top-list {
162
+ list-style: none;
163
+ margin: 8px 0 0;
164
+ padding: 0;
165
+ }
166
+ .home-top-list li {
167
+ display: grid;
168
+ grid-template-columns: 28px 1fr auto;
169
+ gap: 10px;
170
+ align-items: center;
171
+ padding: 8px 0;
172
+ border-bottom: 1px solid #ddd6fe;
173
+ color: #1e1b4b;
174
+ }
175
+ .home-rank {
176
+ width: 28px;
177
+ height: 28px;
178
+ border-radius: 999px;
179
+ background: #ede9fe;
180
+ color: #5b21b6;
181
+ font-weight: 700;
182
+ font-size: 13px;
183
+ display: inline-flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ }
187
+ .home-model {
188
+ font-weight: 600;
189
+ word-break: break-word;
190
+ color: #1e1b4b;
191
+ }
192
+ .home-score {
193
+ font-variant-numeric: tabular-nums;
194
+ color: #4c1d95;
195
+ font-weight: 600;
196
+ }
197
+ .home-empty {
198
+ color: #4c1d95;
199
+ }
200
+ .dark .home-callouts > div,
201
+ .dark .home-benchmark-card {
202
+ border-color: #7c3aed;
203
+ background: #1e1b4b;
204
+ }
205
+ .dark .home-callouts span,
206
+ .dark .home-top-label,
207
+ .dark .home-rank {
208
+ color: #c4b5fd;
209
+ }
210
+ .dark .home-rank {
211
+ background: #312e81;
212
+ }
213
+ .dark .home-callouts strong,
214
+ .dark .home-benchmark-title,
215
+ .dark .home-model,
216
+ .dark .home-top-list li {
217
+ color: #faf5ff;
218
+ }
219
+ .dark .home-callouts em,
220
+ .dark .home-benchmark-blurb,
221
+ .dark .home-score,
222
+ .dark .home-empty {
223
+ color: #ddd6fe;
224
+ }
225
+ .dark .home-top-list li {
226
+ border-bottom-color: #4c1d95;
227
+ }
228
+ @media (max-width: 700px) {
229
+ .home-callouts {
230
+ grid-template-columns: 1fr;
231
+ }
232
+ }
233
+ .pareto-plot {
234
+ width: 100%;
235
+ }
236
+ .pareto-plot .plotly {
237
+ width: 100% !important;
238
+ }
239
+ .pareto-plot .js-plotly-plot,
240
+ .pareto-plot .plot-container {
241
+ width: 100% !important;
242
+ }
243
+ /* Hide the Plotly toolbar so it doesn't cover points in the top-right. */
244
+ .pareto-plot .modebar {
245
+ display: none !important;
246
+ }
247
+ .leaderboard-summary {
248
+ display: grid;
249
+ grid-template-columns: repeat(4, minmax(0, 1fr));
250
+ gap: 12px;
251
+ margin: 8px 0 16px;
252
+ }
253
+ .leaderboard-summary > div {
254
+ padding: 14px 16px;
255
+ border: 1px solid #c4b5fd;
256
+ border-radius: 10px;
257
+ background: rgba(139, 92, 246, 0.14);
258
+ }
259
+ .leaderboard-summary span,
260
+ .results-heading {
261
+ color: #7e22ce;
262
+ font-size: 11px;
263
+ font-weight: 700;
264
+ letter-spacing: 0.05em;
265
+ }
266
+ .leaderboard-summary strong {
267
+ display: block;
268
+ margin-top: 4px;
269
+ color: #581c87;
270
+ font-size: 22px;
271
+ }
272
+ .leaderboard-controls {
273
+ align-items: end;
274
+ margin-bottom: 8px;
275
+ }
276
+ .ranking-table-host {
277
+ width: 100%;
278
+ }
279
+ .ranking-table {
280
+ margin-top: 4px;
281
+ width: 100%;
282
+ }
283
+ .results-heading {
284
+ display: flex;
285
+ justify-content: space-between;
286
+ margin: 16px 0 8px;
287
+ text-transform: uppercase;
288
+ }
289
+ .ranking-table-scroll {
290
+ overflow-x: auto;
291
+ border: 1px solid #c4b5fd;
292
+ border-radius: 10px;
293
+ background: rgba(139, 92, 246, 0.12);
294
+ }
295
+ .ranking-table table {
296
+ width: 100%;
297
+ min-width: 800px;
298
+ border-collapse: collapse;
299
+ color: #581c87;
300
+ font-size: 14px;
301
+ }
302
+ .ranking-table th {
303
+ padding: 11px 14px;
304
+ border-bottom: 1px solid #c4b5fd;
305
+ color: #7e22ce;
306
+ font-size: 11px;
307
+ letter-spacing: 0.04em;
308
+ text-align: left;
309
+ text-transform: uppercase;
310
+ }
311
+ .ranking-table th.sortable-col {
312
+ cursor: pointer;
313
+ user-select: none;
314
+ white-space: nowrap;
315
+ }
316
+ .ranking-table th.sortable-col:hover {
317
+ color: #4c1d95;
318
+ }
319
+ .ranking-table th.sortable-col.sorted-asc::after,
320
+ .ranking-table th.sortable-col.sorted-desc::after {
321
+ margin-left: 0.35em;
322
+ font-size: 0.85em;
323
+ opacity: 0.85;
324
+ }
325
+ .ranking-table th.sortable-col.sorted-asc::after {
326
+ content: "▲";
327
+ }
328
+ .ranking-table th.sortable-col.sorted-desc::after {
329
+ content: "▼";
330
+ }
331
+ .ranking-table td {
332
+ padding: 13px 14px;
333
+ border-bottom: 1px solid #e9d5ff;
334
+ }
335
+ .ranking-table tbody tr:last-child td {
336
+ border-bottom: 0;
337
+ }
338
+ .ranking-table tbody tr:hover {
339
+ background: #6d28d9;
340
+ }
341
+ .ranking-table tbody tr:hover td,
342
+ .ranking-table tbody tr:hover .rank,
343
+ .ranking-table tbody tr:hover .metric-score,
344
+ .ranking-table tbody tr:hover .model-cell strong,
345
+ .ranking-table tbody tr:hover .model-cell span {
346
+ color: #ffffff;
347
+ }
348
+ .ranking-table .rank {
349
+ color: #7c3aed;
350
+ font-weight: 700;
351
+ }
352
+ .ranking-table .model-cell strong,
353
+ .ranking-table .model-cell span {
354
+ display: block;
355
+ }
356
+ .ranking-table .model-cell span {
357
+ margin-top: 3px;
358
+ color: #9333ea;
359
+ font-size: 12px;
360
+ }
361
+ .ranking-table .metric-score {
362
+ color: #4c1d95;
363
+ font-variant-numeric: tabular-nums;
364
+ font-weight: 700;
365
+ }
366
+ .ranking-table .empty-state {
367
+ color: #9333ea;
368
+ padding: 32px;
369
+ text-align: center;
370
+ }
371
+ @media (prefers-color-scheme: dark) {
372
+ .leaderboard-summary > div,
373
+ .ranking-table-scroll {
374
+ border-color: rgba(221, 214, 254, 0.45);
375
+ }
376
+ .leaderboard-summary span,
377
+ .results-heading,
378
+ .ranking-table th,
379
+ .ranking-table .model-cell span,
380
+ .ranking-table .empty-state {
381
+ color: #ddd6fe;
382
+ }
383
+ .leaderboard-summary strong,
384
+ .ranking-table table,
385
+ .ranking-table .rank,
386
+ .ranking-table .metric-score {
387
+ color: #f5f3ff;
388
+ }
389
+ .ranking-table td {
390
+ border-bottom-color: rgba(221, 214, 254, 0.2);
391
+ }
392
+ }
393
+ .dark .leaderboard-summary > div,
394
+ .dark .ranking-table-scroll {
395
+ border-color: rgba(221, 214, 254, 0.45);
396
+ }
397
+ .dark .leaderboard-summary span,
398
+ .dark .results-heading,
399
+ .dark .ranking-table th,
400
+ .dark .ranking-table .model-cell span,
401
+ .dark .ranking-table .empty-state {
402
+ color: #ddd6fe;
403
+ }
404
+ .dark .leaderboard-summary strong,
405
+ .dark .ranking-table table,
406
+ .dark .ranking-table .rank,
407
+ .dark .ranking-table .metric-score {
408
+ color: #f5f3ff;
409
+ }
410
+ .compare-samples-help {
411
+ color: #6b21a8;
412
+ margin-bottom: 8px;
413
+ }
414
+ .compare-prompt-block {
415
+ margin: 0 0 22px;
416
+ padding: 14px 16px;
417
+ border: 1px solid #c4b5fd;
418
+ border-radius: 12px;
419
+ background: rgba(139, 92, 246, 0.10);
420
+ }
421
+ .compare-prompt-meta {
422
+ display: flex;
423
+ justify-content: space-between;
424
+ gap: 12px;
425
+ margin-bottom: 8px;
426
+ color: #7e22ce;
427
+ font-size: 12px;
428
+ font-weight: 700;
429
+ letter-spacing: 0.04em;
430
+ text-transform: uppercase;
431
+ }
432
+ .compare-prompt-text {
433
+ margin: 0 0 14px;
434
+ color: #3b0764;
435
+ font-size: 15px;
436
+ line-height: 1.45;
437
+ }
438
+ .compare-row {
439
+ display: grid;
440
+ gap: 12px;
441
+ }
442
+ .compare-cell {
443
+ min-width: 0;
444
+ }
445
+ .compare-model-label {
446
+ margin-bottom: 6px;
447
+ color: #5b21b6;
448
+ font-size: 13px;
449
+ font-weight: 700;
450
+ word-break: break-word;
451
+ }
452
+ .compare-cell img {
453
+ display: block;
454
+ width: 100%;
455
+ aspect-ratio: 1 / 1;
456
+ object-fit: cover;
457
+ border-radius: 10px;
458
+ border: 1px solid #ddd6fe;
459
+ background: #f5f3ff;
460
+ }
461
+ .compare-empty {
462
+ padding: 18px;
463
+ border: 1px dashed #c4b5fd;
464
+ border-radius: 10px;
465
+ color: #6b21a8;
466
+ text-align: center;
467
+ }
468
+ .dark .compare-samples-help,
469
+ .dark .compare-prompt-meta,
470
+ .dark .compare-model-label,
471
+ .dark .compare-empty {
472
+ color: #e9d5ff;
473
+ }
474
+ .dark .compare-prompt-block {
475
+ border-color: #6d28d9;
476
+ background: rgba(76, 29, 149, 0.35);
477
+ }
478
+ .dark .compare-prompt-text {
479
+ color: #f5f3ff;
480
+ }
481
+ .dark .compare-cell img {
482
+ border-color: #6d28d9;
483
+ background: #2e1065;
484
+ }
485
+ .dark .ranking-table td {
486
+ border-bottom-color: rgba(221, 214, 254, 0.2);
487
+ }
488
+ @media (max-width: 700px) {
489
+ .leaderboard-summary {
490
+ grid-template-columns: repeat(2, minmax(0, 1fr));
491
+ }
492
  }
493
  h1, h2 {
494
  margin: 0;
 
496
  }
497
  """
498
 
499
+ theme = gr.Theme.from_hub("Werli/Purple-Crimson-Gradio-Theme").set(
500
+ body_background_fill="#c4b5fd",
501
+ body_background_fill_dark="#120B1B",
502
+ body_text_color="#3b0764",
503
+ body_text_color_dark="#ffffff",
504
+ block_background_fill="#ede9fe",
505
+ block_background_fill_dark="#2e1065",
506
+ )
507
+
508
+ space_root = Path(__file__).resolve().parent
509
+ data_dir = space_root / "data"
510
+
511
 
512
+ def _resolve_data_path(*candidates):
513
+ """Prefer Space-local data/, fall back to monorepo parent for local edits."""
514
+ for path in candidates:
515
+ if path.exists():
516
+ return path
517
+ return candidates[0]
518
 
 
 
519
 
520
+ oneig_path = _resolve_data_path(
521
+ data_dir / "oneig_model_price_and_median_generation_time.csv",
522
+ space_root.parent / "oneig_model_price_and_median_generation_time.csv",
 
 
 
523
  )
524
 
525
+
526
+ def load_sample_comparison_data(folder):
527
+ """Load prompts + generations for the Compare samples tab."""
528
+ folder = Path(folder)
529
+ prompts_path = folder / "prompts.jsonl"
530
+ generations_path = folder / "generations.jsonl"
531
+ if not prompts_path.exists() or not generations_path.exists():
532
+ return None
533
+
534
+ prompts = {}
535
+ with prompts_path.open() as handle:
536
+ for line in handle:
537
+ if not line.strip():
538
+ continue
539
+ row = json.loads(line)
540
+ prompts[row["prompt_id"]] = row.get("text", "")
541
+
542
+ images = defaultdict(dict)
543
+ with generations_path.open() as handle:
544
+ for line in handle:
545
+ if not line.strip():
546
+ continue
547
+ row = json.loads(line)
548
+ model_id = row["model_id"]
549
+ prompt_id = row["prompt_id"]
550
+ image_url = row.get("image")
551
+ if model_id and prompt_id and image_url:
552
+ images[model_id][prompt_id] = image_url
553
+
554
+ models = sorted(images)
555
+ if not models or not prompts:
556
+ return None
557
+
558
+ return {
559
+ "prompts": prompts,
560
+ "images": {model: dict(prompt_map) for model, prompt_map in images.items()},
561
+ "models": models,
562
+ "prompt_ids": sorted(prompts),
563
+ }
564
+
565
+
566
+ def format_url_links(value):
567
+ if pd.isna(value) or not value:
568
+ return ""
569
+
570
+ links = [url.strip() for url in str(value).split(",") if url.strip()]
571
+ if not links:
572
+ return ""
573
+
574
+ return " ".join(
575
+ f'<a target="_blank" href="{url}" style="color: var(--link-text-color); text-decoration: underline;text-decoration-style: dotted;">run {idx}</a>'
576
+ for idx, url in enumerate(links, start=1)
577
+ )
578
+
579
+
580
+ def load_oneig_dataframe(path):
581
+ df = pd.read_csv(path, na_values=["N/A", "n/a", ""])
582
+ df = df.rename(
583
+ columns={
584
+ "Owner": "Endpoint Owner",
585
+ "Optimization": "Optimization Details",
586
+ "Anime Alignment Score": "OneIG (Anime Alignment)",
587
+ "Human Alignment Score": "OneIG (Human Alignment)",
588
+ "Object Alignment Score": "OneIG (Object Alignment)",
589
+ "OneIG (Anime and Stylization) (Alignment Score)": "OneIG (Anime Alignment)",
590
+ "OneIG (Portrait) (Alignment Score)": "OneIG (Human Alignment)",
591
+ "OneIG (General Object) (Alignment Score)": "OneIG (Object Alignment)",
592
+ }
593
+ )
594
+ if "URL" in df.columns:
595
+ df["URL"] = df["URL"].apply(format_url_links)
596
+ if "Optimized" in df.columns:
597
+ df["Optimized"] = df["Optimized"].map(
598
+ {True: "Yes", False: "No", "TRUE": "Yes", "FALSE": "No"}
599
+ ).fillna(df["Optimized"])
600
+
601
+ for column in [
602
+ "Price / Image (USD)",
603
+ "Median Generation Time (s)",
604
+ "Min Generation Time (s)",
605
+ "OneIG (Anime Alignment)",
606
+ "OneIG (Human Alignment)",
607
+ "OneIG (Object Alignment)",
608
+ "OneIG Anime Elo",
609
+ "OneIG Human Elo",
610
+ "OneIG Object Elo",
611
+ "P-Judge Overall",
612
+ "Rapidata Elo",
613
+ ]:
614
+ if column in df.columns:
615
+ df[column] = pd.to_numeric(df[column], errors="coerce")
616
+
617
+ preferred_columns = [
618
  "Platform",
619
+ "Endpoint Owner",
620
  "Device",
621
  "Model",
622
+ "Optimized",
623
+ "Optimization Details",
624
+ "OneIG (Anime Alignment)",
625
+ "OneIG (Human Alignment)",
626
+ "OneIG (Object Alignment)",
627
+ "OneIG Anime Elo",
628
+ "OneIG Human Elo",
629
+ "OneIG Object Elo",
630
  "Median Inference Time",
631
+ "Median Inference Time (s)",
632
+ "Median Generation Time (s)",
633
+ "Min Generation Time (s)",
634
+ "Median Total Duration (s)",
635
  "Price per Image",
636
+ "Price / Image (USD)",
637
+ "Evaluation Date (UTC)",
638
+ "URL",
639
  ]
640
+ present_preferred_columns = [col for col in preferred_columns if col in df.columns]
641
+ df = df[
642
+ present_preferred_columns
643
+ + [col for col in df.columns.tolist() if col not in present_preferred_columns]
 
 
 
 
 
 
 
 
 
 
644
  ]
 
645
 
646
+ if "OneIG (Human Alignment)" in df.columns:
647
+ df = df.sort_values(
648
+ by="OneIG (Human Alignment)", ascending=False, na_position="last"
649
+ )
650
+
 
 
 
 
 
651
  numeric_cols = df.select_dtypes(include=[float, int]).columns.tolist()
652
+ for col in numeric_cols:
653
+ df[col] = df[col].apply(lambda x: round(x, 4) if pd.notna(x) else x)
654
+ return df
655
+
656
+
657
+ def load_qwen_combined_dataframe(path):
658
+ """Load the combined Qwen Image Bench metrics table."""
659
+ df = pd.read_csv(path, na_values=["N/A", "n/a", ""])
660
+ model_column = "Rapidata Model" if "Rapidata Model" in df.columns else "Model"
661
+ df = df.rename(columns={model_column: "Model"})
662
+
663
+ # Rows prefixed with '#' are commented-out variants in the source exports.
664
+ df = df[~df["Model"].astype(str).str.startswith("#")].copy()
665
+ df["Model"] = df["Model"].astype(str).str.strip()
666
+
667
+ for column in [
668
+ "Price / Image (USD)",
669
+ "Median Generation Time (s)",
670
+ "Min Generation Time (s)",
671
+ "P-Judge Overall",
672
+ "Rapidata Elo",
673
+ "Datapoint Elo",
674
+ ]:
675
+ if column in df.columns:
676
+ df[column] = pd.to_numeric(df[column], errors="coerce")
677
+
678
+ sort_column = next(
679
+ (
680
+ col
681
+ for col in [
682
+ "Datapoint Elo",
683
+ "P-Judge Overall",
684
+ "Rapidata Elo",
685
+ ]
686
+ if col in df.columns
687
+ ),
688
+ None,
689
  )
690
+ if sort_column:
691
+ df = df.sort_values(by=sort_column, ascending=False, na_position="last")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
 
693
+ numeric_cols = df.select_dtypes(include=[float, int]).columns.tolist()
694
+ for col in numeric_cols:
695
+ df[col] = df[col].apply(lambda x: round(x, 4) if pd.notna(x) else x)
696
+ return df.reset_index(drop=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
 
 
698
 
699
+ df = load_oneig_dataframe(oneig_path)
 
700
 
701
+ oneig_metric_columns = [
702
+ col
703
+ for col in [
704
+ "OneIG (Anime Alignment)",
705
+ "OneIG (Human Alignment)",
706
+ "OneIG (Object Alignment)",
707
+ ]
708
+ if col in df.columns
709
+ ]
710
+ oneig_df = df.copy()
711
+ if oneig_metric_columns:
712
+ oneig_df["OneIG Overall Score"] = oneig_df[oneig_metric_columns].mean(
713
+ axis=1, skipna=True
714
+ )
715
+ oneig_df = oneig_df.sort_values(
716
+ by="OneIG Overall Score", ascending=False, na_position="last"
717
+ )
718
 
719
+ oneig_display_columns = [
720
+ col
721
+ for col in [
722
+ "Platform",
723
+ "Endpoint Owner",
724
+ "Model",
725
+ "Optimized",
726
+ *oneig_metric_columns,
727
+ "OneIG Anime Elo",
728
+ "OneIG Human Elo",
729
+ "OneIG Object Elo",
730
+ "P-Judge Overall",
731
+ "Rapidata Elo",
732
+ "Evaluation Date (UTC)",
733
+ "Median Generation Time (s)",
734
+ "Min Generation Time (s)",
735
+ "Price / Image (USD)",
736
+ "URL",
737
+ ]
738
+ if col in oneig_df.columns
739
+ ]
740
+ # Top-level Leaderboard tab uses the same OneIG table.
741
+ display_columns = oneig_display_columns
742
 
743
+ oneig_combined_dir = _resolve_data_path(
744
+ data_dir / "oneig_combined",
745
+ space_root.parent / "oneig_combined",
746
+ )
747
+ qwen_combined_dir = _resolve_data_path(
748
+ data_dir / "qwen_image_bench_combined",
749
+ space_root.parent / "qwen_image_bench_combined",
750
+ )
751
+ qwen_path = _resolve_data_path(
752
+ data_dir / "qwen_image_bench_model_price_and_median_generation_time.csv",
753
+ space_root.parent / "qwen_image_bench_model_price_and_median_generation_time.csv",
754
+ )
755
 
756
+ qwen_df = load_qwen_combined_dataframe(qwen_path)
757
+ qwen_score_columns = [
758
+ col
759
+ for col in [
760
+ "P-Judge Overall",
761
+ "Datapoint Elo",
762
+ "Rapidata Elo",
763
+ ]
764
+ if col in qwen_df.columns
765
+ ]
766
+ qwen_display_columns = [
767
+ col
768
+ for col in [
769
+ "Model",
770
+ *qwen_score_columns,
771
+ "Raw Win Rate",
772
+ "Median Generation Time (s)",
773
+ "Min Generation Time (s)",
774
+ "Price / Image (USD)",
775
+ ]
776
+ if col in qwen_df.columns
777
+ ]
778
+ qwen_overall_column = (
779
+ "Datapoint Elo"
780
+ if "Datapoint Elo" in qwen_df.columns
781
+ else (qwen_score_columns[0] if qwen_score_columns else None)
782
+ )
783
 
784
+ oneig_samples = load_sample_comparison_data(oneig_combined_dir)
785
+ qwen_samples = load_sample_comparison_data(qwen_combined_dir)
786
+
787
+ # Dataset-first catalogue: each card is a prompt suite; metrics are leaderboard columns.
788
+ benchmarks = [
789
+ {
790
+ "id": "oneig",
791
+ "title": "OneIG Alignment",
792
+ "emoji": "🎯",
793
+ "card_description": (
794
+ "Anime/stylization, portrait, and general-object alignment prompts — "
795
+ "alignment scores and Datapoint Elo (not the full OneIG suite), plus "
796
+ "side-by-side generations."
797
+ ),
798
+ "intro": (
799
+ "OneIG Alignment covers the alignment slice of OneIG (not every OneIG "
800
+ "dimension). The leaderboard shows category alignment scores and Datapoint "
801
+ "Elo columns; Compare samples uses the combined alignment generations."
802
+ ),
803
+ "data": oneig_df,
804
+ "columns": oneig_display_columns,
805
+ "score_columns": oneig_metric_columns,
806
+ "overall_column": "OneIG Overall Score",
807
+ "best_score_label": "BEST ONEIG ALIGNMENT",
808
+ "metrics_same_scale": True,
809
+ "note": (
810
+ "> Rankings are ordered by the mean of the available alignment category "
811
+ "scores. Missing category scores are not included in that model's mean."
812
+ ),
813
+ "samples": oneig_samples,
814
+ },
815
+ {
816
+ "id": "qwen_image_bench",
817
+ "title": "Qwen Image Bench",
818
+ "emoji": "🖼️",
819
+ "card_description": (
820
+ "Qwen image-bench prompts with P-Judge, Datapoint Elo, and Rapidata Elo "
821
+ "as metric columns, plus combined generations for comparison."
822
+ ),
823
+ "intro": (
824
+ "Qwen Image Bench is a shared prompt suite. The leaderboard joins every "
825
+ "available metric for this benchmark; Compare samples uses the combined "
826
+ "Qwen generations."
827
+ ),
828
+ "data": qwen_df,
829
+ "columns": qwen_display_columns,
830
+ "score_columns": qwen_score_columns,
831
+ "overall_column": qwen_overall_column,
832
+ "best_score_label": "BEST DATAPOINT ELO",
833
+ "metrics_same_scale": False,
834
+ "note": (
835
+ "> Models are ordered by Datapoint Elo when available. Other metric "
836
+ "columns come from the P-Judge and Rapidata evaluations on the same "
837
+ "prompt suite."
838
+ ),
839
+ "samples": qwen_samples,
840
+ },
841
+ ]
842
 
 
843
 
844
+ custom_head = """
845
+ <script>
846
+ (() => {
847
+ if (window.__inferbenchLeaderboardSortBound) return;
848
+ window.__inferbenchLeaderboardSortBound = true;
849
+
850
+ document.addEventListener("click", (event) => {
851
+ const th = event.target.closest(".ranking-table th.sortable-col");
852
+ if (!th) return;
853
+
854
+ const table = th.closest("table");
855
+ const tbody = table && table.querySelector("tbody");
856
+ if (!tbody) return;
857
+
858
+ const col = Number(th.dataset.col);
859
+ const type = th.dataset.type || "text";
860
+ const ascending = !th.classList.contains("sorted-asc");
861
+
862
+ table.querySelectorAll("th.sortable-col").forEach((header) => {
863
+ header.classList.remove("sorted-asc", "sorted-desc");
864
+ });
865
+ th.classList.add(ascending ? "sorted-asc" : "sorted-desc");
866
+
867
+ const rows = Array.from(tbody.querySelectorAll("tr"));
868
+
869
+ const parseValue = (row) => {
870
+ const cell = row.children[col];
871
+ const raw = cell ? cell.getAttribute("data-sort-value") : "";
872
+ if (raw === null || raw === "") return null;
873
+ if (type === "number") {
874
+ const number = Number(raw);
875
+ return Number.isFinite(number) ? number : null;
876
+ }
877
+ return raw;
878
+ };
879
+
880
+ rows.sort((left, right) => {
881
+ const a = parseValue(left);
882
+ const b = parseValue(right);
883
+ if (a === null && b === null) return 0;
884
+ if (a === null) return 1;
885
+ if (b === null) return -1;
886
+ if (type === "number") {
887
+ return ascending ? a - b : b - a;
888
+ }
889
+ const cmp = String(a).localeCompare(String(b), undefined, {
890
+ numeric: true,
891
+ sensitivity: "base",
892
+ });
893
+ return ascending ? cmp : -cmp;
894
+ });
895
+
896
+ rows.forEach((row) => tbody.appendChild(row));
897
+ });
898
+ })();
899
+ </script>
900
+ """
901
 
902
+ with gr.Blocks(
903
+ theme=theme, fill_width=True, css=custom_css, head=custom_head
904
+ ) as demo:
905
+ render_header()
906
+ with gr.Tabs():
907
+ with gr.TabItem("Home"):
908
+ render_home(benchmarks)
909
+ with gr.TabItem("Benchmarks") as benchmarks_tab:
910
+ reset_benchmarks, reset_benchmark_outputs = render_benchmarks(benchmarks)
911
+ benchmarks_tab.select(
912
+ reset_benchmarks,
913
+ outputs=reset_benchmark_outputs,
 
 
 
 
 
 
 
 
 
 
914
  )
915
+ with gr.TabItem("About"):
916
+ render_about()
917
+ render_footer()
918
 
919
 
920
  if __name__ == "__main__":
data/oneig_combined/generations.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7630c12b0c629a2a9fe66a40be90da3db8175d6a542b6a3df3440698e5b01940
3
+ size 15535531
data/oneig_combined/prompts.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
data/oneig_model_price_and_median_generation_time.csv ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Model,Price / Image (USD),Min Generation Time (s),Median Generation Time (s),P-Judge Overall,Rapidata Elo,OneIG Anime Elo,OneIG Human Elo,OneIG Object Elo,Anime Alignment Score,Human Alignment Score,Object Alignment Score
2
+ flux_2_pro,0.1,6.451752478955314,9.642776968947146,54.67505979381443,1085.79,,1021.0,1039.0,,0.8434398864526329,0.9019077567491852
3
+ bria_4_fibo,0.04,11.443314291071149,15.73027866682969,56.735806,1040.34,,,1003.0,,,0.86779827444468
4
+ z_image,0.005,1.0492684720084071,1.4465381670743227,51.31612759856628,998.29,986.0,1026.0,991.0,0.8670047225660046,0.8099311841844237,0.8347049973782066
5
+ juggernaut_base_flux,0.035,3.7550464863112816,4.198047187528573,50.01986354515051,1022.26,974.0,989.0,976.0,0.8457611438462199,0.7737633369864975,0.7907706396009133
6
+ juggernaut_lightning_flux,0.006,0.8921283883197854,1.2383652809658088,49.52208461538461,853.78,931.0,960.0,959.0,0.8571674526848435,0.7957690022519136,0.819303783379011
7
+ juggernaut_pro_flux,0.055,3.325652208412066,3.6792007085168734,50.376797993311015,992.19,972.0,1007.0,984.0,0.8490802701542259,0.7894352383959775,0.7921262047886114
8
+ flux_1_1_pro,0.04,2.299360333631436,3.3512960482621565,51.83815123674916,1051.25,1003.0,1005.0,1001.0,0.8716153899908383,0.7846021555377409,0.831820704487019
9
+ flux_1_1_pro_ultra,0.06,6.1039779025595635,8.003180750412866,51.57635594405592,1002.23,1012.0,986.0,1002.0,0.8872399024525827,0.7953850837498103,0.845667366322335
10
+ flux_2_dev,0.025,3.8249364433577284,4.122797580435872,52.64911717171714,1099.69,1019.0,1002.0,1006.0,0.9200030923152858,0.8408774253506177,0.8818942110481547
11
+ flux_2_flex,0.06,7.688491132847655,9.843284582952036,53.50560874125876,1134.04,1052.0,987.0,1009.0,0.9221061950079986,0.83704222320422,0.8993309905484712
12
+ flux_2_max,0.07,15.186774821311701,25.48462438955903,54.56841249999998,1044.7,1076.0,1023.0,1042.0,0.9269662930757988,0.8477831233094394,0.8948951475407387
13
+ flux_dev,0.025,1.3863036371185444,1.6603330307407305,49.75226476510064,908.99,945.0,962.0,955.0,0.8511931856081816,0.7750798964898262,0.798883650395521
14
+ flux_krea,0.025,1.399834333686158,1.647412791440729,51.85929899328858,1007.32,997.0,990.0,979.0,0.8841535719999664,0.8097028670162356,0.8398559499867212
15
+ flux_schnell,0.003,0.8232578075646112,1.044991439965088,49.1443472972973,915.39,892.0,917.0,936.0,0.8656683997302044,0.7814102991777,0.8287984457553442
16
+ imagen_4,0.04,5.230271888663992,8.709110853960738,51.65737018867922,998.27,1023.0,990.0,997.0,0.894310223704374,0.8137015075340288,0.8518758104139295
17
+ imagen_4_fast,0.02,2.946248896070756,4.254744468489662,50.40180337078652,841.12,,951.0,966.0,,0.7680119279393379,0.8228953629726682
18
+ imagen_4_ultra,0.06,8.757600130187347,12.61268443800509,53.72492471482888,1025.6,1066.0,1006.0,1016.0,0.9250454128525762,0.8453951500548762,0.9016222652489544
19
+ nano_banana_2,0.067,16.819758291666705,21.93129874998704,55.173166211604126,1157.47,1063.0,1050.0,1076.0,0.9253234542983964,0.8489246879018892,0.9043643320237978
20
+ nano_banana_pro,0.15,14.906700378924143,20.077973207924515,55.10878327759201,1062.45,1053.0,1034.0,1057.0,0.9097154955052252,0.8465375480054144,0.9036434044908332
21
+ gpt_image_1_5,0.136,29.07584411953576,37.961403958383016,53.76628423913046,1188.48,1101.0,1066.0,,0.9233708744477797,0.8522874700035479,
22
+ hidream_i1_dev,0.0086,1.9823376253868144,2.192608500132337,50.74670167224078,1118.03,1009.0,1007.0,1012.0,0.8720575175843863,0.7897814812758762,0.8188972054038817
23
+ hidream_i1_fast,0.0051,1.2573314025842894,1.6601914172060788,50.70580066889632,1081.48,998.0,991.0,994.0,0.8674483659597709,0.7840883510194656,0.82328148897844
24
+ hidream_i1_full,0.014,5.691079417127185,5.840440698026214,49.225683919598,945.34,906.0,,938.0,0.8629144755944692,,0.8163581350743205
25
+ p_image,0.005,1.024232491850853,1.0473625104641542,50.47380301003343,955.42,964.0,1011.0,982.0,0.844481587914448,0.7973493346957257,0.7888656304845173
26
+ p_image_2_ideogram_high_1k,0.015,3.1836699013329053,4.883333333333334,54.77,934.81,1014.0,1015.0,1029.0,0.8629618560618941,0.8063002387189694,0.8547826116307153
27
+ p_image_2_ideogram_high_2k,0.03,7.530538211332896,9.75,54.09,913.17,996.0,1028.0,1023.0,0.8803449901921264,0.8307037713318671,0.8615976872714485
28
+ p_image_2_ideogram_low_1k,0.0075,1.8912416643330896,4.97,53.71,864.21,1024.0,1014.0,1040.0,0.8644190279319922,0.8050990548194494,0.8358910999561152
29
+ p_image_2_ideogram_low_2k,0.016,4.135335521333521,6.163333333333334,53.31,908.63,990.0,1011.0,1004.0,0.8801673257513037,0.8248396465935166,0.8412710602520973
30
+ p_image_2_ideogram_medium_1k,0.01,2.1058022906678766,3.0533333333333337,54.27,967.5,1020.0,1028.0,1053.0,0.8560997548632995,0.8094780142672943,0.847484535459381
31
+ p_image_2_ideogram_medium_2k,0.02,6.427383309333284,9.16,53.27,933.86,988.0,1024.0,994.0,0.8687718766012108,0.820451078677085,0.8418239715865493
32
+ p_image_2_ideogram_very_low_1k,0.003,1.9598290003329264,3.4333333333333336,53.19,896.97,995.0,1020.0,1028.0,0.8632302099560225,0.7934524734166761,0.8255385460772456
33
+ p_image_2_ideogram_very_low_2k,0.006,2.9796522429999945,5.556666666666667,52.12,890.07,983.0,990.0,974.0,0.8751773251258547,0.8240413668933404,0.8418756147962801
34
+ sdxl_lightning,0.0014,1.3275378193551053,1.8803678539115936,,866.88,904.0,876.0,941.0,0.7328890298342023,0.6689303836044653,0.7324141421177138
35
+ wan_2_2_image,0.02,2.6036043955245987,2.830037875450216,49.8521512562814,1000.45,,998.0,995.0,,0.7878415998328734,0.821705707700177
36
+ qwen_image,0.025,3.419045856843392,3.5670732414582744,51.27888993288592,1176.37,1008.0,1004.0,966.0,0.905957670968254,0.8277556042985756,0.870535385375026
37
+ hunyuan_image_3,0.08,18.571135867697496,18.82112309767399,52.848213103448295,1065.81,1014.0,991.0,1013.0,0.8981869529944881,0.8246260239317298,0.8582881719127173
38
+ qwen_image_fast,0.021,5.7156417078804225,6.883911125012673,51.37910050505055,1127.4,,999.0,978.0,,0.8236622305555534,0.8629531817993643
39
+ seedream_3,0.018,5.998649687040597,7.340932093793526,53.91764270833332,1157.55,,1031.0,1034.0,,0.8268986114060047,0.879781957515387
40
+ sdxl,0.0019,3.6809837920591235,4.272879093885422,46.38236185567008,766.4,,868.0,953.0,,0.6990518231924773,0.764374879014914
data/qwen_image_bench_combined/generations.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:98eb8211f242274d3afea988aacad318b13642f98d3aef1a371c40491a032afd
3
+ size 5207632
data/qwen_image_bench_combined/prompts.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
data/qwen_image_bench_model_price_and_median_generation_time.csv ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Model,Price / Image (USD),Median Generation Time (s),Min Generation Time (s),P-Judge Overall,Raw Win Rate,Rapidata Elo,Datapoint Elo,Benchmark.ai Elo
2
+ reve_2_1,N/A,N/A,28.3,,,,,1173.2
3
+ ideogram_4_0_quality,N/A,N/A,66.6,,,,,1131.0
4
+ gpt_image_2,0.21,77.8,77.8,59.02083099999998,,1172.58,1116,1124.5
5
+ nano_banana_2_0,N/A,N/A,N/A,56.362956,59.4%,1071.79,1067,1056.3
6
+ gpt_image_1_5,0.135,38.0,38.0,57.864434999999986,64.5%,1102.09,1064,910.3
7
+ hidream_i1_dev,0.0086,2.823569217998738,2.06,49.17682099999999,51.7%,999.17,984,
8
+ gpt_image_1,0.167,38.8,38.8,54.975741000000006,,1095.39,987,
9
+ imagen_4_0_ultra,0.06,11.4,11.4,53.385603999999965,59.7%,1075.22,1023,
10
+ flux_2_flex,0.06,10.856533817990567,8.07,53.9245676767677,57.8%,1054.34,1018,921.3
11
+ qwen_image,0.025,4.8,4.8,51.561746,51.5%,1073.72,1005,975.6
12
+ seedream_5_0,N/A,N/A,N/A,55.715153,,1070.35,1012,
13
+ nano_banana_pro,0.134,17.2,17.2,56.452189898989914,58.0%,1029.51,1045,1102.4
14
+ hidream_i1_fast,0.0051,9.920469530501578,1.40,48.89298600000002,50.1%,1024.67,984,
15
+ imagen_4_fast,0.02,3.7531301500021073,2.71,50.155055208333344,,972.6,981,
16
+ seedream_4_5,0.04,16.6,16.6,55.66328800000001,,1048.96,1035,964.2
17
+ seedream_4_0,0.03,12.2,12.2,55.241443999999994,,1050.41,1035,
18
+ p_image_2_ideogram_low_1k,0.0075,2.59,1.55,54.827397,53.2%,1000.07,1009,1103.9
19
+ #p_image_2_ideogram_low_2k,0.016,5.17,4.25,53.971723,48.4%,1074.91,1003,
20
+ qwen_image_2_0_pro,0.035,35.5,35.5,56.181776,,1033.25,1017,959.9
21
+ juggernaut_base_flux,0.035,4.115834823496698,3.84,49.474676,46.4%,1038.22,972,
22
+ flux_2_pro,N/A,N/A,N/A,54.43423900000002,,993.54,1019,1011.6
23
+ z_image,0.005,1.5122045120006078,1.22,49.946227999999984,48.1%,1028.1,1001,
24
+ p_image_2_ideogram_high_1k,0.015,4.28,3.07,55.754507999999994,52.1%,972.92,1022,1104.0
25
+ #p_image_2_ideogram_high_2k,0.03,8.69,7.12,54.757842000000004,49.4%,1074.91,1007,
26
+ qwen_image_2512,0.02,19.1,19.1,51.677326,,1029.6,1009,
27
+ flux_2_max,0.07,26.4,26.4,54.047976,60.9%,955.19,1028,1001.6
28
+ flux_1_1_pro_ultra,0.06,9.026992494000297,6.20,50.358445,52.0%,979.56,995,
29
+ juggernaut_pro_flux,0.055,3.699636150500737,3.36,49.741183,46.4%,972.14,964,
30
+ flux_dev,0.025,1.6931055715031107,1.49,48.241479,42.1%,925.04,940,
31
+ p_image_2_ideogram_very_low_1k,0.003,2.56,1.49,53.51894200000001,48.1%,959.86,995,1071.7
32
+ #p_image_2_ideogram_very_low_2k,0.006,3.94,3.05,53.68248699999998,47.2%,962.38,980,
33
+ #p_image_2_ideogram_very_low_1k_no_upsampling,0.005,0.82,,46.33,,,,1071.7
34
+ #p_image_2_ideogram_very_low_2k_no_upsampling,0.005,2.18,,46.39,,,,1071.7
35
+ #p_image_2_ideogram_low_1k_no_upsampling,0.01,3.33,,45.32,,,,1103.9
36
+ #p_image_2_ideogram_low_2k_no_upsampling,0.01,3.34,,46.25,,,,1103.9
37
+ #p_image_2_ideogram_medium_1k_no_upsampling,0.015,2.13,,46.88,,,,1115.1
38
+ #p_image_2_ideogram_medium_2k_no_upsampling,0.015,5.55,,47.11,,,,1115.1
39
+ #p_image_2_ideogram_high_1k_no_upsampling,0.03,3.11,,46.88,,,,1104.0
40
+ #p_image_2_ideogram_high_2k_no_upsampling,0.03,5.55,,47.06,,,,1104.0
41
+ hidream_i1_full,0.014,6.008430051002506,5.69,46.82559300000002,36.9%,955.46,944,
42
+ flux_2_dev,0.025,4.310278721997747,4.03,52.71644489795918,53.1%,1007.6,1021,942.0
43
+ imagen_4_0,0.04,14.1,14.1,52.08996199999999,53.5%,979.62,1005,
44
+ wan_2_2_image,0.02,3.005390542501118,2.96,48.19959399999999,,944.87,960,
45
+ flux_krea,0.025,1.7150160090022837,1.7150160090022837,50.35734,50.0%,919.73,975,
46
+ p_image_2_ideogram_medium_1k,0.01,3.06,2.05,54.719193000000004,51.7%,941.46,1002,1115.1
47
+ #p_image_2_ideogram_medium_2k,0.02,7.44,6.47,54.23124444444446,48.1%,949.35,1000,
48
+ glm_image,0.05,188.2,188.2,51.42623399999999,,923.35,953,
49
+ p_image,0.005,1.0640762715011078,0.95,48.75217099999999,44.8%,924.37,961,1098.7
50
+ hunyuanimage_3_0,0.09,41.0,41.0,52.32440099999998,52.4%,1009.61,979,765.3
51
+ juggernaut_lightning_flux,0.006,1.1787893719956628,0.93,48.30471699999998,40.5%,916.68,929,
52
+ flux_1_1_pro,0.04,3.0104645500032348,2.34,49.92882700000001,50.6%,925.04,984,
53
+ flux_schnell,0.003,0.8411653029907029,0.80,46.685981818181816,34.8%,892.32,915,
54
+ kling_v2_1,N/A,N/A,N/A,51.044512,,870.04,981,
55
+ #p_image_2_ideogram_very_high_high_1k,0.075,9.34,7.25,58.45,,,1025,
56
+ #p_image_2_ideogram_very_high_low_1k,0.0375,26.17,10.44,57.59,,,1024,
57
+ #p_image_2_ideogram_very_high_medium_1k,0.05,9.28,5.96,57.88,,,1020,
58
+ #p_image_2_ideogram_very_high_very_low_1k,0.015,26.52,10.60,56.92,,,1002,
59
+ #p_image_2_ideogram_final_1k,0.0375,11.17,5.55,58.28,,,,
60
+ #p_image_2_ideogram_final_2k,0.075,14.34,9.96,57.68,,,,
requirements.txt CHANGED
@@ -1 +1,2 @@
1
- gradio-leaderboard
 
 
1
+ gradio-leaderboard
2
+ plotly
ui.py ADDED
@@ -0,0 +1,1123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from html import escape
2
+ import random
3
+
4
+ import gradio as gr
5
+ import pandas as pd
6
+ import plotly.graph_objects as go
7
+
8
+
9
+ FILTER_COLUMNS = ["Platform", "Endpoint Owner", "Model", "Optimized"]
10
+ MAX_COMPARE_MODELS = 4
11
+ DEFAULT_COMPARE_PROMPTS = 3
12
+ MAX_COMPARE_PROMPTS = 8
13
+
14
+ ALL_LEADERBOARD_NOTE = """
15
+ > **💡 Note:** Each efficiency metric and quality metric captures only one dimension of
16
+ > model capacity. Rankings may vary when considering other metrics. "Endpoint Owner"
17
+ > refers to the publisher of the evaluated endpoint and may differ from the original
18
+ > model creator. "Optimized" indicates that the endpoint uses an explicit optimization
19
+ > mode or serves a modified/accelerated variant.
20
+ """
21
+
22
+ ONEIG_SCORES_CONTENT = """
23
+ ### Reported OneIG scores
24
+
25
+ - **Anime Alignment** — alignment for anime and stylization prompts
26
+ - **Human Alignment** — alignment for portrait prompts
27
+ - **Object Alignment** — alignment for general-object prompts
28
+
29
+ The leaderboard's **OneIG Overall Score** is the mean of each model's available
30
+ category scores.
31
+ """
32
+
33
+ ONEIG_RUN_DETAILS_CONTENT = """
34
+ ### Reading the results
35
+
36
+ Each row records the evaluation date, generation time, price per image, and links to
37
+ the source evaluation runs when available. Use the filters in the leaderboard to
38
+ compare providers, models, and optimized variants.
39
+ """
40
+
41
+ P_JUDGE_SCORES_CONTENT = """
42
+ ### Reported P-Judge scores
43
+
44
+ - **P-Judge Overall** — automatic preference / quality score for generated images
45
+
46
+ Higher scores indicate stronger performance on the P-Judge evaluation.
47
+ """
48
+
49
+ P_JUDGE_RUN_DETAILS_CONTENT = """
50
+ ### Reading the results
51
+
52
+ Each row records the evaluation date, generation time, and price per image when
53
+ available. Rankings are ordered by **P-Judge Overall**.
54
+ """
55
+
56
+ DATAPOINT_ELO_SCORES_CONTENT = """
57
+ ### Reported Datapoint Elo scores
58
+
59
+ - **Datapoint Elo** — human-preference Elo rating from pairwise comparisons
60
+
61
+ Elo values move whenever new models are added and re-evaluated against the field,
62
+ so the Date column reflects the leaderboard snapshot date.
63
+ """
64
+
65
+ DATAPOINT_ELO_RUN_DETAILS_CONTENT = """
66
+ ### Reading the results
67
+
68
+ Each row records the snapshot date, generation time, and price per image when
69
+ available. Rankings are ordered by **Datapoint Elo**.
70
+ """
71
+
72
+ RAPIDATA_ELO_SCORES_CONTENT = """
73
+ ### Reported Rapidata Elo scores
74
+
75
+ - **Rapidata Elo** — Elo rating from the Rapidata evaluation suite
76
+
77
+ Higher Elo indicates stronger relative performance on that suite.
78
+ """
79
+
80
+ RAPIDATA_ELO_RUN_DETAILS_CONTENT = """
81
+ ### Reading the results
82
+
83
+ Each row records generation time and price per image when available. Rankings are
84
+ ordered by **Rapidata Elo**.
85
+ """
86
+
87
+ BENCHMARK_AI_ELO_SCORES_CONTENT = """
88
+ ### Reported Benchmark.ai Elo scores
89
+
90
+ - **Benchmark.ai Elo** — Elo rating from the Benchmark.ai leaderboard
91
+
92
+ This score is not from the Qwen Image Bench prompt suite; it is shown alongside for
93
+ cross-benchmark comparison.
94
+ """
95
+
96
+ BENCHMARK_AI_ELO_RUN_DETAILS_CONTENT = """
97
+ ### Reading the results
98
+
99
+ Each row records generation time and price per image when available. Rankings for
100
+ this metric alone are ordered by **Benchmark.ai Elo**.
101
+ """
102
+
103
+ ABOUT_OVERVIEW_CONTENT = """
104
+ # About InferBench
105
+
106
+ InferBench compares **text-to-image models** on quality, preference, latency, and
107
+ price. Results are organized by prompt suite (benchmark), not by a single opaque
108
+ score.
109
+
110
+ ## What you can do here
111
+
112
+ - **Home** — snapshot of each prompt suite and unique model count.
113
+ - **Benchmarks** — open a prompt suite to see its metric columns, graphs (including a
114
+ quality-vs-price Pareto frontier), and side-by-side sample generations.
115
+ - **About** — this page.
116
+
117
+ ## Current prompt suites
118
+
119
+ ### OneIG Alignment
120
+ The **alignment** slice of OneIG (not the full OneIG suite), across three categories:
121
+
122
+ - Anime / stylization
123
+ - Human / portrait
124
+ - General object
125
+
126
+ The table reports category **alignment scores**, **Datapoint Elo** columns (Anime /
127
+ Human / Object), median / min generation time, and price per image. Rankings use the
128
+ mean of each model's available category alignment scores (missing categories are
129
+ skipped for that model).
130
+
131
+ ### Qwen Image Bench
132
+ A shared prompt suite with multiple evaluation tracks shown as columns:
133
+
134
+ - **P-Judge Overall** — automatic preference / quality score
135
+ - **Datapoint Elo** — human-preference Elo (default sort key)
136
+ - **Rapidata Elo** — Elo from the Rapidata evaluation on this suite
137
+
138
+ Plus latency and price metadata, and combined generations for visual comparison.
139
+
140
+ ## How to read the numbers
141
+
142
+ Quality metrics from different suites are **not interchangeable** — a high OneIG
143
+ Alignment score is not the same quantity as a Datapoint Elo. Prefer comparing models
144
+ *within* a benchmark column, and use price / generation time when you care about
145
+ efficiency.
146
+
147
+ The Pareto plot highlights models that are not dominated on both **higher score** and
148
+ **lower price**.
149
+ """
150
+
151
+ ABOUT_DETAILS_CONTENT = """
152
+ # Data & caveats
153
+
154
+ - Endpoint **price** and **generation time** come from the evaluation snapshots linked
155
+ in each table where available.
156
+ - Some models are missing individual metric columns; empty cells mean that track was
157
+ not run (or not reported) for that model.
158
+ - Elo ratings can shift when the comparison pool changes — treat them as relative
159
+ rankings for the snapshot, not absolute constants.
160
+ - Optimized / accelerated endpoints (when labeled) may differ from the base model
161
+ publisher's default serving stack.
162
+
163
+ Built by [Pruna AI](https://www.pruna.ai/). Contributions and new evaluation runs welcome.
164
+ """
165
+
166
+ COMMUNITY_CONTENT = """
167
+ <div class="community-footer">
168
+ <h3>Join the Pruna AI community</h3>
169
+ <div class="community-footer-links">
170
+ <a rel="nofollow" href="https://x.com/PrunaAI" target="_blank">X / Twitter</a>
171
+ <a rel="nofollow" href="https://github.com/PrunaAI/pruna" target="_blank">GitHub</a>
172
+ <a rel="nofollow" href="https://www.linkedin.com/company/pruna-ai" target="_blank">LinkedIn</a>
173
+ <a rel="nofollow" href="https://discord.com/invite/rskEr4BZJx" target="_blank">Discord</a>
174
+ <a rel="nofollow" href="https://www.reddit.com/r/PrunaAI/" target="_blank">Reddit</a>
175
+ <a rel="nofollow" href="https://www.pruna.ai/" target="_blank">pruna.ai</a>
176
+ </div>
177
+ </div>
178
+ """
179
+
180
+ CITATION_CONTENT = """
181
+ ```bibtex
182
+ @misc{InferBench,
183
+ title={InferBench: A Leaderboard for Text-to-Image Models},
184
+ author={PrunaAI},
185
+ year={2026},
186
+ howpublished={\\url{https://huggingface.co/spaces/PrunaAI/InferBench}}
187
+ }
188
+ ```
189
+ """
190
+
191
+
192
+ def render_header():
193
+ gr.HTML(
194
+ """
195
+ <div style="text-align: center;">
196
+ <h1>InferBench</h1>
197
+ <h2>Compare text-to-image models on quality, speed, and price</h2>
198
+ </div>
199
+ """
200
+ )
201
+
202
+
203
+ def _top_models(data, score_column, n=3):
204
+ if score_column not in data.columns or "Model" not in data.columns:
205
+ return []
206
+ ranked = (
207
+ data[["Model", score_column]]
208
+ .dropna(subset=[score_column])
209
+ .loc[lambda df: ~df["Model"].astype(str).str.startswith("#")]
210
+ .sort_values(score_column, ascending=False)
211
+ .head(n)
212
+ )
213
+ return [
214
+ (str(row["Model"]), float(row[score_column]))
215
+ for _, row in ranked.iterrows()
216
+ ]
217
+
218
+
219
+ def _home_highlights(benchmarks):
220
+ """Quality leaders per suite — more relevant than cheapest/fastest outliers."""
221
+ highlights = []
222
+ unique_models = set()
223
+ for benchmark in benchmarks:
224
+ data = benchmark.get("data")
225
+ if data is None or "Model" not in getattr(data, "columns", []):
226
+ continue
227
+ active = data[~data["Model"].astype(str).str.startswith("#")]
228
+ unique_models.update(active["Model"].astype(str).tolist())
229
+
230
+ score_column = benchmark.get("overall_column")
231
+ score_columns = benchmark.get("score_columns") or []
232
+ if not score_column or score_column not in data.columns:
233
+ score_column = score_columns[0] if score_columns else None
234
+ top = _top_models(data, score_column, n=1) if score_column else []
235
+ if not top:
236
+ continue
237
+ model, score = top[0]
238
+ highlights.append(
239
+ {
240
+ "label": f"BEST {benchmark['title'].upper()}",
241
+ "model": model,
242
+ "detail": f"{_display_label(score_column)} · {_format_score(score)}",
243
+ }
244
+ )
245
+
246
+ if unique_models:
247
+ highlights.append(
248
+ {
249
+ "label": "MODELS SCORED",
250
+ "model": str(len(unique_models)),
251
+ "detail": "unique across prompt suites",
252
+ }
253
+ )
254
+ return highlights
255
+
256
+
257
+ def render_home(benchmarks):
258
+ highlights = _home_highlights(benchmarks)
259
+
260
+ gr.Markdown(
261
+ """
262
+ InferBench is organized by **prompt suite**. There is no single global
263
+ “best model” score — open a benchmark for full tables, graphs, and sample
264
+ comparisons.
265
+ """
266
+ )
267
+
268
+ if highlights:
269
+ callout_bits = [
270
+ f"<div><span>{escape(item['label'])}</span>"
271
+ f"<strong>{escape(item['model'])}</strong>"
272
+ f"<em>{escape(item['detail'])}</em></div>"
273
+ for item in highlights
274
+ ]
275
+ gr.HTML(f'<div class="home-callouts">{"".join(callout_bits)}</div>')
276
+
277
+ gr.Markdown("### Benchmark snapshots")
278
+ with gr.Row(equal_height=True, elem_classes="benchmark-catalogue-row"):
279
+ for benchmark in benchmarks:
280
+ data = benchmark["data"]
281
+ score_column = benchmark.get("overall_column")
282
+ score_columns = benchmark.get("score_columns") or []
283
+ if not score_column or score_column not in data.columns:
284
+ score_column = score_columns[0] if score_columns else None
285
+ top = _top_models(data, score_column, n=3) if score_column else []
286
+ score_label = _display_label(score_column) if score_column else "Score"
287
+ rows_html = "".join(
288
+ f"<li><span class='home-rank'>{idx}</span>"
289
+ f"<span class='home-model'>{escape(model)}</span>"
290
+ f"<span class='home-score'>{_format_score(score)}</span></li>"
291
+ for idx, (model, score) in enumerate(top, start=1)
292
+ ) or "<li class='home-empty'>No scores yet.</li>"
293
+
294
+ with gr.Column(scale=1, min_width=280):
295
+ gr.HTML(
296
+ f"""
297
+ <div class="home-benchmark-card">
298
+ <div class="home-benchmark-title">
299
+ {escape(benchmark.get("emoji", "📊"))}
300
+ {escape(benchmark["title"])}
301
+ </div>
302
+ <p class="home-benchmark-blurb">
303
+ {escape(benchmark.get("card_description", ""))}
304
+ </p>
305
+ <div class="home-top-label">Top 3 by {escape(score_label)}</div>
306
+ <ol class="home-top-list">{rows_html}</ol>
307
+ </div>
308
+ """
309
+ )
310
+
311
+
312
+ def _format_leaderboard_cell(column, value):
313
+ if pd.isna(value) or value is None or value == "":
314
+ return "—"
315
+ label = str(column).lower()
316
+ if label == "rank":
317
+ return str(int(value))
318
+ if "price" in label:
319
+ return _format_price(value)
320
+ if "time" in label or "generation" in label:
321
+ try:
322
+ return f"{float(value):.2f}"
323
+ except (TypeError, ValueError):
324
+ return escape(str(value))
325
+ if label in {"model", "platform", "endpoint owner", "optimized"}:
326
+ return escape(str(value))
327
+ try:
328
+ number = float(value)
329
+ except (TypeError, ValueError):
330
+ return escape(str(value))
331
+ if abs(number) >= 100:
332
+ return f"{number:.1f}"
333
+ return f"{number:.4f}".rstrip("0").rstrip(".")
334
+
335
+
336
+ def _leaderboard_sort_value(column, value):
337
+ """Raw value used by client-side column sorting."""
338
+ if pd.isna(value) or value is None or value == "":
339
+ return ""
340
+ label = str(column).lower()
341
+ if label in {"model", "platform", "endpoint owner", "optimized", "date"}:
342
+ return str(value).casefold()
343
+ try:
344
+ return f"{float(value):.10g}"
345
+ except (TypeError, ValueError):
346
+ return str(value).casefold()
347
+
348
+
349
+ def _leaderboard_sort_type(column):
350
+ label = str(column).lower()
351
+ if label in {"model", "platform", "endpoint owner", "optimized", "date"}:
352
+ return "text"
353
+ return "number"
354
+
355
+
356
+ def _leaderboard_html(data, columns, score_columns, overall_column):
357
+ leaderboard = _leaderboard_dataframe(
358
+ data, columns, score_columns, overall_column
359
+ )
360
+ if leaderboard.empty:
361
+ return (
362
+ '<div class="ranking-table-scroll">'
363
+ '<div class="ranking-table empty-state">No models match the current filters.</div>'
364
+ "</div>"
365
+ )
366
+
367
+ header_cells = []
368
+ for index, column in enumerate(leaderboard.columns):
369
+ sort_type = _leaderboard_sort_type(column)
370
+ header_cells.append(
371
+ f'<th class="sortable-col" data-col="{index}" data-type="{sort_type}" '
372
+ f'title="Sort by {escape(str(column))}">{escape(str(column))}</th>'
373
+ )
374
+ body_rows = []
375
+ for _, row in leaderboard.iterrows():
376
+ cells = []
377
+ for column in leaderboard.columns:
378
+ css = "rank" if column == "Rank" else "metric-score"
379
+ sort_value = escape(_leaderboard_sort_value(column, row[column]), quote=True)
380
+ if column == "Model":
381
+ css = "model-cell"
382
+ cells.append(
383
+ f'<td class="{css}" data-sort-value="{sort_value}">'
384
+ f"<strong>{_format_leaderboard_cell(column, row[column])}</strong></td>"
385
+ )
386
+ else:
387
+ cells.append(
388
+ f'<td class="{css}" data-sort-value="{sort_value}">'
389
+ f"{_format_leaderboard_cell(column, row[column])}</td>"
390
+ )
391
+ body_rows.append(f"<tr>{''.join(cells)}</tr>")
392
+
393
+ return f"""
394
+ <div class="ranking-table-scroll">
395
+ <div class="ranking-table">
396
+ <table>
397
+ <thead><tr>{''.join(header_cells)}</tr></thead>
398
+ <tbody>{''.join(body_rows)}</tbody>
399
+ </table>
400
+ </div>
401
+ </div>
402
+ """
403
+
404
+
405
+ def render_leaderboard(
406
+ data,
407
+ columns,
408
+ note=None,
409
+ score_columns=None,
410
+ overall_column=None,
411
+ ):
412
+ score_columns = list(score_columns or _infer_score_columns(columns))
413
+ overall_column = overall_column or _default_overall_column(score_columns)
414
+ platform_choices = _filter_choices(data, "Platform")
415
+ owner_choices = _filter_choices(data, "Endpoint Owner")
416
+ optimized_choices = _filter_choices(data, "Optimized")
417
+
418
+ if note:
419
+ gr.Markdown(note)
420
+
421
+ filter_inputs = []
422
+ with gr.Row(elem_classes="leaderboard-controls"):
423
+ search = gr.Textbox(
424
+ label="Search models",
425
+ placeholder="Search by model or provider",
426
+ scale=3,
427
+ )
428
+ filter_inputs.append(search)
429
+ platform = None
430
+ owner = None
431
+ optimized = None
432
+ if platform_choices:
433
+ platform = gr.Dropdown(
434
+ choices=platform_choices,
435
+ value=[],
436
+ label="Providers",
437
+ multiselect=True,
438
+ scale=1,
439
+ )
440
+ filter_inputs.append(platform)
441
+ if owner_choices:
442
+ owner = gr.Dropdown(
443
+ choices=owner_choices,
444
+ value=[],
445
+ label="Endpoint owners",
446
+ multiselect=True,
447
+ scale=1,
448
+ )
449
+ filter_inputs.append(owner)
450
+ if optimized_choices:
451
+ optimized = gr.Dropdown(
452
+ choices=optimized_choices,
453
+ value=[],
454
+ label="Optimized",
455
+ multiselect=True,
456
+ scale=1,
457
+ )
458
+ filter_inputs.append(optimized)
459
+
460
+ ranking = gr.HTML(
461
+ _leaderboard_html(data, columns, score_columns, overall_column),
462
+ elem_classes="ranking-table-host",
463
+ )
464
+
465
+ def update_ranking(
466
+ search_term,
467
+ platform_value=None,
468
+ owner_value=None,
469
+ optimized_value=None,
470
+ ):
471
+ filtered_data = _filter_leaderboard(
472
+ data,
473
+ search_term,
474
+ platform_value or [],
475
+ owner_value or [],
476
+ optimized_value or [],
477
+ )
478
+ return _leaderboard_html(
479
+ filtered_data, columns, score_columns, overall_column
480
+ )
481
+
482
+ # Wire only the filters that actually exist for this table.
483
+ change_inputs = [search]
484
+ if platform is not None:
485
+ change_inputs.append(platform)
486
+ if owner is not None:
487
+ change_inputs.append(owner)
488
+ if optimized is not None:
489
+ change_inputs.append(optimized)
490
+
491
+ for component in filter_inputs:
492
+ component.change(
493
+ update_ranking,
494
+ inputs=change_inputs,
495
+ outputs=ranking,
496
+ )
497
+
498
+
499
+ def _infer_score_columns(columns):
500
+ return [column for column in columns if column.startswith("OneIG (")]
501
+
502
+
503
+ def _default_overall_column(score_columns):
504
+ if len(score_columns) == 1:
505
+ return score_columns[0]
506
+ return "OneIG Overall Score"
507
+
508
+
509
+ def _filter_choices(data, column):
510
+ if column not in data.columns:
511
+ return []
512
+ return sorted(data[column].dropna().astype(str).unique().tolist())
513
+
514
+
515
+ def _filter_leaderboard(data, search_term, platform, owner, optimized):
516
+ filtered = data.copy()
517
+ if search_term:
518
+ search_columns = [
519
+ column
520
+ for column in ["Model", "Platform", "Endpoint Owner"]
521
+ if column in filtered.columns
522
+ ]
523
+ matches = pd.Series(False, index=filtered.index)
524
+ for column in search_columns:
525
+ matches |= filtered[column].astype(str).str.contains(
526
+ search_term, case=False, na=False
527
+ )
528
+ filtered = filtered[matches]
529
+
530
+ for column, values in [
531
+ ("Platform", platform),
532
+ ("Endpoint Owner", owner),
533
+ ("Optimized", optimized),
534
+ ]:
535
+ if values and column in filtered.columns:
536
+ filtered = filtered[filtered[column].astype(str).isin(values)]
537
+ return filtered
538
+
539
+
540
+ def _leaderboard_dataframe(data, columns, score_columns, overall_column):
541
+ # Honor the caller-provided column list so extra metrics (e.g. Elo) are not
542
+ # dropped just because they are not part of the ranking score_columns.
543
+ skip_columns = {"URL", "Rank"}
544
+ preferred_prefix = [
545
+ column
546
+ for column in ["Model", "Platform", "Endpoint Owner", "Optimized"]
547
+ if column in data.columns
548
+ ]
549
+ preferred_suffix = [
550
+ column
551
+ for column in [
552
+ "Median Generation Time (s)",
553
+ "Min Generation Time (s)",
554
+ "Price / Image (USD)",
555
+ "Evaluation Date (UTC)",
556
+ "Date",
557
+ ]
558
+ if column in data.columns
559
+ ]
560
+ # Keep overall_column visible when the caller includes it (e.g. Datapoint Elo).
561
+ # Synthetic aggregates like OneIG Overall Score are simply omitted from `columns`.
562
+ middle = [
563
+ column
564
+ for column in columns
565
+ if column in data.columns
566
+ and column not in skip_columns
567
+ and column not in preferred_prefix
568
+ and column not in preferred_suffix
569
+ ]
570
+
571
+ ordered_columns = []
572
+ seen = set()
573
+ for column in [*preferred_prefix, *middle, *preferred_suffix]:
574
+ if column not in seen:
575
+ seen.add(column)
576
+ ordered_columns.append(column)
577
+
578
+ leaderboard = data[ordered_columns].copy()
579
+
580
+ # Rank by overall when available, even if that column is not displayed.
581
+ if overall_column and overall_column in data.columns:
582
+ leaderboard = (
583
+ leaderboard.assign(_sort_key=data[overall_column])
584
+ .sort_values("_sort_key", ascending=False, na_position="last")
585
+ .drop(columns=["_sort_key"])
586
+ .reset_index(drop=True)
587
+ )
588
+ else:
589
+ leaderboard = leaderboard.reset_index(drop=True)
590
+
591
+ leaderboard.insert(0, "Rank", leaderboard.index + 1)
592
+ return leaderboard.rename(columns=_display_label)
593
+
594
+
595
+ def _display_label(column):
596
+ labels = {
597
+ "_overall_score": "Overall score",
598
+ "OneIG Overall Score": "Overall",
599
+ "OneIG (Anime Alignment)": "Anime",
600
+ "OneIG (Human Alignment)": "Human",
601
+ "OneIG (Object Alignment)": "Object",
602
+ "OneIG Anime Elo": "Anime Elo (Datapoint)",
603
+ "OneIG Human Elo": "Human Elo (Datapoint)",
604
+ "OneIG Object Elo": "Object Elo (Datapoint)",
605
+ "P-Judge Overall": "P-Judge",
606
+ "Datapoint Elo": "Datapoint Elo",
607
+ "Rapidata Elo": "Rapidata Elo",
608
+ "Benchmark.ai Elo": "Benchmark.ai Elo",
609
+ "Raw Win Rate": "Raw win rate",
610
+ "Median Generation Time (s)": "Median generation time",
611
+ "Min Generation Time (s)": "Min generation time",
612
+ "Price / Image (USD)": "Price per image",
613
+ "Evaluation Date (UTC)": "Date",
614
+ "Date": "Date",
615
+ }
616
+ return labels.get(column, column)
617
+
618
+
619
+ def _text_value(value):
620
+ return "—" if pd.isna(value) or value is None else escape(str(value))
621
+
622
+
623
+ def _format_score(value):
624
+ return "—" if pd.isna(value) or value is None else f"{float(value):.3f}"
625
+
626
+
627
+ def _format_price(value):
628
+ return "—" if pd.isna(value) or value is None else f"${float(value):.3f}"
629
+
630
+
631
+ def render_benchmark_detail(benchmark):
632
+ gr.Markdown(
633
+ f"""
634
+ # {benchmark["title"]}
635
+
636
+ {benchmark["intro"]}
637
+ """
638
+ )
639
+ with gr.Tabs(elem_classes="subtabs", selected=0) as detail_tabs:
640
+ with gr.TabItem("Leaderboard"):
641
+ render_leaderboard(
642
+ benchmark["data"],
643
+ benchmark["columns"],
644
+ note=benchmark.get("note"),
645
+ score_columns=benchmark.get("score_columns"),
646
+ overall_column=benchmark.get("overall_column"),
647
+ )
648
+ with gr.TabItem("Graphs"):
649
+ render_benchmark_graphs(benchmark)
650
+ with gr.TabItem("Compare samples"):
651
+ render_compare_samples(benchmark)
652
+ return detail_tabs
653
+
654
+
655
+ def render_compare_samples(benchmark):
656
+ samples = benchmark.get("samples")
657
+ if not samples:
658
+ gr.Markdown(
659
+ """
660
+ Sample comparison is not available for this benchmark yet.
661
+
662
+ When generations are linked, you will be able to pick models and browse
663
+ side-by-side outputs for the same prompts.
664
+ """
665
+ )
666
+ return
667
+
668
+ models = samples["models"]
669
+ default_models = models[: min(2, len(models))]
670
+
671
+ gr.Markdown(
672
+ f"""
673
+ <p class="compare-samples-help">
674
+ Pick up to <strong>{MAX_COMPARE_MODELS}</strong> models, then browse shared
675
+ prompts side by side. Images come from the public generation URLs for this
676
+ benchmark.
677
+ </p>
678
+ """
679
+ )
680
+ with gr.Row(elem_classes="leaderboard-controls"):
681
+ model_picker = gr.Dropdown(
682
+ choices=models,
683
+ value=default_models,
684
+ multiselect=True,
685
+ max_choices=MAX_COMPARE_MODELS,
686
+ label="Models",
687
+ info=f"Select 1–{MAX_COMPARE_MODELS} models to compare",
688
+ scale=3,
689
+ )
690
+ prompt_count = gr.Slider(
691
+ minimum=1,
692
+ maximum=MAX_COMPARE_PROMPTS,
693
+ value=DEFAULT_COMPARE_PROMPTS,
694
+ step=1,
695
+ label="Prompts to show",
696
+ scale=1,
697
+ )
698
+ shuffle_button = gr.Button("Shuffle prompts", scale=1)
699
+
700
+ gallery = gr.HTML(
701
+ value=_build_compare_samples_html(
702
+ samples,
703
+ default_models,
704
+ DEFAULT_COMPARE_PROMPTS,
705
+ seed=0,
706
+ )
707
+ )
708
+ seed_state = gr.State(0)
709
+
710
+ def update_gallery(selected_models, num_prompts, seed):
711
+ return _build_compare_samples_html(
712
+ samples,
713
+ selected_models,
714
+ int(num_prompts),
715
+ seed=int(seed or 0),
716
+ )
717
+
718
+ def shuffle_gallery(selected_models, num_prompts, seed):
719
+ next_seed = int(seed or 0) + 1
720
+ return next_seed, _build_compare_samples_html(
721
+ samples,
722
+ selected_models,
723
+ int(num_prompts),
724
+ seed=next_seed,
725
+ )
726
+
727
+ model_picker.change(
728
+ update_gallery,
729
+ inputs=[model_picker, prompt_count, seed_state],
730
+ outputs=gallery,
731
+ )
732
+ prompt_count.change(
733
+ update_gallery,
734
+ inputs=[model_picker, prompt_count, seed_state],
735
+ outputs=gallery,
736
+ )
737
+ shuffle_button.click(
738
+ shuffle_gallery,
739
+ inputs=[model_picker, prompt_count, seed_state],
740
+ outputs=[seed_state, gallery],
741
+ )
742
+
743
+
744
+ def _build_compare_samples_html(samples, selected_models, num_prompts, seed=0):
745
+ selected_models = [
746
+ model
747
+ for model in (selected_models or [])
748
+ if model in samples["images"]
749
+ ][:MAX_COMPARE_MODELS]
750
+
751
+ if not selected_models:
752
+ return (
753
+ '<div class="compare-empty">'
754
+ "Select at least one model to compare samples."
755
+ "</div>"
756
+ )
757
+
758
+ shared_prompt_ids = None
759
+ for model in selected_models:
760
+ model_prompt_ids = set(samples["images"][model])
761
+ shared_prompt_ids = (
762
+ model_prompt_ids
763
+ if shared_prompt_ids is None
764
+ else shared_prompt_ids & model_prompt_ids
765
+ )
766
+
767
+ shared_prompt_ids = sorted(shared_prompt_ids or [])
768
+ if not shared_prompt_ids:
769
+ return (
770
+ '<div class="compare-empty">'
771
+ "No shared prompts found for the selected models."
772
+ "</div>"
773
+ )
774
+
775
+ rng = random.Random(seed)
776
+ prompt_pool = list(shared_prompt_ids)
777
+ rng.shuffle(prompt_pool)
778
+ chosen = prompt_pool[: max(1, min(int(num_prompts), len(prompt_pool)))]
779
+
780
+ columns = len(selected_models)
781
+ blocks = []
782
+ for index, prompt_id in enumerate(chosen, start=1):
783
+ prompt_text = escape(samples["prompts"].get(prompt_id, ""))
784
+ cells = []
785
+ for model in selected_models:
786
+ image_url = escape(samples["images"][model][prompt_id], quote=True)
787
+ cells.append(
788
+ f"""
789
+ <div class="compare-cell">
790
+ <div class="compare-model-label">{escape(model)}</div>
791
+ <a href="{image_url}" target="_blank" rel="noopener noreferrer">
792
+ <img src="{image_url}" alt="{escape(model)} sample" loading="lazy" />
793
+ </a>
794
+ </div>
795
+ """
796
+ )
797
+ blocks.append(
798
+ f"""
799
+ <div class="compare-prompt-block">
800
+ <div class="compare-prompt-meta">
801
+ <span>Prompt {index}</span>
802
+ <span>{escape(prompt_id)}</span>
803
+ </div>
804
+ <p class="compare-prompt-text">{prompt_text}</p>
805
+ <div class="compare-row" style="grid-template-columns: repeat({columns}, minmax(0, 1fr));">
806
+ {''.join(cells)}
807
+ </div>
808
+ </div>
809
+ """
810
+ )
811
+
812
+ return "\n".join(blocks)
813
+
814
+
815
+ def render_benchmarks(benchmarks):
816
+ """Catalogue cards + detail pages; back button returns to the catalogue."""
817
+ open_buttons = []
818
+ detail_entries = []
819
+
820
+ with gr.Column(visible=True, elem_classes="benchmark-catalogue") as catalogue:
821
+ gr.Markdown(
822
+ """
823
+ # Benchmarks
824
+
825
+ Choose a prompt suite. Each one has a **Leaderboard** table, **Graphs**,
826
+ and **Compare samples**.
827
+ """
828
+ )
829
+ card_rows = [benchmarks[i : i + 2] for i in range(0, len(benchmarks), 2)]
830
+ for row in card_rows:
831
+ with gr.Row(equal_height=True, elem_classes="benchmark-catalogue-row"):
832
+ for benchmark in row:
833
+ with gr.Column(scale=1, min_width=280):
834
+ with gr.Group(elem_classes="benchmark-card"):
835
+ gr.Markdown(
836
+ f"""
837
+ ## {benchmark.get("emoji", "📊")} {benchmark["title"]}
838
+
839
+ {benchmark["card_description"]}
840
+ """
841
+ )
842
+ open_buttons.append(
843
+ (
844
+ benchmark["id"],
845
+ gr.Button("View benchmark →", variant="primary"),
846
+ )
847
+ )
848
+
849
+ for benchmark in benchmarks:
850
+ with gr.Column(visible=False) as detail:
851
+ back_button = gr.Button("← All benchmarks", size="sm")
852
+ render_benchmark_detail(benchmark)
853
+ detail_entries.append((benchmark["id"], detail, back_button))
854
+
855
+ nav_outputs = [catalogue, *[detail for _, detail, _ in detail_entries]]
856
+
857
+ def show_catalogue(_evt=None):
858
+ return (
859
+ gr.Column(visible=True),
860
+ *[gr.Column(visible=False) for _ in detail_entries],
861
+ )
862
+
863
+ def show_detail(selected_id):
864
+ return (
865
+ gr.Column(visible=False),
866
+ *[
867
+ gr.Column(visible=(benchmark_id == selected_id))
868
+ for benchmark_id, _, _ in detail_entries
869
+ ],
870
+ )
871
+
872
+ for benchmark_id, button in open_buttons:
873
+ button.click(
874
+ lambda selected_id=benchmark_id: show_detail(selected_id),
875
+ outputs=nav_outputs,
876
+ )
877
+
878
+ for _, _, back_button in detail_entries:
879
+ back_button.click(show_catalogue, outputs=nav_outputs)
880
+
881
+ return show_catalogue, nav_outputs
882
+
883
+
884
+ def _pareto_frontier_mask(x_values, scores):
885
+ """True for non-dominated points when maximizing score and minimizing x."""
886
+ n = len(x_values)
887
+ mask = [True] * n
888
+ for i in range(n):
889
+ for j in range(n):
890
+ if i == j:
891
+ continue
892
+ better_or_equal = x_values[j] <= x_values[i] and scores[j] >= scores[i]
893
+ strictly_better = x_values[j] < x_values[i] or scores[j] > scores[i]
894
+ if better_or_equal and strictly_better:
895
+ mask[i] = False
896
+ break
897
+ return mask
898
+
899
+
900
+ def _build_pareto_figure(
901
+ data,
902
+ score_column,
903
+ x_column,
904
+ x_title,
905
+ x_hover_prefix="",
906
+ x_hover_suffix="",
907
+ ):
908
+ scatter = (
909
+ data[["Model", score_column, x_column]]
910
+ .dropna()
911
+ .copy()
912
+ .reset_index(drop=True)
913
+ )
914
+ if scatter.empty:
915
+ return None
916
+
917
+ x_values = scatter[x_column].astype(float).tolist()
918
+ scores = scatter[score_column].astype(float).tolist()
919
+ on_frontier = _pareto_frontier_mask(x_values, scores)
920
+
921
+ dominated = scatter.loc[[not flag for flag in on_frontier]]
922
+ frontier = scatter.loc[on_frontier].sort_values(x_column)
923
+ hover = (
924
+ "<b>%{text}</b><br>"
925
+ f"{escape(x_title)}: {x_hover_prefix}%{{x:.4f}}{x_hover_suffix}"
926
+ "<br>Score: %{y:.4f}<extra></extra>"
927
+ )
928
+
929
+ fig = go.Figure()
930
+ if not dominated.empty:
931
+ fig.add_trace(
932
+ go.Scatter(
933
+ x=dominated[x_column],
934
+ y=dominated[score_column],
935
+ mode="markers",
936
+ name="Below frontier",
937
+ text=dominated["Model"],
938
+ hovertemplate=hover,
939
+ marker={
940
+ "size": 9,
941
+ "color": "#c4b5fd",
942
+ "opacity": 0.75,
943
+ "line": {"width": 0},
944
+ },
945
+ )
946
+ )
947
+ if not frontier.empty:
948
+ fig.add_trace(
949
+ go.Scatter(
950
+ x=frontier[x_column],
951
+ y=frontier[score_column],
952
+ mode="lines+markers",
953
+ name="On frontier",
954
+ text=frontier["Model"],
955
+ hovertemplate=hover,
956
+ line={"color": "#7c3aed", "width": 2.5},
957
+ marker={
958
+ "size": 12,
959
+ "color": "#db2777",
960
+ "line": {"width": 1.5, "color": "#7c3aed"},
961
+ },
962
+ )
963
+ )
964
+
965
+ score_label = _display_label(score_column)
966
+ fig.update_layout(
967
+ title=None,
968
+ xaxis_title=x_title,
969
+ yaxis_title=score_label,
970
+ autosize=True,
971
+ height=420,
972
+ margin={"l": 56, "r": 28, "t": 28, "b": 80},
973
+ legend={
974
+ "orientation": "h",
975
+ "yanchor": "top",
976
+ "y": -0.24,
977
+ "xanchor": "center",
978
+ "x": 0.5,
979
+ "bgcolor": "rgba(0,0,0,0)",
980
+ "font": {"color": "#e9d5ff", "size": 12},
981
+ },
982
+ # Dark-theme plot: soft purple panel + light text (readable, not a white flash).
983
+ plot_bgcolor="#1e1b4b",
984
+ paper_bgcolor="#17153b",
985
+ font={"color": "#e9d5ff", "size": 13},
986
+ )
987
+ axis_font = {"color": "#f3e8ff", "size": 13}
988
+ tick_font = {"color": "#ddd6fe", "size": 12}
989
+ fig.update_xaxes(
990
+ showgrid=True,
991
+ gridcolor="rgba(167, 139, 250, 0.28)",
992
+ zeroline=False,
993
+ title_font=axis_font,
994
+ tickfont=tick_font,
995
+ color="#e9d5ff",
996
+ )
997
+ fig.update_yaxes(
998
+ showgrid=True,
999
+ gridcolor="rgba(167, 139, 250, 0.28)",
1000
+ zeroline=False,
1001
+ title_font=axis_font,
1002
+ tickfont=tick_font,
1003
+ color="#e9d5ff",
1004
+ )
1005
+ return fig
1006
+
1007
+
1008
+ def render_benchmark_graphs(benchmark):
1009
+ data = benchmark["data"]
1010
+ score_columns = [
1011
+ column
1012
+ for column in (benchmark.get("score_columns") or [])
1013
+ if column in data.columns
1014
+ ]
1015
+ overall_column = benchmark.get("overall_column")
1016
+
1017
+ if not score_columns and overall_column and overall_column in data.columns:
1018
+ score_columns = [overall_column]
1019
+
1020
+ if not score_columns:
1021
+ gr.Markdown("No score data is available yet.")
1022
+ return
1023
+
1024
+ # Pareto every displayed quality metric vs price.
1025
+ # Skip only synthetic aggregates (e.g. OneIG mean), not real sort keys like Datapoint Elo.
1026
+ pareto_skip = {
1027
+ "Model",
1028
+ "Platform",
1029
+ "Endpoint Owner",
1030
+ "Optimized",
1031
+ "URL",
1032
+ "Rank",
1033
+ "Median Generation Time (s)",
1034
+ "Min Generation Time (s)",
1035
+ "Price / Image (USD)",
1036
+ "Evaluation Date (UTC)",
1037
+ "Date",
1038
+ "Raw Win Rate",
1039
+ "OneIG Overall Score",
1040
+ }
1041
+
1042
+ display_columns = benchmark.get("columns") or []
1043
+ pareto_columns = []
1044
+ for column in [*score_columns, *display_columns]:
1045
+ if (
1046
+ column in data.columns
1047
+ and column not in pareto_skip
1048
+ and column not in pareto_columns
1049
+ and pd.api.types.is_numeric_dtype(data[column])
1050
+ ):
1051
+ pareto_columns.append(column)
1052
+
1053
+ price_column = "Price / Image (USD)"
1054
+ time_column = "Min Generation Time (s)"
1055
+ price_figures = []
1056
+ time_figures = []
1057
+ for plot_column in pareto_columns:
1058
+ if price_column in data.columns:
1059
+ price_fig = _build_pareto_figure(
1060
+ data,
1061
+ plot_column,
1062
+ x_column=price_column,
1063
+ x_title="Price per image (USD)",
1064
+ x_hover_prefix="$",
1065
+ )
1066
+ if price_fig is not None:
1067
+ price_figures.append((plot_column, price_fig))
1068
+ if time_column in data.columns:
1069
+ time_fig = _build_pareto_figure(
1070
+ data,
1071
+ plot_column,
1072
+ x_column=time_column,
1073
+ x_title="Min generation time (s)",
1074
+ x_hover_suffix="s",
1075
+ )
1076
+ if time_fig is not None:
1077
+ time_figures.append((plot_column, time_fig))
1078
+
1079
+ if price_figures or time_figures:
1080
+ gr.Markdown(
1081
+ "### Pareto frontiers\n\n"
1082
+ "<span style='color:#4c1d95;font-size:0.95rem;'>"
1083
+ "Pink = on the frontier (lower cost or time at the same or better score). "
1084
+ "Light purple = below the frontier."
1085
+ "</span>"
1086
+ )
1087
+ with gr.Row(equal_height=False):
1088
+ with gr.Column(scale=1, min_width=320):
1089
+ gr.Markdown("#### Price vs score")
1090
+ if not price_figures:
1091
+ gr.Markdown("_No price data available._")
1092
+ for plot_column, pareto_fig in price_figures:
1093
+ gr.Markdown(f"**{_display_label(plot_column)}**")
1094
+ gr.Plot(
1095
+ value=pareto_fig,
1096
+ show_label=False,
1097
+ elem_classes="pareto-plot",
1098
+ )
1099
+ with gr.Column(scale=1, min_width=320):
1100
+ gr.Markdown("#### Min generation time vs score")
1101
+ if not time_figures:
1102
+ gr.Markdown("_No min generation time data available._")
1103
+ for plot_column, pareto_fig in time_figures:
1104
+ gr.Markdown(f"**{_display_label(plot_column)}**")
1105
+ gr.Plot(
1106
+ value=pareto_fig,
1107
+ show_label=False,
1108
+ elem_classes="pareto-plot",
1109
+ )
1110
+
1111
+
1112
+ def render_about():
1113
+ with gr.Row():
1114
+ with gr.Column():
1115
+ gr.Markdown(ABOUT_OVERVIEW_CONTENT)
1116
+ with gr.Column():
1117
+ gr.Markdown(ABOUT_DETAILS_CONTENT)
1118
+
1119
+
1120
+ def render_footer():
1121
+ gr.HTML(COMMUNITY_CONTENT)
1122
+ with gr.Accordion("Citation", open=False):
1123
+ gr.Markdown(CITATION_CONTENT)