Skip to content

Commit 8eb098f

Browse files
authored
Merge branch 'main' into arekay/feat-consolidate-accuracy-reporting
2 parents c67b105 + 55e518a commit 8eb098f

3 files changed

Lines changed: 176 additions & 6 deletions

File tree

src/inference_endpoint/commands/benchmark/execute.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,16 @@ def _build_phases(
563563
)
564564
)
565565

566+
# Accuracy mirrors the perf load pattern so evaluation exercises the
567+
# endpoint the same way it was benchmarked. AGENTIC_INFERENCE can't drive
568+
# the (non-agentic) accuracy datasets — create_load_strategy rejects it —
569+
# so it (and a missing perf pattern) falls back to MAX_THROUGHPUT.
570+
perf_lp = ctx.rt_settings.load_pattern if ctx.rt_settings is not None else None
571+
if perf_lp is None or perf_lp.type == LoadPatternType.AGENTIC_INFERENCE:
572+
acc_load_pattern = LoadPattern(type=LoadPatternType.MAX_THROUGHPUT)
573+
else:
574+
acc_load_pattern = perf_lp
575+
566576
# Accuracy phases — use eval_cfg.dataset_name as phase name so it matches
567577
# what Scorer._load_sample_index_map() looks up in sample_idx_map.json
568578
for eval_cfg in ctx.eval_configs:
@@ -575,15 +585,14 @@ def _build_phases(
575585
"AgenticInferenceDataset, which is not yet supported for "
576586
"accuracy evaluation."
577587
)
578-
# Accuracy phases run at MAX_THROUGHPUT; inheriting perf_lp (e.g. POISSON)
579-
# would silently rate-limit evaluation until an agentic inference accuracy strategy
580-
# and QPS-budgeting support are added.
588+
logger.info(
589+
"Accuracy issuer '%s' load mode: %s",
590+
eval_cfg.dataset_name,
591+
acc_load_pattern,
592+
)
581593
rng_settings = ctx.rt_settings or RuntimeSettings.from_config(
582594
ctx.config, acc_ds.num_samples()
583595
)
584-
acc_load_pattern: LoadPattern | None = LoadPattern(
585-
type=LoadPatternType.MAX_THROUGHPUT
586-
)
587596
acc_settings = RuntimeSettings(
588597
metric_target=rng_settings.metric_target,
589598
reported_metrics=rng_settings.reported_metrics,

src/inference_endpoint/config/schema.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,20 @@ def _validate_completeness(self) -> Self:
719719
)
720720
return self
721721

722+
def __str__(self) -> str:
723+
"""Human-readable "type (param=value)" form for logging, e.g.
724+
``concurrency (target_concurrency=7)`` / ``poisson (target_qps=10.0)``.
725+
Patterns without a driving parameter render as just the type name.
726+
"""
727+
if self.type in (
728+
LoadPatternType.CONCURRENCY,
729+
LoadPatternType.AGENTIC_INFERENCE,
730+
):
731+
return f"{self.type.value} (target_concurrency={self.target_concurrency})"
732+
if self.type == LoadPatternType.POISSON:
733+
return f"{self.type.value} (target_qps={self.target_qps})"
734+
return self.type.value
735+
722736

723737
@cyclopts.Parameter(name="*")
724738
class WarmupConfig(BaseModel):

tests/unit/commands/test_benchmark.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
"""Tests for benchmark CLI models, config building, and command handlers."""
1717

