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({ ); diff --git a/src/ui/lib/store/runInfoWindowData.ts b/src/ui/lib/store/runInfoWindowData.ts index 1fe0e21d..43b99909 100644 --- a/src/ui/lib/store/runInfoWindowData.ts +++ b/src/ui/lib/store/runInfoWindowData.ts @@ -1,4 +1,4 @@ -export const runInfoScript = `window.run_info = { +export const runInfoScript = `window.runInfo = { "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8", "size": 0 diff --git a/src/ui/lib/store/slices/runInfo/runInfo.api.ts b/src/ui/lib/store/slices/runInfo/runInfo.api.ts index 879ef19f..158a04fd 100644 --- a/src/ui/lib/store/slices/runInfo/runInfo.api.ts +++ b/src/ui/lib/store/slices/runInfo/runInfo.api.ts @@ -5,7 +5,7 @@ import { RunInfo } from './runInfo.interfaces'; const USE_MOCK_API = process.env.NEXT_PUBLIC_USE_MOCK_API === 'true'; const fetchRunInfo = () => { - return { data: window.run_info as RunInfo }; + return { data: window.runInfo as RunInfo }; }; export const runInfoApi = createApi({ diff --git a/src/ui/lib/store/slices/workloadDetails/workloadDetails.api.ts b/src/ui/lib/store/slices/workloadDetails/workloadDetails.api.ts index 398b208a..386164ee 100644 --- a/src/ui/lib/store/slices/workloadDetails/workloadDetails.api.ts +++ b/src/ui/lib/store/slices/workloadDetails/workloadDetails.api.ts @@ -5,7 +5,7 @@ import { WorkloadDetails } from './workloadDetails.interfaces'; const USE_MOCK_API = process.env.NEXT_PUBLIC_USE_MOCK_API === 'true'; const fetchWorkloadDetails = () => { - return { data: window.workload_details as WorkloadDetails }; + return { data: window.workloadDetails as WorkloadDetails }; }; export const workloadDetailsApi = createApi({ diff --git a/src/ui/lib/store/workloadDetailsWindowData.ts b/src/ui/lib/store/workloadDetailsWindowData.ts index 8913f5c2..634e5eef 100644 --- a/src/ui/lib/store/workloadDetailsWindowData.ts +++ b/src/ui/lib/store/workloadDetailsWindowData.ts @@ -1,4 +1,4 @@ -export const workloadDetailsScript = `window.workload_details = { +export const workloadDetailsScript = `window.workloadDetails = { "prompts": { "samples": [ "such a sacrifice to her advantage as years of gratitude cannot enough acknowledge. By this time she is actually with them! If such goodness does not make her miserable now, she will never deserve to be happy! What a meeting for her, when she first sees my aunt! We must endeavour to forget all that has passed on either side, said Jane I hope and trust they will yet be happy. His consenting to marry her is a proof, I will believe, that he is come to a right way of thinking. Their mutual affection will steady them; and I flatter myself they will settle so quietly, and live in so rational a manner", diff --git a/src/ui/types/declaration.d.ts b/src/ui/types/declaration.d.ts index 6a070e24..3a4ac93b 100644 --- a/src/ui/types/declaration.d.ts +++ b/src/ui/types/declaration.d.ts @@ -4,8 +4,8 @@ import { WorkloadDetails } from './src/lib/store/slices/workloadDetails/workload declare global { interface Window { - run_info?: RunInfo; - workload_details?: WorkloadDetails; + runInfo?: RunInfo; + workloadDetails?: WorkloadDetails; benchmarks?: Benchmarks; } } diff --git a/tests/unit/presentation/test_injector.py b/tests/unit/presentation/test_injector.py index cdaa7619..b2ff7116 100644 --- a/tests/unit/presentation/test_injector.py +++ b/tests/unit/presentation/test_injector.py @@ -14,15 +14,15 @@ class ExampleModel(BaseModel): @pytest.mark.smoke def test_inject_data(): - html = "
" + 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"