guidellm 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of guidellm might be problematic. Click here for more details.

Files changed (69) hide show
  1. guidellm/__init__.py +38 -6
  2. guidellm/__main__.py +294 -0
  3. guidellm/backend/__init__.py +19 -6
  4. guidellm/backend/backend.py +238 -0
  5. guidellm/backend/openai.py +532 -122
  6. guidellm/backend/response.py +132 -0
  7. guidellm/benchmark/__init__.py +73 -0
  8. guidellm/benchmark/aggregator.py +760 -0
  9. guidellm/benchmark/benchmark.py +838 -0
  10. guidellm/benchmark/benchmarker.py +334 -0
  11. guidellm/benchmark/entrypoints.py +141 -0
  12. guidellm/benchmark/output.py +946 -0
  13. guidellm/benchmark/profile.py +409 -0
  14. guidellm/benchmark/progress.py +720 -0
  15. guidellm/config.py +34 -56
  16. guidellm/data/__init__.py +4 -0
  17. guidellm/data/prideandprejudice.txt.gz +0 -0
  18. guidellm/dataset/__init__.py +22 -0
  19. guidellm/dataset/creator.py +213 -0
  20. guidellm/dataset/entrypoints.py +42 -0
  21. guidellm/dataset/file.py +90 -0
  22. guidellm/dataset/hf_datasets.py +62 -0
  23. guidellm/dataset/in_memory.py +132 -0
  24. guidellm/dataset/synthetic.py +262 -0
  25. guidellm/objects/__init__.py +18 -0
  26. guidellm/objects/pydantic.py +60 -0
  27. guidellm/objects/statistics.py +947 -0
  28. guidellm/request/__init__.py +12 -10
  29. guidellm/request/loader.py +281 -0
  30. guidellm/request/request.py +79 -0
  31. guidellm/scheduler/__init__.py +51 -3
  32. guidellm/scheduler/result.py +137 -0
  33. guidellm/scheduler/scheduler.py +382 -0
  34. guidellm/scheduler/strategy.py +493 -0
  35. guidellm/scheduler/types.py +7 -0
  36. guidellm/scheduler/worker.py +511 -0
  37. guidellm/utils/__init__.py +16 -29
  38. guidellm/utils/colors.py +8 -0
  39. guidellm/utils/hf_transformers.py +35 -0
  40. guidellm/utils/random.py +43 -0
  41. guidellm/utils/text.py +118 -357
  42. {guidellm-0.1.0.dist-info → guidellm-0.2.0.dist-info}/METADATA +96 -79
  43. guidellm-0.2.0.dist-info/RECORD +48 -0
  44. {guidellm-0.1.0.dist-info → guidellm-0.2.0.dist-info}/WHEEL +1 -1
  45. guidellm-0.2.0.dist-info/entry_points.txt +2 -0
  46. guidellm/backend/base.py +0 -320
  47. guidellm/core/__init__.py +0 -24
  48. guidellm/core/distribution.py +0 -190
  49. guidellm/core/report.py +0 -321
  50. guidellm/core/request.py +0 -44
  51. guidellm/core/result.py +0 -545
  52. guidellm/core/serializable.py +0 -169
  53. guidellm/executor/__init__.py +0 -10
  54. guidellm/executor/base.py +0 -213
  55. guidellm/executor/profile_generator.py +0 -343
  56. guidellm/main.py +0 -336
  57. guidellm/request/base.py +0 -194
  58. guidellm/request/emulated.py +0 -391
  59. guidellm/request/file.py +0 -76
  60. guidellm/request/transformers.py +0 -100
  61. guidellm/scheduler/base.py +0 -374
  62. guidellm/scheduler/load_generator.py +0 -196
  63. guidellm/utils/injector.py +0 -70
  64. guidellm/utils/progress.py +0 -196
  65. guidellm/utils/transformers.py +0 -151
  66. guidellm-0.1.0.dist-info/RECORD +0 -35
  67. guidellm-0.1.0.dist-info/entry_points.txt +0 -3
  68. {guidellm-0.1.0.dist-info → guidellm-0.2.0.dist-info/licenses}/LICENSE +0 -0
  69. {guidellm-0.1.0.dist-info → guidellm-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,132 @@
1
+ from collections.abc import Iterable
2
+ from pathlib import Path
3
+ from typing import Any, Optional, Union
4
+
5
+ from datasets import (
6
+ Dataset,
7
+ DatasetDict,
8
+ IterableDataset,
9
+ IterableDatasetDict,
10
+ )
11
+ from transformers import PreTrainedTokenizerBase # type: ignore[import]
12
+
13
+ from guidellm.dataset.creator import DatasetCreator
14
+
15
+ __all__ = ["InMemoryDatasetCreator"]
16
+
17
+
18
+ class InMemoryDatasetCreator(DatasetCreator):
19
+ @classmethod
20
+ def is_supported(cls, data: Any, data_args: Optional[dict[str, Any]]) -> bool: # noqa: ARG003
21
+ return isinstance(data, Iterable) and not isinstance(data, str)
22
+
23
+ @classmethod
24
+ def handle_create(
25
+ cls,
26
+ data: Any,
27
+ data_args: Optional[dict[str, Any]],
28
+ processor: Optional[Union[str, Path, PreTrainedTokenizerBase]], # noqa: ARG003
29
+ processor_args: Optional[dict[str, Any]], # noqa: ARG003
30
+ random_seed: int, # noqa: ARG003
31
+ ) -> Union[Dataset, DatasetDict, IterableDataset, IterableDatasetDict]:
32
+ if not isinstance(data, Iterable):
33
+ raise TypeError(
34
+ f"Unsupported data format. Expected Iterable[Any], got {type(data)}"
35
+ )
36
+
37
+ if not data:
38
+ raise ValueError("Data is empty")
39
+
40
+ if isinstance(data, dict):
41
+ # assume data is a dictionary of columns and values: {"c1": ["i1", "i2"]}
42
+ data_dict = cls.format_data_dict(data)
43
+ elif isinstance(data[0], dict): # type: ignore[index]
44
+ # assume data is a list of dictionaries: [{"c1": "i1"}, {"c1": "i2"}]
45
+ data_dict = cls.format_data_iterable_dicts(data)
46
+ else:
47
+ # assume data is a list of items with no columns: ["i1", "i2"]
48
+ data_dict = cls.format_data_iterable_values(data)
49
+
50
+ return Dataset.from_dict(data_dict, **(data_args or {}))
51
+
52
+ @classmethod
53
+ def format_data_dict(cls, data: dict[Any, Any]) -> dict[str, Any]:
54
+ if not isinstance(data, dict):
55
+ raise TypeError(
56
+ f"Unsupported data format. Expected Dict[str, Iterable[Any]], "
57
+ f"got {type(data)}"
58
+ )
59
+
60
+ if not all(
61
+ isinstance(key, str) and isinstance(val, Iterable)
62
+ for key, val in data.items()
63
+ ):
64
+ raise TypeError(
65
+ "Unsupported data format. Expected Dict[str, Iterable[Any]], "
66
+ f"got {type(data)}"
67
+ )
68
+
69
+ samples = len(list(data.values())[0])
70
+ if not all(len(val) == samples for val in data.values()):
71
+ raise ValueError(
72
+ "Unsupported data format. Not all columns have the same number samples "
73
+ f"for {data}"
74
+ )
75
+
76
+ return data
77
+
78
+ @classmethod
79
+ def format_data_iterable_dicts(
80
+ cls, data: Iterable[dict[Any, Any]]
81
+ ) -> dict[str, Any]:
82
+ if not isinstance(data, Iterable):
83
+ raise TypeError(
84
+ f"Unsupported data format. Expected Iterable[Dict[str, Any]], "
85
+ f"got {type(data)}"
86
+ )
87
+
88
+ if not all(isinstance(item, dict) for item in data):
89
+ raise TypeError(
90
+ f"Unsupported data format. Expected Iterable[Dict[str, Any]], "
91
+ f"got {type(data)}"
92
+ )
93
+
94
+ if not all(isinstance(key, str) for key in data[0]): # type: ignore[index]
95
+ raise TypeError(
96
+ "Unsupported data format. Expected Dict[str, Any], "
97
+ f"but one of the items had a non string column for {data}"
98
+ )
99
+
100
+ columns = list(data[0].keys()) # type: ignore[index]
101
+ if not all(
102
+ len(item) == len(columns) and all(key in item for key in columns)
103
+ for item in data
104
+ ):
105
+ raise ValueError(
106
+ "Unsupported data format. Not all items have the same columns "
107
+ f"for {data}"
108
+ )
109
+
110
+ data_dict: dict[str, Any] = {key: [] for key in columns}
111
+ for item in data:
112
+ for key, value in item.items():
113
+ data_dict[key].append(value)
114
+
115
+ return data_dict
116
+
117
+ @classmethod
118
+ def format_data_iterable_values(cls, data: Iterable[Any]) -> dict[str, Any]:
119
+ if not isinstance(data, Iterable):
120
+ raise TypeError(
121
+ f"Unsupported data format. Expected Iterable[Iterable[Any]], "
122
+ f"got {type(data)}"
123
+ )
124
+
125
+ first_item = next(iter(data), None)
126
+ first_type = type(first_item)
127
+ if not all(isinstance(item, first_type) for item in data):
128
+ raise TypeError(
129
+ f"Unsupported data format. Not all types are the same for {data}"
130
+ )
131
+
132
+ return {"data": list(data)}
@@ -0,0 +1,262 @@
1
+ import json
2
+ import random
3
+ from collections.abc import Iterable, Iterator
4
+ from pathlib import Path
5
+ from typing import Any, Literal, Optional, Union
6
+
7
+ import yaml
8
+ from datasets import (
9
+ Dataset,
10
+ DatasetDict,
11
+ IterableDataset,
12
+ IterableDatasetDict,
13
+ )
14
+ from pydantic import BaseModel, Field
15
+ from transformers import PreTrainedTokenizerBase # type: ignore[import]
16
+
17
+ from guidellm.dataset.creator import ColumnInputTypes, DatasetCreator
18
+ from guidellm.utils import EndlessTextCreator, IntegerRangeSampler, check_load_processor
19
+
20
+ __all__ = [
21
+ "SyntheticDatasetCreator",
22
+ "SyntheticDatasetConfig",
23
+ "SyntheticTextItemsGenerator",
24
+ ]
25
+
26
+
27
+ class SyntheticDatasetConfig(BaseModel):
28
+ prompt_tokens: int = Field(
29
+ description="The average number of text tokens generated for prompts.",
30
+ gt=0,
31
+ )
32
+ prompt_tokens_stdev: Optional[int] = Field(
33
+ description="The standard deviation of the tokens generated for prompts.",
34
+ gt=0,
35
+ default=None,
36
+ )
37
+ prompt_tokens_min: Optional[int] = Field(
38
+ description="The minimum number of text tokens generated for prompts.",
39
+ gt=0,
40
+ default=None,
41
+ )
42
+ prompt_tokens_max: Optional[int] = Field(
43
+ description="The maximum number of text tokens generated for prompts.",
44
+ gt=0,
45
+ default=None,
46
+ )
47
+ output_tokens: int = Field(
48
+ description="The average number of text tokens generated for outputs.",
49
+ gt=0,
50
+ )
51
+ output_tokens_stdev: Optional[int] = Field(
52
+ description="The standard deviation of the tokens generated for outputs.",
53
+ gt=0,
54
+ default=None,
55
+ )
56
+ output_tokens_min: Optional[int] = Field(
57
+ description="The minimum number of text tokens generated for outputs.",
58
+ gt=0,
59
+ default=None,
60
+ )
61
+ output_tokens_max: Optional[int] = Field(
62
+ description="The maximum number of text tokens generated for outputs.",
63
+ gt=0,
64
+ default=None,
65
+ )
66
+ samples: int = Field(
67
+ description="The number of samples to generate for the dataset.",
68
+ gt=0,
69
+ default=1000,
70
+ )
71
+ source: str = Field(
72
+ description="The source of the text data to be used for generation.",
73
+ default="data:prideandprejudice.txt.gz",
74
+ )
75
+
76
+ @staticmethod
77
+ def parse_str(data: Union[str, Path]) -> "SyntheticDatasetConfig":
78
+ if (
79
+ isinstance(data, Path)
80
+ or data.strip().endswith(".config")
81
+ or data.strip().endswith(".yaml")
82
+ ):
83
+ return SyntheticDatasetConfig.parse_config_file(data)
84
+
85
+ if data.strip().startswith("{"):
86
+ return SyntheticDatasetConfig.parse_json(data)
87
+
88
+ if data.count("=") > 1:
89
+ return SyntheticDatasetConfig.parse_key_value_pairs(data)
90
+
91
+ raise ValueError(
92
+ f"Unsupported data format. Expected JSON or key-value pairs, got {data}"
93
+ )
94
+
95
+ @staticmethod
96
+ def parse_json(data: str) -> "SyntheticDatasetConfig":
97
+ config_dict = json.loads(data.strip())
98
+
99
+ return SyntheticDatasetConfig(**config_dict)
100
+
101
+ @staticmethod
102
+ def parse_key_value_pairs(data: str) -> "SyntheticDatasetConfig":
103
+ config_dict = {}
104
+ items = data.strip().split(",")
105
+ for item in items:
106
+ key, value = item.split("=")
107
+ config_dict[key.strip()] = (
108
+ int(value.strip()) if value.strip().isnumeric() else value.strip()
109
+ )
110
+
111
+ return SyntheticDatasetConfig(**config_dict) # type: ignore[arg-type]
112
+
113
+ @staticmethod
114
+ def parse_config_file(data: Union[str, Path]) -> "SyntheticDatasetConfig":
115
+ with Path(data).open("r") as file:
116
+ config_dict = yaml.safe_load(file)
117
+
118
+ return SyntheticDatasetConfig(**config_dict)
119
+
120
+
121
+ class SyntheticTextItemsGenerator(
122
+ Iterable[
123
+ dict[
124
+ Literal["prompt", "prompt_tokens_count", "output_tokens_count"],
125
+ Union[str, int],
126
+ ]
127
+ ]
128
+ ):
129
+ def __init__(
130
+ self,
131
+ config: SyntheticDatasetConfig,
132
+ processor: PreTrainedTokenizerBase,
133
+ random_seed: int,
134
+ ):
135
+ self.config = config
136
+ self.processor = processor
137
+ self.random_seed = random_seed
138
+ self.text_creator = EndlessTextCreator(
139
+ data=config.source,
140
+ )
141
+
142
+ def __iter__(
143
+ self,
144
+ ) -> Iterator[
145
+ dict[
146
+ Literal["prompt", "prompt_tokens_count", "output_tokens_count"],
147
+ Union[str, int],
148
+ ]
149
+ ]:
150
+ prompt_tokens_sampler = IntegerRangeSampler(
151
+ average=self.config.prompt_tokens,
152
+ variance=self.config.prompt_tokens_stdev,
153
+ min_value=self.config.prompt_tokens_min,
154
+ max_value=self.config.prompt_tokens_max,
155
+ random_seed=self.random_seed,
156
+ )
157
+ output_tokens_sampler = IntegerRangeSampler(
158
+ average=self.config.output_tokens,
159
+ variance=self.config.output_tokens_stdev,
160
+ min_value=self.config.output_tokens_min,
161
+ max_value=self.config.output_tokens_max,
162
+ random_seed=self.random_seed + 1, # ensure diff dist from prompts
163
+ )
164
+ # ensure diff distribution from output tokens
165
+ rand = random.Random(self.random_seed + 2) # noqa: S311
166
+
167
+ for _, prompt_tokens, output_tokens in zip(
168
+ range(self.config.samples),
169
+ prompt_tokens_sampler,
170
+ output_tokens_sampler,
171
+ ):
172
+ start_index = rand.randint(0, len(self.text_creator.words))
173
+ yield {
174
+ "prompt": self._create_prompt(prompt_tokens, start_index),
175
+ "prompt_tokens_count": prompt_tokens,
176
+ "output_tokens_count": output_tokens,
177
+ }
178
+
179
+ def _create_prompt(self, prompt_tokens: int, start_index: int) -> str:
180
+ if prompt_tokens <= 0:
181
+ return ""
182
+
183
+ left = start_index
184
+ right = start_index + 4 * prompt_tokens
185
+
186
+ while left < right:
187
+ mid = (left + right) // 2
188
+ test_prompt = self.text_creator.create_text(start_index, mid - start_index)
189
+ test_tokens = len(self.processor.tokenize(test_prompt))
190
+
191
+ if test_tokens == prompt_tokens:
192
+ return test_prompt
193
+ elif test_tokens < prompt_tokens:
194
+ left = mid + 1
195
+ else:
196
+ right = mid
197
+
198
+ return self.text_creator.create_text(start_index, left - start_index)
199
+
200
+
201
+ class SyntheticDatasetCreator(DatasetCreator):
202
+ @classmethod
203
+ def is_supported(cls, data: Any, data_args: Optional[dict[str, Any]]) -> bool: # noqa: ARG003
204
+ if (
205
+ isinstance(data, Path)
206
+ and data.exists()
207
+ and data.suffix in {".config", ".yaml"}
208
+ ):
209
+ return True
210
+
211
+ if isinstance(data, str):
212
+ data_str: str = data.strip()
213
+ if (
214
+ data_str.startswith("{")
215
+ or data_str.count("=") > 1
216
+ or data_str.endswith((".config", ".yaml"))
217
+ ):
218
+ return True
219
+
220
+ return False
221
+
222
+ @classmethod
223
+ def handle_create(
224
+ cls,
225
+ data: Any,
226
+ data_args: Optional[dict[str, Any]],
227
+ processor: Optional[Union[str, Path, PreTrainedTokenizerBase]],
228
+ processor_args: Optional[dict[str, Any]],
229
+ random_seed: int,
230
+ ) -> Union[Dataset, DatasetDict, IterableDataset, IterableDatasetDict]:
231
+ processor = check_load_processor(
232
+ processor,
233
+ processor_args,
234
+ error_msg=(
235
+ "Processor/tokenizer required for synthetic dataset generation."
236
+ ),
237
+ )
238
+
239
+ config = SyntheticDatasetConfig.parse_str(data)
240
+ generator = SyntheticTextItemsGenerator(config, processor, random_seed)
241
+ items = list(generator)
242
+
243
+ return Dataset.from_list(items, **(data_args or {}))
244
+
245
+ @classmethod
246
+ def extract_args_column_mappings(
247
+ cls,
248
+ data_args: Optional[dict[str, Any]],
249
+ ) -> dict[ColumnInputTypes, str]:
250
+ data_args_columns = super().extract_args_column_mappings(data_args)
251
+
252
+ if data_args_columns:
253
+ raise ValueError(
254
+ f"Column mappings are not supported for synthetic datasets. "
255
+ f"Got {data_args_columns}"
256
+ )
257
+
258
+ return {
259
+ "prompt_column": "prompt",
260
+ "prompt_tokens_count_column": "prompt_tokens_count",
261
+ "output_tokens_count_column": "output_tokens_count",
262
+ }
@@ -0,0 +1,18 @@
1
+ from .pydantic import StandardBaseModel, StatusBreakdown
2
+ from .statistics import (
3
+ DistributionSummary,
4
+ Percentiles,
5
+ RunningStats,
6
+ StatusDistributionSummary,
7
+ TimeRunningStats,
8
+ )
9
+
10
+ __all__ = [
11
+ "StandardBaseModel",
12
+ "StatusBreakdown",
13
+ "DistributionSummary",
14
+ "Percentiles",
15
+ "RunningStats",
16
+ "StatusDistributionSummary",
17
+ "TimeRunningStats",
18
+ ]
@@ -0,0 +1,60 @@
1
+ from typing import Any, Generic, TypeVar
2
+
3
+ from loguru import logger
4
+ from pydantic import BaseModel, ConfigDict, Field
5
+
6
+ __all__ = ["StandardBaseModel", "StatusBreakdown"]
7
+
8
+
9
+ class StandardBaseModel(BaseModel):
10
+ """
11
+ A base class for Pydantic models throughout GuideLLM enabling standard
12
+ configuration and logging.
13
+ """
14
+
15
+ model_config = ConfigDict(
16
+ extra="ignore",
17
+ use_enum_values=True,
18
+ validate_assignment=True,
19
+ from_attributes=True,
20
+ )
21
+
22
+ def __init__(self, /, **data: Any) -> None:
23
+ super().__init__(**data)
24
+ logger.debug(
25
+ "Initialized new instance of {} with data: {}",
26
+ self.__class__.__name__,
27
+ data,
28
+ )
29
+
30
+
31
+ SuccessfulT = TypeVar("SuccessfulT")
32
+ ErroredT = TypeVar("ErroredT")
33
+ IncompleteT = TypeVar("IncompleteT")
34
+ TotalT = TypeVar("TotalT")
35
+
36
+
37
+ class StatusBreakdown(BaseModel, Generic[SuccessfulT, ErroredT, IncompleteT, TotalT]):
38
+ """
39
+ A base class for Pydantic models that are separated by statuses including
40
+ successful, incomplete, and errored. It additionally enables the inclusion
41
+ of total, which is intended as the combination of all statuses.
42
+ Total may or may not be used depending on if it duplicates information.
43
+ """
44
+
45
+ successful: SuccessfulT = Field(
46
+ description="The results with a successful status.",
47
+ default=None, # type: ignore[assignment]
48
+ )
49
+ errored: ErroredT = Field(
50
+ description="The results with an errored status.",
51
+ default=None, # type: ignore[assignment]
52
+ )
53
+ incomplete: IncompleteT = Field(
54
+ description="The results with an incomplete status.",
55
+ default=None, # type: ignore[assignment]
56
+ )
57
+ total: TotalT = Field(
58
+ description="The combination of all statuses.",
59
+ default=None, # type: ignore[assignment]
60
+ )