Skip to content

Commit 5bce286

Browse files
committed
Added --exit-on-first-fail and --no-results-on-fail flags
1 parent bbfbebc commit 5bce286

3 files changed

Lines changed: 53 additions & 14 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ Generally you don't need to disable prompt caching on the server, as a probabili
176176
- `--format`: Output format: 'md', 'json', 'csv' (Default: 'md').
177177
- `--save-total-throughput-timeseries`: Save calculated TOTAL throughput for each 1 second window inside peak throughput calculation during the run (default: off).
178178
- `--save-all-throughput-timeseries`: Save calculated throughput timeseries for EACH individual request (default: off).
179+
- `--exit-on-first-fail`: Stop execution on first failed test and exit with non-zero status.
180+
- `--no-results-on-fail`: Prevent saving/printing any results when error is experienced, turns on --exit-on-first-fail as well.
179181

180182
### Metrics
181183

src/llama_benchy/config.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class BenchmarkConfig(BaseModel):
2727
result_format: str = Field("md", description="Output format (md, json, csv)")
2828
save_total_throughput_timeseries: bool = Field(False, description="Save calculated TOTAL throughput for each 1 second window inside peak throughput calculation during the run.")
2929
save_all_throughput_timeseries: bool = Field(False, description="Save calculated throughput timeseries for EACH individual request.")
30+
exit_on_first_fail: bool = Field(False, description="Stop execution on first failed test and exit with non-zero status")
31+
no_results_on_fail: bool = Field(False, description="Prevent saving/printing results when error is experienced, turns on --exit-on-first-fail as well")
3032

3133
@classmethod
3234
def from_args(cls):
@@ -55,8 +57,13 @@ def from_args(cls):
5557
parser.add_argument("--format", type=str, default="md", choices=["md", "json", "csv"], help="Output format")
5658
parser.add_argument("--save-total-throughput-timeseries", action="store_true", help="Save calculated TOTAL throughput for each 1 second window inside peak throughput calculation during the run.")
5759
parser.add_argument("--save-all-throughput-timeseries", action="store_true", help="Save calculated throughput timeseries for EACH individual request.")
60+
parser.add_argument("--exit-on-first-fail", action="store_true", help="Stop execution on first failed test and exit with non-zero status")
61+
parser.add_argument("--no-results-on-fail", action="store_true", help="Prevent saving/printing results when error is experienced, turns on --exit-on-first-fail as well")
5862

5963
args = parser.parse_args()
64+
65+
if args.no_results_on_fail:
66+
args.exit_on_first_fail = True
6067

6168
return cls(
6269
base_url=args.base_url,
@@ -80,5 +87,7 @@ def from_args(cls):
8087
save_result=args.save_result,
8188
result_format=args.format,
8289
save_total_throughput_timeseries=args.save_total_throughput_timeseries,
83-
save_all_throughput_timeseries=args.save_all_throughput_timeseries
90+
save_all_throughput_timeseries=args.save_all_throughput_timeseries,
91+
exit_on_first_fail=args.exit_on_first_fail,
92+
no_results_on_fail=args.no_results_on_fail
8493
)

src/llama_benchy/runner.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import subprocess
33
import time
4+
import sys
45
from datetime import datetime, timezone
56
from typing import List
67
import aiohttp
@@ -11,6 +12,9 @@
1112
from .prompts import PromptGenerator
1213
from .results import BenchmarkResults, BenchmarkMetadata
1314

15+
class BenchmarkFailure(Exception):
16+
pass
17+
1418
class BenchmarkRunner:
1519
def __init__(self, config: BenchmarkConfig, client: LLMClient, prompt_generator: PromptGenerator):
1620
self.config = config
@@ -101,6 +105,11 @@ async def run_suite(self):
101105
load_results = await asyncio.gather(*load_tasks)
102106
run_ctx_results.append(load_results)
103107

108+
if self.config.exit_on_first_fail and any(r.error for r in load_results):
109+
first_error = next(r.error for r in load_results if r.error)
110+
print(f"\n[Error] Stopping due to error in context load: {first_error}")
111+
raise BenchmarkFailure()
112+
104113
# Phase 2: Inference
105114
print(f" Run {run+1}/{self.config.num_runs} (Inference, batch size {concurrency})...")
106115
inf_tasks = []
@@ -117,6 +126,11 @@ async def run_suite(self):
117126
batch_results = await asyncio.gather(*inf_tasks)
118127
run_std_results.append(batch_results)
119128

129+
if self.config.exit_on_first_fail and any(r.error for r in batch_results):
130+
first_error = next(r.error for r in batch_results if r.error)
131+
print(f"\n[Error] Stopping due to error in inference: {first_error}")
132+
raise BenchmarkFailure()
133+
120134
else:
121135
# Standard Run
122136
print(f" Run {run+1}/{self.config.num_runs} (batch size {concurrency})...")
@@ -135,6 +149,11 @@ async def run_suite(self):
135149
batch_results = await asyncio.gather(*batch_tasks)
136150
run_std_results.append(batch_results)
137151

152+
if self.config.exit_on_first_fail and any(r.error for r in batch_results):
153+
first_error = next(r.error for r in batch_results if r.error)
154+
print(f"\n[Error] Stopping due to error in standard run: {first_error}")
155+
raise BenchmarkFailure()
156+
138157

139158
# Post Run Command
140159
if self.config.post_run_cmd:
@@ -164,18 +183,27 @@ async def run_suite(self):
164183

165184
self.results.save_report(self.config.save_result, self.config.result_format, max(self.config.concurrency_levels) if self.config.concurrency_levels else 1)
166185

167-
except (asyncio.CancelledError, KeyboardInterrupt):
186+
except (asyncio.CancelledError, KeyboardInterrupt, BenchmarkFailure) as e:
168187
if self.results.runs:
169-
print("\n[Interrupted] Saving partial results...")
170-
if self.results.metadata is None:
171-
self.results.metadata = BenchmarkMetadata(
172-
version=__version__,
173-
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%SZ"),
174-
latency_mode=self.config.latency_mode,
175-
latency_ms=latency * 1000,
176-
model=self.config.model,
177-
prefix_caching_enabled=self.config.enable_prefix_caching,
178-
max_concurrency=max_concurrency
179-
)
180-
self.results.save_report(self.config.save_result, self.config.result_format, max_concurrency)
188+
should_save = True
189+
if isinstance(e, BenchmarkFailure) and self.config.no_results_on_fail:
190+
should_save = False
191+
print("\n[Failed] Results discarded per --no-results-on-fail.")
192+
193+
if should_save:
194+
print("\n[Interrupted/Failed] Saving partial results...")
195+
if self.results.metadata is None:
196+
self.results.metadata = BenchmarkMetadata(
197+
version=__version__,
198+
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%SZ"),
199+
latency_mode=self.config.latency_mode,
200+
latency_ms=latency * 1000,
201+
model=self.config.model,
202+
prefix_caching_enabled=self.config.enable_prefix_caching,
203+
max_concurrency=max_concurrency
204+
)
205+
self.results.save_report(self.config.save_result, self.config.result_format, max_concurrency)
206+
207+
if isinstance(e, BenchmarkFailure):
208+
sys.exit(1)
181209
raise

0 commit comments

Comments
 (0)