diff --git a/pyproject.toml b/pyproject.toml index 0b1014cb..8115b1fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,6 @@ dependencies = [ "protobuf", "pydantic>=2.11.7", "pydantic-settings>=2.0.0", - "pyhumps>=3.8.0", "pyyaml>=6.0.0", "rich", "transformers", diff --git a/src/guidellm/benchmark/output.py b/src/guidellm/benchmark/output.py index 8a113f72..da868106 100644 --- a/src/guidellm/benchmark/output.py +++ b/src/guidellm/benchmark/output.py @@ -2,11 +2,11 @@ import json import math from collections import OrderedDict +from copy import deepcopy from datetime import datetime from pathlib import Path from typing import Any, Literal, Optional, Union -import humps # type: ignore[import-not-found] import yaml from pydantic import Field from rich.console import Console @@ -30,6 +30,8 @@ from guidellm.presentation.injector import create_report from guidellm.scheduler import strategy_display_str from guidellm.utils import Colors, split_text_list_by_length +from guidellm.utils.dict import recursive_key_update +from guidellm.utils.text import camelize_str __all__ = [ "GenerativeBenchmarksConsole", @@ -239,11 +241,11 @@ def save_html(self, path: Union[str, Path]) -> Path: data_builder = UIDataBuilder(self.benchmarks) data = data_builder.to_dict() - camel_data = humps.camelize(data) + camel_data = recursive_key_update(deepcopy(data), camelize_str) ui_api_data = {} for k, v in camel_data.items(): - key = f"window.{humps.decamelize(k)} = {{}};" - value = f"window.{humps.decamelize(k)} = {json.dumps(v, indent=2)};\n" + key = f"window.{k} = {{}};" + value = f"window.{k} = {json.dumps(v, indent=2)};\n" ui_api_data[key] = value return create_report(ui_api_data, path) diff --git a/src/guidellm/utils/__init__.py b/src/guidellm/utils/__init__.py index fb9262c3..02f2427f 100644 --- a/src/guidellm/utils/__init__.py +++ b/src/guidellm/utils/__init__.py @@ -1,5 +1,6 @@ from .colors import Colors from .default_group import DefaultGroupHandler +from .dict import recursive_key_update from .hf_datasets import ( SUPPORTED_TYPES, save_dataset_to_file, @@ -10,6 +11,7 @@ from .random import IntegerRangeSampler from .text import ( EndlessTextCreator, + camelize_str, clean_text, filter_text, is_puncutation, @@ -24,11 +26,13 @@ "DefaultGroupHandler", "EndlessTextCreator", "IntegerRangeSampler", + "camelize_str", "check_load_processor", "clean_text", "filter_text", "is_puncutation", "load_text", + "recursive_key_update", "save_dataset_to_file", "split_text", "split_text_list_by_length", diff --git a/src/guidellm/utils/dict.py b/src/guidellm/utils/dict.py new file mode 100644 index 00000000..9432bacc --- /dev/null +++ b/src/guidellm/utils/dict.py @@ -0,0 +1,23 @@ +def recursive_key_update(d, key_update_func): + if not isinstance(d, dict) and not isinstance(d, list): + return d + + if isinstance(d, list): + for item in d: + recursive_key_update(item, key_update_func) + return d + + updated_key_pairs = [] + for key, _ in d.items(): + updated_key = key_update_func(key) + if key != updated_key: + updated_key_pairs.append((key, updated_key )) + + for key_pair in updated_key_pairs: + old_key, updated_key = key_pair + d[updated_key] = d[old_key] + del d[old_key] + + for _, value in d.items(): + recursive_key_update(value, key_update_func) + return d diff --git a/src/guidellm/utils/text.py b/src/guidellm/utils/text.py index cdefaa14..e11598de 100644 --- a/src/guidellm/utils/text.py +++ b/src/guidellm/utils/text.py @@ -14,6 +14,7 @@ __all__ = [ "EndlessTextCreator", + "camelize_str", "clean_text", "filter_text", "is_puncutation", @@ -189,6 +190,14 @@ def is_puncutation(text: str) -> bool: return len(text) == 1 and not text.isalnum() and not text.isspace() +def camelize_str(snake_case_string: str) -> str: + return ( + words := + snake_case_string.split("_"))[0].lower() + "".join(word.capitalize() + for word in words[1:] + ) + + class EndlessTextCreator: def __init__( self, diff --git a/src/ui/app/layout.tsx b/src/ui/app/layout.tsx index ea33b775..5e9eb8e6 100644 --- a/src/ui/app/layout.tsx +++ b/src/ui/app/layout.tsx @@ -34,7 +34,7 @@ export default function RootLayout({ " + html = "" expected_html = ( "" ) js_data = { - "window.run_info = {};": "window.run_info =" + "window.runInfo = {};": "window.runInfo =" '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };' } result = inject_data( @@ -35,13 +35,13 @@ def test_inject_data(): @pytest.mark.smoke def test_create_report_to_file(tmpdir): js_data = { - "window.run_info = {};": "window.run_info =" + "window.runInfo = {};": "window.runInfo =" '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };' } - html_content = "" + html_content = "" expected_html_content = ( "" ) @@ -61,13 +61,13 @@ def test_create_report_to_file(tmpdir): @pytest.mark.smoke def test_create_report_with_file_nested_in_dir(tmpdir): js_data = { - "window.run_info = {};": "window.run_info =" + "window.runInfo = {};": "window.runInfo =" '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };' } - html_content = "" + html_content = "" expected_html_content = ( "" ) diff --git a/tests/unit/utils/dict.py b/tests/unit/utils/dict.py new file mode 100644 index 00000000..82ec22a2 --- /dev/null +++ b/tests/unit/utils/dict.py @@ -0,0 +1,76 @@ +import pytest + +from guidellm.utils.dict import recursive_key_update + + +def update_str(string): + return string + "_updated" + +@pytest.mark.smoke +def test_recursive_key_update_updates_keys(): + my_dict = { + "my_key": { + "my_nested_key": { + "my_double_nested_key": "someValue" + }, + "my_other_nested_key": "someValue" + }, + "my_other_key": "value" + } + my_updated_dict = { + "my_key_updated": { + "my_nested_key_updated": { + "my_double_nested_key_updated": "someValue" + }, + "my_other_nested_key_updated": "someValue" + }, + "my_other_key_updated": "value" + } + recursive_key_update(my_dict, update_str) + assert my_dict == my_updated_dict + + +def truncate_str_to_ten(string): + return string[:10] + + +@pytest.mark.smoke +def test_recursive_key_update_leaves_unchanged_keys(): + my_dict = { + "my_key": { + "my_nested_key": { + "my_double_nested_key": "someValue" + }, + "my_other_nested_key": "someValue" + }, + "my_other_key": "value" + } + my_updated_dict = { + "my_key": { + "my_nested_": { + "my_double_": "someValue" + }, + "my_other_n": "someValue" + }, + "my_other_k": "value" + } + recursive_key_update(my_dict, truncate_str_to_ten) + assert my_dict == my_updated_dict + + +@pytest.mark.smoke +def test_recursive_key_update_updates_dicts_in_list(): + my_dict = { + "my_key": + [{ "my_list_item_key_1": "someValue" }, + { "my_list_item_key_2": "someValue" }, + { "my_list_item_key_3": "someValue" }] + } + my_updated_dict = { + "my_key_updated": + [{ "my_list_item_key_1_updated": "someValue" }, + { "my_list_item_key_2_updated": "someValue" }, + { "my_list_item_key_3_updated": "someValue" }] + } + recursive_key_update(my_dict, update_str) + assert my_dict == my_updated_dict diff --git a/tests/unit/utils/text.py b/tests/unit/utils/text.py new file mode 100644 index 00000000..b37ea950 --- /dev/null +++ b/tests/unit/utils/text.py @@ -0,0 +1,12 @@ +import pytest + +from guidellm.utils.text import camelize_str + + +@pytest.mark.smoke +def test_camelize_str_camelizes_string(): + assert camelize_str("no_longer_snake_case") == "noLongerSnakeCase" + +@pytest.mark.smoke +def test_camelize_str_leaves_non_snake_case_text_untouched(): + assert camelize_str("notsnakecase") == "notsnakecase"