guidellm 0.4.0a18__py3-none-any.whl → 0.4.0a155__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 +5 -2
- guidellm/__main__.py +451 -252
- guidellm/backends/__init__.py +33 -0
- guidellm/backends/backend.py +110 -0
- guidellm/backends/openai.py +355 -0
- guidellm/backends/response_handlers.py +455 -0
- guidellm/benchmark/__init__.py +53 -39
- guidellm/benchmark/benchmarker.py +148 -317
- guidellm/benchmark/entrypoints.py +466 -128
- guidellm/benchmark/output.py +517 -771
- guidellm/benchmark/profile.py +580 -280
- guidellm/benchmark/progress.py +568 -549
- guidellm/benchmark/scenarios/__init__.py +40 -0
- guidellm/benchmark/scenarios/chat.json +6 -0
- guidellm/benchmark/scenarios/rag.json +6 -0
- guidellm/benchmark/schemas.py +2085 -0
- guidellm/data/__init__.py +28 -4
- guidellm/data/collators.py +16 -0
- guidellm/data/deserializers/__init__.py +53 -0
- guidellm/data/deserializers/deserializer.py +109 -0
- guidellm/data/deserializers/file.py +222 -0
- guidellm/data/deserializers/huggingface.py +94 -0
- guidellm/data/deserializers/memory.py +192 -0
- guidellm/data/deserializers/synthetic.py +346 -0
- guidellm/data/loaders.py +145 -0
- guidellm/data/preprocessors/__init__.py +25 -0
- guidellm/data/preprocessors/formatters.py +412 -0
- guidellm/data/preprocessors/mappers.py +198 -0
- guidellm/data/preprocessors/preprocessor.py +29 -0
- guidellm/data/processor.py +30 -0
- guidellm/data/schemas.py +13 -0
- guidellm/data/utils/__init__.py +10 -0
- guidellm/data/utils/dataset.py +94 -0
- guidellm/data/utils/functions.py +18 -0
- guidellm/extras/__init__.py +4 -0
- guidellm/extras/audio.py +215 -0
- guidellm/extras/vision.py +242 -0
- guidellm/logger.py +2 -2
- guidellm/mock_server/__init__.py +8 -0
- guidellm/mock_server/config.py +84 -0
- guidellm/mock_server/handlers/__init__.py +17 -0
- guidellm/mock_server/handlers/chat_completions.py +280 -0
- guidellm/mock_server/handlers/completions.py +280 -0
- guidellm/mock_server/handlers/tokenizer.py +142 -0
- guidellm/mock_server/models.py +510 -0
- guidellm/mock_server/server.py +168 -0
- guidellm/mock_server/utils.py +302 -0
- guidellm/preprocess/dataset.py +23 -26
- guidellm/presentation/builder.py +2 -2
- guidellm/presentation/data_models.py +25 -21
- guidellm/presentation/injector.py +2 -3
- guidellm/scheduler/__init__.py +65 -26
- guidellm/scheduler/constraints.py +1035 -0
- guidellm/scheduler/environments.py +252 -0
- guidellm/scheduler/scheduler.py +140 -368
- guidellm/scheduler/schemas.py +272 -0
- guidellm/scheduler/strategies.py +519 -0
- guidellm/scheduler/worker.py +391 -420
- guidellm/scheduler/worker_group.py +707 -0
- guidellm/schemas/__init__.py +31 -0
- guidellm/schemas/info.py +159 -0
- guidellm/schemas/request.py +216 -0
- guidellm/schemas/response.py +119 -0
- guidellm/schemas/stats.py +228 -0
- guidellm/{config.py → settings.py} +32 -21
- guidellm/utils/__init__.py +95 -8
- guidellm/utils/auto_importer.py +98 -0
- guidellm/utils/cli.py +46 -2
- guidellm/utils/console.py +183 -0
- guidellm/utils/encoding.py +778 -0
- guidellm/utils/functions.py +134 -0
- guidellm/utils/hf_datasets.py +1 -2
- guidellm/utils/hf_transformers.py +4 -4
- guidellm/utils/imports.py +9 -0
- guidellm/utils/messaging.py +1118 -0
- guidellm/utils/mixins.py +115 -0
- guidellm/utils/pydantic_utils.py +411 -0
- guidellm/utils/random.py +3 -4
- guidellm/utils/registry.py +220 -0
- guidellm/utils/singleton.py +133 -0
- guidellm/{objects → utils}/statistics.py +341 -247
- guidellm/utils/synchronous.py +159 -0
- guidellm/utils/text.py +163 -50
- guidellm/utils/typing.py +41 -0
- guidellm/version.py +1 -1
- {guidellm-0.4.0a18.dist-info → guidellm-0.4.0a155.dist-info}/METADATA +33 -10
- guidellm-0.4.0a155.dist-info/RECORD +96 -0
- guidellm/backend/__init__.py +0 -23
- guidellm/backend/backend.py +0 -259
- guidellm/backend/openai.py +0 -705
- guidellm/backend/response.py +0 -136
- guidellm/benchmark/aggregator.py +0 -760
- guidellm/benchmark/benchmark.py +0 -837
- guidellm/benchmark/scenario.py +0 -104
- guidellm/data/prideandprejudice.txt.gz +0 -0
- guidellm/dataset/__init__.py +0 -22
- guidellm/dataset/creator.py +0 -213
- guidellm/dataset/entrypoints.py +0 -42
- guidellm/dataset/file.py +0 -92
- guidellm/dataset/hf_datasets.py +0 -62
- guidellm/dataset/in_memory.py +0 -132
- guidellm/dataset/synthetic.py +0 -287
- guidellm/objects/__init__.py +0 -18
- guidellm/objects/pydantic.py +0 -89
- guidellm/request/__init__.py +0 -18
- guidellm/request/loader.py +0 -284
- guidellm/request/request.py +0 -79
- guidellm/request/types.py +0 -10
- guidellm/scheduler/queues.py +0 -25
- guidellm/scheduler/result.py +0 -155
- guidellm/scheduler/strategy.py +0 -495
- guidellm-0.4.0a18.dist-info/RECORD +0 -62
- {guidellm-0.4.0a18.dist-info → guidellm-0.4.0a155.dist-info}/WHEEL +0 -0
- {guidellm-0.4.0a18.dist-info → guidellm-0.4.0a155.dist-info}/entry_points.txt +0 -0
- {guidellm-0.4.0a18.dist-info → guidellm-0.4.0a155.dist-info}/licenses/LICENSE +0 -0
- {guidellm-0.4.0a18.dist-info → guidellm-0.4.0a155.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request statistics and metrics for generative AI benchmark analysis.
|
|
3
|
+
|
|
4
|
+
Provides data structures for capturing and analyzing performance metrics from
|
|
5
|
+
generative AI workloads. Contains request-level statistics including token counts,
|
|
6
|
+
latency measurements, and throughput calculations for text generation benchmarks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
from pydantic import Field, computed_field
|
|
14
|
+
|
|
15
|
+
from guidellm.schemas.info import RequestInfo
|
|
16
|
+
from guidellm.schemas.request import GenerativeRequestType, UsageMetrics
|
|
17
|
+
from guidellm.utils import StandardBaseDict
|
|
18
|
+
|
|
19
|
+
__all__ = ["GenerativeRequestStats"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GenerativeRequestStats(StandardBaseDict):
|
|
23
|
+
"""
|
|
24
|
+
Request statistics for generative AI text generation workloads.
|
|
25
|
+
|
|
26
|
+
Captures comprehensive performance metrics for individual generative requests,
|
|
27
|
+
including token counts, timing measurements, and derived performance statistics.
|
|
28
|
+
Provides computed properties for latency analysis, throughput calculations,
|
|
29
|
+
and token generation metrics essential for benchmark evaluation.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
::
|
|
33
|
+
stats = GenerativeRequestStats(
|
|
34
|
+
request_id="req_123",
|
|
35
|
+
request_type="text_completion",
|
|
36
|
+
info=request_info,
|
|
37
|
+
input_metrics=input_usage,
|
|
38
|
+
output_metrics=output_usage
|
|
39
|
+
)
|
|
40
|
+
throughput = stats.output_tokens_per_second
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
type_: Literal["generative_request_stats"] = "generative_request_stats"
|
|
44
|
+
request_id: str = Field(description="Unique identifier for the request")
|
|
45
|
+
request_type: GenerativeRequestType | str = Field(
|
|
46
|
+
description="Type of generative request: text or chat completion"
|
|
47
|
+
)
|
|
48
|
+
request_args: str | None = Field(
|
|
49
|
+
default=None, description="Arguments passed to the backend for this request"
|
|
50
|
+
)
|
|
51
|
+
output: str | None = Field(
|
|
52
|
+
description="Generated text output, if request completed successfully"
|
|
53
|
+
)
|
|
54
|
+
info: RequestInfo = Field(
|
|
55
|
+
description="Metadata and timing information for the request"
|
|
56
|
+
)
|
|
57
|
+
input_metrics: UsageMetrics = Field(
|
|
58
|
+
description="Usage statistics for the input prompt"
|
|
59
|
+
)
|
|
60
|
+
output_metrics: UsageMetrics = Field(
|
|
61
|
+
description="Usage statistics for the generated output"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Request stats
|
|
65
|
+
@computed_field # type: ignore[misc]
|
|
66
|
+
@property
|
|
67
|
+
def request_latency(self) -> float | None:
|
|
68
|
+
"""
|
|
69
|
+
End-to-end request processing latency in seconds.
|
|
70
|
+
|
|
71
|
+
:return: Duration from request start to completion, or None if unavailable.
|
|
72
|
+
"""
|
|
73
|
+
if not self.info.timings.request_end or not self.info.timings.request_start:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
return self.info.timings.request_end - self.info.timings.request_start
|
|
77
|
+
|
|
78
|
+
# General token stats
|
|
79
|
+
@computed_field # type: ignore[misc]
|
|
80
|
+
@property
|
|
81
|
+
def prompt_tokens(self) -> int | None:
|
|
82
|
+
"""
|
|
83
|
+
Number of tokens in the input prompt.
|
|
84
|
+
|
|
85
|
+
:return: Input prompt token count, or None if unavailable.
|
|
86
|
+
"""
|
|
87
|
+
return self.input_metrics.text_tokens
|
|
88
|
+
|
|
89
|
+
@computed_field # type: ignore[misc]
|
|
90
|
+
@property
|
|
91
|
+
def input_tokens(self) -> int | None:
|
|
92
|
+
"""
|
|
93
|
+
Number of tokens in the input prompt.
|
|
94
|
+
|
|
95
|
+
:return: Input prompt token count, or None if unavailable.
|
|
96
|
+
"""
|
|
97
|
+
return self.input_metrics.total_tokens
|
|
98
|
+
|
|
99
|
+
@computed_field # type: ignore[misc]
|
|
100
|
+
@property
|
|
101
|
+
def output_tokens(self) -> int | None:
|
|
102
|
+
"""
|
|
103
|
+
Number of tokens in the generated output.
|
|
104
|
+
|
|
105
|
+
:return: Generated output token count, or None if unavailable.
|
|
106
|
+
"""
|
|
107
|
+
return self.output_metrics.total_tokens
|
|
108
|
+
|
|
109
|
+
@computed_field # type: ignore[misc]
|
|
110
|
+
@property
|
|
111
|
+
def total_tokens(self) -> int | None:
|
|
112
|
+
"""
|
|
113
|
+
Total token count including prompt and output tokens.
|
|
114
|
+
|
|
115
|
+
:return: Sum of prompt and output tokens, or None if either is unavailable.
|
|
116
|
+
"""
|
|
117
|
+
input_tokens = self.input_metrics.total_tokens
|
|
118
|
+
output_tokens = self.output_metrics.total_tokens
|
|
119
|
+
|
|
120
|
+
if input_tokens is None and output_tokens is None:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
return (input_tokens or 0) + (output_tokens or 0)
|
|
124
|
+
|
|
125
|
+
@computed_field # type: ignore[misc]
|
|
126
|
+
@property
|
|
127
|
+
def time_to_first_token_ms(self) -> float | None:
|
|
128
|
+
"""
|
|
129
|
+
Time to first token generation in milliseconds.
|
|
130
|
+
|
|
131
|
+
:return: Latency from request start to first token, or None if unavailable.
|
|
132
|
+
"""
|
|
133
|
+
if (
|
|
134
|
+
not self.info.timings.first_iteration
|
|
135
|
+
or not self.info.timings.request_start
|
|
136
|
+
or self.info.timings.first_iteration == self.info.timings.last_iteration
|
|
137
|
+
):
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
return 1000 * (
|
|
141
|
+
self.info.timings.first_iteration - self.info.timings.request_start
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@computed_field # type: ignore[misc]
|
|
145
|
+
@property
|
|
146
|
+
def time_per_output_token_ms(self) -> float | None:
|
|
147
|
+
"""
|
|
148
|
+
Average time per output token in milliseconds.
|
|
149
|
+
|
|
150
|
+
Includes time for first token and all subsequent tokens.
|
|
151
|
+
|
|
152
|
+
:return: Average milliseconds per output token, or None if unavailable.
|
|
153
|
+
"""
|
|
154
|
+
if (
|
|
155
|
+
not self.info.timings.request_start
|
|
156
|
+
or not self.info.timings.last_iteration
|
|
157
|
+
or not self.output_metrics.total_tokens
|
|
158
|
+
):
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
1000
|
|
163
|
+
* (self.info.timings.last_iteration - self.info.timings.request_start)
|
|
164
|
+
/ self.output_metrics.total_tokens
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
@computed_field # type: ignore[misc]
|
|
168
|
+
@property
|
|
169
|
+
def inter_token_latency_ms(self) -> float | None:
|
|
170
|
+
"""
|
|
171
|
+
Average inter-token latency in milliseconds.
|
|
172
|
+
|
|
173
|
+
Measures time between token generations, excluding first token.
|
|
174
|
+
|
|
175
|
+
:return: Average milliseconds between tokens, or None if unavailable.
|
|
176
|
+
"""
|
|
177
|
+
if (
|
|
178
|
+
not self.info.timings.first_iteration
|
|
179
|
+
or not self.info.timings.last_iteration
|
|
180
|
+
or not self.output_metrics.total_tokens
|
|
181
|
+
or self.output_metrics.total_tokens <= 1
|
|
182
|
+
):
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
1000
|
|
187
|
+
* (self.info.timings.last_iteration - self.info.timings.first_iteration)
|
|
188
|
+
/ (self.output_metrics.total_tokens - 1)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
@computed_field # type: ignore[misc]
|
|
192
|
+
@property
|
|
193
|
+
def tokens_per_second(self) -> float | None:
|
|
194
|
+
"""
|
|
195
|
+
Overall token throughput including prompt and output tokens.
|
|
196
|
+
|
|
197
|
+
:return: Total tokens per second, or None if unavailable.
|
|
198
|
+
"""
|
|
199
|
+
if not (latency := self.request_latency) or self.total_tokens is None:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
return self.total_tokens / latency
|
|
203
|
+
|
|
204
|
+
@computed_field # type: ignore[misc]
|
|
205
|
+
@property
|
|
206
|
+
def output_tokens_per_second(self) -> float | None:
|
|
207
|
+
"""
|
|
208
|
+
Output token generation throughput.
|
|
209
|
+
|
|
210
|
+
:return: Output tokens per second, or None if unavailable.
|
|
211
|
+
"""
|
|
212
|
+
if not (latency := self.request_latency) or self.output_tokens is None:
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
return self.output_tokens / latency
|
|
216
|
+
|
|
217
|
+
@computed_field # type: ignore[misc]
|
|
218
|
+
@property
|
|
219
|
+
def output_tokens_per_iteration(self) -> float | None:
|
|
220
|
+
"""
|
|
221
|
+
Average output tokens generated per iteration.
|
|
222
|
+
|
|
223
|
+
:return: Output tokens per iteration, or None if unavailable.
|
|
224
|
+
"""
|
|
225
|
+
if self.output_tokens is None or not self.info.timings.iterations:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
return self.output_tokens / self.info.timings.iterations
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
|
-
import os
|
|
3
4
|
from collections.abc import Sequence
|
|
4
5
|
from enum import Enum
|
|
5
|
-
from typing import Literal
|
|
6
|
+
from typing import Literal
|
|
6
7
|
|
|
7
8
|
from pydantic import BaseModel, Field, model_validator
|
|
8
9
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
@@ -46,8 +47,8 @@ class LoggingSettings(BaseModel):
|
|
|
46
47
|
disabled: bool = False
|
|
47
48
|
clear_loggers: bool = True
|
|
48
49
|
console_log_level: str = "WARNING"
|
|
49
|
-
log_file:
|
|
50
|
-
log_file_level:
|
|
50
|
+
log_file: str | None = None
|
|
51
|
+
log_file_level: str | None = None
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
class DatasetSettings(BaseModel):
|
|
@@ -80,14 +81,18 @@ class OpenAISettings(BaseModel):
|
|
|
80
81
|
for OpenAI server based pathways
|
|
81
82
|
"""
|
|
82
83
|
|
|
83
|
-
api_key:
|
|
84
|
-
bearer_token:
|
|
85
|
-
headers:
|
|
86
|
-
organization:
|
|
87
|
-
project:
|
|
84
|
+
api_key: str | None = None
|
|
85
|
+
bearer_token: str | None = None
|
|
86
|
+
headers: dict[str, str] | None = None
|
|
87
|
+
organization: str | None = None
|
|
88
|
+
project: str | None = None
|
|
88
89
|
base_url: str = "http://localhost:8000"
|
|
89
90
|
max_output_tokens: int = 16384
|
|
90
91
|
verify: bool = True
|
|
92
|
+
max_output_key: dict[Literal["text_completions", "chat_completions"], str] = {
|
|
93
|
+
"text_completions": "max_tokens",
|
|
94
|
+
"chat_completions": "max_completion_tokens",
|
|
95
|
+
}
|
|
91
96
|
|
|
92
97
|
|
|
93
98
|
class ReportGenerationSettings(BaseModel):
|
|
@@ -131,24 +136,30 @@ class Settings(BaseSettings):
|
|
|
131
136
|
request_http2: bool = True
|
|
132
137
|
|
|
133
138
|
# Scheduler settings
|
|
139
|
+
mp_context_type: Literal["spawn", "fork", "forkserver"] | None = "fork"
|
|
140
|
+
mp_serialization: Literal["dict", "sequence"] | None = "dict"
|
|
141
|
+
mp_encoding: (
|
|
142
|
+
Literal["msgpack", "msgspec"]
|
|
143
|
+
| None
|
|
144
|
+
| list[Literal["msgpack", "msgspec"] | None]
|
|
145
|
+
) = ["msgspec", "msgpack", None]
|
|
146
|
+
mp_messaging_object: Literal["queue", "manager_queue", "pipe"] = "queue"
|
|
147
|
+
mp_requests_send_buffer_size: int = 1
|
|
148
|
+
mp_poll_interval: float = 0.1
|
|
149
|
+
mp_max_pending_buffer_percent: float = 0.5
|
|
150
|
+
mp_max_worker_buffer_percent: float = 0.2
|
|
134
151
|
max_concurrency: int = 512
|
|
135
|
-
max_worker_processes: int =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
min_queued_requests: int = 20
|
|
140
|
-
scheduler_start_delay: float = 5
|
|
152
|
+
max_worker_processes: int = 10
|
|
153
|
+
scheduler_start_delay_non_distributed: float = 1.0
|
|
154
|
+
constraint_error_window_size: float = 30
|
|
155
|
+
constraint_error_min_processed: float = 30
|
|
141
156
|
|
|
142
157
|
# Data settings
|
|
143
158
|
dataset: DatasetSettings = DatasetSettings()
|
|
144
159
|
|
|
145
160
|
# Request/stats settings
|
|
146
|
-
preferred_prompt_tokens_source:
|
|
147
|
-
|
|
148
|
-
] = "response"
|
|
149
|
-
preferred_output_tokens_source: Optional[
|
|
150
|
-
Literal["request", "response", "local"]
|
|
151
|
-
] = "response"
|
|
161
|
+
preferred_prompt_tokens_source: Literal["request", "response"] = "response"
|
|
162
|
+
preferred_output_tokens_source: Literal["request", "response"] = "response"
|
|
152
163
|
preferred_backend: Literal["openai"] = "openai"
|
|
153
164
|
preferred_route: Literal["text_completions", "chat_completions"] = (
|
|
154
165
|
"text_completions"
|
guidellm/utils/__init__.py
CHANGED
|
@@ -1,39 +1,126 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .auto_importer import AutoImporterMixin
|
|
2
|
+
from .console import Colors, Console, ConsoleUpdateStep, StatusIcons, StatusStyles
|
|
2
3
|
from .default_group import DefaultGroupHandler
|
|
3
4
|
from .dict import recursive_key_update
|
|
4
|
-
from .
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
from .encoding import (
|
|
6
|
+
Encoder,
|
|
7
|
+
EncodingTypesAlias,
|
|
8
|
+
MessageEncoding,
|
|
9
|
+
SerializationTypesAlias,
|
|
10
|
+
Serializer,
|
|
7
11
|
)
|
|
8
|
-
from .
|
|
9
|
-
|
|
12
|
+
from .functions import (
|
|
13
|
+
all_defined,
|
|
14
|
+
safe_add,
|
|
15
|
+
safe_divide,
|
|
16
|
+
safe_format_timestamp,
|
|
17
|
+
safe_getattr,
|
|
18
|
+
safe_multiply,
|
|
19
|
+
)
|
|
20
|
+
from .hf_datasets import SUPPORTED_TYPES, save_dataset_to_file
|
|
21
|
+
from .hf_transformers import check_load_processor
|
|
22
|
+
from .imports import json
|
|
23
|
+
from .messaging import (
|
|
24
|
+
InterProcessMessaging,
|
|
25
|
+
InterProcessMessagingManagerQueue,
|
|
26
|
+
InterProcessMessagingPipe,
|
|
27
|
+
InterProcessMessagingQueue,
|
|
28
|
+
SendMessageT,
|
|
29
|
+
)
|
|
30
|
+
from .mixins import InfoMixin
|
|
31
|
+
from .pydantic_utils import (
|
|
32
|
+
PydanticClassRegistryMixin,
|
|
33
|
+
ReloadableBaseModel,
|
|
34
|
+
StandardBaseDict,
|
|
35
|
+
StandardBaseModel,
|
|
36
|
+
StatusBreakdown,
|
|
10
37
|
)
|
|
11
38
|
from .random import IntegerRangeSampler
|
|
39
|
+
from .registry import RegistryMixin, RegistryObjT
|
|
40
|
+
from .singleton import SingletonMixin, ThreadSafeSingletonMixin
|
|
41
|
+
from .statistics import (
|
|
42
|
+
DistributionSummary,
|
|
43
|
+
Percentiles,
|
|
44
|
+
RunningStats,
|
|
45
|
+
StatusDistributionSummary,
|
|
46
|
+
TimeRunningStats,
|
|
47
|
+
)
|
|
48
|
+
from .synchronous import (
|
|
49
|
+
wait_for_sync_barrier,
|
|
50
|
+
wait_for_sync_event,
|
|
51
|
+
wait_for_sync_objects,
|
|
52
|
+
)
|
|
12
53
|
from .text import (
|
|
13
54
|
EndlessTextCreator,
|
|
14
55
|
camelize_str,
|
|
15
56
|
clean_text,
|
|
16
57
|
filter_text,
|
|
17
|
-
|
|
58
|
+
format_value_display,
|
|
59
|
+
is_punctuation,
|
|
18
60
|
load_text,
|
|
19
61
|
split_text,
|
|
20
62
|
split_text_list_by_length,
|
|
21
63
|
)
|
|
64
|
+
from .typing import get_literal_vals
|
|
22
65
|
|
|
23
66
|
__all__ = [
|
|
24
67
|
"SUPPORTED_TYPES",
|
|
68
|
+
"AutoImporterMixin",
|
|
69
|
+
"Colors",
|
|
25
70
|
"Colors",
|
|
71
|
+
"Console",
|
|
72
|
+
"ConsoleUpdateStep",
|
|
26
73
|
"DefaultGroupHandler",
|
|
74
|
+
"DistributionSummary",
|
|
75
|
+
"Encoder",
|
|
76
|
+
"EncodingTypesAlias",
|
|
27
77
|
"EndlessTextCreator",
|
|
78
|
+
"InfoMixin",
|
|
28
79
|
"IntegerRangeSampler",
|
|
80
|
+
"InterProcessMessaging",
|
|
81
|
+
"InterProcessMessagingManagerQueue",
|
|
82
|
+
"InterProcessMessagingPipe",
|
|
83
|
+
"InterProcessMessagingQueue",
|
|
84
|
+
"MessageEncoding",
|
|
85
|
+
"MessageEncoding",
|
|
86
|
+
"Percentiles",
|
|
87
|
+
"PydanticClassRegistryMixin",
|
|
88
|
+
"RegistryMixin",
|
|
89
|
+
"RegistryObjT",
|
|
90
|
+
"ReloadableBaseModel",
|
|
91
|
+
"RunningStats",
|
|
92
|
+
"SendMessageT",
|
|
93
|
+
"SerializationTypesAlias",
|
|
94
|
+
"Serializer",
|
|
95
|
+
"SingletonMixin",
|
|
96
|
+
"StandardBaseDict",
|
|
97
|
+
"StandardBaseModel",
|
|
98
|
+
"StatusBreakdown",
|
|
99
|
+
"StatusDistributionSummary",
|
|
100
|
+
"StatusIcons",
|
|
101
|
+
"StatusStyles",
|
|
102
|
+
"ThreadSafeSingletonMixin",
|
|
103
|
+
"TimeRunningStats",
|
|
104
|
+
"all_defined",
|
|
29
105
|
"camelize_str",
|
|
30
106
|
"check_load_processor",
|
|
31
107
|
"clean_text",
|
|
32
108
|
"filter_text",
|
|
33
|
-
"
|
|
109
|
+
"format_value_display",
|
|
110
|
+
"get_literal_vals",
|
|
111
|
+
"is_punctuation",
|
|
112
|
+
"json",
|
|
34
113
|
"load_text",
|
|
35
114
|
"recursive_key_update",
|
|
115
|
+
"safe_add",
|
|
116
|
+
"safe_divide",
|
|
117
|
+
"safe_format_timestamp",
|
|
118
|
+
"safe_getattr",
|
|
119
|
+
"safe_multiply",
|
|
36
120
|
"save_dataset_to_file",
|
|
37
121
|
"split_text",
|
|
38
122
|
"split_text_list_by_length",
|
|
123
|
+
"wait_for_sync_barrier",
|
|
124
|
+
"wait_for_sync_event",
|
|
125
|
+
"wait_for_sync_objects",
|
|
39
126
|
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Automatic module importing utilities for dynamic class discovery.
|
|
3
|
+
|
|
4
|
+
This module provides a mixin class for automatic module importing within a package,
|
|
5
|
+
enabling dynamic discovery of classes and implementations without explicit imports.
|
|
6
|
+
It is particularly useful for auto-registering classes in a registry pattern where
|
|
7
|
+
subclasses need to be discoverable at runtime.
|
|
8
|
+
|
|
9
|
+
The AutoImporterMixin can be combined with registration mechanisms to create
|
|
10
|
+
extensible systems where new implementations are automatically discovered and
|
|
11
|
+
registered when they are placed in the correct package structure.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib
|
|
17
|
+
import pkgutil
|
|
18
|
+
import sys
|
|
19
|
+
from typing import ClassVar
|
|
20
|
+
|
|
21
|
+
__all__ = ["AutoImporterMixin"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AutoImporterMixin:
|
|
25
|
+
"""
|
|
26
|
+
Mixin class for automatic module importing within packages.
|
|
27
|
+
|
|
28
|
+
This mixin enables dynamic discovery of classes and implementations without
|
|
29
|
+
explicit imports by automatically importing all modules within specified
|
|
30
|
+
packages. It is designed for use with class registration mechanisms to enable
|
|
31
|
+
automatic discovery and registration of classes when they are placed in the
|
|
32
|
+
correct package structure.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
::
|
|
36
|
+
from guidellm.utils import AutoImporterMixin
|
|
37
|
+
|
|
38
|
+
class MyRegistry(AutoImporterMixin):
|
|
39
|
+
auto_package = "my_package.implementations"
|
|
40
|
+
|
|
41
|
+
MyRegistry.auto_import_package_modules()
|
|
42
|
+
|
|
43
|
+
:cvar auto_package: Package name or tuple of package names to import modules from
|
|
44
|
+
:cvar auto_ignore_modules: Module names to ignore during import
|
|
45
|
+
:cvar auto_imported_modules: List tracking which modules have been imported
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
auto_package: ClassVar[str | tuple[str, ...] | None] = None
|
|
49
|
+
auto_ignore_modules: ClassVar[tuple[str, ...] | None] = None
|
|
50
|
+
auto_imported_modules: ClassVar[list[str] | None] = None
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def auto_import_package_modules(cls) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Automatically import all modules within the specified package(s).
|
|
56
|
+
|
|
57
|
+
Scans the package(s) defined in the `auto_package` class variable and imports
|
|
58
|
+
all modules found, tracking them in `auto_imported_modules`. Skips packages
|
|
59
|
+
(directories) and any modules listed in `auto_ignore_modules`.
|
|
60
|
+
|
|
61
|
+
:raises ValueError: If the `auto_package` class variable is not set
|
|
62
|
+
"""
|
|
63
|
+
if cls.auto_package is None:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
"The class variable 'auto_package' must be set to the package name to "
|
|
66
|
+
"import modules from."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
cls.auto_imported_modules = []
|
|
70
|
+
packages = (
|
|
71
|
+
cls.auto_package
|
|
72
|
+
if isinstance(cls.auto_package, tuple)
|
|
73
|
+
else (cls.auto_package,)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
for package_name in packages:
|
|
77
|
+
package = importlib.import_module(package_name)
|
|
78
|
+
|
|
79
|
+
for _, module_name, is_pkg in pkgutil.walk_packages(
|
|
80
|
+
package.__path__, package.__name__ + "."
|
|
81
|
+
):
|
|
82
|
+
if (
|
|
83
|
+
is_pkg
|
|
84
|
+
or (
|
|
85
|
+
cls.auto_ignore_modules is not None
|
|
86
|
+
and module_name in cls.auto_ignore_modules
|
|
87
|
+
)
|
|
88
|
+
or module_name in cls.auto_imported_modules
|
|
89
|
+
):
|
|
90
|
+
# Skip packages and ignored modules
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if module_name in sys.modules:
|
|
94
|
+
# Avoid circular imports
|
|
95
|
+
cls.auto_imported_modules.append(module_name)
|
|
96
|
+
else:
|
|
97
|
+
importlib.import_module(module_name)
|
|
98
|
+
cls.auto_imported_modules.append(module_name)
|
guidellm/utils/cli.py
CHANGED
|
@@ -3,10 +3,31 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
5
|
|
|
6
|
+
__all__ = ["Union", "format_list_arg", "parse_json", "set_if_not_default"]
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
def parse_json(ctx, param, value): # noqa: ARG001
|
|
8
|
-
if value is None:
|
|
10
|
+
if value is None or value == [None]:
|
|
9
11
|
return None
|
|
12
|
+
if isinstance(value, list | tuple):
|
|
13
|
+
return [parse_json(ctx, param, val) for val in value]
|
|
14
|
+
|
|
15
|
+
if "{" not in value and "}" not in value and "=" in value:
|
|
16
|
+
# Treat it as a key=value pair if it doesn't look like JSON.
|
|
17
|
+
result = {}
|
|
18
|
+
for pair in value.split(","):
|
|
19
|
+
if "=" not in pair:
|
|
20
|
+
raise click.BadParameter(
|
|
21
|
+
f"{param.name} must be a valid JSON string or key=value pairs."
|
|
22
|
+
)
|
|
23
|
+
key, val = pair.split("=", 1)
|
|
24
|
+
result[key.strip()] = val.strip()
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
if "{" not in value and "}" not in value:
|
|
28
|
+
# Treat it as a plain string if it doesn't look like JSON.
|
|
29
|
+
return value
|
|
30
|
+
|
|
10
31
|
try:
|
|
11
32
|
return json.loads(value)
|
|
12
33
|
except json.JSONDecodeError as err:
|
|
@@ -26,6 +47,29 @@ def set_if_not_default(ctx: click.Context, **kwargs) -> dict[str, Any]:
|
|
|
26
47
|
return values
|
|
27
48
|
|
|
28
49
|
|
|
50
|
+
def format_list_arg(
|
|
51
|
+
value: Any, default: Any = None, simplify_single: bool = False
|
|
52
|
+
) -> list[Any] | Any:
|
|
53
|
+
"""
|
|
54
|
+
Format a multi-argument value for display.
|
|
55
|
+
|
|
56
|
+
:param value: The value to format, which can be a single value or a list/tuple.
|
|
57
|
+
:param default: The default value to set if the value is non truthy.
|
|
58
|
+
:param simplify_single: If True and the value is a single-item list/tuple,
|
|
59
|
+
return the single item instead of a list.
|
|
60
|
+
:return: Formatted list of values, or single value if simplify_single and applicable
|
|
61
|
+
"""
|
|
62
|
+
if not value:
|
|
63
|
+
return default
|
|
64
|
+
|
|
65
|
+
if isinstance(value, tuple):
|
|
66
|
+
value = list(value)
|
|
67
|
+
elif not isinstance(value, list):
|
|
68
|
+
value = [value]
|
|
69
|
+
|
|
70
|
+
return value if not simplify_single or len(value) != 1 else value[0]
|
|
71
|
+
|
|
72
|
+
|
|
29
73
|
class Union(click.ParamType):
|
|
30
74
|
"""
|
|
31
75
|
A custom click parameter type that allows for multiple types to be accepted.
|
|
@@ -35,7 +79,7 @@ class Union(click.ParamType):
|
|
|
35
79
|
self.types = types
|
|
36
80
|
self.name = "".join(t.name for t in types)
|
|
37
81
|
|
|
38
|
-
def convert(self, value, param, ctx):
|
|
82
|
+
def convert(self, value, param, ctx):
|
|
39
83
|
fails = []
|
|
40
84
|
for t in self.types:
|
|
41
85
|
try:
|