guidellm 0.3.0rc20250507__py3-none-any.whl → 0.4.0a0__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.
- guidellm/__init__.py +8 -13
- guidellm/__main__.py +290 -69
- guidellm/backend/__init__.py +6 -6
- guidellm/backend/backend.py +25 -4
- guidellm/backend/openai.py +147 -27
- guidellm/backend/response.py +6 -2
- guidellm/benchmark/__init__.py +16 -22
- guidellm/benchmark/aggregator.py +3 -3
- guidellm/benchmark/benchmark.py +11 -12
- guidellm/benchmark/benchmarker.py +2 -2
- guidellm/benchmark/entrypoints.py +34 -10
- guidellm/benchmark/output.py +57 -5
- guidellm/benchmark/profile.py +4 -4
- guidellm/benchmark/progress.py +2 -2
- guidellm/benchmark/scenario.py +104 -0
- guidellm/benchmark/scenarios/__init__.py +0 -0
- guidellm/config.py +28 -7
- guidellm/dataset/__init__.py +4 -4
- guidellm/dataset/creator.py +1 -1
- guidellm/dataset/synthetic.py +36 -11
- guidellm/logger.py +8 -4
- guidellm/objects/__init__.py +2 -2
- guidellm/objects/pydantic.py +30 -1
- guidellm/objects/statistics.py +20 -14
- guidellm/preprocess/__init__.py +3 -0
- guidellm/preprocess/dataset.py +374 -0
- guidellm/presentation/__init__.py +28 -0
- guidellm/presentation/builder.py +27 -0
- guidellm/presentation/data_models.py +232 -0
- guidellm/presentation/injector.py +66 -0
- guidellm/request/__init__.py +6 -3
- guidellm/request/loader.py +5 -5
- guidellm/{scheduler → request}/types.py +4 -1
- guidellm/scheduler/__init__.py +10 -15
- guidellm/scheduler/queues.py +25 -0
- guidellm/scheduler/result.py +21 -3
- guidellm/scheduler/scheduler.py +68 -60
- guidellm/scheduler/strategy.py +26 -24
- guidellm/scheduler/worker.py +64 -103
- guidellm/utils/__init__.py +17 -5
- guidellm/utils/cli.py +62 -0
- guidellm/utils/default_group.py +105 -0
- guidellm/utils/dict.py +23 -0
- guidellm/utils/hf_datasets.py +36 -0
- guidellm/utils/random.py +1 -1
- guidellm/utils/text.py +12 -5
- guidellm/version.py +6 -0
- guidellm-0.4.0a0.dist-info/METADATA +317 -0
- guidellm-0.4.0a0.dist-info/RECORD +62 -0
- {guidellm-0.3.0rc20250507.dist-info → guidellm-0.4.0a0.dist-info}/WHEEL +1 -1
- guidellm-0.3.0rc20250507.dist-info/METADATA +0 -451
- guidellm-0.3.0rc20250507.dist-info/RECORD +0 -48
- {guidellm-0.3.0rc20250507.dist-info → guidellm-0.4.0a0.dist-info}/entry_points.txt +0 -0
- {guidellm-0.3.0rc20250507.dist-info → guidellm-0.4.0a0.dist-info}/licenses/LICENSE +0 -0
- {guidellm-0.3.0rc20250507.dist-info → guidellm-0.4.0a0.dist-info}/top_level.txt +0 -0
guidellm/scheduler/worker.py
CHANGED
|
@@ -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.
|
|
30
|
-
from guidellm.scheduler.
|
|
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
|
-
"
|
|
34
|
-
"
|
|
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:
|
|
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
|
-
|
|
138
|
-
queued_time: float,
|
|
120
|
+
process_request: WorkerProcessRequest[RequestT, ResponseT],
|
|
139
121
|
dequeued_time: float,
|
|
140
122
|
start_time: float,
|
|
141
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
queued_time=process_request.queued_time,
|
|
215
|
+
process_request=process_request,
|
|
247
216
|
dequeued_time=dequeued_time,
|
|
248
|
-
start_time=
|
|
249
|
-
|
|
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(
|
|
255
|
-
|
|
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
|
-
|
|
328
|
-
|
|
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
|
-
|
|
335
|
-
|
|
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,
|
guidellm/utils/__init__.py
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
23
|
-
"load_text",
|
|
32
|
+
"filter_text",
|
|
24
33
|
"is_puncutation",
|
|
25
|
-
"
|
|
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
|
|
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
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"EndlessTextCreator",
|
|
17
|
+
"camelize_str",
|
|
18
18
|
"clean_text",
|
|
19
|
-
"
|
|
20
|
-
"load_text",
|
|
19
|
+
"filter_text",
|
|
21
20
|
"is_puncutation",
|
|
22
|
-
"
|
|
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,
|