4444
4545import msgspec
4646import msgspec .json
47+ import msgspec .structs
4748from huggingface_hub import model_info
4849from tqdm import tqdm
4950from transformers .utils import logging as transformers_logging
8384from inference_endpoint .endpoint_client .http_client import HTTPEndpointClient
8485from inference_endpoint .endpoint_client .http_sample_issuer import HttpClientSampleIssuer
8586from inference_endpoint .evaluation import Extractor
86- from inference_endpoint .evaluation .scoring import Scorer
87+ from inference_endpoint .evaluation .scoring import (
88+ GptOssAccuracyScorer ,
89+ GptOssMember ,
90+ Scorer ,
91+ )
8792from inference_endpoint .exceptions import (
8893 ExecutionError ,
8994 InputValidationError ,
@@ -160,6 +165,9 @@ class AccuracyConfiguration:
160165 ground_truth_column : str | None
161166 num_repeats : int
162167 extras : dict [str , Any ] = field (default_factory = dict )
168+ # Consolidation group tag; members sharing a group (>=2) roll up into one
169+ # combined score. None -> scored on its own.
170+ group : str | None = None
163171
164172
165173@dataclass
@@ -274,6 +282,19 @@ def _resolve_accuracy_components(
274282 return scorer_cls , extractor_cls
275283
276284
285+ def _accuracy_group (accuracy_config : Any | None ) -> str | None :
286+ """Explicit consolidation group for an accuracy dataset.
287+
288+ Only an explicit ``accuracy_config.group`` triggers consolidation. The
289+ ``::<variant>`` name suffix is a naming convention (``open_orca::llama2_70b``,
290+ ``cnn_dailymail::llama3_8b``), NOT a grouping signal, so it must not roll
291+ datasets up implicitly — that would silently consolidate any future config
292+ with two accuracy datasets sharing a model suffix. The gpt-oss example
293+ configs set ``group: "gptoss"`` explicitly.
294+ """
295+ return getattr (accuracy_config , "group" , None ) if accuracy_config else None
296+
297+
277298def _load_datasets (
278299 config : BenchmarkConfig ,
279300 report_dir : Path ,
@@ -319,6 +340,7 @@ def _load_datasets(
319340 acc_cfg .accuracy_config .ground_truth ,
320341 acc_cfg .accuracy_config .num_repeats ,
321342 acc_cfg .accuracy_config .extras or {},
343+ group = _accuracy_group (acc_cfg .accuracy_config ),
322344 )
323345 )
324346 ds .load (
@@ -373,6 +395,7 @@ def _load_datasets(
373395 accuracy_config .ground_truth ,
374396 accuracy_config .num_repeats ,
375397 accuracy_config .extras or {},
398+ group = _accuracy_group (accuracy_config ),
376399 )
377400 )
378401
@@ -1169,41 +1192,76 @@ def _salvage_tmpfs(report_dir: Path, tmpfs_dir: Path) -> None:
11691192 logger .debug (f"Copied { src_events } -> { dst_events } " )
11701193
11711194
1172- def finalize_benchmark (ctx : BenchmarkContext , bench : BenchmarkResult ) -> None :
1173- """Score accuracy, aggregate results, write JSON."""
1174- config = ctx .config
1175- result = bench .session
1176- collector = bench .collector
1177- report = bench .report
1195+ def _score_accuracy (ctx : BenchmarkContext , result : SessionResult ) -> dict [str , Any ]:
1196+ """Run accuracy scorers, consolidating grouped datasets into one entry each.
11781197
1179- # Display report if available (from MetricsAggregator pub/sub snapshot).
1180- # result_summary.json is the self-complete machine-readable report (carries
1181- # qps/tps/seeds via Report.to_json); report.txt is the full human-readable
1182- # dump (histograms + percentiles); the console log shows just the summary.
1183- if report is not None :
1184- report .display (fn = lambda s : logger .info (s ), summary_only = True )
1185- report .to_json (save_to = ctx .report_dir / "result_summary.json" )
1186-
1187- report_txt = ctx .report_dir / "report.txt"
1188- with report_txt .open ("w" ) as f :
1189- report .display (fn = lambda s : print (s , file = f ))
1190- if bench .profiling is not None :
1191- _write_profiling_section (f , bench .profiling )
1192- logger .info ("Report written to %s" , report_txt )
1198+ Datasets sharing a consolidation group with >=2 members (e.g. gpt-oss's
1199+ aime25/gpqa/livecodebench subsets) are rolled up by GptOssAccuracyScorer into
1200+ a single combined entry keyed by the group name; everything else is scored on
1201+ its own exactly as before.
1202+ """
1203+ accuracy_scores : dict [str , Any ] = {}
11931204
1194- # Sibling profiling.json — kept separate so Report stays a pure
1195- # snapshot-derived struct.
1196- if bench .profiling is not None :
1197- (ctx .report_dir / "profiling.json" ).write_text (
1198- json .dumps (bench .profiling , indent = 2 )
1205+ groups : dict [str , list [AccuracyConfiguration ]] = {}
1206+ for cfg in ctx .eval_configs :
1207+ if cfg .group :
1208+ groups .setdefault (cfg .group , []).append (cfg )
1209+ combined = {g : members for g , members in groups .items () if len (members ) >= 2 }
1210+ combined_ids = {id (cfg ) for members in combined .values () for cfg in members }
1211+
1212+ for group_name , members in combined .items ():
1213+ gpt_members = [
1214+ GptOssMember (
1215+ full_name = cfg .dataset_name ,
1216+ subset = cfg .dataset_name .split ("::" )[0 ],
1217+ scorer_cls = cfg .scorer ,
1218+ extractor = cfg .extractor ,
1219+ dataset = cfg .dataset ,
1220+ ground_truth_column = cfg .ground_truth_column ,
1221+ extras = cfg .extras ,
1222+ )
1223+ for cfg in members
1224+ ]
1225+ scorer = GptOssAccuracyScorer (gpt_members , members [0 ].report_dir )
1226+ score , n_repeats = scorer .score ()
1227+ breakdown = scorer .score_breakdown ()
1228+ entry : dict [str , Any ] = {
1229+ "dataset_name" : group_name ,
1230+ "num_samples" : (breakdown or {}).get ("total_samples" , 0 ),
1231+ "score" : score ,
1232+ "complete" : scorer .complete ,
1233+ }
1234+ if breakdown is not None :
1235+ entry ["breakdown" ] = breakdown
1236+ accuracy_scores [group_name ] = entry
1237+ logger .info (
1238+ f"Score for { group_name } (combined { len (members )} subsets): { score } "
1239+ f"({ n_repeats } repeats, complete={ scorer .complete } )"
11991240 )
12001241
1201- # Write scoring artifacts + copy event log from tmpfs to disk
1202- _write_scoring_artifacts (ctx , result , bench .tmpfs_dir )
1242+ # Per-subset audit entries from the same scoring pass (no re-scoring):
1243+ # keeps the individual <subset>::gptoss scores in results.json for audit
1244+ # and for downstream consumers that read them. These carry no breakdown,
1245+ # so only the consolidated entry surfaces in the report headline.
1246+ for cfg in members :
1247+ member_score , member_complete = scorer .member_scores .get (
1248+ cfg .dataset_name , (None , False )
1249+ )
1250+ assert cfg .dataset .data is not None
1251+ accuracy_scores [cfg .dataset_name ] = {
1252+ "dataset_name" : cfg .dataset_name ,
1253+ "num_samples" : len (cfg .dataset .data ),
1254+ "extractor" : (
1255+ cfg .extractor .__name__ if cfg .extractor is not None else None
1256+ ),
1257+ "ground_truth_column" : cfg .ground_truth_column ,
1258+ "score" : member_score ,
1259+ "complete" : member_complete ,
1260+ }
12031261
1204- # Accuracy scoring
1205- accuracy_scores : dict [str , Any ] = {}
12061262 for eval_cfg in ctx .eval_configs :
1263+ if id (eval_cfg ) in combined_ids :
1264+ continue
12071265 try :
12081266 scorer_instance = eval_cfg .scorer (
12091267 eval_cfg .dataset_name ,
@@ -1223,7 +1281,7 @@ def finalize_benchmark(ctx: BenchmarkContext, bench: BenchmarkResult) -> None:
12231281 num_samples = len (eval_cfg .dataset .data )
12241282 if eval_cfg .dataset_name == "performance" :
12251283 num_samples = sum (phase .issued_count for phase in result .perf_results )
1226- entry : dict [str , Any ] = {
1284+ singleton_entry : dict [str , Any ] = {
12271285 "dataset_name" : eval_cfg .dataset_name ,
12281286 "num_samples" : num_samples ,
12291287 "extractor" : (
@@ -1232,25 +1290,84 @@ def finalize_benchmark(ctx: BenchmarkContext, bench: BenchmarkResult) -> None:
12321290 "ground_truth_column" : eval_cfg .ground_truth_column ,
12331291 "score" : score ,
12341292 # False when the scorer produced only a partial headline (e.g.
1235- # LegacyMLPerfDeepSeekR1Scorer when the lcb-service container was unreachable),
1236- # so a partial number is never mistaken for a complete one.
1293+ # LegacyMLPerfDeepSeekR1Scorer when the lcb-service container was
1294+ # unreachable), so a partial number is never mistaken for a complete one.
12371295 "complete" : scorer_instance .complete ,
12381296 }
12391297 breakdown = scorer_instance .score_breakdown ()
12401298 if breakdown is not None :
1241- entry ["breakdown" ] = breakdown
1242- accuracy_scores [eval_cfg .dataset_name ] = entry
1299+ singleton_entry ["breakdown" ] = breakdown
1300+ accuracy_scores [eval_cfg .dataset_name ] = singleton_entry
12431301 logger .info (
12441302 f"Score for { eval_cfg .dataset_name } : { score } "
12451303 f"({ n_repeats } repeats, complete={ scorer_instance .complete } )"
12461304 )
12471305
1306+ return accuracy_scores
1307+
1308+
1309+ def finalize_benchmark (ctx : BenchmarkContext , bench : BenchmarkResult ) -> None :
1310+ """Score accuracy, aggregate results, write JSON."""
1311+ config = ctx .config
1312+ result = bench .session
1313+ collector = bench .collector
1314+ report = bench .report
1315+
1316+ # Sibling profiling.json — kept separate so Report stays a pure
1317+ # snapshot-derived struct.
1318+ if bench .profiling is not None :
1319+ (ctx .report_dir / "profiling.json" ).write_text (
1320+ json .dumps (bench .profiling , indent = 2 )
1321+ )
1322+
1323+ # Write scoring artifacts + copy event log from tmpfs to disk (scorers read
1324+ # sample_idx_map.json + events.jsonl from here).
1325+ _write_scoring_artifacts (ctx , result , bench .tmpfs_dir )
1326+
1327+ # Accuracy scoring (grouped datasets consolidated into one entry each).
1328+ # Scoring runs before the report is written so the accuracy headline can be
1329+ # attached, but the report is written in the `finally` below so a scoring
1330+ # failure (e.g. lcb-service unreachable, missing eval subproject, bad extras)
1331+ # still leaves the perf run's result_summary.json / report.txt on disk
1332+ # instead of discarding them — then the exception propagates as before.
1333+ accuracy_scores : dict [str , Any ] = {}
1334+ try :
1335+ accuracy_scores = _score_accuracy (ctx , result )
1336+ finally :
1337+ # Attach accuracy breakdowns so result_summary.json, the console summary,
1338+ # and report.txt all carry the headline from this one scoring pass
1339+ # (absent on a scoring failure — accuracy_scores stays {}).
1340+ if report is not None :
1341+ accuracy_for_report = {
1342+ name : entry ["breakdown" ]
1343+ for name , entry in accuracy_scores .items ()
1344+ if "breakdown" in entry
1345+ }
1346+ if accuracy_for_report :
1347+ report = msgspec .structs .replace (report , accuracy = accuracy_for_report )
1348+
1349+ # Display report if available (from MetricsAggregator pub/sub snapshot).
1350+ # result_summary.json is the self-complete machine-readable report
1351+ # (carries qps/tps/seeds/accuracy via Report.to_json); report.txt is the
1352+ # full human-readable dump; the console log shows the summary.
1353+ if report is not None :
1354+ report .display (fn = lambda s : logger .info (s ), summary_only = True )
1355+ report .to_json (save_to = ctx .report_dir / "result_summary.json" )
1356+
1357+ report_txt = ctx .report_dir / "report.txt"
1358+ with report_txt .open ("w" ) as f :
1359+ report .display (fn = lambda s : print (s , file = f ))
1360+ if bench .profiling is not None :
1361+ _write_profiling_section (f , bench .profiling )
1362+ logger .info ("Report written to %s" , report_txt )
1363+
12481364 # Report metrics: prefer Report from MetricsSnapshot, fall back to SessionResult
12491365 if report is not None and report .duration_ns is not None :
12501366 perf_elapsed = report .duration_ns / 1e9
12511367 total_issued = report .n_samples_issued
12521368 n_errors = report .n_samples_failed
12531369 qps = report .qps or 0.0
1370+ tps = report .tps
12541371 else :
12551372 perf = result .perf_results [0 ] if result .perf_results else None
12561373 if perf :
@@ -1261,6 +1378,8 @@ def finalize_benchmark(ctx: BenchmarkContext, bench: BenchmarkResult) -> None:
12611378 total_issued = 0
12621379 n_errors = len (collector .errors )
12631380 qps = total_issued / perf_elapsed if perf_elapsed > 0 else 0.0
1381+ # No OSL source outside the metrics snapshot, so TPS is unknown here.
1382+ tps = None
12641383
12651384 logger .info (f"Completed in { perf_elapsed :.1f} s" )
12661385 if ctx .accuracy_only :
@@ -1295,6 +1414,7 @@ def finalize_benchmark(ctx: BenchmarkContext, bench: BenchmarkResult) -> None:
12951414 "failed" : n_errors ,
12961415 "elapsed_time" : perf_elapsed ,
12971416 "qps" : qps ,
1417+ "tps" : tps ,
12981418 },
12991419 }
13001420 if accuracy_scores :
0 commit comments