1818
import asyncio
19+
import dataclasses
1920
import io
2021
import json
22+
import logging
2123
import random
2224
import tempfile
2325
from pathlib import Path
@@ -1195,6 +1197,151 @@ def test_accuracy_drain_timeout_defaults_to_unbounded(
11951197
acc = next(p for p in phases if p.phase_type == PhaseType.ACCURACY)
11961198
assert acc.drain_timeout is None
11971199

1200+
@pytest.mark.unit
1201+
def test_accuracy_phase_inherits_perf_concurrency(
1202+
self, base_rt_settings, simple_dataset
1203+
):
1204+
"""When the perf phase runs CONCURRENCY, the accuracy phase mirrors the
1205+
same fixed concurrency instead of bursting at MAX_THROUGHPUT."""
1206+
rt = dataclasses.replace(
1207+
base_rt_settings,
1208+
load_pattern=LoadPattern(
1209+
type=LoadPatternType.CONCURRENCY, target_concurrency=7
1210+
),
1211+
)
1212+
config = OfflineConfig(**_OFFLINE_KWARGS)
1213+
ctx = self._make_ctx(config, rt, simple_dataset)
1214+
ctx.eval_configs = [self._make_eval_config(simple_dataset)]
1215+
phases = _build_phases(ctx)
1216+
1217+
acc = next(p for p in phases if p.phase_type == PhaseType.ACCURACY)
1218+
assert acc.runtime_settings.load_pattern is not None
1219+
assert acc.runtime_settings.load_pattern.type == LoadPatternType.CONCURRENCY
1220+
assert acc.runtime_settings.load_pattern.target_concurrency == 7
1221+
1222+
@pytest.mark.unit
1223+
def test_accuracy_phase_inherits_perf_poisson(
1224+
self, base_rt_settings, simple_dataset
1225+
):
1226+
"""POISSON perf: accuracy mirrors the same POISSON config (target_qps)."""
1227+
rt = dataclasses.replace(
1228+
base_rt_settings,
1229+
load_pattern=LoadPattern(type=LoadPatternType.POISSON, target_qps=10.0),
1230+
)
1231+
config = OfflineConfig(**_OFFLINE_KWARGS)
1232+
ctx = self._make_ctx(config, rt, simple_dataset)
1233+
ctx.eval_configs = [self._make_eval_config(simple_dataset)]
1234+
phases = _build_phases(ctx)
1235+
1236+
acc = next(p for p in phases if p.phase_type == PhaseType.ACCURACY)
1237+
assert acc.runtime_settings.load_pattern is not None
1238+
assert acc.runtime_settings.load_pattern.type == LoadPatternType.POISSON
1239+
assert acc.runtime_settings.load_pattern.target_qps == 10.0
1240+
1241+
@pytest.mark.unit
1242+
def test_accuracy_phase_max_throughput_when_perf_offline(
1243+
self, base_rt_settings, simple_dataset
1244+
):
1245+
"""Offline (MAX_THROUGHPUT) perf leaves accuracy at MAX_THROUGHPUT."""
1246+
config = OfflineConfig(**_OFFLINE_KWARGS)
1247+
ctx = self._make_ctx(config, base_rt_settings, simple_dataset)
1248+
ctx.eval_configs = [self._make_eval_config(simple_dataset)]
1249+
phases = _build_phases(ctx)
1250+
1251+
acc = next(p for p in phases if p.phase_type == PhaseType.ACCURACY)
1252+
assert acc.runtime_settings.load_pattern is not None
1253+
assert acc.runtime_settings.load_pattern.type == LoadPatternType.MAX_THROUGHPUT
1254+
1255+
@pytest.mark.unit
1256+
def test_accuracy_phase_max_throughput_when_perf_agentic(
1257+
self, base_rt_settings, simple_dataset
1258+
):
1259+
"""AGENTIC_INFERENCE can't drive a non-agentic accuracy dataset, so the
1260+
accuracy phase falls back to MAX_THROUGHPUT instead of crashing."""
1261+
rt = dataclasses.replace(
1262+
base_rt_settings,
1263+
load_pattern=LoadPattern(
1264+
type=LoadPatternType.AGENTIC_INFERENCE, target_concurrency=8
1265+
),
1266+
)
1267+
config = OfflineConfig(**_OFFLINE_KWARGS)
1268+
ctx = self._make_ctx(config, rt, simple_dataset)
1269+
ctx.eval_configs = [self._make_eval_config(simple_dataset)]
1270+
phases = _build_phases(ctx)
1271+
1272+
acc = next(p for p in phases if p.phase_type == PhaseType.ACCURACY)
1273+
assert acc.runtime_settings.load_pattern is not None
1274+
assert acc.runtime_settings.load_pattern.type == LoadPatternType.MAX_THROUGHPUT
1275+
1276+
@pytest.mark.unit
1277+
def test_accuracy_phase_max_throughput_when_perf_none(
1278+
self, base_rt_settings, simple_dataset
1279+
):
1280+
"""A missing perf load pattern falls back to MAX_THROUGHPUT for accuracy."""
1281+
rt = dataclasses.replace(base_rt_settings, load_pattern=None)
1282+
config = OfflineConfig(**_OFFLINE_KWARGS)
1283+
ctx = self._make_ctx(config, rt, simple_dataset)
1284+
ctx.eval_configs = [self._make_eval_config(simple_dataset)]
1285+
phases = _build_phases(ctx)
1286+
1287+
acc = next(p for p in phases if p.phase_type == PhaseType.ACCURACY)
1288+
assert acc.runtime_settings.load_pattern is not None
1289+
assert acc.runtime_settings.load_pattern.type == LoadPatternType.MAX_THROUGHPUT
1290+
1291+
@pytest.mark.unit
1292+
def test_accuracy_issuer_logs_load_mode(
1293+
self, base_rt_settings, simple_dataset, caplog
1294+
):
1295+
"""The accuracy issuer logs which load mode it will run in."""
1296+
rt = dataclasses.replace(
1297+
base_rt_settings,
1298+
load_pattern=LoadPattern(
1299+
type=LoadPatternType.CONCURRENCY, target_concurrency=4
1300+
),
1301+
)
1302+
config = OfflineConfig(**_OFFLINE_KWARGS)
1303+
ctx = self._make_ctx(config, rt, simple_dataset)
1304+
ctx.eval_configs = [self._make_eval_config(simple_dataset)]
1305+
1306+
with caplog.at_level(
1307+
logging.INFO, logger="inference_endpoint.commands.benchmark.execute"
1308+
):
1309+
_build_phases(ctx)
1310+
1311+
msgs = [r.getMessage() for r in caplog.records]
1312+
assert any(
1313+
"load mode" in m and "concurrency" in m and "4" in m for m in msgs
1314+
), msgs
1315+
1316+
@pytest.mark.unit
1317+
def test_accuracy_issuer_logs_poisson_when_perf_poisson(
1318+
self, base_rt_settings, simple_dataset, caplog
1319+
):
1320+
"""POISSON perf logs the inherited poisson mode with its target_qps and
1321+
must NOT emit a target_concurrency suffix (the concurrency-only branch)."""
1322+
rt = dataclasses.replace(
1323+
base_rt_settings,
1324+
load_pattern=LoadPattern(type=LoadPatternType.POISSON, target_qps=10.0),
1325+
)
1326+
config = OfflineConfig(**_OFFLINE_KWARGS)
1327+
ctx = self._make_ctx(config, rt, simple_dataset)
1328+
ctx.eval_configs = [self._make_eval_config(simple_dataset)]
1329+
1330+
with caplog.at_level(
1331+
logging.INFO, logger="inference_endpoint.commands.benchmark.execute"
1332+
):
1333+
_build_phases(ctx)
1334+
1335+
msgs = [r.getMessage() for r in caplog.records]
1336+
load_mode_msgs = [m for m in msgs if "load mode" in m]
1337+
assert load_mode_msgs, msgs
1338+
assert any(
1339+
"poisson" in m and "target_qps=10.0" in m for m in load_mode_msgs
1340+
), load_mode_msgs
1341+
assert all(
1342+
"target_concurrency" not in m for m in load_mode_msgs
1343+
), load_mode_msgs
1344+
11981345
@pytest.mark.unit
11991346
def test_warmup_uses_independent_rng_instances(
12001347
self, base_rt_settings, simple_dataset

0 commit comments

Comments
 (0)