guidellm 0.3.0rc20250507__py3-none-any.whl → 0.3.1__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 (55) hide show
  1. guidellm/__init__.py +8 -13
  2. guidellm/__main__.py +290 -69
  3. guidellm/backend/__init__.py +6 -6
  4. guidellm/backend/backend.py +25 -4
  5. guidellm/backend/openai.py +153 -30
  6. guidellm/backend/response.py +6 -2
  7. guidellm/benchmark/__init__.py +16 -22
  8. guidellm/benchmark/aggregator.py +3 -3
  9. guidellm/benchmark/benchmark.py +11 -12
  10. guidellm/benchmark/benchmarker.py +2 -2
  11. guidellm/benchmark/entrypoints.py +34 -10
  12. guidellm/benchmark/output.py +59 -8
  13. guidellm/benchmark/profile.py +4 -4
  14. guidellm/benchmark/progress.py +2 -2
  15. guidellm/benchmark/scenario.py +104 -0
  16. guidellm/benchmark/scenarios/__init__.py +0 -0
  17. guidellm/config.py +32 -7
  18. guidellm/dataset/__init__.py +4 -4
  19. guidellm/dataset/creator.py +1 -1
  20. guidellm/dataset/synthetic.py +36 -11
  21. guidellm/logger.py +8 -4
  22. guidellm/objects/__init__.py +2 -2
  23. guidellm/objects/pydantic.py +30 -1
  24. guidellm/objects/statistics.py +20 -14
  25. guidellm/preprocess/__init__.py +3 -0
  26. guidellm/preprocess/dataset.py +374 -0
  27. guidellm/presentation/__init__.py +28 -0
  28. guidellm/presentation/builder.py +27 -0
  29. guidellm/presentation/data_models.py +232 -0
  30. guidellm/presentation/injector.py +66 -0
  31. guidellm/request/__init__.py +6 -3
  32. guidellm/request/loader.py +5 -5
  33. guidellm/{scheduler → request}/types.py +4 -1
  34. guidellm/scheduler/__init__.py +10 -15
  35. guidellm/scheduler/queues.py +25 -0
  36. guidellm/scheduler/result.py +21 -3
  37. guidellm/scheduler/scheduler.py +68 -60
  38. guidellm/scheduler/strategy.py +26 -24
  39. guidellm/scheduler/worker.py +64 -103
  40. guidellm/utils/__init__.py +17 -5
  41. guidellm/utils/cli.py +62 -0
  42. guidellm/utils/default_group.py +105 -0
  43. guidellm/utils/dict.py +23 -0
  44. guidellm/utils/hf_datasets.py +36 -0
  45. guidellm/utils/random.py +1 -1
  46. guidellm/utils/text.py +12 -5
  47. guidellm/version.py +6 -0
  48. guidellm-0.3.1.dist-info/METADATA +329 -0
  49. guidellm-0.3.1.dist-info/RECORD +62 -0
  50. {guidellm-0.3.0rc20250507.dist-info → guidellm-0.3.1.dist-info}/WHEEL +1 -1
  51. guidellm-0.3.0rc20250507.dist-info/METADATA +0 -451
  52. guidellm-0.3.0rc20250507.dist-info/RECORD +0 -48
  53. {guidellm-0.3.0rc20250507.dist-info → guidellm-0.3.1.dist-info}/entry_points.txt +0 -0
  54. {guidellm-0.3.0rc20250507.dist-info → guidellm-0.3.1.dist-info}/licenses/LICENSE +0 -0
  55. {guidellm-0.3.0rc20250507.dist-info → guidellm-0.3.1.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,11 @@
1
1
  import asyncio
2
2
  import math
3
- import multiprocessing
4
- import multiprocessing.queues
5
3
  import time
6
4
  from abc import ABC, abstractmethod
7
5
  from collections.abc import AsyncGenerator
8
6
  from dataclasses import dataclass
7
+ from itertools import islice
8
+ from threading import Event
9
9
  from typing import (
10
10
  Any,
11
11
  Generic,
@@ -26,36 +26,24 @@ from guidellm.backend import (
26
26
  )
27
27
  from guidellm.objects import StandardBaseModel
28
28
  from guidellm.request import GenerationRequest
29
- from guidellm.scheduler.result import SchedulerRequestInfo
30
- from guidellm.scheduler.types import RequestT, ResponseT
29
+ from guidellm.request.types import RequestT, ResponseT
30
+ from guidellm.scheduler.queues import MPQueues, Queue, QueueEmpty
31
+ from guidellm.scheduler.result import (
32
+ SchedulerRequestInfo,
33
+ WorkerProcessRequest,
34
+ WorkerProcessResult,
35
+ )
36
+ from guidellm.scheduler.strategy import SchedulingStrategy
31
37
 
32
38
  __all__ = [
33
- "WorkerProcessRequest",
34
- "WorkerProcessResult",
39
+ "GenerativeRequestsWorker",
40
+ "GenerativeRequestsWorkerDescription",
41
+ "RequestsWorker",
35
42
  "ResolveStatus",
36
43
  "WorkerDescription",
37
- "RequestsWorker",
38
- "GenerativeRequestsWorkerDescription",
39
- "GenerativeRequestsWorker",
40
44
  ]
41
45
 
42
46
 
43
- @dataclass
44
- class WorkerProcessRequest(Generic[RequestT]):
45
- request: RequestT
46
- start_time: float
47
- timeout_time: float
48
- queued_time: float
49
-
50
-
51
- @dataclass
52
- class WorkerProcessResult(Generic[RequestT, ResponseT]):
53
- type_: Literal["request_scheduled", "request_start", "request_complete"]
54
- request: RequestT
55
- response: Optional[ResponseT]
56
- info: SchedulerRequestInfo
57
-
58
-
59
47
  @dataclass
60
48
  class ResolveStatus:
61
49
  requested: bool
@@ -120,28 +108,25 @@ class RequestsWorker(ABC, Generic[RequestT, ResponseT]):
120
108
  """
121
109
  ...
122
110
 
123
- async def get_request(
124
- self, requests_queue: multiprocessing.Queue
125
- ) -> Optional[WorkerProcessRequest[RequestT]]:
126
- return await asyncio.to_thread(requests_queue.get) # type: ignore[attr-defined]
127
-
128
111
  async def send_result(
129
112
  self,
130
- results_queue: multiprocessing.Queue,
113
+ results_queue: Queue[WorkerProcessResult[RequestT, ResponseT]],
131
114
  result: WorkerProcessResult[RequestT, ResponseT],
132
115
  ):
133
116
  await asyncio.to_thread(results_queue.put, result) # type: ignore[attr-defined]
134
117
 
135
118
  async def resolve_scheduler_request(
136
119
  self,
137
- request: Any,
138
- queued_time: float,
120
+ process_request: WorkerProcessRequest[RequestT, ResponseT],
139
121
  dequeued_time: float,
140
122
  start_time: float,
141
- timeout_time: float,
142
- results_queue: multiprocessing.Queue,
123
+ results_queue: Queue[WorkerProcessResult[RequestT, ResponseT]],
143
124
  process_id: int,
144
125
  ):
126
+ request = process_request.request
127
+ timeout_time = process_request.timeout_time
128
+ queued_time = process_request.queued_time
129
+
145
130
  info = SchedulerRequestInfo(
146
131
  targeted_start_time=start_time,
147
132
  queued_time=queued_time,
@@ -185,74 +170,57 @@ class RequestsWorker(ABC, Generic[RequestT, ResponseT]):
185
170
  )
186
171
  asyncio.create_task(self.send_result(results_queue, result))
187
172
 
188
- def process_loop_synchronous(
189
- self,
190
- requests_queue: multiprocessing.Queue,
191
- results_queue: multiprocessing.Queue,
192
- process_id: int,
193
- ):
194
- async def _process_runner():
195
- while (
196
- process_request := await self.get_request(requests_queue)
197
- ) is not None:
198
- dequeued_time = time.time()
199
-
200
- await self.resolve_scheduler_request(
201
- request=process_request.request,
202
- queued_time=process_request.queued_time,
203
- dequeued_time=dequeued_time,
204
- start_time=process_request.start_time,
205
- timeout_time=process_request.timeout_time,
206
- results_queue=results_queue,
207
- process_id=process_id,
208
- )
209
-
210
- try:
211
- asyncio.run(_process_runner())
212
- except Exception as exc: # noqa: BLE001
213
- logger.error(
214
- f"Error in worker process {process_id}: {exc}",
215
- exc_info=True,
216
- stack_info=True,
217
- )
218
-
219
173
  def process_loop_asynchronous(
220
174
  self,
221
- requests_queue: multiprocessing.Queue,
222
- results_queue: multiprocessing.Queue,
175
+ queues: MPQueues[RequestT, ResponseT],
176
+ strategy: SchedulingStrategy,
177
+ stop_event: Event,
223
178
  max_concurrency: int,
224
179
  process_id: int,
180
+ num_processes: int,
225
181
  ):
226
182
  async def _process_runner():
227
- pending = asyncio.Semaphore(max_concurrency)
183
+ lock = asyncio.Semaphore(max_concurrency)
184
+ times_iter = islice(
185
+ strategy.request_times(),
186
+ process_id,
187
+ None,
188
+ num_processes,
189
+ )
228
190
 
229
- if pending.locked():
230
- raise ValueError("Async worker called with max_concurrency < 1")
191
+ start_time = None
192
+ while not stop_event.is_set():
193
+ if start_time is None:
194
+ start_time = next(times_iter)
231
195
 
232
- while (
233
- process_request := await self.get_request(requests_queue)
234
- ) is not None:
235
- dequeued_time = time.time()
196
+ # Yield control to the event loop. Sleep if we are way ahead
197
+ await asyncio.sleep(start_time - time.time() - 1)
198
+ await lock.acquire()
236
199
 
237
- await pending.acquire()
200
+ try:
201
+ process_request = queues.requests.get_nowait()
202
+ dequeued_time = time.time()
203
+ except QueueEmpty:
204
+ lock.release()
205
+ continue
238
206
 
239
- def _task_done(_: asyncio.Task):
240
- nonlocal pending
241
- pending.release()
207
+ def _request_callback(
208
+ _: asyncio.Future[WorkerProcessRequest[RequestT, ResponseT]],
209
+ ):
210
+ nonlocal lock
211
+ lock.release()
242
212
 
243
213
  task = asyncio.create_task(
244
214
  self.resolve_scheduler_request(
245
- request=process_request.request,
246
- queued_time=process_request.queued_time,
215
+ process_request=process_request,
247
216
  dequeued_time=dequeued_time,
248
- start_time=process_request.start_time,
249
- timeout_time=process_request.timeout_time,
250
- results_queue=results_queue,
217
+ start_time=start_time,
218
+ results_queue=queues.responses,
251
219
  process_id=process_id,
252
220
  )
253
221
  )
254
- task.add_done_callback(_task_done)
255
- await asyncio.sleep(0) # enable start task immediately
222
+ task.add_done_callback(_request_callback)
223
+ start_time = None
256
224
 
257
225
  try:
258
226
  asyncio.run(_process_runner())
@@ -309,32 +277,23 @@ class GenerativeRequestsWorker(RequestsWorker[GenerationRequest, ResponseSummary
309
277
  """
310
278
  await self.backend.prepare_multiprocessing()
311
279
 
312
- def process_loop_synchronous(
313
- self,
314
- requests_queue: multiprocessing.Queue,
315
- results_queue: multiprocessing.Queue,
316
- process_id: int,
317
- ):
318
- asyncio.run(self.backend.validate())
319
- super().process_loop_synchronous(
320
- requests_queue=requests_queue,
321
- results_queue=results_queue,
322
- process_id=process_id,
323
- )
324
-
325
280
  def process_loop_asynchronous(
326
281
  self,
327
- requests_queue: multiprocessing.Queue,
328
- results_queue: multiprocessing.Queue,
282
+ queues: MPQueues[GenerationRequest, ResponseSummary],
283
+ strategy: SchedulingStrategy,
284
+ stop_event: Event,
329
285
  max_concurrency: int,
330
286
  process_id: int,
287
+ num_processes: int,
331
288
  ):
332
289
  asyncio.run(self.backend.validate())
333
290
  super().process_loop_asynchronous(
334
- requests_queue=requests_queue,
335
- results_queue=results_queue,
291
+ queues=queues,
292
+ strategy=strategy,
293
+ stop_event=stop_event,
336
294
  max_concurrency=max_concurrency,
337
295
  process_id=process_id,
296
+ num_processes=num_processes,
338
297
  )
339
298
 
340
299
  async def resolve(
@@ -475,6 +434,7 @@ class GenerativeRequestsWorker(RequestsWorker[GenerationRequest, ResponseSummary
475
434
  request_args=RequestArgs(
476
435
  target=self.backend.target,
477
436
  headers={},
437
+ params={},
478
438
  payload={},
479
439
  ),
480
440
  start_time=resolve_start_time,
@@ -490,6 +450,7 @@ class GenerativeRequestsWorker(RequestsWorker[GenerationRequest, ResponseSummary
490
450
  request_args=RequestArgs(
491
451
  target=self.backend.target,
492
452
  headers={},
453
+ params={},
493
454
  payload={},
494
455
  ),
495
456
  start_time=response.start_time,
@@ -1,10 +1,17 @@
1
1
  from .colors import Colors
2
+ from .default_group import DefaultGroupHandler
3
+ from .dict import recursive_key_update
4
+ from .hf_datasets import (
5
+ SUPPORTED_TYPES,
6
+ save_dataset_to_file,
7
+ )
2
8
  from .hf_transformers import (
3
9
  check_load_processor,
4
10
  )
5
11
  from .random import IntegerRangeSampler
6
12
  from .text import (
7
13
  EndlessTextCreator,
14
+ camelize_str,
8
15
  clean_text,
9
16
  filter_text,
10
17
  is_puncutation,
@@ -14,14 +21,19 @@ from .text import (
14
21
  )
15
22
 
16
23
  __all__ = [
17
- "IntegerRangeSampler",
24
+ "SUPPORTED_TYPES",
18
25
  "Colors",
26
+ "DefaultGroupHandler",
27
+ "EndlessTextCreator",
28
+ "IntegerRangeSampler",
29
+ "camelize_str",
19
30
  "check_load_processor",
20
- "filter_text",
21
31
  "clean_text",
22
- "split_text",
23
- "load_text",
32
+ "filter_text",
24
33
  "is_puncutation",
25
- "EndlessTextCreator",
34
+ "load_text",
35
+ "recursive_key_update",
36
+ "save_dataset_to_file",
37
+ "split_text",
26
38
  "split_text_list_by_length",
27
39
  ]
guidellm/utils/cli.py ADDED
@@ -0,0 +1,62 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ import click
5
+
6
+
7
+ def parse_json(ctx, param, value): # noqa: ARG001
8
+ if value is None:
9
+ return None
10
+ try:
11
+ return json.loads(value)
12
+ except json.JSONDecodeError as err:
13
+ raise click.BadParameter(f"{param.name} must be a valid JSON string.") from err
14
+
15
+
16
+ def set_if_not_default(ctx: click.Context, **kwargs) -> dict[str, Any]:
17
+ """
18
+ Set the value of a click option if it is not the default value.
19
+ This is useful for setting options that are not None by default.
20
+ """
21
+ values = {}
22
+ for k, v in kwargs.items():
23
+ if ctx.get_parameter_source(k) != click.core.ParameterSource.DEFAULT: # type: ignore[attr-defined]
24
+ values[k] = v
25
+
26
+ return values
27
+
28
+
29
+ class Union(click.ParamType):
30
+ """
31
+ A custom click parameter type that allows for multiple types to be accepted.
32
+ """
33
+
34
+ def __init__(self, *types: click.ParamType):
35
+ self.types = types
36
+ self.name = "".join(t.name for t in types)
37
+
38
+ def convert(self, value, param, ctx): # noqa: RET503
39
+ fails = []
40
+ for t in self.types:
41
+ try:
42
+ return t.convert(value, param, ctx)
43
+ except click.BadParameter as e:
44
+ fails.append(str(e))
45
+ continue
46
+
47
+ self.fail("; ".join(fails) or f"Invalid value: {value}") # noqa: RET503
48
+
49
+ def get_metavar(self, param: click.Parameter) -> str:
50
+ def get_choices(t: click.ParamType) -> str:
51
+ meta = t.get_metavar(param)
52
+ return meta if meta is not None else t.name
53
+
54
+ # Get the choices for each type in the union.
55
+ choices_str = "|".join(map(get_choices, self.types))
56
+
57
+ # Use curly braces to indicate a required argument.
58
+ if param.required and param.param_type_name == "argument":
59
+ return f"{{{choices_str}}}"
60
+
61
+ # Use square braces to indicate an option or optional argument.
62
+ return f"[{choices_str}]"
@@ -0,0 +1,105 @@
1
+ """
2
+ File uses code adapted from code with the following license:
3
+
4
+ Copyright (c) 2015-2023, Heungsub Lee
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without modification,
8
+ are permitted provided that the following conditions are met:
9
+
10
+ Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ Redistributions in binary form must reproduce the above copyright notice, this
14
+ list of conditions and the following disclaimer in the documentation and/or
15
+ other materials provided with the distribution.
16
+
17
+ Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ """
32
+
33
+ __all__ = ["DefaultGroupHandler"]
34
+
35
+ import collections.abc as cabc
36
+
37
+ import click
38
+
39
+
40
+ class DefaultGroupHandler(click.Group):
41
+ """
42
+ Allows the migration to a new sub-command by allowing the group to run
43
+ one of its sub-commands as the no-args default command.
44
+ """
45
+
46
+ def __init__(self, *args, **kwargs):
47
+ # To resolve as the default command.
48
+ if not kwargs.get("ignore_unknown_options", True):
49
+ raise ValueError("Default group accepts unknown options")
50
+ self.ignore_unknown_options = True
51
+ self.default_cmd_name = kwargs.pop("default", None)
52
+ self.default_if_no_args = kwargs.pop("default_if_no_args", False)
53
+ super().__init__(*args, **kwargs)
54
+
55
+ def parse_args(self, ctx, args):
56
+ if not args and self.default_if_no_args:
57
+ args.insert(0, self.default_cmd_name)
58
+ return super().parse_args(ctx, args)
59
+
60
+ def get_command(self, ctx, cmd_name):
61
+ if cmd_name not in self.commands:
62
+ # If it doesn't match an existing command, use the default command name.
63
+ ctx.arg0 = cmd_name
64
+ cmd_name = self.default_cmd_name
65
+ return super().get_command(ctx, cmd_name)
66
+
67
+ def resolve_command(self, ctx, args):
68
+ cmd_name, cmd, args = super().resolve_command(ctx, args)
69
+ if hasattr(ctx, "arg0"):
70
+ args.insert(0, ctx.arg0)
71
+ if cmd is not None:
72
+ cmd_name = cmd.name
73
+ return cmd_name, cmd, args
74
+
75
+ def format_commands(self, ctx, formatter):
76
+ """
77
+ Used to wrap the default formatter to clarify which command is the default.
78
+ """
79
+ formatter = DefaultCommandFormatter(self, formatter, mark=" (default)")
80
+ return super().format_commands(ctx, formatter)
81
+
82
+
83
+ class DefaultCommandFormatter(click.HelpFormatter):
84
+ """
85
+ Wraps a formatter to edit the line for the default command to mark it
86
+ with the specified mark string.
87
+ """
88
+
89
+ def __init__(self, group, formatter, mark="*"):
90
+ self.group = group
91
+ self.formatter = formatter
92
+ self.mark = mark
93
+ super().__init__()
94
+
95
+ def __getattr__(self, attr):
96
+ return getattr(self.formatter, attr)
97
+
98
+ def write_dl(self, rows: cabc.Sequence[tuple[str, str]], *args, **kwargs):
99
+ rows_: list[tuple[str, str]] = []
100
+ for cmd_name, help_msg in rows:
101
+ if cmd_name == self.group.default_cmd_name:
102
+ rows_.insert(0, (cmd_name + self.mark, help_msg))
103
+ else:
104
+ rows_.append((cmd_name, help_msg))
105
+ return self.formatter.write_dl(rows_, *args, **kwargs)
guidellm/utils/dict.py ADDED
@@ -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
@@ -0,0 +1,36 @@
1
+ from pathlib import Path
2
+ from typing import Union
3
+
4
+ from datasets import Dataset
5
+
6
+ SUPPORTED_TYPES = {
7
+ ".json",
8
+ ".jsonl",
9
+ ".csv",
10
+ ".parquet",
11
+ }
12
+
13
+
14
+ def save_dataset_to_file(dataset: Dataset, output_path: Union[str, Path]) -> None:
15
+ """
16
+ Saves a HuggingFace Dataset to file in a supported format.
17
+
18
+ :param dataset: Dataset to save.
19
+ :param output_path: Output file path (.json, .jsonl, .csv, .parquet).
20
+ :raises ValueError: If the file extension is not supported.
21
+ """
22
+ output_path = Path(output_path)
23
+ output_path.parent.mkdir(parents=True, exist_ok=True)
24
+ suffix = output_path.suffix.lower()
25
+
26
+ if suffix == ".csv":
27
+ dataset.to_csv(output_path)
28
+ elif suffix in {".json", ".jsonl"}:
29
+ dataset.to_json(output_path)
30
+ elif suffix == ".parquet":
31
+ dataset.to_parquet(output_path)
32
+ else:
33
+ raise ValueError(
34
+ f"Unsupported file suffix '{suffix}' in output_path'{output_path}'."
35
+ f" Only {SUPPORTED_TYPES} are supported."
36
+ )
guidellm/utils/random.py CHANGED
@@ -37,7 +37,7 @@ class IntegerRangeSampler:
37
37
  if calc_min == calc_max:
38
38
  yield calc_min
39
39
  elif not self.variance:
40
- yield self.rng.randint(calc_min, calc_max + 1)
40
+ yield self.rng.randint(calc_min, calc_max)
41
41
  else:
42
42
  rand = self.rng.gauss(self.average, self.variance)
43
43
  yield round(max(calc_min, min(calc_max, rand)))
guidellm/utils/text.py CHANGED
@@ -13,13 +13,14 @@ from guidellm import data as package_data
13
13
  from guidellm.config import settings
14
14
 
15
15
  __all__ = [
16
- "split_text_list_by_length",
17
- "filter_text",
16
+ "EndlessTextCreator",
17
+ "camelize_str",
18
18
  "clean_text",
19
- "split_text",
20
- "load_text",
19
+ "filter_text",
21
20
  "is_puncutation",
22
- "EndlessTextCreator",
21
+ "load_text",
22
+ "split_text",
23
+ "split_text_list_by_length",
23
24
  ]
24
25
 
25
26
  MAX_PATH_LENGTH = 4096
@@ -189,6 +190,12 @@ def is_puncutation(text: str) -> bool:
189
190
  return len(text) == 1 and not text.isalnum() and not text.isspace()
190
191
 
191
192
 
193
+ def camelize_str(snake_case_string: str) -> str:
194
+ return (words := snake_case_string.split("_"))[0].lower() + "".join(
195
+ word.capitalize() for word in words[1:]
196
+ )
197
+
198
+
192
199
  class EndlessTextCreator:
193
200
  def __init__(
194
201
  self,
guidellm/version.py ADDED
@@ -0,0 +1,6 @@
1
+ version = "0.0.0"
2
+ build_type = "release"
3
+ build_iteration = "0"
4
+ git_commit = "None"
5
+ git_branch = "None"
6
+ git_last_tag = "None"