Added JEV 27B VL AWQ

#2
by atdokmeci - opened
Files changed (3) hide show
  1. data/results.json +490 -475
  2. util/data.py +363 -359
  3. util/ranking.py +116 -116
data/results.json CHANGED
@@ -1,475 +1,490 @@
1
- {
2
- "schema_version": "1.0",
3
- "benchmark_version": "1.0",
4
- "updated": "2026-10-06",
5
- "models": [
6
- {
7
- "id": "openai-gpt-6-astra",
8
- "name": "GPT-6 Astra",
9
- "organization": "OpenAI",
10
- "params": "—",
11
- "type": "closed",
12
- "result_type": "single",
13
- "verified": true,
14
- "ensemble": false,
15
- "is_new": true,
16
- "date_evaluated": "2026-10-06",
17
- "model_url": "https://deploymentsafety.openai.com/gpt-6-astra",
18
- "overall": 75.26,
19
- "spatial": 83.12,
20
- "spatio_temporal": 81.2,
21
- "temporal": 51.64,
22
- "semantic": 85.08,
23
- "2d_localization": 82.2,
24
- "2d_referring_expressions": 80.18,
25
- "2d_spatial_pointing": 86.97,
26
- "single_object_tracking": 81.2,
27
- "temporal_localization": 65.1,
28
- "dense_video_captioning": 38.17,
29
- "event_verification": 86.9,
30
- "video_qa": 83.26
31
- },
32
- {
33
- "id": "google-gemini-3-6-flash",
34
- "name": "Gemini 3.6 Flash",
35
- "organization": "Google",
36
- "params": "—",
37
- "type": "closed",
38
- "result_type": "single",
39
- "verified": true,
40
- "ensemble": false,
41
- "is_new": true,
42
- "date_evaluated": "2026-07-21",
43
- "model_url": "https://deepmind.google/technologies/gemini/",
44
- "overall": 69.52,
45
- "spatial": 78.85,
46
- "spatio_temporal": 75.99,
47
- "temporal": 43.83,
48
- "semantic": 79.41,
49
- "2d_localization": 81.57,
50
- "2d_referring_expressions": 75.78,
51
- "2d_spatial_pointing": 79.2,
52
- "single_object_tracking": 75.99,
53
- "temporal_localization": 51.5,
54
- "dense_video_captioning": 36.15,
55
- "event_verification": 82.0,
56
- "video_qa": 76.82
57
- },
58
- {
59
- "id": "openai-gpt-5-6-sol",
60
- "name": "GPT-5.6 Sol",
61
- "organization": "OpenAI",
62
- "params": "—",
63
- "type": "closed",
64
- "result_type": "single",
65
- "verified": true,
66
- "ensemble": false,
67
- "is_new": true,
68
- "date_evaluated": "2026-07-09",
69
- "model_url": "https://openai.com/index/gpt-5-6/",
70
- "overall": 68.04,
71
- "spatial": 73.55,
72
- "spatio_temporal": 75.02,
73
- "temporal": 46.5,
74
- "semantic": 77.11,
75
- "2d_localization": 69.3,
76
- "2d_referring_expressions": 69.44,
77
- "2d_spatial_pointing": 81.9,
78
- "single_object_tracking": 75.02,
79
- "temporal_localization": 55.71,
80
- "dense_video_captioning": 37.28,
81
- "event_verification": 76.14,
82
- "video_qa": 78.08
83
- },
84
- {
85
- "id": "nvidia-cosmos3-super",
86
- "name": "Cosmos3-Super",
87
- "organization": "NVIDIA",
88
- "params": "64B",
89
- "type": "open",
90
- "result_type": "single",
91
- "verified": true,
92
- "ensemble": false,
93
- "is_new": true,
94
- "date_evaluated": "2026-05-29",
95
- "model_url": "https://huggingface.co/nvidia/Cosmos3-Super",
96
- "overall": 63.62,
97
- "spatial": 78.71,
98
- "spatio_temporal": 64.66,
99
- "temporal": 40.72,
100
- "semantic": 70.37,
101
- "2d_localization": 86.97,
102
- "2d_referring_expressions": 76.23,
103
- "2d_spatial_pointing": 72.94,
104
- "single_object_tracking": 64.66,
105
- "temporal_localization": 51.9,
106
- "dense_video_captioning": 29.54,
107
- "event_verification": 71.28,
108
- "video_qa": 69.46
109
- },
110
- {
111
- "id": "google-gemini-3-1-pro",
112
- "name": "Gemini 3.1 Pro",
113
- "organization": "Google",
114
- "params": "—",
115
- "type": "closed",
116
- "result_type": "single",
117
- "verified": true,
118
- "ensemble": false,
119
- "is_new": false,
120
- "date_evaluated": "2026-05-27",
121
- "model_url": "https://deepmind.google/technologies/gemini/",
122
- "overall": 62.66,
123
- "spatial": 72.14,
124
- "spatio_temporal": 67.88,
125
- "temporal": 40.62,
126
- "semantic": 70.01,
127
- "2d_localization": 77.21,
128
- "2d_referring_expressions": 67.17,
129
- "2d_spatial_pointing": 72.04,
130
- "single_object_tracking": 67.88,
131
- "temporal_localization": 45.69,
132
- "dense_video_captioning": 35.55,
133
- "event_verification": 68.57,
134
- "video_qa": 71.46
135
- },
136
- {
137
- "id": "nvidia-cosmos3-nano",
138
- "name": "Cosmos3-Nano",
139
- "organization": "NVIDIA",
140
- "params": "16B",
141
- "type": "open",
142
- "result_type": "single",
143
- "verified": true,
144
- "ensemble": false,
145
- "is_new": true,
146
- "date_evaluated": "2026-05-29",
147
- "model_url": "https://huggingface.co/nvidia/Cosmos3-Nano",
148
- "overall": 60.67,
149
- "spatial": 74.84,
150
- "spatio_temporal": 59.21,
151
- "temporal": 39.73,
152
- "semantic": 68.91,
153
- "2d_localization": 74.11,
154
- "2d_referring_expressions": 75.57,
155
- "2d_spatial_pointing": 74.83,
156
- "single_object_tracking": 59.21,
157
- "temporal_localization": 48.04,
158
- "dense_video_captioning": 31.42,
159
- "event_verification": 68.88,
160
- "video_qa": 68.95
161
- },
162
- {
163
- "id": "alibaba-qwen3-vl-32b",
164
- "name": "Qwen3-VL-32B-Instruct",
165
- "organization": "Alibaba",
166
- "params": "32B",
167
- "type": "open",
168
- "result_type": "single",
169
- "verified": true,
170
- "ensemble": false,
171
- "is_new": false,
172
- "date_evaluated": "2026-05-27",
173
- "model_url": "https://huggingface.co/Qwen/Qwen3-VL-32B-Instruct",
174
- "overall": 55.38,
175
- "spatial": 73.56,
176
- "spatio_temporal": 44.19,
177
- "temporal": 38.1,
178
- "semantic": 65.67,
179
- "2d_localization": 72.67,
180
- "2d_referring_expressions": 72.39,
181
- "2d_spatial_pointing": 75.62,
182
- "single_object_tracking": 44.19,
183
- "temporal_localization": 46.8,
184
- "dense_video_captioning": 29.4,
185
- "event_verification": 60.04,
186
- "video_qa": 71.3
187
- },
188
- {
189
- "id": "nvidia-cosmos-reason2-8b",
190
- "name": "Cosmos-Reason2-8B",
191
- "organization": "NVIDIA",
192
- "params": "8B",
193
- "type": "open",
194
- "result_type": "single",
195
- "verified": true,
196
- "ensemble": false,
197
- "is_new": false,
198
- "date_evaluated": "2026-05-27",
199
- "model_url": "https://huggingface.co/nvidia/Cosmos-Reason2-8B",
200
- "overall": 54.46,
201
- "spatial": 74.25,
202
- "spatio_temporal": 37.69,
203
- "temporal": 39.9,
204
- "semantic": 66.02,
205
- "2d_localization": 83.88,
206
- "2d_referring_expressions": 70.26,
207
- "2d_spatial_pointing": 68.6,
208
- "single_object_tracking": 37.69,
209
- "temporal_localization": 47.3,
210
- "dense_video_captioning": 32.5,
211
- "event_verification": 64.09,
212
- "video_qa": 67.95
213
- },
214
- {
215
- "id": "google-gemini-3-5-flash-lite",
216
- "name": "Gemini 3.5 Flash-Lite",
217
- "organization": "Google",
218
- "params": "—",
219
- "type": "closed",
220
- "result_type": "single",
221
- "verified": true,
222
- "ensemble": false,
223
- "is_new": true,
224
- "date_evaluated": "2026-07-21",
225
- "model_url": "https://deepmind.google/models/gemini/flash-lite/",
226
- "overall": 53.25,
227
- "spatial": 61.09,
228
- "spatio_temporal": 55.77,
229
- "temporal": 28.14,
230
- "semantic": 68.01,
231
- "2d_localization": 65.73,
232
- "2d_referring_expressions": 74.46,
233
- "2d_spatial_pointing": 43.08,
234
- "single_object_tracking": 55.77,
235
- "temporal_localization": 24.0,
236
- "dense_video_captioning": 32.29,
237
- "event_verification": 70.41,
238
- "video_qa": 65.61
239
- },
240
- {
241
- "id": "google-gemini-3-2-flash-lite",
242
- "name": "Gemini 3.1 Flash Lite",
243
- "organization": "Google",
244
- "params": "—",
245
- "type": "closed",
246
- "result_type": "single",
247
- "verified": true,
248
- "ensemble": false,
249
- "is_new": false,
250
- "date_evaluated": "2026-05-27",
251
- "model_url": "https://deepmind.google/technologies/gemini/",
252
- "overall": 52.61,
253
- "spatial": 62.71,
254
- "spatio_temporal": 46.68,
255
- "temporal": 35.13,
256
- "semantic": 65.91,
257
- "2d_localization": 71.74,
258
- "2d_referring_expressions": 52.7,
259
- "2d_spatial_pointing": 63.68,
260
- "single_object_tracking": 46.68,
261
- "temporal_localization": 37.72,
262
- "dense_video_captioning": 32.54,
263
- "event_verification": 63.78,
264
- "video_qa": 68.03
265
- },
266
- {
267
- "id": "alibaba-qwen3-vl-8b",
268
- "name": "Qwen3-VL-8B-Instruct",
269
- "organization": "Alibaba",
270
- "params": "8B",
271
- "type": "open",
272
- "result_type": "single",
273
- "verified": true,
274
- "ensemble": false,
275
- "is_new": false,
276
- "date_evaluated": "2026-05-27",
277
- "model_url": "https://huggingface.co/Qwen/Qwen3-VL-8B-Instruct",
278
- "overall": 50.07,
279
- "spatial": 67.26,
280
- "spatio_temporal": 33.12,
281
- "temporal": 36.98,
282
- "semantic": 62.91,
283
- "2d_localization": 59.93,
284
- "2d_referring_expressions": 73.29,
285
- "2d_spatial_pointing": 68.56,
286
- "single_object_tracking": 33.12,
287
- "temporal_localization": 44.32,
288
- "dense_video_captioning": 29.64,
289
- "event_verification": 59.39,
290
- "video_qa": 66.44
291
- },
292
- {
293
- "id": "alibaba-qwen3-5-vl-27b",
294
- "name": "Qwen3.5-27B",
295
- "organization": "Alibaba",
296
- "params": "27B",
297
- "type": "open",
298
- "result_type": "single",
299
- "verified": true,
300
- "ensemble": false,
301
- "is_new": false,
302
- "date_evaluated": "2026-05-27",
303
- "model_url": "https://huggingface.co/Qwen/Qwen3.5-VL-27B-Instruct",
304
- "overall": 49.25,
305
- "spatial": 79.04,
306
- "spatio_temporal": 24.4,
307
- "temporal": 31.93,
308
- "semantic": 61.65,
309
- "2d_localization": 85.46,
310
- "2d_referring_expressions": 76.35,
311
- "2d_spatial_pointing": 75.32,
312
- "single_object_tracking": 24.4,
313
- "temporal_localization": 36.88,
314
- "dense_video_captioning": 26.97,
315
- "event_verification": 55.34,
316
- "video_qa": 67.95
317
- },
318
- {
319
- "id": "alibaba-qwen3-5-vl-9b",
320
- "name": "Qwen3.5-9B",
321
- "organization": "Alibaba",
322
- "params": "9B",
323
- "type": "open",
324
- "result_type": "single",
325
- "verified": true,
326
- "ensemble": false,
327
- "is_new": false,
328
- "date_evaluated": "2026-05-27",
329
- "model_url": "https://huggingface.co/Qwen/Qwen3.5-VL-9B-Instruct",
330
- "overall": 48.3,
331
- "spatial": 75.26,
332
- "spatio_temporal": 23.52,
333
- "temporal": 35.2,
334
- "semantic": 59.2,
335
- "2d_localization": 76.87,
336
- "2d_referring_expressions": 77.16,
337
- "2d_spatial_pointing": 71.74,
338
- "single_object_tracking": 23.52,
339
- "temporal_localization": 42.53,
340
- "dense_video_captioning": 27.88,
341
- "event_verification": 46.78,
342
- "video_qa": 71.63
343
- },
344
- {
345
- "id": "nvidia-cosmos-reason2-32b",
346
- "name": "Cosmos-Reason2-32B",
347
- "organization": "NVIDIA",
348
- "params": "32B",
349
- "type": "open",
350
- "result_type": "single",
351
- "verified": true,
352
- "ensemble": false,
353
- "is_new": false,
354
- "date_evaluated": "2026-05-27",
355
- "model_url": "https://huggingface.co/nvidia/Cosmos-Reason2-32B",
356
- "overall": 47.03,
357
- "spatial": 42.81,
358
- "spatio_temporal": 33.12,
359
- "temporal": 40.25,
360
- "semantic": 71.94,
361
- "2d_localization": 4.35,
362
- "2d_referring_expressions": 50.04,
363
- "2d_spatial_pointing": 74.03,
364
- "single_object_tracking": 33.12,
365
- "temporal_localization": 50.47,
366
- "dense_video_captioning": 30.04,
367
- "event_verification": 73.58,
368
- "video_qa": 70.29
369
- },
370
- {
371
- "id": "nvidia-cosmos3-edge",
372
- "name": "Cosmos3-Edge",
373
- "organization": "NVIDIA",
374
- "params": "4B",
375
- "type": "open",
376
- "result_type": "single",
377
- "verified": true,
378
- "ensemble": false,
379
- "is_new": true,
380
- "date_evaluated": "2026-07-16",
381
- "model_url": "https://huggingface.co/nvidia/Cosmos3-Edge",
382
- "overall": 46.94,
383
- "spatial": 72.02,
384
- "spatio_temporal": 24.65,
385
- "temporal": 28.07,
386
- "semantic": 63.02,
387
- "2d_localization": 79.02,
388
- "2d_referring_expressions": 69.27,
389
- "2d_spatial_pointing": 67.76,
390
- "single_object_tracking": 24.65,
391
- "temporal_localization": 39.51,
392
- "dense_video_captioning": 16.64,
393
- "event_verification": 63.36,
394
- "video_qa": 62.68
395
- },
396
- {
397
- "id": "nvidia-cosmos-reason2-2b",
398
- "name": "Cosmos-Reason2-2B",
399
- "organization": "NVIDIA",
400
- "params": "2B",
401
- "type": "open",
402
- "result_type": "single",
403
- "verified": true,
404
- "ensemble": false,
405
- "is_new": true,
406
- "date_evaluated": "2026-07-16",
407
- "model_url": "https://huggingface.co/nvidia/Cosmos-Reason2-2B",
408
- "overall": 45.94,
409
- "spatial": 63.2,
410
- "spatio_temporal": 26.85,
411
- "temporal": 33.73,
412
- "semantic": 59.98,
413
- "2d_localization": 73.56,
414
- "2d_referring_expressions": 56.33,
415
- "2d_spatial_pointing": 59.7,
416
- "single_object_tracking": 26.85,
417
- "temporal_localization": 38.95,
418
- "dense_video_captioning": 28.52,
419
- "event_verification": 55.27,
420
- "video_qa": 64.69
421
- },
422
- {
423
- "id": "alibaba-qwen3-vl-2b",
424
- "name": "Qwen3-VL-2B-Instruct",
425
- "organization": "Alibaba",
426
- "params": "2B",
427
- "type": "open",
428
- "result_type": "single",
429
- "verified": true,
430
- "ensemble": false,
431
- "is_new": true,
432
- "date_evaluated": "2026-07-16",
433
- "model_url": "https://huggingface.co/Qwen/Qwen3-VL-2B-Instruct",
434
- "overall": 41.86,
435
- "spatial": 55.22,
436
- "spatio_temporal": 30.38,
437
- "temporal": 27.55,
438
- "semantic": 54.29,
439
- "2d_localization": 47.42,
440
- "2d_referring_expressions": 65.12,
441
- "2d_spatial_pointing": 53.13,
442
- "single_object_tracking": 30.38,
443
- "temporal_localization": 35.21,
444
- "dense_video_captioning": 19.9,
445
- "event_verification": 44.73,
446
- "video_qa": 63.85
447
- },
448
- {
449
- "id": "google-gemma-4-e2b-it",
450
- "name": "Gemma-4-E2B-it",
451
- "organization": "Google",
452
- "params": "2B",
453
- "type": "open",
454
- "result_type": "single",
455
- "verified": true,
456
- "ensemble": false,
457
- "is_new": true,
458
- "date_evaluated": "2026-07-16",
459
- "model_url": "https://huggingface.co/google/gemma-4-e2b-it",
460
- "overall": 21.28,
461
- "spatial": 27.55,
462
- "spatio_temporal": 11.58,
463
- "temporal": 9.08,
464
- "semantic": 36.92,
465
- "2d_localization": 45.4,
466
- "2d_referring_expressions": 5.52,
467
- "2d_spatial_pointing": 31.74,
468
- "single_object_tracking": 11.58,
469
- "temporal_localization": 9.02,
470
- "dense_video_captioning": 9.14,
471
- "event_verification": 27.65,
472
- "video_qa": 46.19
473
- }
474
- ]
475
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": "1.0",
3
+ "benchmark_version": "1.0",
4
+ "updated": "2026-10-21",
5
+ "models": [
6
+ {
7
+ "id": "openai-gpt-6-astra",
8
+ "name": "GPT-6 Astra",
9
+ "organization": "OpenAI",
10
+ "params": "—",
11
+ "type": "closed",
12
+ "result_type": "single",
13
+ "verified": true,
14
+ "ensemble": false,
15
+ "is_new": true,
16
+ "date_evaluated": "2026-10-06",
17
+ "model_url": "https://deploymentsafety.openai.com/gpt-6-astra",
18
+ "overall": 75.26,
19
+ "spatial": 83.12,
20
+ "spatio_temporal": 81.2,
21
+ "temporal": 51.64,
22
+ "semantic": 85.08,
23
+ "2d_localization": 82.2,
24
+ "2d_referring_expressions": 80.18,
25
+ "2d_spatial_pointing": 86.97,
26
+ "single_object_tracking": 81.2,
27
+ "temporal_localization": 65.1,
28
+ "dense_video_captioning": 38.17,
29
+ "event_verification": 86.9,
30
+ "video_qa": 83.26
31
+ },
32
+ {
33
+ "id": "google-gemini-3-6-flash",
34
+ "name": "Gemini 3.6 Flash",
35
+ "organization": "Google",
36
+ "params": "—",
37
+ "type": "closed",
38
+ "result_type": "single",
39
+ "verified": true,
40
+ "ensemble": false,
41
+ "is_new": true,
42
+ "date_evaluated": "2026-07-21",
43
+ "model_url": "https://deepmind.google/technologies/gemini/",
44
+ "overall": 69.52,
45
+ "spatial": 78.85,
46
+ "spatio_temporal": 75.99,
47
+ "temporal": 43.83,
48
+ "semantic": 79.41,
49
+ "2d_localization": 81.57,
50
+ "2d_referring_expressions": 75.78,
51
+ "2d_spatial_pointing": 79.2,
52
+ "single_object_tracking": 75.99,
53
+ "temporal_localization": 51.5,
54
+ "dense_video_captioning": 36.15,
55
+ "event_verification": 82.0,
56
+ "video_qa": 76.82
57
+ },
58
+ {
59
+ "id": "openai-gpt-5-6-sol",
60
+ "name": "GPT-5.6 Sol",
61
+ "organization": "OpenAI",
62
+ "params": "—",
63
+ "type": "closed",
64
+ "result_type": "single",
65
+ "verified": true,
66
+ "ensemble": false,
67
+ "is_new": true,
68
+ "date_evaluated": "2026-07-09",
69
+ "model_url": "https://openai.com/index/gpt-5-6/",
70
+ "overall": 68.04,
71
+ "spatial": 73.55,
72
+ "spatio_temporal": 75.02,
73
+ "temporal": 46.5,
74
+ "semantic": 77.11,
75
+ "2d_localization": 69.3,
76
+ "2d_referring_expressions": 69.44,
77
+ "2d_spatial_pointing": 81.9,
78
+ "single_object_tracking": 75.02,
79
+ "temporal_localization": 55.71,
80
+ "dense_video_captioning": 37.28,
81
+ "event_verification": 76.14,
82
+ "video_qa": 78.08
83
+ },
84
+ {
85
+ "id": "nvidia-cosmos3-super",
86
+ "name": "Cosmos3-Super",
87
+ "organization": "NVIDIA",
88
+ "params": "64B",
89
+ "type": "open",
90
+ "result_type": "single",
91
+ "verified": true,
92
+ "ensemble": false,
93
+ "is_new": true,
94
+ "date_evaluated": "2026-05-29",
95
+ "model_url": "https://huggingface.co/nvidia/Cosmos3-Super",
96
+ "overall": 63.62,
97
+ "spatial": 78.71,
98
+ "spatio_temporal": 64.66,
99
+ "temporal": 40.72,
100
+ "semantic": 70.37,
101
+ "2d_localization": 86.97,
102
+ "2d_referring_expressions": 76.23,
103
+ "2d_spatial_pointing": 72.94,
104
+ "single_object_tracking": 64.66,
105
+ "temporal_localization": 51.9,
106
+ "dense_video_captioning": 29.54,
107
+ "event_verification": 71.28,
108
+ "video_qa": 69.46
109
+ },
110
+ {
111
+ "id": "google-gemini-3-1-pro",
112
+ "name": "Gemini 3.1 Pro",
113
+ "organization": "Google",
114
+ "params": "—",
115
+ "type": "closed",
116
+ "result_type": "single",
117
+ "verified": true,
118
+ "ensemble": false,
119
+ "is_new": false,
120
+ "date_evaluated": "2026-05-27",
121
+ "model_url": "https://deepmind.google/technologies/gemini/",
122
+ "overall": 62.66,
123
+ "spatial": 72.14,
124
+ "spatio_temporal": 67.88,
125
+ "temporal": 40.62,
126
+ "semantic": 70.01,
127
+ "2d_localization": 77.21,
128
+ "2d_referring_expressions": 67.17,
129
+ "2d_spatial_pointing": 72.04,
130
+ "single_object_tracking": 67.88,
131
+ "temporal_localization": 45.69,
132
+ "dense_video_captioning": 35.55,
133
+ "event_verification": 68.57,
134
+ "video_qa": 71.46
135
+ },
136
+ {
137
+ "id": "nvidia-cosmos3-nano",
138
+ "name": "Cosmos3-Nano",
139
+ "organization": "NVIDIA",
140
+ "params": "16B",
141
+ "type": "open",
142
+ "result_type": "single",
143
+ "verified": true,
144
+ "ensemble": false,
145
+ "is_new": true,
146
+ "date_evaluated": "2026-05-29",
147
+ "model_url": "https://huggingface.co/nvidia/Cosmos3-Nano",
148
+ "overall": 60.67,
149
+ "spatial": 74.84,
150
+ "spatio_temporal": 59.21,
151
+ "temporal": 39.73,
152
+ "semantic": 68.91,
153
+ "2d_localization": 74.11,
154
+ "2d_referring_expressions": 75.57,
155
+ "2d_spatial_pointing": 74.83,
156
+ "single_object_tracking": 59.21,
157
+ "temporal_localization": 48.04,
158
+ "dense_video_captioning": 31.42,
159
+ "event_verification": 68.88,
160
+ "video_qa": 68.95
161
+ },
162
+ {
163
+ "id": "alibaba-qwen3-vl-32b",
164
+ "name": "Qwen3-VL-32B-Instruct",
165
+ "organization": "Alibaba",
166
+ "params": "32B",
167
+ "type": "open",
168
+ "result_type": "single",
169
+ "verified": true,
170
+ "ensemble": false,
171
+ "is_new": false,
172
+ "date_evaluated": "2026-05-27",
173
+ "model_url": "https://huggingface.co/Qwen/Qwen3-VL-32B-Instruct",
174
+ "overall": 55.38,
175
+ "spatial": 73.56,
176
+ "spatio_temporal": 44.19,
177
+ "temporal": 38.1,
178
+ "semantic": 65.67,
179
+ "2d_localization": 72.67,
180
+ "2d_referring_expressions": 72.39,
181
+ "2d_spatial_pointing": 75.62,
182
+ "single_object_tracking": 44.19,
183
+ "temporal_localization": 46.8,
184
+ "dense_video_captioning": 29.4,
185
+ "event_verification": 60.04,
186
+ "video_qa": 71.3
187
+ },
188
+ {
189
+ "id": "nvidia-cosmos-reason2-8b",
190
+ "name": "Cosmos-Reason2-8B",
191
+ "organization": "NVIDIA",
192
+ "params": "8B",
193
+ "type": "open",
194
+ "result_type": "single",
195
+ "verified": true,
196
+ "ensemble": false,
197
+ "is_new": false,
198
+ "date_evaluated": "2026-05-27",
199
+ "model_url": "https://huggingface.co/nvidia/Cosmos-Reason2-8B",
200
+ "overall": 54.46,
201
+ "spatial": 74.25,
202
+ "spatio_temporal": 37.69,
203
+ "temporal": 39.9,
204
+ "semantic": 66.02,
205
+ "2d_localization": 83.88,
206
+ "2d_referring_expressions": 70.26,
207
+ "2d_spatial_pointing": 68.6,
208
+ "single_object_tracking": 37.69,
209
+ "temporal_localization": 47.3,
210
+ "dense_video_captioning": 32.5,
211
+ "event_verification": 64.09,
212
+ "video_qa": 67.95
213
+ },
214
+ {
215
+ "id": "google-gemini-3-5-flash-lite",
216
+ "name": "Gemini 3.5 Flash-Lite",
217
+ "organization": "Google",
218
+ "params": "—",
219
+ "type": "closed",
220
+ "result_type": "single",
221
+ "verified": true,
222
+ "ensemble": false,
223
+ "is_new": true,
224
+ "date_evaluated": "2026-07-21",
225
+ "model_url": "https://deepmind.google/models/gemini/flash-lite/",
226
+ "overall": 53.25,
227
+ "spatial": 61.09,
228
+ "spatio_temporal": 55.77,
229
+ "temporal": 28.14,
230
+ "semantic": 68.01,
231
+ "2d_localization": 65.73,
232
+ "2d_referring_expressions": 74.46,
233
+ "2d_spatial_pointing": 43.08,
234
+ "single_object_tracking": 55.77,
235
+ "temporal_localization": 24.0,
236
+ "dense_video_captioning": 32.29,
237
+ "event_verification": 70.41,
238
+ "video_qa": 65.61
239
+ },
240
+ {
241
+ "id": "google-gemini-3-2-flash-lite",
242
+ "name": "Gemini 3.1 Flash Lite",
243
+ "organization": "Google",
244
+ "params": "—",
245
+ "type": "closed",
246
+ "result_type": "single",
247
+ "verified": true,
248
+ "ensemble": false,
249
+ "is_new": false,
250
+ "date_evaluated": "2026-05-27",
251
+ "model_url": "https://deepmind.google/technologies/gemini/",
252
+ "overall": 52.61,
253
+ "spatial": 62.71,
254
+ "spatio_temporal": 46.68,
255
+ "temporal": 35.13,
256
+ "semantic": 65.91,
257
+ "2d_localization": 71.74,
258
+ "2d_referring_expressions": 52.7,
259
+ "2d_spatial_pointing": 63.68,
260
+ "single_object_tracking": 46.68,
261
+ "temporal_localization": 37.72,
262
+ "dense_video_captioning": 32.54,
263
+ "event_verification": 63.78,
264
+ "video_qa": 68.03
265
+ },
266
+ {
267
+ "id": "alibaba-qwen3-vl-8b",
268
+ "name": "Qwen3-VL-8B-Instruct",
269
+ "organization": "Alibaba",
270
+ "params": "8B",
271
+ "type": "open",
272
+ "result_type": "single",
273
+ "verified": true,
274
+ "ensemble": false,
275
+ "is_new": false,
276
+ "date_evaluated": "2026-05-27",
277
+ "model_url": "https://huggingface.co/Qwen/Qwen3-VL-8B-Instruct",
278
+ "overall": 50.07,
279
+ "spatial": 67.26,
280
+ "spatio_temporal": 33.12,
281
+ "temporal": 36.98,
282
+ "semantic": 62.91,
283
+ "2d_localization": 59.93,
284
+ "2d_referring_expressions": 73.29,
285
+ "2d_spatial_pointing": 68.56,
286
+ "single_object_tracking": 33.12,
287
+ "temporal_localization": 44.32,
288
+ "dense_video_captioning": 29.64,
289
+ "event_verification": 59.39,
290
+ "video_qa": 66.44
291
+ },
292
+ {
293
+ "id": "alibaba-qwen3-5-vl-27b",
294
+ "name": "Qwen3.5-27B",
295
+ "organization": "Alibaba",
296
+ "params": "27B",
297
+ "type": "open",
298
+ "result_type": "single",
299
+ "verified": true,
300
+ "ensemble": false,
301
+ "is_new": false,
302
+ "date_evaluated": "2026-05-27",
303
+ "model_url": "https://huggingface.co/Qwen/Qwen3.5-VL-27B-Instruct",
304
+ "overall": 49.25,
305
+ "spatial": 79.04,
306
+ "spatio_temporal": 24.4,
307
+ "temporal": 31.93,
308
+ "semantic": 61.65,
309
+ "2d_localization": 85.46,
310
+ "2d_referring_expressions": 76.35,
311
+ "2d_spatial_pointing": 75.32,
312
+ "single_object_tracking": 24.4,
313
+ "temporal_localization": 36.88,
314
+ "dense_video_captioning": 26.97,
315
+ "event_verification": 55.34,
316
+ "video_qa": 67.95
317
+ },
318
+ {
319
+ "id": "alibaba-qwen3-5-vl-9b",
320
+ "name": "Qwen3.5-9B",
321
+ "organization": "Alibaba",
322
+ "params": "9B",
323
+ "type": "open",
324
+ "result_type": "single",
325
+ "verified": true,
326
+ "ensemble": false,
327
+ "is_new": false,
328
+ "date_evaluated": "2026-05-27",
329
+ "model_url": "https://huggingface.co/Qwen/Qwen3.5-VL-9B-Instruct",
330
+ "overall": 48.3,
331
+ "spatial": 75.26,
332
+ "spatio_temporal": 23.52,
333
+ "temporal": 35.2,
334
+ "semantic": 59.2,
335
+ "2d_localization": 76.87,
336
+ "2d_referring_expressions": 77.16,
337
+ "2d_spatial_pointing": 71.74,
338
+ "single_object_tracking": 23.52,
339
+ "temporal_localization": 42.53,
340
+ "dense_video_captioning": 27.88,
341
+ "event_verification": 46.78,
342
+ "video_qa": 71.63
343
+ },
344
+ {
345
+ "id": "nvidia-cosmos-reason2-32b",
346
+ "name": "Cosmos-Reason2-32B",
347
+ "organization": "NVIDIA",
348
+ "params": "32B",
349
+ "type": "open",
350
+ "result_type": "single",
351
+ "verified": true,
352
+ "ensemble": false,
353
+ "is_new": false,
354
+ "date_evaluated": "2026-05-27",
355
+ "model_url": "https://huggingface.co/nvidia/Cosmos-Reason2-32B",
356
+ "overall": 47.03,
357
+ "spatial": 42.81,
358
+ "spatio_temporal": 33.12,
359
+ "temporal": 40.25,
360
+ "semantic": 71.94,
361
+ "2d_localization": 4.35,
362
+ "2d_referring_expressions": 50.04,
363
+ "2d_spatial_pointing": 74.03,
364
+ "single_object_tracking": 33.12,
365
+ "temporal_localization": 50.47,
366
+ "dense_video_captioning": 30.04,
367
+ "event_verification": 73.58,
368
+ "video_qa": 70.29
369
+ },
370
+ {
371
+ "id": "nvidia-cosmos3-edge",
372
+ "name": "Cosmos3-Edge",
373
+ "organization": "NVIDIA",
374
+ "params": "4B",
375
+ "type": "open",
376
+ "result_type": "single",
377
+ "verified": true,
378
+ "ensemble": false,
379
+ "is_new": true,
380
+ "date_evaluated": "2026-07-16",
381
+ "model_url": "https://huggingface.co/nvidia/Cosmos3-Edge",
382
+ "overall": 46.94,
383
+ "spatial": 72.02,
384
+ "spatio_temporal": 24.65,
385
+ "temporal": 28.07,
386
+ "semantic": 63.02,
387
+ "2d_localization": 79.02,
388
+ "2d_referring_expressions": 69.27,
389
+ "2d_spatial_pointing": 67.76,
390
+ "single_object_tracking": 24.65,
391
+ "temporal_localization": 39.51,
392
+ "dense_video_captioning": 16.64,
393
+ "event_verification": 63.36,
394
+ "video_qa": 62.68
395
+ },
396
+ {
397
+ "id": "nvidia-cosmos-reason2-2b",
398
+ "name": "Cosmos-Reason2-2B",
399
+ "organization": "NVIDIA",
400
+ "params": "2B",
401
+ "type": "open",
402
+ "result_type": "single",
403
+ "verified": true,
404
+ "ensemble": false,
405
+ "is_new": true,
406
+ "date_evaluated": "2026-07-16",
407
+ "model_url": "https://huggingface.co/nvidia/Cosmos-Reason2-2B",
408
+ "overall": 45.94,
409
+ "spatial": 63.2,
410
+ "spatio_temporal": 26.85,
411
+ "temporal": 33.73,
412
+ "semantic": 59.98,
413
+ "2d_localization": 73.56,
414
+ "2d_referring_expressions": 56.33,
415
+ "2d_spatial_pointing": 59.7,
416
+ "single_object_tracking": 26.85,
417
+ "temporal_localization": 38.95,
418
+ "dense_video_captioning": 28.52,
419
+ "event_verification": 55.27,
420
+ "video_qa": 64.69
421
+ },
422
+ {
423
+ "id": "alibaba-qwen3-vl-2b",
424
+ "name": "Qwen3-VL-2B-Instruct",
425
+ "organization": "Alibaba",
426
+ "params": "2B",
427
+ "type": "open",
428
+ "result_type": "single",
429
+ "verified": true,
430
+ "ensemble": false,
431
+ "is_new": true,
432
+ "date_evaluated": "2026-07-16",
433
+ "model_url": "https://huggingface.co/Qwen/Qwen3-VL-2B-Instruct",
434
+ "overall": 41.86,
435
+ "spatial": 55.22,
436
+ "spatio_temporal": 30.38,
437
+ "temporal": 27.55,
438
+ "semantic": 54.29,
439
+ "2d_localization": 47.42,
440
+ "2d_referring_expressions": 65.12,
441
+ "2d_spatial_pointing": 53.13,
442
+ "single_object_tracking": 30.38,
443
+ "temporal_localization": 35.21,
444
+ "dense_video_captioning": 19.9,
445
+ "event_verification": 44.73,
446
+ "video_qa": 63.85
447
+ },
448
+ {
449
+ "id": "google-gemma-4-e2b-it",
450
+ "name": "Gemma-4-E2B-it",
451
+ "organization": "Google",
452
+ "params": "2B",
453
+ "type": "open",
454
+ "result_type": "single",
455
+ "verified": true,
456
+ "ensemble": false,
457
+ "is_new": true,
458
+ "date_evaluated": "2026-07-16",
459
+ "model_url": "https://huggingface.co/google/gemma-4-e2b-it",
460
+ "overall": 21.28,
461
+ "spatial": 27.55,
462
+ "spatio_temporal": 11.58,
463
+ "temporal": 9.08,
464
+ "semantic": 36.92,
465
+ "2d_localization": 45.4,
466
+ "2d_referring_expressions": 5.52,
467
+ "2d_spatial_pointing": 31.74,
468
+ "single_object_tracking": 11.58,
469
+ "temporal_localization": 9.02,
470
+ "dense_video_captioning": 9.14,
471
+ "event_verification": 27.65,
472
+ "video_qa": 46.19
473
+ },
474
+ {
475
+ "id": "antrino-labs-jev-27b-vl-awq",
476
+ "name": "JEV 27B VL AWQ",
477
+ "organization": "Antrino Labs",
478
+ "params": "27B",
479
+ "type": "open",
480
+ "result_type": "single",
481
+ "verified": false,
482
+ "ensemble": false,
483
+ "is_new": true,
484
+ "date_evaluated": "2026-10-10",
485
+ "semantic": 61.63,
486
+ "event_verification": 56.98,
487
+ "video_qa": 66.28
488
+ }
489
+ ]
490
+ }
util/data.py CHANGED
@@ -1,359 +1,363 @@
1
- """Load and validate the static leaderboard JSON.
2
-
3
- Public surface:
4
- ModelRecord — frozen dataclass for one row of the leaderboard.
5
- LeaderboardData — frozen dataclass wrapping the full dataset + index.
6
- load_results_json — read + validate + build the dataset.
7
-
8
- Validation is strict: any schema violation raises ValueError with a
9
- single multi-line message listing every problem encountered. The app
10
- should let this propagate so the HF Space build fails loudly when the
11
- JSON is bad.
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import json
17
- import re
18
- from dataclasses import dataclass
19
- from pathlib import Path
20
-
21
- from .config import (
22
- ALLOWED_RESULT_TYPES,
23
- ALLOWED_TYPES,
24
- OPTIONAL_TASK_FIELDS,
25
- REQUIRED_MODEL_FIELDS,
26
- REQUIRED_SCORE_FIELDS,
27
- SCHEMA_VERSION,
28
- TYPE_DISPLAY,
29
- )
30
-
31
-
32
- # -- Dataclasses -----------------------------------------------------------
33
-
34
-
35
- @dataclass(frozen=True)
36
- class ModelRecord:
37
- """One model row, post-validation.
38
-
39
- `scores` is a flat dict keyed by JSON field name (e.g. "overall",
40
- "spatial", "2d_localization"). Optional task scores may be absent;
41
- callers should use ``.score(field)`` which returns None when missing.
42
- """
43
-
44
- # Required fields
45
- id: str
46
- name: str
47
- organization: str
48
- params: str
49
- type: str # "open" or "closed"
50
- result_type: str # "single" or "ensemble"
51
- scores: dict[str, float]
52
- param_value: float | None
53
- param_bucket: str | None
54
- type_display: str
55
-
56
- # Optional fields with defaults
57
- url: str | None = None
58
- verified: bool = False
59
- ensemble: bool = False
60
- is_new: bool = False
61
- date_evaluated: str = ""
62
- model_url: str = ""
63
-
64
- def score(self, field_name: str) -> float | None:
65
- return self.scores.get(field_name)
66
-
67
-
68
- @dataclass(frozen=True)
69
- class LeaderboardData:
70
- schema_version: str
71
- benchmark_version: str
72
- updated: str # YYYY-MM-DD as in the source JSON
73
- models: list[ModelRecord]
74
- model_by_id: dict[str, ModelRecord]
75
-
76
-
77
- # -- Internals -------------------------------------------------------------
78
-
79
-
80
- _PARAMS_RE = re.compile(r"^\s*(\d+(?:\.\d+)?)\s*B\s*$")
81
- _DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
82
- _EM_DASH = "—"
83
-
84
-
85
- def _parse_param_value(params: str) -> float | None:
86
- """Return the numeric param count in billions, or None if undisclosed/unparseable."""
87
- if params.strip() == _EM_DASH:
88
- return None
89
- m = _PARAMS_RE.match(params)
90
- return float(m.group(1)) if m else None
91
-
92
-
93
- def _param_bucket(value: float | None) -> str | None:
94
- """Map a numeric param count (in billions) to a coarse bucket label.
95
-
96
- Boundaries match util.config.PARAM_BUCKETS:
97
- v <= 4 → "<4B"
98
- 4 < v < 10 → "4B–10B"
99
- 10 ≤ v ≤ 40 → "10B–40B"
100
- v > 40 → ">40B"
101
-
102
- None inputs return None (used to mark models with undisclosed sizes —
103
- those models are visible only under the "All sizes" filter, never
104
- under a numeric bucket).
105
- """
106
- if value is None:
107
- return None
108
- if value <= 4.0:
109
- return "<4B"
110
- if value < 10.0:
111
- return "4B–10B"
112
- if value <= 40.0:
113
- return "10B–40B"
114
- return ">40B"
115
-
116
-
117
- def _id_label(model_dict: dict, index: int) -> str:
118
- raw = model_dict.get("id")
119
- if isinstance(raw, str) and raw:
120
- return f'models[{index}].id="{raw}"'
121
- return f"models[{index}].id=<missing>"
122
-
123
-
124
- def _validate_score(
125
- model_dict: dict, field: str, prefix: str, errors: list[str], required: bool
126
- ) -> float | None:
127
- """Validate one score field. Returns the float on success, None on failure
128
- (or when an optional field is absent)."""
129
- if field not in model_dict:
130
- if required:
131
- errors.append(f'{prefix}: missing required field "{field}"')
132
- return None
133
- v = model_dict[field]
134
- if v is None:
135
- errors.append(
136
- f'{prefix}: field "{field}" is null; omit the field instead of using null'
137
- )
138
- return None
139
- # bool is a subclass of int; reject it explicitly.
140
- if isinstance(v, bool) or not isinstance(v, (int, float)):
141
- errors.append(
142
- f'{prefix}: field "{field}" must be numeric, got {type(v).__name__}'
143
- )
144
- return None
145
- fv = float(v)
146
- if not (0.0 <= fv <= 100.0):
147
- errors.append(f'{prefix}: field "{field}" must be in [0, 100], got {fv}')
148
- return None
149
- return fv
150
-
151
-
152
- def _validate_optional_bool(
153
- model_dict: dict, field_name: str, prefix: str, errors: list[str]
154
- ) -> bool:
155
- """Validate an optional bool field. Returns the value if valid, False if absent."""
156
- if field_name not in model_dict:
157
- return False
158
- v = model_dict[field_name]
159
- if not isinstance(v, bool):
160
- errors.append(
161
- f'{prefix}: field "{field_name}" must be a boolean, got {type(v).__name__}'
162
- )
163
- return False
164
- return v
165
-
166
-
167
- def _fail(errors: list[str]) -> None:
168
- msg = "[VANTAGE-Bench leaderboard] Schema validation failed:\n" + "\n".join(
169
- f" - {e}" for e in errors
170
- )
171
- raise ValueError(msg)
172
-
173
-
174
- # -- Public loader ---------------------------------------------------------
175
-
176
-
177
- def load_results_json(path: str | Path) -> LeaderboardData:
178
- """Read, validate, and build the in-memory leaderboard dataset.
179
-
180
- Hard-fails with ValueError listing every problem if validation does not pass.
181
- """
182
- p = Path(path)
183
- try:
184
- raw_text = p.read_text(encoding="utf-8")
185
- except OSError as e:
186
- raise ValueError(
187
- f"[VANTAGE-Bench leaderboard] Could not read {p}: {e}"
188
- ) from e
189
-
190
- try:
191
- data = json.loads(raw_text)
192
- except json.JSONDecodeError as e:
193
- raise ValueError(
194
- f"[VANTAGE-Bench leaderboard] JSON parse error in {p}: {e}"
195
- ) from e
196
-
197
- if not isinstance(data, dict):
198
- raise ValueError(
199
- "[VANTAGE-Bench leaderboard] Schema validation failed:\n"
200
- " - top-level JSON must be an object"
201
- )
202
-
203
- errors: list[str] = []
204
-
205
- # -- Top-level fields ---------------------------------------------------
206
-
207
- sv = data.get("schema_version")
208
- if sv != SCHEMA_VERSION:
209
- errors.append(
210
- f'schema_version must equal "{SCHEMA_VERSION}", got {sv!r}'
211
- )
212
-
213
- bv = data.get("benchmark_version")
214
- if not isinstance(bv, str) or not bv:
215
- errors.append("benchmark_version must be a non-empty string")
216
-
217
- updated = data.get("updated")
218
- if not isinstance(updated, str) or not _DATE_RE.match(updated):
219
- errors.append(f"updated must be a YYYY-MM-DD string, got {updated!r}")
220
-
221
- models_raw = data.get("models")
222
- if not isinstance(models_raw, list) or not models_raw:
223
- errors.append("models must be a non-empty list")
224
- _fail(errors)
225
- return # unreachable; satisfies type checkers
226
-
227
- # -- Per-model validation ----------------------------------------------
228
-
229
- seen_ids: set[str] = set()
230
-
231
- for i, m in enumerate(models_raw):
232
- if not isinstance(m, dict):
233
- errors.append(
234
- f"models[{i}] must be an object, got {type(m).__name__}"
235
- )
236
- continue
237
-
238
- prefix = _id_label(m, i)
239
-
240
- # Required string fields
241
- for fld in REQUIRED_MODEL_FIELDS:
242
- if fld not in m:
243
- errors.append(f'{prefix}: missing required field "{fld}"')
244
- elif not isinstance(m[fld], str) or not m[fld]:
245
- errors.append(
246
- f'{prefix}: field "{fld}" must be a non-empty string, got {m[fld]!r}'
247
- )
248
-
249
- # type must be in allowed set
250
- if isinstance(m.get("type"), str) and m["type"] not in ALLOWED_TYPES:
251
- errors.append(
252
- f'{prefix}: field "type" must be one of {ALLOWED_TYPES}, got {m["type"]!r}'
253
- )
254
-
255
- # result_type must be in allowed set
256
- rt = m.get("result_type")
257
- if isinstance(rt, str) and rt not in ALLOWED_RESULT_TYPES:
258
- errors.append(
259
- f'{prefix}: field "result_type" must be one of {ALLOWED_RESULT_TYPES}, got {rt!r}'
260
- )
261
-
262
- # Optional bool fields
263
- _validate_optional_bool(m, "verified", prefix, errors)
264
- _validate_optional_bool(m, "ensemble", prefix, errors)
265
- _validate_optional_bool(m, "is_new", prefix, errors)
266
-
267
- # Uniqueness on id
268
- mid = m.get("id")
269
- if isinstance(mid, str) and mid:
270
- if mid in seen_ids:
271
- errors.append(f"{prefix}: duplicate id")
272
- seen_ids.add(mid)
273
-
274
- # url (optional legacy field — model name link)
275
- if "url" in m:
276
- url = m["url"]
277
- if url is None:
278
- errors.append(
279
- f'{prefix}: field "url" is null; omit the field instead of using null'
280
- )
281
- elif not isinstance(url, str) or not url:
282
- errors.append(
283
- f'{prefix}: field "url" must be a non-empty string when present, got {url!r}'
284
- )
285
-
286
- # model_url (optional — HF model card or project page link)
287
- if "model_url" in m:
288
- mu = m["model_url"]
289
- if not isinstance(mu, str):
290
- errors.append(
291
- f'{prefix}: field "model_url" must be a string, got {type(mu).__name__}'
292
- )
293
-
294
- # date_evaluated (optional — YYYY-MM-DD or empty string)
295
- if "date_evaluated" in m:
296
- de = m["date_evaluated"]
297
- if not isinstance(de, str):
298
- errors.append(
299
- f'{prefix}: field "date_evaluated" must be a string, got {type(de).__name__}'
300
- )
301
- elif de and not _DATE_RE.match(de):
302
- errors.append(
303
- f'{prefix}: field "date_evaluated" must be YYYY-MM-DD or empty, got {de!r}'
304
- )
305
-
306
- # Required score fields
307
- for fld in REQUIRED_SCORE_FIELDS:
308
- _validate_score(m, fld, prefix, errors, required=True)
309
-
310
- # Optional task score fields
311
- for fld in OPTIONAL_TASK_FIELDS:
312
- _validate_score(m, fld, prefix, errors, required=False)
313
-
314
- if errors:
315
- _fail(errors)
316
-
317
- # -- Build records (guaranteed valid past this point) ------------------
318
-
319
- records: list[ModelRecord] = []
320
- for m in models_raw:
321
- scores: dict[str, float] = {}
322
- for fld in REQUIRED_SCORE_FIELDS:
323
- scores[fld] = float(m[fld])
324
- for fld in OPTIONAL_TASK_FIELDS:
325
- if fld in m:
326
- scores[fld] = float(m[fld])
327
-
328
- params_str = m["params"]
329
- pv = _parse_param_value(params_str)
330
- pb = _param_bucket(pv)
331
-
332
- records.append(
333
- ModelRecord(
334
- id=m["id"],
335
- name=m["name"],
336
- organization=m["organization"],
337
- params=params_str,
338
- type=m["type"],
339
- result_type=m["result_type"],
340
- scores=scores,
341
- param_value=pv,
342
- param_bucket=pb,
343
- type_display=TYPE_DISPLAY[m["type"]],
344
- url=m.get("url"),
345
- verified=bool(m.get("verified", False)),
346
- ensemble=bool(m.get("ensemble", False)),
347
- is_new=bool(m.get("is_new", False)),
348
- date_evaluated=m.get("date_evaluated", ""),
349
- model_url=m.get("model_url", ""),
350
- )
351
- )
352
-
353
- return LeaderboardData(
354
- schema_version=sv,
355
- benchmark_version=bv,
356
- updated=updated,
357
- models=records,
358
- model_by_id={r.id: r for r in records},
359
- )
 
 
 
 
 
1
+ """Load and validate the static leaderboard JSON.
2
+
3
+ Public surface:
4
+ ModelRecord — frozen dataclass for one row of the leaderboard.
5
+ LeaderboardData — frozen dataclass wrapping the full dataset + index.
6
+ load_results_json — read + validate + build the dataset.
7
+
8
+ Validation is strict: any schema violation raises ValueError with a
9
+ single multi-line message listing every problem encountered. The app
10
+ should let this propagate so the HF Space build fails loudly when the
11
+ JSON is bad.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import re
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+
21
+ from .config import (
22
+ ALLOWED_RESULT_TYPES,
23
+ ALLOWED_TYPES,
24
+ OPTIONAL_TASK_FIELDS,
25
+ REQUIRED_MODEL_FIELDS,
26
+ REQUIRED_SCORE_FIELDS,
27
+ SCHEMA_VERSION,
28
+ TYPE_DISPLAY,
29
+ )
30
+
31
+
32
+ # -- Dataclasses -----------------------------------------------------------
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class ModelRecord:
37
+ """One model row, post-validation.
38
+
39
+ `scores` is a flat dict keyed by JSON field name (e.g. "overall",
40
+ "spatial", "2d_localization"). Any score may be absent;
41
+ callers should use ``.score(field)`` which returns None when missing.
42
+ """
43
+
44
+ # Required fields
45
+ id: str
46
+ name: str
47
+ organization: str
48
+ params: str
49
+ type: str # "open" or "closed"
50
+ result_type: str # "single" or "ensemble"
51
+ scores: dict[str, float]
52
+ param_value: float | None
53
+ param_bucket: str | None
54
+ type_display: str
55
+
56
+ # Optional fields with defaults
57
+ url: str | None = None
58
+ verified: bool = False
59
+ ensemble: bool = False
60
+ is_new: bool = False
61
+ date_evaluated: str = ""
62
+ model_url: str = ""
63
+
64
+ def score(self, field_name: str) -> float | None:
65
+ return self.scores.get(field_name)
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class LeaderboardData:
70
+ schema_version: str
71
+ benchmark_version: str
72
+ updated: str # YYYY-MM-DD as in the source JSON
73
+ models: list[ModelRecord]
74
+ model_by_id: dict[str, ModelRecord]
75
+
76
+
77
+ # -- Internals -------------------------------------------------------------
78
+
79
+
80
+ _PARAMS_RE = re.compile(r"^\s*(\d+(?:\.\d+)?)\s*B\s*$")
81
+ _DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
82
+ _EM_DASH = "—"
83
+
84
+
85
+ def _parse_param_value(params: str) -> float | None:
86
+ """Return the numeric param count in billions, or None if undisclosed/unparseable."""
87
+ if params.strip() == _EM_DASH:
88
+ return None
89
+ m = _PARAMS_RE.match(params)
90
+ return float(m.group(1)) if m else None
91
+
92
+
93
+ def _param_bucket(value: float | None) -> str | None:
94
+ """Map a numeric param count (in billions) to a coarse bucket label.
95
+
96
+ Boundaries match util.config.PARAM_BUCKETS:
97
+ v <= 4 → "<4B"
98
+ 4 < v < 10 → "4B–10B"
99
+ 10 ≤ v ≤ 40 → "10B–40B"
100
+ v > 40 → ">40B"
101
+
102
+ None inputs return None (used to mark models with undisclosed sizes —
103
+ those models are visible only under the "All sizes" filter, never
104
+ under a numeric bucket).
105
+ """
106
+ if value is None:
107
+ return None
108
+ if value <= 4.0:
109
+ return "<4B"
110
+ if value < 10.0:
111
+ return "4B–10B"
112
+ if value <= 40.0:
113
+ return "10B–40B"
114
+ return ">40B"
115
+
116
+
117
+ def _id_label(model_dict: dict, index: int) -> str:
118
+ raw = model_dict.get("id")
119
+ if isinstance(raw, str) and raw:
120
+ return f'models[{index}].id="{raw}"'
121
+ return f"models[{index}].id=<missing>"
122
+
123
+
124
+ def _validate_score(
125
+ model_dict: dict, field: str, prefix: str, errors: list[str], required: bool
126
+ ) -> float | None:
127
+ """Validate one score field. Returns the float on success, None on failure
128
+ (or when an optional field is absent)."""
129
+ if field not in model_dict:
130
+ if required:
131
+ errors.append(f'{prefix}: missing required field "{field}"')
132
+ return None
133
+ v = model_dict[field]
134
+ if v is None:
135
+ errors.append(
136
+ f'{prefix}: field "{field}" is null; omit the field instead of using null'
137
+ )
138
+ return None
139
+ # bool is a subclass of int; reject it explicitly.
140
+ if isinstance(v, bool) or not isinstance(v, (int, float)):
141
+ errors.append(
142
+ f'{prefix}: field "{field}" must be numeric, got {type(v).__name__}'
143
+ )
144
+ return None
145
+ fv = float(v)
146
+ if not (0.0 <= fv <= 100.0):
147
+ errors.append(f'{prefix}: field "{field}" must be in [0, 100], got {fv}')
148
+ return None
149
+ return fv
150
+
151
+
152
+ def _validate_optional_bool(
153
+ model_dict: dict, field_name: str, prefix: str, errors: list[str]
154
+ ) -> bool:
155
+ """Validate an optional bool field. Returns the value if valid, False if absent."""
156
+ if field_name not in model_dict:
157
+ return False
158
+ v = model_dict[field_name]
159
+ if not isinstance(v, bool):
160
+ errors.append(
161
+ f'{prefix}: field "{field_name}" must be a boolean, got {type(v).__name__}'
162
+ )
163
+ return False
164
+ return v
165
+
166
+
167
+ def _fail(errors: list[str]) -> None:
168
+ msg = "[VANTAGE-Bench leaderboard] Schema validation failed:\n" + "\n".join(
169
+ f" - {e}" for e in errors
170
+ )
171
+ raise ValueError(msg)
172
+
173
+
174
+ # -- Public loader ---------------------------------------------------------
175
+
176
+
177
+ def load_results_json(path: str | Path) -> LeaderboardData:
178
+ """Read, validate, and build the in-memory leaderboard dataset.
179
+
180
+ Hard-fails with ValueError listing every problem if validation does not pass.
181
+ """
182
+ p = Path(path)
183
+ try:
184
+ raw_text = p.read_text(encoding="utf-8")
185
+ except OSError as e:
186
+ raise ValueError(
187
+ f"[VANTAGE-Bench leaderboard] Could not read {p}: {e}"
188
+ ) from e
189
+
190
+ try:
191
+ data = json.loads(raw_text)
192
+ except json.JSONDecodeError as e:
193
+ raise ValueError(
194
+ f"[VANTAGE-Bench leaderboard] JSON parse error in {p}: {e}"
195
+ ) from e
196
+
197
+ if not isinstance(data, dict):
198
+ raise ValueError(
199
+ "[VANTAGE-Bench leaderboard] Schema validation failed:\n"
200
+ " - top-level JSON must be an object"
201
+ )
202
+
203
+ errors: list[str] = []
204
+
205
+ # -- Top-level fields ---------------------------------------------------
206
+
207
+ sv = data.get("schema_version")
208
+ if sv != SCHEMA_VERSION:
209
+ errors.append(
210
+ f'schema_version must equal "{SCHEMA_VERSION}", got {sv!r}'
211
+ )
212
+
213
+ bv = data.get("benchmark_version")
214
+ if not isinstance(bv, str) or not bv:
215
+ errors.append("benchmark_version must be a non-empty string")
216
+
217
+ updated = data.get("updated")
218
+ if not isinstance(updated, str) or not _DATE_RE.match(updated):
219
+ errors.append(f"updated must be a YYYY-MM-DD string, got {updated!r}")
220
+
221
+ models_raw = data.get("models")
222
+ if not isinstance(models_raw, list) or not models_raw:
223
+ errors.append("models must be a non-empty list")
224
+ _fail(errors)
225
+ return # unreachable; satisfies type checkers
226
+
227
+ # -- Per-model validation ----------------------------------------------
228
+
229
+ seen_ids: set[str] = set()
230
+
231
+ for i, m in enumerate(models_raw):
232
+ if not isinstance(m, dict):
233
+ errors.append(
234
+ f"models[{i}] must be an object, got {type(m).__name__}"
235
+ )
236
+ continue
237
+
238
+ prefix = _id_label(m, i)
239
+
240
+ # Required string fields
241
+ for fld in REQUIRED_MODEL_FIELDS:
242
+ if fld not in m:
243
+ errors.append(f'{prefix}: missing required field "{fld}"')
244
+ elif not isinstance(m[fld], str) or not m[fld]:
245
+ errors.append(
246
+ f'{prefix}: field "{fld}" must be a non-empty string, got {m[fld]!r}'
247
+ )
248
+
249
+ # type must be in allowed set
250
+ if isinstance(m.get("type"), str) and m["type"] not in ALLOWED_TYPES:
251
+ errors.append(
252
+ f'{prefix}: field "type" must be one of {ALLOWED_TYPES}, got {m["type"]!r}'
253
+ )
254
+
255
+ # result_type must be in allowed set
256
+ rt = m.get("result_type")
257
+ if isinstance(rt, str) and rt not in ALLOWED_RESULT_TYPES:
258
+ errors.append(
259
+ f'{prefix}: field "result_type" must be one of {ALLOWED_RESULT_TYPES}, got {rt!r}'
260
+ )
261
+
262
+ # Optional bool fields
263
+ _validate_optional_bool(m, "verified", prefix, errors)
264
+ _validate_optional_bool(m, "ensemble", prefix, errors)
265
+ _validate_optional_bool(m, "is_new", prefix, errors)
266
+
267
+ # Uniqueness on id
268
+ mid = m.get("id")
269
+ if isinstance(mid, str) and mid:
270
+ if mid in seen_ids:
271
+ errors.append(f"{prefix}: duplicate id")
272
+ seen_ids.add(mid)
273
+
274
+ # url (optional legacy field — model name link)
275
+ if "url" in m:
276
+ url = m["url"]
277
+ if url is None:
278
+ errors.append(
279
+ f'{prefix}: field "url" is null; omit the field instead of using null'
280
+ )
281
+ elif not isinstance(url, str) or not url:
282
+ errors.append(
283
+ f'{prefix}: field "url" must be a non-empty string when present, got {url!r}'
284
+ )
285
+
286
+ # model_url (optional — HF model card or project page link)
287
+ if "model_url" in m:
288
+ mu = m["model_url"]
289
+ if not isinstance(mu, str):
290
+ errors.append(
291
+ f'{prefix}: field "model_url" must be a string, got {type(mu).__name__}'
292
+ )
293
+
294
+ # date_evaluated (optional — YYYY-MM-DD or empty string)
295
+ if "date_evaluated" in m:
296
+ de = m["date_evaluated"]
297
+ if not isinstance(de, str):
298
+ errors.append(
299
+ f'{prefix}: field "date_evaluated" must be a string, got {type(de).__name__}'
300
+ )
301
+ elif de and not _DATE_RE.match(de):
302
+ errors.append(
303
+ f'{prefix}: field "date_evaluated" must be YYYY-MM-DD or empty, got {de!r}'
304
+ )
305
+
306
+ # Pillar and task scores are all optional: a model scored on only
307
+ # some tasks is listed with "—" for the rest. It needs at least one
308
+ # score, and may carry "overall" only when all four pillars are present.
309
+ for fld in REQUIRED_SCORE_FIELDS + OPTIONAL_TASK_FIELDS:
310
+ _validate_score(m, fld, prefix, errors, required=False)
311
+ if not any(f in m for f in REQUIRED_SCORE_FIELDS + OPTIONAL_TASK_FIELDS):
312
+ errors.append(f"{prefix}: model has no scores")
313
+ if "overall" in m:
314
+ missing = [f for f in REQUIRED_SCORE_FIELDS if f not in m]
315
+ if missing:
316
+ errors.append(
317
+ f'{prefix}: "overall" requires all pillar scores, missing {missing}'
318
+ )
319
+
320
+ if errors:
321
+ _fail(errors)
322
+
323
+ # -- Build records (guaranteed valid past this point) ------------------
324
+
325
+ records: list[ModelRecord] = []
326
+ for m in models_raw:
327
+ scores: dict[str, float] = {}
328
+ for fld in REQUIRED_SCORE_FIELDS + OPTIONAL_TASK_FIELDS:
329
+ if fld in m:
330
+ scores[fld] = float(m[fld])
331
+
332
+ params_str = m["params"]
333
+ pv = _parse_param_value(params_str)
334
+ pb = _param_bucket(pv)
335
+
336
+ records.append(
337
+ ModelRecord(
338
+ id=m["id"],
339
+ name=m["name"],
340
+ organization=m["organization"],
341
+ params=params_str,
342
+ type=m["type"],
343
+ result_type=m["result_type"],
344
+ scores=scores,
345
+ param_value=pv,
346
+ param_bucket=pb,
347
+ type_display=TYPE_DISPLAY[m["type"]],
348
+ url=m.get("url"),
349
+ verified=bool(m.get("verified", False)),
350
+ ensemble=bool(m.get("ensemble", False)),
351
+ is_new=bool(m.get("is_new", False)),
352
+ date_evaluated=m.get("date_evaluated", ""),
353
+ model_url=m.get("model_url", ""),
354
+ )
355
+ )
356
+
357
+ return LeaderboardData(
358
+ schema_version=sv,
359
+ benchmark_version=bv,
360
+ updated=updated,
361
+ models=records,
362
+ model_by_id={r.id: r for r in records},
363
+ )
util/ranking.py CHANGED
@@ -1,116 +1,116 @@
1
- """Global rank computation for the leaderboard tabs.
2
-
3
- Ranks are computed once at startup over the full model list, before any
4
- filtering or partitioning into Open-Weight / Proprietary tables.
5
- They are properties of the model on this benchmark version and do not
6
- change when users apply filters or re-sort interactively.
7
-
8
- Tie handling: standard competition ranking ("1224").
9
- scores 70, 65, 65, 60 → ranks 1, 2, 2, 4
10
-
11
- Module-level export
12
- -------------------
13
- GLOBAL_RANKS : dict[str, dict[str, int]]
14
- Keyed by the short pillar names from config.PILLARS:
15
- 'overall', 'spatial', 'st', 'temporal', 'semantic'.
16
- Populated the first time compute_global_ranks() is called at startup.
17
- Import directly::
18
-
19
- from util.ranking import GLOBAL_RANKS
20
- """
21
-
22
- from __future__ import annotations
23
-
24
- from typing import Iterable
25
-
26
- from .config import HEADLINE_FIELD_BY_TAB
27
- from .data import ModelRecord
28
-
29
- # Map short pillar key (config.PILLARS) → score field in ModelRecord.scores.
30
- # 'st' is the short form of 'spatio_temporal' used by the new UI layer.
31
- _PILLAR_SCORE_FIELD: dict[str, str] = {
32
- "overall": "overall",
33
- "spatial": "spatial",
34
- "st": "spatio_temporal",
35
- "temporal": "temporal",
36
- "semantic": "semantic",
37
- }
38
-
39
- # Module-level export. Starts empty; populated by compute_global_ranks()
40
- # on the first (and only) call at app startup.
41
- GLOBAL_RANKS: dict[str, dict[str, int]] = {}
42
-
43
-
44
- # -- Core ranking primitive ------------------------------------------------
45
-
46
-
47
- def _rank_models(models: list[ModelRecord], score_field: str) -> dict[str, int]:
48
- """Return ``{model_id: rank}`` using 1-2-2-4 competition ranking.
49
-
50
- Sort key: descending score, ascending name as deterministic tiebreaker.
51
- The score field must be present in every model's scores dict.
52
- """
53
- sorted_models = sorted(
54
- models,
55
- key=lambda m: (-_headline(m, score_field), m.name),
56
- )
57
- ranks: dict[str, int] = {}
58
- prev_score: float | None = None
59
- prev_rank: int = 0
60
- for position, m in enumerate(sorted_models, start=1):
61
- s = m.scores[score_field]
62
- if prev_score is not None and s == prev_score:
63
- ranks[m.id] = prev_rank
64
- else:
65
- ranks[m.id] = position
66
- prev_rank = position
67
- prev_score = s
68
- return ranks
69
-
70
-
71
- # -- Public API ------------------------------------------------------------
72
-
73
-
74
- def compute_global_ranks(
75
- models: list[ModelRecord], tabs: Iterable[str]
76
- ) -> dict[str, dict[str, int]]:
77
- """Return ``{tab: {model_id: rank}}`` with global standard-competition ranks.
78
-
79
- Sort key per tab: descending by the tab's headline score, ascending by
80
- model name as deterministic tiebreaker. Required score fields are
81
- guaranteed present by upstream validation.
82
-
83
- Side-effect: populates the module-level ``GLOBAL_RANKS`` dict using the
84
- short pillar keys from ``config.PILLARS`` ('overall', 'spatial', 'st',
85
- 'temporal', 'semantic'). This happens once at app startup so any module
86
- can ``from util.ranking import GLOBAL_RANKS`` after startup.
87
- """
88
- # Build return value keyed by caller-supplied tab names (backward-compat).
89
- result: dict[str, dict[str, int]] = {}
90
- for tab in tabs:
91
- field = HEADLINE_FIELD_BY_TAB[tab]
92
- result[tab] = _rank_models(models, field)
93
-
94
- # Populate GLOBAL_RANKS with short pillar keys for the new UI layer.
95
- for pillar_key, score_field in _PILLAR_SCORE_FIELD.items():
96
- GLOBAL_RANKS[pillar_key] = _rank_models(models, score_field)
97
-
98
- return result
99
-
100
-
101
- def sort_model_ids_by_rank(
102
- model_ids: Iterable[str], rank_map: dict[str, int]
103
- ) -> list[str]:
104
- """Return ``model_ids`` sorted by ascending rank.
105
-
106
- Ids missing from ``rank_map`` are placed last in their input order.
107
- """
108
- sentinel = float("inf")
109
- return sorted(model_ids, key=lambda mid: rank_map.get(mid, sentinel))
110
-
111
-
112
- def _headline(m: ModelRecord, field: str) -> float:
113
- # Required scores guaranteed by validation; defensive default keeps
114
- # this function total in case it is called pre-validation.
115
- v = m.scores.get(field)
116
- return v if v is not None else float("-inf")
 
