Skip to content

Commit 3c6819f

Browse files
committed
Fixes for benchmarker aggregator and fixes from cursor for openai.py, aggregator.py, and benchmarker.py
1 parent 8fe4e2e commit 3c6819f

File tree

8 files changed

+866
-332
lines changed

8 files changed

+866
-332
lines changed

src/guidellm/backend/openai.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def __init__(
139139
self._in_process = False
140140
self._async_client: Optional[httpx.AsyncClient] = None
141141

142+
@property
142143
def info(self) -> dict[str, Any]:
143144
"""
144145
:return: Dictionary containing backend configuration details.

src/guidellm/benchmark/aggregator.py

Lines changed: 510 additions & 293 deletions
Large diffs are not rendered by default.

src/guidellm/benchmark/benchmarker.py

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
)
2727

2828
from guidellm.benchmark.aggregator import Aggregator, CompilableAggregator
29-
from guidellm.benchmark.objects import BenchmarkT
29+
from guidellm.benchmark.objects import BenchmarkerDict, BenchmarkT, SchedulerDict
3030
from guidellm.benchmark.profile import Profile
3131
from guidellm.scheduler import (
3232
BackendInterface,
@@ -41,6 +41,7 @@
4141
SchedulingStrategy,
4242
)
4343
from guidellm.utils import InfoMixin, ThreadSafeSingletonMixin
44+
from guidellm.utils.pydantic_utils import StandardBaseDict
4445

4546
__all__ = ["Benchmarker"]
4647

@@ -114,9 +115,7 @@ async def run(
114115
request,
115116
request_info,
116117
scheduler_state,
117-
) in Scheduler[
118-
BackendInterface, RequestT, MeasuredRequestTimingsT, ResponseT
119-
]().run(
118+
) in Scheduler[RequestT, MeasuredRequestTimingsT, ResponseT]().run(
120119
requests=requests,
121120
backend=backend,
122121
strategy=strategy,
@@ -200,43 +199,65 @@ def _compile_benchmark_kwargs(
200199
benchmark_kwargs = {
201200
"run_id": run_id,
202201
"run_index": run_index,
203-
"scheduler": {
204-
"strategy": strategy,
205-
"constraints": {
206-
key: InfoMixin.extract_from_obj(val) for key, val in constraints
202+
"scheduler": SchedulerDict(
203+
strategy=strategy,
204+
constraints={
205+
key: InfoMixin.extract_from_obj(val)
206+
for key, val in constraints.items()
207207
},
208-
"state": scheduler_state,
209-
},
210-
"benchmarker": {
211-
"profile": profile,
212-
"requests": InfoMixin.extract_from_obj(requests),
213-
"backend": InfoMixin.extract_from_obj(backend),
214-
"environment": InfoMixin.extract_from_obj(environment),
215-
"aggregators": {
208+
state=scheduler_state,
209+
),
210+
"benchmarker": BenchmarkerDict(
211+
profile=profile,
212+
requests=InfoMixin.extract_from_obj(requests),
213+
backend=backend.info,
214+
environment=environment.info,
215+
aggregators={
216216
key: InfoMixin.extract_from_obj(aggregator)
217217
for key, aggregator in aggregators.items()
218218
},
219-
},
220-
"system": {},
221-
"extras": {},
219+
),
220+
"env_args": StandardBaseDict(),
221+
"extras": StandardBaseDict(),
222222
}
223+
224+
def _combine(
225+
existing: dict[str, Any] | StandardBaseDict,
226+
addition: dict[str, Any] | StandardBaseDict,
227+
) -> dict[str, Any] | StandardBaseDict:
228+
if not isinstance(existing, (dict, StandardBaseDict)):
229+
raise ValueError(
230+
f"Existing value {existing} (type: {type(existing).__name__}) "
231+
f"is not a valid type for merging."
232+
)
233+
if not isinstance(addition, (dict, StandardBaseDict)):
234+
raise ValueError(
235+
f"Addition value {addition} (type: {type(addition).__name__}) "
236+
f"is not a valid type for merging."
237+
)
238+
239+
add_kwargs = (
240+
addition if isinstance(addition, dict) else addition.model_dump()
241+
)
242+
243+
if isinstance(existing, dict):
244+
return {**add_kwargs, **existing}
245+
246+
return existing.__class__(**{**add_kwargs, **existing.model_dump()})
247+
223248
for key, aggregator in aggregators.items():
224249
if not isinstance(aggregator, CompilableAggregator):
225250
continue
226251

227252
compiled = aggregator.compile(aggregators_state[key], scheduler_state)
228253

229-
if key not in benchmark_kwargs:
230-
benchmark_kwargs[key] = compiled
231-
continue
232-
233-
existing_val = benchmark_kwargs[key]
234-
if not (isinstance(existing_val, dict) and isinstance(compiled, dict)):
235-
raise ValueError(
236-
f"Key '{key}' already exists with value {existing_val} "
237-
f"(type: {type(existing_val).__name__}) and cannot be "
238-
f"overwritten with {compiled} (type: {type(compiled).__name__})"
239-
)
240-
existing_val.update(compiled)
254+
for field_name, field_val in compiled.items():
255+
if field_name in benchmark_kwargs:
256+
# If the key already exists, merge the values
257+
benchmark_kwargs[field_name] = _combine(
258+
benchmark_kwargs[field_name], field_val
259+
)
260+
else:
261+
benchmark_kwargs[field_name] = field_val
241262

242263
return benchmark_kwargs

src/guidellm/benchmark/objects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,10 @@ def total_tokens(self) -> int | None:
240240
241241
:return: Sum of prompt and output tokens, or None if either is unavailable.
242242
"""
243-
if self.prompt_tokens is None or self.output_tokens is None:
243+
if self.prompt_tokens is None and self.output_tokens is None:
244244
return None
245245

246-
return self.prompt_tokens + self.output_tokens
246+
return (self.prompt_tokens or 0) + (self.output_tokens or 0)
247247

248248
@computed_field # type: ignore[misc]
249249
@property

src/guidellm/scheduler/objects.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ def requests_limit(self) -> int | None:
224224
:return: The maximum concurrent requests supported, or None if unlimited
225225
"""
226226

227+
@property
227228
@abstractmethod
228229
def info(self) -> dict[str, Any]:
229230
"""

src/guidellm/utils/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22
from .colors import Colors
33
from .default_group import DefaultGroupHandler
44
from .encoding import MsgpackEncoding
5-
from .general import UNSET, UnsetType
5+
from .general import (
6+
UNSET,
7+
UnsetType,
8+
all_defined,
9+
safe_add,
10+
safe_divide,
11+
safe_getattr,
12+
safe_multiply,
13+
safe_subtract,
14+
)
615
from .hf_datasets import (
716
SUPPORTED_TYPES,
817
save_dataset_to_file,
@@ -64,12 +73,18 @@
6473
"ThreadSafeSingletonMixin",
6574
"TimeRunningStats",
6675
"UnsetType",
76+
"all_defined",
6777
"check_load_processor",
6878
"clean_text",
6979
"filter_text",
7080
"format_value_display",
7181
"is_puncutation",
7282
"load_text",
83+
"safe_add",
84+
"safe_divide",
85+
"safe_getattr",
86+
"safe_multiply",
87+
"safe_subtract",
7388
"save_dataset_to_file",
7489
"split_text",
7590
"split_text_list_by_length",

src/guidellm/utils/general.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
from typing import Final
1+
from __future__ import annotations
22

3-
__all__ = ["UNSET", "UnsetType"]
3+
from typing import Any, Final
4+
5+
__all__ = [
6+
"UNSET",
7+
"UnsetType",
8+
"all_defined",
9+
"safe_add",
10+
"safe_divide",
11+
"safe_getattr",
12+
"safe_multiply",
13+
"safe_subtract",
14+
]
415

516

617
class UnsetType:
@@ -11,3 +22,61 @@ def __repr__(self) -> str:
1122

1223

1324
UNSET: Final = UnsetType()
25+
26+
27+
def safe_getattr(obj: Any | None, attr: str, default: Any = None) -> Any:
28+
"""
29+
Safely get an attribute from an object or return a default value.
30+
31+
:param obj: The object to get the attribute from.
32+
:param attr: The name of the attribute to get.
33+
:param default: The default value to return if the attribute is not found.
34+
:return: The value of the attribute or the default value.
35+
"""
36+
if obj is None:
37+
return default
38+
39+
return getattr(obj, attr, default)
40+
41+
42+
def all_defined(*values: Any | None) -> bool:
43+
"""
44+
Check if all values are defined (not None).
45+
46+
:param values: The values to check.
47+
:return: True if all values are defined, False otherwise.
48+
"""
49+
return all(value is not None for value in values)
50+
51+
52+
def safe_divide(
53+
numerator: float | None,
54+
denominator: float | None,
55+
num_default: float = 0.0,
56+
den_default: float = 1.0,
57+
) -> float:
58+
numerator = numerator if numerator is not None else num_default
59+
denominator = denominator if denominator is not None else den_default
60+
61+
return numerator / (denominator or 1e-10)
62+
63+
64+
def safe_multiply(*values: int | float | None, default: float = 1.0) -> float:
65+
result = default
66+
for val in values:
67+
result *= val if val is not None else 1.0
68+
return result
69+
70+
71+
def safe_add(*values: int | float | None, default: float = 0.0) -> float:
72+
result = default
73+
for val in values:
74+
result += val if val is not None else 0.0
75+
return result
76+
77+
78+
def safe_subtract(*values: int | float | None, default: float = 0.0) -> float:
79+
result = default
80+
for val in values:
81+
result -= val if val is not None else 0.0
82+
return result

0 commit comments

Comments
 (0)