Skip to content

Commit 89113d8

Browse files
[GuideLLM Refactor] Features/refactor fix html (#377)
## Summary Fixes HTML output in the refactor branch. ## Details This PR updates the references for the new data, and corrects a problem that prevented the injection of the data into the HTML. They broke in the refactor. Here is a screenshot of the current output using this branch: <img width="1449" height="777" alt="image" src="https://github.com/user-attachments/assets/d2b01c5e-9a4f-4ae0-8892-20e43e616dff" /> There will be a follow up PR to make HTML and CSV outputs default in the CLI. ## Test Plan To test this you need to locally host the web server as defined in the readme, then run the following command, with variables replaced for your setup: `guidellm benchmark run --target <your target> --data "prompt_tokens=256,output_tokens=128" --rate-type constant --rate 2 --max-seconds 10 --output-path <your output path> --output-formats html` --- - [x] "I certify that all code in this PR is my own, except as noted below." ## Use of AI - [ ] Includes AI-assisted code completion - [ ] Includes code generated by an AI application - [ ] Includes AI-generated tests (NOTE: AI written tests should have a docstring that includes `## WRITTEN BY AI ##`)
2 parents 27d5702 + edd6317 commit 89113d8

File tree

7 files changed

+130
-12
lines changed

7 files changed

+130
-12
lines changed

src/guidellm/benchmark/output.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import math
66
from abc import ABC, abstractmethod
77
from collections import OrderedDict
8+
from copy import deepcopy
89
from datetime import datetime
910
from pathlib import Path
1011
from typing import Any, ClassVar
@@ -36,6 +37,7 @@
3637
safe_format_timestamp,
3738
split_text_list_by_length,
3839
)
40+
from guidellm.utils import recursive_key_update, camelize_str
3941

