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,1118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inter-process messaging abstractions for distributed scheduler coordination.
|
|
3
|
+
|
|
4
|
+
Provides high-level interfaces for asynchronous message passing between worker
|
|
5
|
+
processes using various transport mechanisms including queues and pipes. Supports
|
|
6
|
+
configurable encoding, serialization, error handling, and flow control with
|
|
7
|
+
buffering and stop event coordination for distributed scheduler operations.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import contextlib
|
|
14
|
+
import multiprocessing
|
|
15
|
+
import queue
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from collections.abc import Callable, Iterable
|
|
20
|
+
from multiprocessing.connection import Connection
|
|
21
|
+
from multiprocessing.context import BaseContext
|
|
22
|
+
from multiprocessing.managers import SyncManager
|
|
23
|
+
from multiprocessing.synchronize import Event as ProcessingEvent
|
|
24
|
+
from threading import Event as ThreadingEvent
|
|
25
|
+
from typing import Any, Generic, Protocol, TypeVar, cast
|
|
26
|
+
|
|
27
|
+
import culsans
|
|
28
|
+
from pydantic import BaseModel
|
|
29
|
+
|
|
30
|
+
from guidellm.utils.encoding import (
|
|
31
|
+
EncodingTypesAlias,
|
|
32
|
+
MessageEncoding,
|
|
33
|
+
SerializationTypesAlias,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"InterProcessMessaging",
|
|
38
|
+
"InterProcessMessagingManagerQueue",
|
|
39
|
+
"InterProcessMessagingPipe",
|
|
40
|
+
"InterProcessMessagingQueue",
|
|
41
|
+
"MessagingStopCallback",
|
|
42
|
+
"ReceiveMessageT",
|
|
43
|
+
"SendMessageT",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
SendMessageT = TypeVar("SendMessageT", bound=Any)
|
|
47
|
+
"""Generic type variable for messages sent through the messaging system"""
|
|
48
|
+
ReceiveMessageT = TypeVar("ReceiveMessageT", bound=Any)
|
|
49
|
+
"""Generic type variable for messages received through the messaging system"""
|
|
50
|
+
|
|
51
|
+
CheckStopCallableT = Callable[[bool, int], bool]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MessagingStopCallback(Protocol):
|
|
55
|
+
"""Protocol for evaluating stop conditions in messaging operations."""
|
|
56
|
+
|
|
57
|
+
def __call__(
|
|
58
|
+
self, messaging: InterProcessMessaging, pending: bool, queue_empty_count: int
|
|
59
|
+
) -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Evaluate whether messaging operations should stop.
|
|
62
|
+
|
|
63
|
+
:param messaging: The messaging instance to evaluate
|
|
64
|
+
:param pending: Whether there are pending operations
|
|
65
|
+
:param queue_empty_count: The number of times in a row the queue has been empty
|
|
66
|
+
:return: True if operations should stop, False otherwise
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class InterProcessMessaging(Generic[SendMessageT, ReceiveMessageT], ABC):
|
|
72
|
+
"""
|
|
73
|
+
Abstract base for inter-process messaging in distributed scheduler coordination.
|
|
74
|
+
|
|
75
|
+
Provides unified interface for asynchronous message passing between scheduler
|
|
76
|
+
components using configurable transport mechanisms, encoding schemes, and
|
|
77
|
+
flow control policies. Manages buffering, serialization, error handling,
|
|
78
|
+
and coordinated shutdown across worker processes for distributed operations.
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
::
|
|
82
|
+
from guidellm.utils.messaging import InterProcessMessagingQueue
|
|
83
|
+
|
|
84
|
+
messaging = InterProcessMessagingQueue(
|
|
85
|
+
serialization="pickle",
|
|
86
|
+
max_pending_size=100
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
await messaging.start()
|
|
90
|
+
await messaging.put(request_data)
|
|
91
|
+
response = await messaging.get(timeout=5.0)
|
|
92
|
+
await messaging.stop()
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
STOP_REQUIRED_QUEUE_EMPTY_COUNT: int = 3
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
mp_context: BaseContext | None = None,
|
|
100
|
+
serialization: SerializationTypesAlias = "dict",
|
|
101
|
+
encoding: EncodingTypesAlias | list[EncodingTypesAlias] = None,
|
|
102
|
+
max_pending_size: int | None = None,
|
|
103
|
+
max_buffer_send_size: int | None = None,
|
|
104
|
+
max_done_size: int | None = None,
|
|
105
|
+
max_buffer_receive_size: int | None = None,
|
|
106
|
+
poll_interval: float = 0.1,
|
|
107
|
+
worker_index: int | None = None,
|
|
108
|
+
):
|
|
109
|
+
"""
|
|
110
|
+
Initialize inter-process messaging coordinator.
|
|
111
|
+
|
|
112
|
+
:param serialization: Message serialization method for transport encoding
|
|
113
|
+
:param encoding: Optional encoding scheme for serialized message data
|
|
114
|
+
:param max_pending_size: Maximum items in send queue before blocking
|
|
115
|
+
:param max_buffer_send_size: Maximum items in buffer send queue
|
|
116
|
+
:param max_done_size: Maximum items in done queue before blocking
|
|
117
|
+
:param max_buffer_receive_size: Maximum items in buffer receive queue
|
|
118
|
+
:param poll_interval: Time interval for checking queue status and events
|
|
119
|
+
:param worker_index: Index identifying this worker in the process group
|
|
120
|
+
"""
|
|
121
|
+
self.worker_index: int | None = worker_index
|
|
122
|
+
self.mp_context = mp_context or multiprocessing.get_context()
|
|
123
|
+
self.serialization = serialization
|
|
124
|
+
self.encoding = encoding
|
|
125
|
+
self.max_pending_size = max_pending_size
|
|
126
|
+
self.max_buffer_send_size = max_buffer_send_size
|
|
127
|
+
self.max_done_size = max_done_size
|
|
128
|
+
self.max_buffer_receive_size = max_buffer_receive_size
|
|
129
|
+
self.poll_interval = poll_interval
|
|
130
|
+
|
|
131
|
+
self.send_stopped_event: ThreadingEvent | ProcessingEvent | None = None
|
|
132
|
+
self.receive_stopped_event: ThreadingEvent | ProcessingEvent | None = None
|
|
133
|
+
self.shutdown_event: ThreadingEvent | None = None
|
|
134
|
+
self.buffer_send_queue: culsans.Queue[SendMessageT] | None = None
|
|
135
|
+
self.buffer_receive_queue: culsans.Queue[ReceiveMessageT] | None = None
|
|
136
|
+
self.send_task: asyncio.Task | None = None
|
|
137
|
+
self.receive_task: asyncio.Task | None = None
|
|
138
|
+
self.running = False
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
def create_worker_copy(
|
|
142
|
+
self, worker_index: int, **kwargs
|
|
143
|
+
) -> InterProcessMessaging[ReceiveMessageT, SendMessageT]:
|
|
144
|
+
"""
|
|
145
|
+
Create worker-specific copy for distributed process coordination.
|
|
146
|
+
|
|
147
|
+
:param worker_index: Index of the worker process for message routing
|
|
148
|
+
:return: Configured messaging instance for the specified worker
|
|
149
|
+
"""
|
|
150
|
+
...
|
|
151
|
+
|
|
152
|
+
@abstractmethod
|
|
153
|
+
def create_send_messages_threads(
|
|
154
|
+
self,
|
|
155
|
+
send_items: Iterable[Any] | None,
|
|
156
|
+
message_encoding: MessageEncoding,
|
|
157
|
+
check_stop: CheckStopCallableT,
|
|
158
|
+
) -> list[tuple[Callable, tuple[Any, ...]]]:
|
|
159
|
+
"""
|
|
160
|
+
Create send message processing threads for transport implementation.
|
|
161
|
+
|
|
162
|
+
:param send_items: Optional collection of items to send during processing
|
|
163
|
+
:param message_encoding: Message encoding configuration for serialization
|
|
164
|
+
:param check_stop: Callable for evaluating stop conditions during processing
|
|
165
|
+
:return: List of thread callables with their arguments for execution
|
|
166
|
+
"""
|
|
167
|
+
...
|
|
168
|
+
|
|
169
|
+
@abstractmethod
|
|
170
|
+
def create_receive_messages_threads(
|
|
171
|
+
self,
|
|
172
|
+
receive_callback: Callable[[Any], Any] | None,
|
|
173
|
+
message_encoding: MessageEncoding,
|
|
174
|
+
check_stop: CheckStopCallableT,
|
|
175
|
+
) -> list[tuple[Callable, tuple[Any, ...]]]:
|
|
176
|
+
"""
|
|
177
|
+
Create receive message processing threads for transport implementation.
|
|
178
|
+
|
|
179
|
+
:param receive_callback: Optional callback for processing received messages
|
|
180
|
+
:param message_encoding: Message encoding configuration for deserialization
|
|
181
|
+
:param check_stop: Callable for evaluating stop conditions during processing
|
|
182
|
+
:return: List of thread callables with their arguments for execution
|
|
183
|
+
"""
|
|
184
|
+
...
|
|
185
|
+
|
|
186
|
+
async def start(
|
|
187
|
+
self,
|
|
188
|
+
send_items: Iterable[Any] | None = None,
|
|
189
|
+
receive_callback: Callable[[Any], Any] | None = None,
|
|
190
|
+
send_stop_criteria: (
|
|
191
|
+
list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
|
|
192
|
+
) = None,
|
|
193
|
+
send_stopped_event: ThreadingEvent | ProcessingEvent | None = None,
|
|
194
|
+
receive_stop_criteria: (
|
|
195
|
+
list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
|
|
196
|
+
) = None,
|
|
197
|
+
receive_stopped_event: ThreadingEvent | ProcessingEvent | None = None,
|
|
198
|
+
pydantic_models: list[type[BaseModel]] | None = None,
|
|
199
|
+
):
|
|
200
|
+
"""
|
|
201
|
+
Start asynchronous message processing tasks with buffering.
|
|
202
|
+
|
|
203
|
+
:param send_items: Optional collection of items to send during processing
|
|
204
|
+
:param receive_callback: Optional callback for processing received messages
|
|
205
|
+
:param send_stop_criteria: Events and callables that trigger send task shutdown
|
|
206
|
+
:param send_stopped_event: Event set when send task has fully stopped
|
|
207
|
+
:param receive_stop_criteria: Events and callables that trigger receive shutdown
|
|
208
|
+
:param receive_stopped_event: Event set when receive task has fully stopped
|
|
209
|
+
:param pydantic_models: Optional list of Pydantic models for serialization
|
|
210
|
+
"""
|
|
211
|
+
self.running = True
|
|
212
|
+
self.send_stopped_event = send_stopped_event or ThreadingEvent()
|
|
213
|
+
self.receive_stopped_event = receive_stopped_event or ThreadingEvent()
|
|
214
|
+
self.shutdown_event = ThreadingEvent()
|
|
215
|
+
self.buffer_send_queue = culsans.Queue[SendMessageT](
|
|
216
|
+
maxsize=self.max_buffer_send_size or 0
|
|
217
|
+
)
|
|
218
|
+
self.buffer_receive_queue = culsans.Queue[ReceiveMessageT](
|
|
219
|
+
maxsize=self.max_buffer_receive_size or 0
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
message_encoding: MessageEncoding = MessageEncoding(
|
|
223
|
+
serialization=self.serialization,
|
|
224
|
+
encoding=self.encoding,
|
|
225
|
+
pydantic_models=pydantic_models,
|
|
226
|
+
)
|
|
227
|
+
send_stop_criteria = send_stop_criteria or []
|
|
228
|
+
receive_stop_events = receive_stop_criteria or []
|
|
229
|
+
|
|
230
|
+
self.send_task = asyncio.create_task(
|
|
231
|
+
self.send_messages_coroutine(
|
|
232
|
+
send_items=send_items,
|
|
233
|
+
message_encoding=message_encoding,
|
|
234
|
+
send_stop_criteria=send_stop_criteria,
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
self.receive_task = asyncio.create_task(
|
|
238
|
+
self.receive_messages_coroutine(
|
|
239
|
+
receive_callback=receive_callback,
|
|
240
|
+
message_encoding=message_encoding,
|
|
241
|
+
receive_stop_criteria=receive_stop_events,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
async def stop(self):
|
|
246
|
+
"""
|
|
247
|
+
Stop message processing tasks and clean up resources.
|
|
248
|
+
"""
|
|
249
|
+
if self.shutdown_event is not None:
|
|
250
|
+
self.shutdown_event.set()
|
|
251
|
+
else:
|
|
252
|
+
raise RuntimeError(
|
|
253
|
+
"shutdown_event is not set; was start() not called or "
|
|
254
|
+
"is this a redundant stop() call?"
|
|
255
|
+
)
|
|
256
|
+
tasks = [self.send_task, self.receive_task]
|
|
257
|
+
tasks_to_run: list[asyncio.Task[Any]] = [
|
|
258
|
+
task for task in tasks if task is not None
|
|
259
|
+
]
|
|
260
|
+
if len(tasks_to_run) > 0:
|
|
261
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
262
|
+
await asyncio.gather(*tasks_to_run, return_exceptions=True)
|
|
263
|
+
self.send_task = None
|
|
264
|
+
self.receive_task = None
|
|
265
|
+
if self.worker_index is None:
|
|
266
|
+
if self.buffer_send_queue is not None:
|
|
267
|
+
self.buffer_send_queue.clear()
|
|
268
|
+
await self.buffer_send_queue.aclose()
|
|
269
|
+
if self.buffer_receive_queue is not None:
|
|
270
|
+
self.buffer_receive_queue.clear()
|
|
271
|
+
await self.buffer_receive_queue.aclose()
|
|
272
|
+
self.buffer_send_queue = None
|
|
273
|
+
self.buffer_receive_queue = None
|
|
274
|
+
self.send_stopped_event = None
|
|
275
|
+
self.receive_stopped_event = None
|
|
276
|
+
self.shutdown_event = None
|
|
277
|
+
self.running = False
|
|
278
|
+
|
|
279
|
+
async def send_messages_coroutine(
|
|
280
|
+
self,
|
|
281
|
+
send_items: Iterable[Any] | None,
|
|
282
|
+
message_encoding: MessageEncoding,
|
|
283
|
+
send_stop_criteria: (
|
|
284
|
+
list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
|
|
285
|
+
),
|
|
286
|
+
):
|
|
287
|
+
"""
|
|
288
|
+
Execute send message processing with encoding and stop condition handling.
|
|
289
|
+
|
|
290
|
+
:param send_items: Optional collection of items to send during processing
|
|
291
|
+
:param message_encoding: Message encoding configuration for serialization
|
|
292
|
+
:param send_stop_criteria: Events and callables that trigger send task shutdown
|
|
293
|
+
"""
|
|
294
|
+
canceled_event = ThreadingEvent()
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
await asyncio.gather(
|
|
298
|
+
*[
|
|
299
|
+
asyncio.to_thread(thread, *args)
|
|
300
|
+
for (thread, args) in self.create_send_messages_threads(
|
|
301
|
+
send_items=send_items,
|
|
302
|
+
message_encoding=message_encoding,
|
|
303
|
+
check_stop=self._create_check_stop_callable(
|
|
304
|
+
send_stop_criteria, canceled_event
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
]
|
|
308
|
+
)
|
|
309
|
+
except asyncio.CancelledError:
|
|
310
|
+
canceled_event.set()
|
|
311
|
+
raise
|
|
312
|
+
finally:
|
|
313
|
+
if self.send_stopped_event is not None:
|
|
314
|
+
self.send_stopped_event.set()
|
|
315
|
+
|
|
316
|
+
async def receive_messages_coroutine(
|
|
317
|
+
self,
|
|
318
|
+
receive_callback: Callable[[Any], Any] | None,
|
|
319
|
+
message_encoding: MessageEncoding,
|
|
320
|
+
receive_stop_criteria: (
|
|
321
|
+
list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
|
|
322
|
+
),
|
|
323
|
+
):
|
|
324
|
+
"""
|
|
325
|
+
Execute receive message processing with decoding and callback handling.
|
|
326
|
+
|
|
327
|
+
:param receive_callback: Optional callback for processing received messages
|
|
328
|
+
:param message_encoding: Message encoding configuration for deserialization
|
|
329
|
+
:param receive_stop_criteria: Events and callables that trigger receive shutdown
|
|
330
|
+
"""
|
|
331
|
+
canceled_event = ThreadingEvent()
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
await asyncio.gather(
|
|
335
|
+
*[
|
|
336
|
+
asyncio.to_thread(thread, *args)
|
|
337
|
+
for thread, args in self.create_receive_messages_threads(
|
|
338
|
+
receive_callback=receive_callback,
|
|
339
|
+
message_encoding=message_encoding,
|
|
340
|
+
check_stop=self._create_check_stop_callable(
|
|
341
|
+
receive_stop_criteria, canceled_event
|
|
342
|
+
),
|
|
343
|
+
)
|
|
344
|
+
]
|
|
345
|
+
)
|
|
346
|
+
except asyncio.CancelledError:
|
|
347
|
+
canceled_event.set()
|
|
348
|
+
raise
|
|
349
|
+
finally:
|
|
350
|
+
if self.receive_stopped_event is not None:
|
|
351
|
+
self.receive_stopped_event.set()
|
|
352
|
+
|
|
353
|
+
async def get(self, timeout: float | None = None) -> ReceiveMessageT:
|
|
354
|
+
"""
|
|
355
|
+
Retrieve a message from receive buffer with optional timeout.
|
|
356
|
+
|
|
357
|
+
:param timeout: Maximum time to wait for a message
|
|
358
|
+
:return: Decoded message from the receive buffer
|
|
359
|
+
"""
|
|
360
|
+
if self.buffer_receive_queue is None:
|
|
361
|
+
raise RuntimeError(
|
|
362
|
+
"buffer receive queue is None; check start()/stop() calls"
|
|
363
|
+
)
|
|
364
|
+
return await asyncio.wait_for(
|
|
365
|
+
self.buffer_receive_queue.async_get(), timeout=timeout
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
def get_sync(self, timeout: float | None = None) -> ReceiveMessageT:
|
|
369
|
+
"""
|
|
370
|
+
Retrieve message from receive buffer synchronously with optional timeout.
|
|
371
|
+
|
|
372
|
+
:param timeout: Maximum time to wait for a message, if <=0 uses get_nowait
|
|
373
|
+
:return: Decoded message from the receive buffer
|
|
374
|
+
"""
|
|
375
|
+
if self.buffer_receive_queue is None:
|
|
376
|
+
raise RuntimeError(
|
|
377
|
+
"buffer receive queue is None; check start()/stop() calls"
|
|
378
|
+
)
|
|
379
|
+
if timeout is not None and timeout <= 0:
|
|
380
|
+
return self.buffer_receive_queue.get_nowait()
|
|
381
|
+
else:
|
|
382
|
+
return self.buffer_receive_queue.sync_get(timeout=timeout)
|
|
383
|
+
|
|
384
|
+
async def put(self, item: SendMessageT, timeout: float | None = None):
|
|
385
|
+
"""
|
|
386
|
+
Add message to send buffer with optional timeout.
|
|
387
|
+
|
|
388
|
+
:param item: Message item to add to the send buffer
|
|
389
|
+
:param timeout: Maximum time to wait for buffer space
|
|
390
|
+
"""
|
|
391
|
+
if self.buffer_send_queue is None:
|
|
392
|
+
raise RuntimeError(
|
|
393
|
+
"buffer receive queue is None; check start()/stop() calls"
|
|
394
|
+
)
|
|
395
|
+
await asyncio.wait_for(self.buffer_send_queue.async_put(item), timeout=timeout)
|
|
396
|
+
|
|
397
|
+
def put_sync(self, item: SendMessageT, timeout: float | None = None):
|
|
398
|
+
"""
|
|
399
|
+
Add message to send buffer synchronously with optional timeout.
|
|
400
|
+
|
|
401
|
+
:param item: Message item to add to the send buffer
|
|
402
|
+
:param timeout: Maximum time to wait for buffer space, if <=0 uses put_nowait
|
|
403
|
+
"""
|
|
404
|
+
if self.buffer_send_queue is None:
|
|
405
|
+
raise RuntimeError(
|
|
406
|
+
"buffer receive queue is None; check start()/stop() calls"
|
|
407
|
+
)
|
|
408
|
+
if timeout is not None and timeout <= 0:
|
|
409
|
+
self.buffer_send_queue.put_nowait(item)
|
|
410
|
+
else:
|
|
411
|
+
self.buffer_send_queue.sync_put(item, timeout=timeout)
|
|
412
|
+
|
|
413
|
+
def _create_check_stop_callable(
|
|
414
|
+
self,
|
|
415
|
+
stop_criteria: (
|
|
416
|
+
list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
|
|
417
|
+
),
|
|
418
|
+
canceled_event: ThreadingEvent,
|
|
419
|
+
):
|
|
420
|
+
stop_events = tuple(
|
|
421
|
+
item
|
|
422
|
+
for item in stop_criteria or []
|
|
423
|
+
if isinstance(item, ThreadingEvent | ProcessingEvent)
|
|
424
|
+
)
|
|
425
|
+
stop_callbacks = tuple(item for item in stop_criteria or [] if callable(item))
|
|
426
|
+
|
|
427
|
+
def check_stop(pending: bool, queue_empty_count: int) -> bool:
|
|
428
|
+
if canceled_event.is_set():
|
|
429
|
+
return True
|
|
430
|
+
|
|
431
|
+
if stop_callbacks and any(
|
|
432
|
+
cb(self, pending, queue_empty_count) for cb in stop_callbacks
|
|
433
|
+
):
|
|
434
|
+
return True
|
|
435
|
+
|
|
436
|
+
if self.shutdown_event is None:
|
|
437
|
+
return True
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
not pending
|
|
441
|
+
and queue_empty_count >= self.STOP_REQUIRED_QUEUE_EMPTY_COUNT
|
|
442
|
+
and (
|
|
443
|
+
self.shutdown_event.is_set()
|
|
444
|
+
or any(event.is_set() for event in stop_events)
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
return check_stop
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class InterProcessMessagingQueue(InterProcessMessaging[SendMessageT, ReceiveMessageT]):
|
|
452
|
+
"""
|
|
453
|
+
Queue-based inter-process messaging for distributed scheduler coordination.
|
|
454
|
+
|
|
455
|
+
Provides message passing using multiprocessing.Queue objects for communication
|
|
456
|
+
between scheduler workers and main process. Handles message encoding, buffering,
|
|
457
|
+
flow control, and coordinated shutdown with configurable queue behavior and
|
|
458
|
+
error handling policies for distributed operations.
|
|
459
|
+
|
|
460
|
+
Example:
|
|
461
|
+
::
|
|
462
|
+
from guidellm.utils.messaging import InterProcessMessagingQueue
|
|
463
|
+
|
|
464
|
+
messaging = InterProcessMessagingQueue(
|
|
465
|
+
serialization="pickle",
|
|
466
|
+
max_pending_size=100
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
# Create worker copy for distributed processing
|
|
470
|
+
worker_messaging = messaging.create_worker_copy(worker_index=0)
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
pending_queue: multiprocessing.Queue | queue.Queue[Any] | None
|
|
474
|
+
done_queue: multiprocessing.Queue | queue.Queue[Any] | None
|
|
475
|
+
|
|
476
|
+
def __init__(
|
|
477
|
+
self,
|
|
478
|
+
mp_context: BaseContext | None = None,
|
|
479
|
+
serialization: SerializationTypesAlias = "dict",
|
|
480
|
+
encoding: EncodingTypesAlias | list[EncodingTypesAlias] = None,
|
|
481
|
+
max_pending_size: int | None = None,
|
|
482
|
+
max_buffer_send_size: int | None = None,
|
|
483
|
+
max_done_size: int | None = None,
|
|
484
|
+
max_buffer_receive_size: int | None = None,
|
|
485
|
+
poll_interval: float = 0.1,
|
|
486
|
+
worker_index: int | None = None,
|
|
487
|
+
pending_queue: multiprocessing.Queue | queue.Queue[Any] | None = None,
|
|
488
|
+
done_queue: multiprocessing.Queue | queue.Queue[Any] | None = None,
|
|
489
|
+
):
|
|
490
|
+
"""
|
|
491
|
+
Initialize queue-based messaging for inter-process communication.
|
|
492
|
+
|
|
493
|
+
:param serialization: Message serialization method for transport encoding
|
|
494
|
+
:param encoding: Optional encoding scheme for serialized message data
|
|
495
|
+
:param max_pending_size: Maximum items in send queue before blocking
|
|
496
|
+
:param max_buffer_send_size: Maximum items in buffer send queue
|
|
497
|
+
:param max_done_size: Maximum items in receive queue before blocking
|
|
498
|
+
:param max_buffer_receive_size: Maximum items in buffer receive queue
|
|
499
|
+
:param poll_interval: Time interval for checking queue status and events
|
|
500
|
+
:param worker_index: Index identifying this worker in the process group
|
|
501
|
+
:param pending_queue: Multiprocessing queue for sending messages
|
|
502
|
+
:param done_queue: Multiprocessing queue for receiving completed messages
|
|
503
|
+
:param context: Multiprocessing context for creating queues
|
|
504
|
+
"""
|
|
505
|
+
super().__init__(
|
|
506
|
+
mp_context=mp_context,
|
|
507
|
+
serialization=serialization,
|
|
508
|
+
encoding=encoding,
|
|
509
|
+
max_pending_size=max_pending_size,
|
|
510
|
+
max_buffer_send_size=max_buffer_send_size,
|
|
511
|
+
max_done_size=max_done_size,
|
|
512
|
+
max_buffer_receive_size=max_buffer_receive_size,
|
|
513
|
+
poll_interval=poll_interval,
|
|
514
|
+
worker_index=worker_index,
|
|
515
|
+
)
|
|
516
|
+
self.pending_queue = pending_queue or self.mp_context.Queue(
|
|
517
|
+
maxsize=max_pending_size or 0
|
|
518
|
+
)
|
|
519
|
+
self.done_queue = done_queue or self.mp_context.Queue(
|
|
520
|
+
maxsize=max_done_size or 0
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
def create_worker_copy(
|
|
524
|
+
self, worker_index: int, **kwargs
|
|
525
|
+
) -> InterProcessMessagingQueue[ReceiveMessageT, SendMessageT]:
|
|
526
|
+
"""
|
|
527
|
+
Create worker-specific copy for distributed queue-based coordination.
|
|
528
|
+
|
|
529
|
+
:param worker_index: Index of the worker process for message routing
|
|
530
|
+
:return: Configured queue messaging instance for the specified worker
|
|
531
|
+
"""
|
|
532
|
+
copy_args = {
|
|
533
|
+
"mp_context": self.mp_context,
|
|
534
|
+
"serialization": self.serialization,
|
|
535
|
+
"encoding": self.encoding,
|
|
536
|
+
"max_pending_size": self.max_pending_size,
|
|
537
|
+
"max_buffer_send_size": self.max_buffer_send_size,
|
|
538
|
+
"max_done_size": self.max_done_size,
|
|
539
|
+
"max_buffer_receive_size": self.max_buffer_receive_size,
|
|
540
|
+
"poll_interval": self.poll_interval,
|
|
541
|
+
"worker_index": worker_index,
|
|
542
|
+
"pending_queue": self.pending_queue,
|
|
543
|
+
"done_queue": self.done_queue,
|
|
544
|
+
}
|
|
545
|
+
final_args = {**copy_args, **kwargs}
|
|
546
|
+
|
|
547
|
+
return InterProcessMessagingQueue[ReceiveMessageT, SendMessageT](**final_args)
|
|
548
|
+
|
|
549
|
+
async def stop(self):
|
|
550
|
+
"""
|
|
551
|
+
Stop the messaging system and wait for all tasks to complete.
|
|
552
|
+
"""
|
|
553
|
+
await super().stop()
|
|
554
|
+
if self.worker_index is None:
|
|
555
|
+
# only main process should close the queues
|
|
556
|
+
if self.pending_queue is None:
|
|
557
|
+
raise RuntimeError("pending_queue is None; was stop() already called?")
|
|
558
|
+
with contextlib.suppress(queue.Empty):
|
|
559
|
+
while True:
|
|
560
|
+
self.pending_queue.get_nowait()
|
|
561
|
+
if hasattr(self.pending_queue, "close"):
|
|
562
|
+
self.pending_queue.close()
|
|
563
|
+
|
|
564
|
+
if self.done_queue is None:
|
|
565
|
+
raise RuntimeError("done_queue is None; was stop() already called?")
|
|
566
|
+
with contextlib.suppress(queue.Empty):
|
|
567
|
+
while True:
|
|
568
|
+
self.done_queue.get_nowait()
|
|
569
|
+
if hasattr(self.done_queue, "close"):
|
|
570
|
+
self.done_queue.close()
|
|
571
|
+
|
|
572
|
+
self.pending_queue = None
|
|
573
|
+
self.done_queue = None
|
|
574
|
+
|
|
575
|
+
def create_send_messages_threads(
|
|
576
|
+
self,
|
|
577
|
+
send_items: Iterable[Any] | None,
|
|
578
|
+
message_encoding: MessageEncoding,
|
|
579
|
+
check_stop: CheckStopCallableT,
|
|
580
|
+
) -> list[tuple[Callable, tuple[Any, ...]]]:
|
|
581
|
+
"""
|
|
582
|
+
Create send message processing threads for queue-based transport.
|
|
583
|
+
|
|
584
|
+
:param send_items: Optional collection of items to send during processing
|
|
585
|
+
:param message_encoding: Message encoding configuration for serialization
|
|
586
|
+
:param check_stop: Callable for evaluating stop conditions during processing
|
|
587
|
+
:return: List of thread callables with their arguments for execution
|
|
588
|
+
"""
|
|
589
|
+
return [
|
|
590
|
+
(
|
|
591
|
+
self._send_messages_task_thread,
|
|
592
|
+
(send_items, message_encoding, check_stop),
|
|
593
|
+
)
|
|
594
|
+
]
|
|
595
|
+
|
|
596
|
+
def create_receive_messages_threads(
|
|
597
|
+
self,
|
|
598
|
+
receive_callback: Callable[[Any], Any] | None,
|
|
599
|
+
message_encoding: MessageEncoding,
|
|
600
|
+
check_stop: CheckStopCallableT,
|
|
601
|
+
) -> list[tuple[Callable, tuple[Any, ...]]]:
|
|
602
|
+
"""
|
|
603
|
+
Create receive message processing threads for queue-based transport.
|
|
604
|
+
|
|
605
|
+
:param receive_callback: Optional callback for processing received messages
|
|
606
|
+
:param message_encoding: Message encoding configuration for deserialization
|
|
607
|
+
:param check_stop: Callable for evaluating stop conditions during processing
|
|
608
|
+
:return: List of thread callables with their arguments for execution
|
|
609
|
+
"""
|
|
610
|
+
return [
|
|
611
|
+
(
|
|
612
|
+
self._receive_messages_task_thread,
|
|
613
|
+
(receive_callback, message_encoding, check_stop),
|
|
614
|
+
)
|
|
615
|
+
]
|
|
616
|
+
|
|
617
|
+
def _send_messages_task_thread( # noqa: C901, PLR0912
|
|
618
|
+
self,
|
|
619
|
+
send_items: Iterable[Any] | None,
|
|
620
|
+
message_encoding: MessageEncoding,
|
|
621
|
+
check_stop: CheckStopCallableT,
|
|
622
|
+
):
|
|
623
|
+
send_items_iter = iter(send_items) if send_items is not None else None
|
|
624
|
+
pending_item = None
|
|
625
|
+
queue_empty_count = 0
|
|
626
|
+
|
|
627
|
+
while not check_stop(pending_item is not None, queue_empty_count):
|
|
628
|
+
if pending_item is None:
|
|
629
|
+
try:
|
|
630
|
+
if send_items_iter is not None:
|
|
631
|
+
item = next(send_items_iter)
|
|
632
|
+
else:
|
|
633
|
+
if self.buffer_send_queue is None:
|
|
634
|
+
raise RuntimeError(
|
|
635
|
+
"buffer_send_queue is None; was stop() already called?"
|
|
636
|
+
)
|
|
637
|
+
item = self.buffer_send_queue.sync_get(
|
|
638
|
+
timeout=self.poll_interval
|
|
639
|
+
)
|
|
640
|
+
pending_item = message_encoding.encode(item)
|
|
641
|
+
queue_empty_count = 0
|
|
642
|
+
except (culsans.QueueEmpty, queue.Empty, StopIteration):
|
|
643
|
+
queue_empty_count += 1
|
|
644
|
+
|
|
645
|
+
if pending_item is not None:
|
|
646
|
+
try:
|
|
647
|
+
if self.worker_index is None:
|
|
648
|
+
# Main publisher
|
|
649
|
+
if self.pending_queue is None:
|
|
650
|
+
raise RuntimeError(
|
|
651
|
+
"pending_queue is None; was stop() already called?"
|
|
652
|
+
)
|
|
653
|
+
self.pending_queue.put(pending_item, timeout=self.poll_interval)
|
|
654
|
+
else:
|
|
655
|
+
# Worker
|
|
656
|
+
if self.done_queue is None:
|
|
657
|
+
raise RuntimeError(
|
|
658
|
+
"done_queue is None; was stop() already called?"
|
|
659
|
+
)
|
|
660
|
+
self.done_queue.put(pending_item, timeout=self.poll_interval)
|
|
661
|
+
if send_items_iter is None:
|
|
662
|
+
if self.buffer_send_queue is None:
|
|
663
|
+
raise RuntimeError(
|
|
664
|
+
"buffer_send_queue is None; was stop() already called?"
|
|
665
|
+
)
|
|
666
|
+
self.buffer_send_queue.task_done()
|
|
667
|
+
pending_item = None
|
|
668
|
+
except (culsans.QueueFull, queue.Full):
|
|
669
|
+
pass
|
|
670
|
+
|
|
671
|
+
time.sleep(0) # Yield to other threads
|
|
672
|
+
|
|
673
|
+
def _receive_messages_task_thread( # noqa: C901
|
|
674
|
+
self,
|
|
675
|
+
receive_callback: Callable[[Any], Any] | None,
|
|
676
|
+
message_encoding: MessageEncoding,
|
|
677
|
+
check_stop: CheckStopCallableT,
|
|
678
|
+
):
|
|
679
|
+
pending_item = None
|
|
680
|
+
received_item = None
|
|
681
|
+
queue_empty_count = 0
|
|
682
|
+
|
|
683
|
+
while not check_stop(pending_item is not None, queue_empty_count):
|
|
684
|
+
if pending_item is None:
|
|
685
|
+
try:
|
|
686
|
+
if self.worker_index is None:
|
|
687
|
+
# Main publisher
|
|
688
|
+
if self.done_queue is None:
|
|
689
|
+
raise RuntimeError(
|
|
690
|
+
"done_queue is None; check start()/stop() calls"
|
|
691
|
+
)
|
|
692
|
+
item = self.done_queue.get(timeout=self.poll_interval)
|
|
693
|
+
else:
|
|
694
|
+
# Worker
|
|
695
|
+
if self.pending_queue is None:
|
|
696
|
+
raise RuntimeError(
|
|
697
|
+
"pending_queue is None; check start()/stop() calls"
|
|
698
|
+
)
|
|
699
|
+
item = self.pending_queue.get(timeout=self.poll_interval)
|
|
700
|
+
pending_item = message_encoding.decode(item)
|
|
701
|
+
queue_empty_count = 0
|
|
702
|
+
except (culsans.QueueEmpty, queue.Empty):
|
|
703
|
+
queue_empty_count += 1
|
|
704
|
+
|
|
705
|
+
if pending_item is not None or received_item is not None:
|
|
706
|
+
try:
|
|
707
|
+
if received_item is None:
|
|
708
|
+
received_item = (
|
|
709
|
+
pending_item
|
|
710
|
+
if not receive_callback
|
|
711
|
+
else receive_callback(pending_item)
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
if self.buffer_receive_queue is None:
|
|
715
|
+
raise RuntimeError(
|
|
716
|
+
"buffer_receive_queue is None; check start()/stop() calls"
|
|
717
|
+
)
|
|
718
|
+
self.buffer_receive_queue.sync_put(
|
|
719
|
+
cast("ReceiveMessageT", received_item)
|
|
720
|
+
)
|
|
721
|
+
pending_item = None
|
|
722
|
+
received_item = None
|
|
723
|
+
except (culsans.QueueFull, queue.Full):
|
|
724
|
+
pass
|
|
725
|
+
|
|
726
|
+
time.sleep(0) # Yield to other threads
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
class InterProcessMessagingManagerQueue(
|
|
730
|
+
InterProcessMessagingQueue[SendMessageT, ReceiveMessageT]
|
|
731
|
+
):
|
|
732
|
+
"""
|
|
733
|
+
Manager-based queue messaging for inter-process scheduler coordination.
|
|
734
|
+
|
|
735
|
+
Extends queue-based messaging with multiprocessing.Manager support for
|
|
736
|
+
shared state coordination across worker processes. Provides managed queues
|
|
737
|
+
for reliable message passing in distributed scheduler environments with
|
|
738
|
+
enhanced process synchronization and resource management capabilities.
|
|
739
|
+
|
|
740
|
+
Example:
|
|
741
|
+
::
|
|
742
|
+
import multiprocessing
|
|
743
|
+
from guidellm.utils.messaging import InterProcessMessagingManagerQueue
|
|
744
|
+
|
|
745
|
+
manager = multiprocessing.Manager()
|
|
746
|
+
messaging = InterProcessMessagingManagerQueue(
|
|
747
|
+
manager=manager,
|
|
748
|
+
serialization="pickle"
|
|
749
|
+
)
|
|
750
|
+
"""
|
|
751
|
+
|
|
752
|
+
def __init__(
|
|
753
|
+
self,
|
|
754
|
+
manager: SyncManager,
|
|
755
|
+
mp_context: BaseContext | None = None,
|
|
756
|
+
serialization: SerializationTypesAlias = "dict",
|
|
757
|
+
encoding: EncodingTypesAlias | list[EncodingTypesAlias] = None,
|
|
758
|
+
max_pending_size: int | None = None,
|
|
759
|
+
max_buffer_send_size: int | None = None,
|
|
760
|
+
max_done_size: int | None = None,
|
|
761
|
+
max_buffer_receive_size: int | None = None,
|
|
762
|
+
poll_interval: float = 0.1,
|
|
763
|
+
worker_index: int | None = None,
|
|
764
|
+
pending_queue: multiprocessing.Queue | None = None,
|
|
765
|
+
done_queue: multiprocessing.Queue | None = None,
|
|
766
|
+
):
|
|
767
|
+
"""
|
|
768
|
+
Initialize manager-based queue messaging for inter-process communication.
|
|
769
|
+
|
|
770
|
+
:param manager: Multiprocessing manager for shared queue creation
|
|
771
|
+
:param serialization: Message serialization method for transport encoding
|
|
772
|
+
:param encoding: Optional encoding scheme for serialized message data
|
|
773
|
+
:param max_pending_size: Maximum items in send queue before blocking
|
|
774
|
+
:param max_buffer_send_size: Maximum items in buffer send queue
|
|
775
|
+
:param max_done_size: Maximum items in receive queue before blocking
|
|
776
|
+
:param max_buffer_receive_size: Maximum items in buffer receive queue
|
|
777
|
+
:param poll_interval: Time interval for checking queue status and events
|
|
778
|
+
:param worker_index: Index identifying this worker in the process group
|
|
779
|
+
:param pending_queue: Managed multiprocessing queue for sending messages
|
|
780
|
+
:param done_queue: Managed multiprocessing queue for receiving completed
|
|
781
|
+
messages
|
|
782
|
+
"""
|
|
783
|
+
super().__init__(
|
|
784
|
+
mp_context=mp_context,
|
|
785
|
+
serialization=serialization,
|
|
786
|
+
encoding=encoding,
|
|
787
|
+
max_pending_size=max_pending_size,
|
|
788
|
+
max_buffer_send_size=max_buffer_send_size,
|
|
789
|
+
max_done_size=max_done_size,
|
|
790
|
+
max_buffer_receive_size=max_buffer_receive_size,
|
|
791
|
+
poll_interval=poll_interval,
|
|
792
|
+
worker_index=worker_index,
|
|
793
|
+
pending_queue=pending_queue or manager.Queue(maxsize=max_pending_size or 0),
|
|
794
|
+
done_queue=done_queue or manager.Queue(maxsize=max_done_size or 0),
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
def create_worker_copy(
|
|
798
|
+
self, worker_index: int, **kwargs
|
|
799
|
+
) -> InterProcessMessagingManagerQueue[ReceiveMessageT, SendMessageT]:
|
|
800
|
+
"""
|
|
801
|
+
Create worker-specific copy for managed queue-based coordination.
|
|
802
|
+
|
|
803
|
+
:param worker_index: Index of the worker process for message routing
|
|
804
|
+
:return: Configured manager queue messaging instance for the specified worker
|
|
805
|
+
"""
|
|
806
|
+
copy_args = {
|
|
807
|
+
"manager": None,
|
|
808
|
+
"mp_context": self.mp_context,
|
|
809
|
+
"serialization": self.serialization,
|
|
810
|
+
"encoding": self.encoding,
|
|
811
|
+
"max_pending_size": self.max_pending_size,
|
|
812
|
+
"max_buffer_send_size": self.max_buffer_send_size,
|
|
813
|
+
"max_done_size": self.max_done_size,
|
|
814
|
+
"max_buffer_receive_size": self.max_buffer_receive_size,
|
|
815
|
+
"poll_interval": self.poll_interval,
|
|
816
|
+
"worker_index": worker_index,
|
|
817
|
+
"pending_queue": self.pending_queue,
|
|
818
|
+
"done_queue": self.done_queue,
|
|
819
|
+
}
|
|
820
|
+
final_args = {**copy_args, **kwargs}
|
|
821
|
+
|
|
822
|
+
return InterProcessMessagingManagerQueue(**final_args)
|
|
823
|
+
|
|
824
|
+
async def stop(self):
|
|
825
|
+
"""
|
|
826
|
+
Stop the messaging system and wait for all tasks to complete.
|
|
827
|
+
"""
|
|
828
|
+
await InterProcessMessaging.stop(self)
|
|
829
|
+
self.pending_queue = None
|
|
830
|
+
self.done_queue = None
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
class InterProcessMessagingPipe(InterProcessMessaging[SendMessageT, ReceiveMessageT]):
|
|
834
|
+
"""
|
|
835
|
+
Pipe-based inter-process messaging for distributed scheduler coordination.
|
|
836
|
+
|
|
837
|
+
Provides message passing using multiprocessing.Pipe objects for direct
|
|
838
|
+
communication between scheduler workers and main process. Offers lower
|
|
839
|
+
latency than queue-based messaging with duplex communication channels
|
|
840
|
+
for high-performance distributed operations.
|
|
841
|
+
|
|
842
|
+
Example:
|
|
843
|
+
::
|
|
844
|
+
from guidellm.utils.messaging import InterProcessMessagingPipe
|
|
845
|
+
|
|
846
|
+
messaging = InterProcessMessagingPipe(
|
|
847
|
+
num_workers=4,
|
|
848
|
+
serialization="pickle",
|
|
849
|
+
poll_interval=0.05
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
# Create worker copy for specific worker process
|
|
853
|
+
worker_messaging = messaging.create_worker_copy(worker_index=0)
|
|
854
|
+
"""
|
|
855
|
+
|
|
856
|
+
def __init__(
|
|
857
|
+
self,
|
|
858
|
+
num_workers: int,
|
|
859
|
+
mp_context: BaseContext | None = None,
|
|
860
|
+
serialization: SerializationTypesAlias = "dict",
|
|
861
|
+
encoding: EncodingTypesAlias | list[EncodingTypesAlias] = None,
|
|
862
|
+
max_pending_size: int | None = None,
|
|
863
|
+
max_buffer_send_size: int | None = None,
|
|
864
|
+
max_done_size: int | None = None,
|
|
865
|
+
max_buffer_receive_size: int | None = None,
|
|
866
|
+
poll_interval: float = 0.1,
|
|
867
|
+
worker_index: int | None = None,
|
|
868
|
+
pipe: tuple[Connection, Connection] | None = None,
|
|
869
|
+
):
|
|
870
|
+
"""
|
|
871
|
+
Initialize pipe-based messaging for inter-process communication.
|
|
872
|
+
|
|
873
|
+
:param num_workers: Number of worker processes requiring pipe connections
|
|
874
|
+
:param serialization: Message serialization method for transport encoding
|
|
875
|
+
:param encoding: Optional encoding scheme for serialized message data
|
|
876
|
+
:param max_pending_size: Maximum items in send queue before blocking
|
|
877
|
+
:param max_buffer_send_size: Maximum items in buffer send queue
|
|
878
|
+
:param max_done_size: Maximum items in receive queue before blocking
|
|
879
|
+
:param max_buffer_receive_size: Maximum items in buffer receive queue
|
|
880
|
+
:param poll_interval: Time interval for checking queue status and events
|
|
881
|
+
:param worker_index: Index identifying this worker in the process group
|
|
882
|
+
:param pipe: Existing pipe connection for worker-specific instances
|
|
883
|
+
"""
|
|
884
|
+
super().__init__(
|
|
885
|
+
mp_context=mp_context,
|
|
886
|
+
serialization=serialization,
|
|
887
|
+
encoding=encoding,
|
|
888
|
+
max_pending_size=max_pending_size,
|
|
889
|
+
max_buffer_send_size=max_buffer_send_size,
|
|
890
|
+
max_done_size=max_done_size,
|
|
891
|
+
max_buffer_receive_size=max_buffer_receive_size,
|
|
892
|
+
poll_interval=poll_interval,
|
|
893
|
+
worker_index=worker_index,
|
|
894
|
+
)
|
|
895
|
+
self.num_workers = num_workers
|
|
896
|
+
|
|
897
|
+
self.pipes: list[tuple[Connection, Connection]]
|
|
898
|
+
if pipe is None:
|
|
899
|
+
self.pipes = [self.mp_context.Pipe(duplex=True) for _ in range(num_workers)]
|
|
900
|
+
else:
|
|
901
|
+
self.pipes = [pipe]
|
|
902
|
+
|
|
903
|
+
def create_worker_copy(
|
|
904
|
+
self, worker_index: int, **kwargs
|
|
905
|
+
) -> InterProcessMessagingPipe[ReceiveMessageT, SendMessageT]:
|
|
906
|
+
"""
|
|
907
|
+
Create worker-specific copy for pipe-based coordination.
|
|
908
|
+
|
|
909
|
+
:param worker_index: Index of the worker process for pipe routing
|
|
910
|
+
:return: Configured pipe messaging instance for the specified worker
|
|
911
|
+
"""
|
|
912
|
+
copy_args = {
|
|
913
|
+
"num_workers": self.num_workers,
|
|
914
|
+
"mp_context": self.mp_context,
|
|
915
|
+
"serialization": self.serialization,
|
|
916
|
+
"encoding": self.encoding,
|
|
917
|
+
"max_pending_size": self.max_pending_size,
|
|
918
|
+
"max_buffer_send_size": self.max_buffer_send_size,
|
|
919
|
+
"max_done_size": self.max_done_size,
|
|
920
|
+
"max_buffer_receive_size": self.max_buffer_receive_size,
|
|
921
|
+
"poll_interval": self.poll_interval,
|
|
922
|
+
"worker_index": worker_index,
|
|
923
|
+
"pipe": self.pipes[worker_index],
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
final_args = {**copy_args, **kwargs}
|
|
927
|
+
|
|
928
|
+
return InterProcessMessagingPipe(**final_args)
|
|
929
|
+
|
|
930
|
+
async def stop(self):
|
|
931
|
+
"""
|
|
932
|
+
Stop the messaging system and wait for all tasks to complete.
|
|
933
|
+
"""
|
|
934
|
+
await super().stop()
|
|
935
|
+
if self.worker_index is None:
|
|
936
|
+
# Only main process should close the pipes
|
|
937
|
+
for main_con, worker_con in self.pipes:
|
|
938
|
+
main_con.close()
|
|
939
|
+
worker_con.close()
|
|
940
|
+
|
|
941
|
+
def create_send_messages_threads(
|
|
942
|
+
self,
|
|
943
|
+
send_items: Iterable[Any] | None,
|
|
944
|
+
message_encoding: MessageEncoding,
|
|
945
|
+
check_stop: CheckStopCallableT,
|
|
946
|
+
) -> list[tuple[Callable, tuple[Any, ...]]]:
|
|
947
|
+
"""
|
|
948
|
+
Create send message processing threads for pipe-based transport.
|
|
949
|
+
|
|
950
|
+
:param send_items: Optional collection of items to send during processing
|
|
951
|
+
:param message_encoding: Message encoding configuration for serialization
|
|
952
|
+
:param check_stop: Callable for evaluating stop conditions during processing
|
|
953
|
+
:return: List of thread callables with their arguments for execution
|
|
954
|
+
"""
|
|
955
|
+
if self.worker_index is None:
|
|
956
|
+
# Create a separate task for each worker's pipe
|
|
957
|
+
return [
|
|
958
|
+
(
|
|
959
|
+
self._send_messages_task_thread,
|
|
960
|
+
(self.pipes[index], send_items, message_encoding, check_stop),
|
|
961
|
+
)
|
|
962
|
+
for index in range(self.num_workers)
|
|
963
|
+
]
|
|
964
|
+
else:
|
|
965
|
+
return [
|
|
966
|
+
(
|
|
967
|
+
self._send_messages_task_thread,
|
|
968
|
+
(self.pipes[0], send_items, message_encoding, check_stop),
|
|
969
|
+
)
|
|
970
|
+
]
|
|
971
|
+
|
|
972
|
+
def create_receive_messages_threads(
|
|
973
|
+
self,
|
|
974
|
+
receive_callback: Callable[[Any], Any] | None,
|
|
975
|
+
message_encoding: MessageEncoding,
|
|
976
|
+
check_stop: CheckStopCallableT,
|
|
977
|
+
) -> list[tuple[Callable, tuple[Any, ...]]]:
|
|
978
|
+
"""
|
|
979
|
+
Create receive message processing threads for pipe-based transport.
|
|
980
|
+
|
|
981
|
+
:param receive_callback: Optional callback for processing received messages
|
|
982
|
+
:param message_encoding: Message encoding configuration for deserialization
|
|
983
|
+
:param check_stop: Callable for evaluating stop conditions during processing
|
|
984
|
+
:return: List of thread callables with their arguments for execution
|
|
985
|
+
"""
|
|
986
|
+
if self.worker_index is None:
|
|
987
|
+
# Create a separate task for each worker's pipe
|
|
988
|
+
return [
|
|
989
|
+
(
|
|
990
|
+
self._receive_messages_task_thread,
|
|
991
|
+
(self.pipes[index], receive_callback, message_encoding, check_stop),
|
|
992
|
+
)
|
|
993
|
+
for index in range(self.num_workers)
|
|
994
|
+
]
|
|
995
|
+
else:
|
|
996
|
+
return [
|
|
997
|
+
(
|
|
998
|
+
self._receive_messages_task_thread,
|
|
999
|
+
(self.pipes[0], receive_callback, message_encoding, check_stop),
|
|
1000
|
+
)
|
|
1001
|
+
]
|
|
1002
|
+
|
|
1003
|
+
def _send_messages_task_thread( # noqa: C901, PLR0912, PLR0915
|
|
1004
|
+
self,
|
|
1005
|
+
pipe: tuple[Connection, Connection],
|
|
1006
|
+
send_items: Iterable[Any] | None,
|
|
1007
|
+
message_encoding: MessageEncoding,
|
|
1008
|
+
check_stop: CheckStopCallableT,
|
|
1009
|
+
):
|
|
1010
|
+
local_stop = ThreadingEvent()
|
|
1011
|
+
send_connection: Connection = pipe[0] if self.worker_index is None else pipe[1]
|
|
1012
|
+
send_items_iter = iter(send_items) if send_items is not None else None
|
|
1013
|
+
pending_item = None
|
|
1014
|
+
queue_empty_count = 0
|
|
1015
|
+
pipe_item = None
|
|
1016
|
+
pipe_lock = threading.Lock()
|
|
1017
|
+
|
|
1018
|
+
def _background_pipe_recv():
|
|
1019
|
+
nonlocal pipe_item
|
|
1020
|
+
|
|
1021
|
+
while not local_stop.is_set():
|
|
1022
|
+
try:
|
|
1023
|
+
with pipe_lock:
|
|
1024
|
+
pending = pipe_item
|
|
1025
|
+
pipe_item = None
|
|
1026
|
+
|
|
1027
|
+
if pending is not None:
|
|
1028
|
+
send_connection.send(pending)
|
|
1029
|
+
except (EOFError, ConnectionResetError):
|
|
1030
|
+
break
|
|
1031
|
+
|
|
1032
|
+
if send_items_iter is None:
|
|
1033
|
+
threading.Thread(target=_background_pipe_recv, daemon=True).start()
|
|
1034
|
+
|
|
1035
|
+
try:
|
|
1036
|
+
while not check_stop(pending_item is not None, queue_empty_count):
|
|
1037
|
+
if pending_item is None:
|
|
1038
|
+
try:
|
|
1039
|
+
if send_items_iter is not None:
|
|
1040
|
+
item = next(send_items_iter)
|
|
1041
|
+
else:
|
|
1042
|
+
if self.buffer_send_queue is None:
|
|
1043
|
+
raise RuntimeError(
|
|
1044
|
+
"buffer_send_queue is None; check start()/stop() calls" # noqa: E501
|
|
1045
|
+
)
|
|
1046
|
+
item = self.buffer_send_queue.sync_get(
|
|
1047
|
+
timeout=self.poll_interval
|
|
1048
|
+
)
|
|
1049
|
+
pending_item = message_encoding.encode(item)
|
|
1050
|
+
queue_empty_count = 0
|
|
1051
|
+
except (culsans.QueueEmpty, queue.Empty, StopIteration):
|
|
1052
|
+
queue_empty_count += 1
|
|
1053
|
+
|
|
1054
|
+
if pending_item is not None:
|
|
1055
|
+
try:
|
|
1056
|
+
with pipe_lock:
|
|
1057
|
+
if pipe_item is not None:
|
|
1058
|
+
time.sleep(self.poll_interval / 100)
|
|
1059
|
+
raise queue.Full
|
|
1060
|
+
else:
|
|
1061
|
+
pipe_item = pending_item
|
|
1062
|
+
if send_items_iter is None:
|
|
1063
|
+
if self.buffer_send_queue is None:
|
|
1064
|
+
raise RuntimeError(
|
|
1065
|
+
"buffer_send_queue is None; check start()/stop() calls" # noqa: E501
|
|
1066
|
+
)
|
|
1067
|
+
self.buffer_send_queue.task_done()
|
|
1068
|
+
pending_item = None
|
|
1069
|
+
except (culsans.QueueFull, queue.Full):
|
|
1070
|
+
pass
|
|
1071
|
+
finally:
|
|
1072
|
+
local_stop.set()
|
|
1073
|
+
|
|
1074
|
+
def _receive_messages_task_thread( # noqa: C901
|
|
1075
|
+
self,
|
|
1076
|
+
pipe: tuple[Connection, Connection],
|
|
1077
|
+
receive_callback: Callable[[Any], Any] | None,
|
|
1078
|
+
message_encoding: MessageEncoding,
|
|
1079
|
+
check_stop: CheckStopCallableT,
|
|
1080
|
+
):
|
|
1081
|
+
receive_connection: Connection = (
|
|
1082
|
+
pipe[0] if self.worker_index is not None else pipe[1]
|
|
1083
|
+
)
|
|
1084
|
+
pending_item = None
|
|
1085
|
+
received_item = None
|
|
1086
|
+
queue_empty_count = 0
|
|
1087
|
+
|
|
1088
|
+
while not check_stop(pending_item is not None, queue_empty_count):
|
|
1089
|
+
if pending_item is None:
|
|
1090
|
+
try:
|
|
1091
|
+
if receive_connection.poll(self.poll_interval):
|
|
1092
|
+
item = receive_connection.recv()
|
|
1093
|
+
pending_item = message_encoding.decode(item)
|
|
1094
|
+
else:
|
|
1095
|
+
raise queue.Empty
|
|
1096
|
+
queue_empty_count = 0
|
|
1097
|
+
except (culsans.QueueEmpty, queue.Empty):
|
|
1098
|
+
queue_empty_count += 1
|
|
1099
|
+
|
|
1100
|
+
if pending_item is not None or received_item is not None:
|
|
1101
|
+
try:
|
|
1102
|
+
if received_item is None:
|
|
1103
|
+
received_item = (
|
|
1104
|
+
pending_item
|
|
1105
|
+
if not receive_callback
|
|
1106
|
+
else receive_callback(pending_item)
|
|
1107
|
+
)
|
|
1108
|
+
if self.buffer_receive_queue is None:
|
|
1109
|
+
raise RuntimeError(
|
|
1110
|
+
"buffer receive queue is None; check start()/stop() calls"
|
|
1111
|
+
)
|
|
1112
|
+
self.buffer_receive_queue.sync_put(
|
|
1113
|
+
cast("ReceiveMessageT", received_item)
|
|
1114
|
+
)
|
|
1115
|
+
pending_item = None
|
|
1116
|
+
received_item = None
|
|
1117
|
+
except (culsans.QueueFull, queue.Full):
|
|
1118
|
+
pass
|