1
+ """Global rank computation for the leaderboard tabs.
2
+
3
+ Ranks are computed once at startup over the full model list, before any
4
+ filtering or partitioning into Open-Weight / Proprietary tables.
5
+ They are properties of the model on this benchmark version and do not
6
+ change when users apply filters or re-sort interactively.
7
+
8
+ Tie handling: standard competition ranking ("1224").
9
+ scores 70, 65, 65, 60 → ranks 1, 2, 2, 4
10
+
11
+ Module-level export
12
+ -------------------
13
+ GLOBAL_RANKS : dict[str, dict[str, int]]
14
+ Keyed by the short pillar names from config.PILLARS:
15
+ 'overall', 'spatial', 'st', 'temporal', 'semantic'.
16
+ Populated the first time compute_global_ranks() is called at startup.
17
+ Import directly::
18
+
19
+ from util.ranking import GLOBAL_RANKS
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Iterable
25
+
26
+ from .config import HEADLINE_FIELD_BY_TAB
27
+ from .data import ModelRecord
28
+
29
+ # Map short pillar key (config.PILLARS) → score field in ModelRecord.scores.
30
+ # 'st' is the short form of 'spatio_temporal' used by the new UI layer.
31
+ _PILLAR_SCORE_FIELD: dict[str, str] = {
32
+ "overall": "overall",
33
+ "spatial": "spatial",
34
+ "st": "spatio_temporal",
35
+ "temporal": "temporal",
36
+ "semantic": "semantic",
37
+ }
38
+
39
+ # Module-level export. Starts empty; populated by compute_global_ranks()
40
+ # on the first (and only) call at app startup.
41
+ GLOBAL_RANKS: dict[str, dict[str, int]] = {}
42
+
43
+
44
+ # -- Core ranking primitive ------------------------------------------------
45
+
46
+
47
+ def _rank_models(models: list[ModelRecord], score_field: str) -> dict[str, int]:
48
+ """Return ``{model_id: rank}`` using 1-2-2-4 competition ranking.
49
+
50
+ Sort key: descending score, ascending name as deterministic tiebreaker.
51
+ Models without the score field (partial submissions) get no rank.
52
+ """
53
+ sorted_models = sorted(
54
+ (m for m in models if score_field in m.scores),
55
+ key=lambda m: (-_headline(m, score_field), m.name),
56
+ )
57
+ ranks: dict[str, int] = {}
58
+ prev_score: float | None = None
59
+ prev_rank: int = 0
60
+ for position, m in enumerate(sorted_models, start=1):
61
+ s = m.scores[score_field]
62
+ if prev_score is not None and s == prev_score:
63
+ ranks[m.id] = prev_rank
64
+ else:
65
+ ranks[m.id] = position
66
+ prev_rank = position
67
+ prev_score = s
68
+ return ranks
69
+
70
+
71
+ # -- Public API ------------------------------------------------------------
72
+
73
+
74
+ def compute_global_ranks(
75
+ models: list[ModelRecord], tabs: Iterable[str]
76
+ ) -> dict[str, dict[str, int]]:
77
+ """Return ``{tab: {model_id: rank}}`` with global standard-competition ranks.
78
+
79
+ Sort key per tab: descending by the tab's headline score, ascending by
80
+ model name as deterministic tiebreaker. Models missing a tab's score
81
+ are left out of that tab's ranking.
82
+
83
+ Side-effect: populates the module-level ``GLOBAL_RANKS`` dict using the
84
+ short pillar keys from ``config.PILLARS`` ('overall', 'spatial', 'st',
85
+ 'temporal', 'semantic'). This happens once at app startup so any module
86
+ can ``from util.ranking import GLOBAL_RANKS`` after startup.
87
+ """
88
+ # Build return value keyed by caller-supplied tab names (backward-compat).
89
+ result: dict[str, dict[str, int]] = {}
90
+ for tab in tabs:
91
+ field = HEADLINE_FIELD_BY_TAB[tab]
92
+ result[tab] = _rank_models(models, field)
93
+
94
+ # Populate GLOBAL_RANKS with short pillar keys for the new UI layer.
95
+ for pillar_key, score_field in _PILLAR_SCORE_FIELD.items():
96
+ GLOBAL_RANKS[pillar_key] = _rank_models(models, score_field)
97
+
98
+ return result
99
+
100
+
101
+ def sort_model_ids_by_rank(
102
+ model_ids: Iterable[str], rank_map: dict[str, int]
103
+ ) -> list[str]:
104
+ """Return ``model_ids`` sorted by ascending rank.
105
+
106
+ Ids missing from ``rank_map`` are placed last in their input order.
107
+ """
108
+ sentinel = float("inf")
109
+ return sorted(model_ids, key=lambda mid: rank_map.get(mid, sentinel))
110
+
111
+
112
+ def _headline(m: ModelRecord, field: str) -> float:
113
+ # Required scores guaranteed by validation; defensive default keeps
114
+ # this function total in case it is called pre-validation.
115
+ v = m.scores.get(field)
116
+ return v if v is not None else float("-inf")