4042
__all__ = [
4143
"GenerativeBenchmarkerCSV",
@@ -711,22 +713,20 @@ async def finalize(self, report: GenerativeBenchmarksReport) -> Path:
711713
:param report: The completed benchmark report.
712714
:return: Path to the saved HTML file.
713715
"""
714-
import humps
715-
716716
output_path = self.output_path
717717
if output_path.is_dir():
718718
output_path = output_path / GenerativeBenchmarkerHTML.DEFAULT_FILE
719719
output_path.parent.mkdir(parents=True, exist_ok=True)
720720

721721
data_builder = UIDataBuilder(report.benchmarks)
722722
data = data_builder.to_dict()
723-
camel_data = humps.camelize(data)
723+
camel_data = recursive_key_update(deepcopy(data), camelize_str)
724724

725725
ui_api_data = {}
726-
for key, value in camel_data.items():
727-
placeholder_key = f"window.{humps.decamelize(key)} = {{}};"
726+
for k, v in camel_data.items():
727+
placeholder_key = f"window.{k} = {{}};"
728728
replacement_value = (
729-
f"window.{humps.decamelize(key)} = {json.dumps(value, indent=2)};\n"
729+
f"window.{k} = {json.dumps(v, indent=2)};\n"
730730
)
731731
ui_api_data[placeholder_key] = replacement_value
732732

src/guidellm/presentation/data_models.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class RunInfo(BaseModel):
6767

6868
@classmethod
6969
def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]):
70-
model = benchmarks[0].worker.backend_model or "N/A"
70+
model = benchmarks[0].benchmarker.backend.get("model", "N/A")
7171
timestamp = max(
7272
bm.run_stats.start_time for bm in benchmarks if bm.start_time is not None
7373
)
@@ -108,8 +108,8 @@ class WorkloadDetails(BaseModel):
108108

109109
@classmethod
110110
def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]):
111-
target = benchmarks[0].worker.backend_target
112-
rate_type = benchmarks[0].args.profile.type_
111+
target = benchmarks[0].benchmarker.backend.get("target", "N/A")
112+
rate_type = benchmarks[0].scheduler.strategy.type_
113113
successful_requests = [
114114
req for bm in benchmarks for req in bm.requests.successful
115115
]
@@ -152,13 +152,13 @@ def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]):
152152
statistics=output_token_stats, buckets=output_token_buckets, bucket_width=1
153153
)
154154

155-
min_start_time = benchmarks[0].run_stats.start_time
155+
min_start_time = benchmarks[0].start_time
156156

157157
all_req_times = [
158-
req.start_time - min_start_time
158+
req.scheduler_info.started_at - min_start_time
159159
for bm in benchmarks
160160
for req in bm.requests.successful
161-
if req.start_time is not None
161+
if req.scheduler_info.started_at is not None
162162
]
163163
number_of_buckets = len(benchmarks)
164164
request_over_time_buckets, bucket_width = Bucket.from_data(

src/guidellm/utils/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .auto_importer import AutoImporterMixin
22
from .console import Colors, Console, ConsoleUpdateStep, StatusIcons, StatusStyles
33
from .default_group import DefaultGroupHandler
4+
from .dict import recursive_key_update
45
from .encoding import (
56
Encoder,
67
EncodingTypesAlias,
@@ -55,6 +56,7 @@
5556
)
5657
from .text import (
5758
EndlessTextCreator,
59+
camelize_str,
5860
clean_text,
5961
filter_text,
6062
format_value_display,
@@ -79,6 +81,7 @@
7981
"EndlessTextCreator",
8082
"InfoMixin",
8183
"IntegerRangeSampler",
84+
"camelize_str",
8285
"InterProcessMessaging",
8386
"InterProcessMessagingManagerQueue",
8487
"InterProcessMessagingPipe",
@@ -110,6 +113,7 @@
110113
"format_value_display",
111114
"get_literal_vals",
112115
"is_punctuation",
116+
"recursive_key_update",
113117
"load_text",
114118
"safe_add",
115119
"safe_divide",

src/guidellm/utils/dict.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
def recursive_key_update(d, key_update_func):
2+
if not isinstance(d, dict) and not isinstance(d, list):
3+
return d
4+
5+
if isinstance(d, list):
6+
for item in d:
7+
recursive_key_update(item, key_update_func)
8+
return d
9+
10+
updated_key_pairs = []
11+
for key, _ in d.items():
12+
updated_key = key_update_func(key)
13+
if key != updated_key:
14+
updated_key_pairs.append((key, updated_key))
15+
16+
for key_pair in updated_key_pairs:
17+
old_key, updated_key = key_pair
18+
d[updated_key] = d[old_key]
19+
del d[old_key]
20+
21+
for _, value in d.items():
22+
recursive_key_update(value, key_update_func)
23+
return d

src/guidellm/utils/text.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
__all__ = [
2929
"MAX_PATH_LENGTH",
3030
"EndlessTextCreator",
31+
"camelize_str",
3132
"clean_text",
3233
"filter_text",
3334
"format_value_display",
@@ -281,6 +282,12 @@ def is_punctuation(text: str) -> bool:
281282
return len(text) == 1 and not text.isalnum() and not text.isspace()
282283

283284

285+
def camelize_str(snake_case_string: str) -> str:
286+
return (words := snake_case_string.split("_"))[0].lower() + "".join(
287+
word.capitalize() for word in words[1:]
288+
)
289+
290+
284291
class EndlessTextCreator:
285292
"""
286293
Infinite text generator for load testing and content creation operations.

tests/unit/utils/dict.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
3+
from guidellm.utils.dict import recursive_key_update
4+
5+
6+
def update_str(string):
7+
return string + "_updated"
8+
9+
10+
@pytest.mark.smoke
11+
def test_recursive_key_update_updates_keys():
12+
my_dict = {
13+
"my_key": {
14+
"my_nested_key": {"my_double_nested_key": "someValue"},
15+
"my_other_nested_key": "someValue",
16+
},
17+
"my_other_key": "value",
18+
}
19+
my_updated_dict = {
20+
"my_key_updated": {
21+
"my_nested_key_updated": {"my_double_nested_key_updated": "someValue"},
22+
"my_other_nested_key_updated": "someValue",
23+
},
24+
"my_other_key_updated": "value",
25+
}
26+
recursive_key_update(my_dict, update_str)
27+
assert my_dict == my_updated_dict
28+
29+
30+
def truncate_str_to_ten(string):
31+
return string[:10]
32+
33+
34+
@pytest.mark.smoke
35+
def test_recursive_key_update_leaves_unchanged_keys():
36+
my_dict = {
37+
"my_key": {
38+
"my_nested_key": {"my_double_nested_key": "someValue"},
39+
"my_other_nested_key": "someValue",
40+
},
41+
"my_other_key": "value",
42+
}
43+
my_updated_dict = {
44+
"my_key": {
45+
"my_nested_": {"my_double_": "someValue"},
46+
"my_other_n": "someValue",
47+
},
48+
"my_other_k": "value",
49+
}
50+
recursive_key_update(my_dict, truncate_str_to_ten)
51+
assert my_dict == my_updated_dict
52+
53+
54+
@pytest.mark.smoke
55+
def test_recursive_key_update_updates_dicts_in_list():
56+
my_dict = {
57+
"my_key": [
58+
{"my_list_item_key_1": "someValue"},
59+
{"my_list_item_key_2": "someValue"},
60+
{"my_list_item_key_3": "someValue"},
61+
]
62+
}
63+
my_updated_dict = {
64+
"my_key_updated": [
65+
{"my_list_item_key_1_updated": "someValue"},
66+
{"my_list_item_key_2_updated": "someValue"},
67+
{"my_list_item_key_3_updated": "someValue"},
68+
]
69+
}
70+
recursive_key_update(my_dict, update_str)
71+
assert my_dict == my_updated_dict

tests/unit/utils/text.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import pytest
2+
3+
from guidellm.utils.text import camelize_str
4+
5+
6+
@pytest.mark.smoke
7+
def test_camelize_str_camelizes_string():
8+
assert camelize_str("no_longer_snake_case") == "noLongerSnakeCase"
9+
10+
11+
@pytest.mark.smoke
12+
def test_camelize_str_leaves_non_snake_case_text_untouched():
13+
assert camelize_str("notsnakecase") == "notsnakecase"

0 commit comments

Comments
 (0)