xoscar 0.7.0__cp312-cp312-macosx_10_13_x86_64.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 xoscar might be problematic. Click here for more details.

Files changed (89) hide show
  1. xoscar/__init__.py +60 -0
  2. xoscar/_utils.cpython-312-darwin.so +0 -0
  3. xoscar/_utils.pxd +36 -0
  4. xoscar/_utils.pyx +246 -0
  5. xoscar/_version.py +693 -0
  6. xoscar/aio/__init__.py +16 -0
  7. xoscar/aio/base.py +86 -0
  8. xoscar/aio/file.py +59 -0
  9. xoscar/aio/lru.py +228 -0
  10. xoscar/aio/parallelism.py +39 -0
  11. xoscar/api.py +493 -0
  12. xoscar/backend.py +67 -0
  13. xoscar/backends/__init__.py +14 -0
  14. xoscar/backends/allocate_strategy.py +160 -0
  15. xoscar/backends/communication/__init__.py +30 -0
  16. xoscar/backends/communication/base.py +315 -0
  17. xoscar/backends/communication/core.py +69 -0
  18. xoscar/backends/communication/dummy.py +253 -0
  19. xoscar/backends/communication/errors.py +20 -0
  20. xoscar/backends/communication/socket.py +444 -0
  21. xoscar/backends/communication/ucx.py +538 -0
  22. xoscar/backends/communication/utils.py +97 -0
  23. xoscar/backends/config.py +157 -0
  24. xoscar/backends/context.py +437 -0
  25. xoscar/backends/core.py +304 -0
  26. xoscar/backends/indigen/__init__.py +16 -0
  27. xoscar/backends/indigen/__main__.py +19 -0
  28. xoscar/backends/indigen/backend.py +51 -0
  29. xoscar/backends/indigen/driver.py +26 -0
  30. xoscar/backends/indigen/fate_sharing.py +221 -0
  31. xoscar/backends/indigen/pool.py +450 -0
  32. xoscar/backends/indigen/shared_memory.py +548 -0
  33. xoscar/backends/message.cpython-312-darwin.so +0 -0
  34. xoscar/backends/message.pyi +255 -0
  35. xoscar/backends/message.pyx +646 -0
  36. xoscar/backends/pool.py +1625 -0
  37. xoscar/backends/router.py +285 -0
  38. xoscar/backends/test/__init__.py +16 -0
  39. xoscar/backends/test/backend.py +38 -0
  40. xoscar/backends/test/pool.py +197 -0
  41. xoscar/batch.py +256 -0
  42. xoscar/collective/__init__.py +27 -0
  43. xoscar/collective/common.py +102 -0
  44. xoscar/collective/core.py +737 -0
  45. xoscar/collective/process_group.py +687 -0
  46. xoscar/collective/utils.py +41 -0
  47. xoscar/collective/xoscar_pygloo.cpython-312-darwin.so +0 -0
  48. xoscar/collective/xoscar_pygloo.pyi +239 -0
  49. xoscar/constants.py +23 -0
  50. xoscar/context.cpython-312-darwin.so +0 -0
  51. xoscar/context.pxd +21 -0
  52. xoscar/context.pyx +368 -0
  53. xoscar/core.cpython-312-darwin.so +0 -0
  54. xoscar/core.pxd +51 -0
  55. xoscar/core.pyx +664 -0
  56. xoscar/debug.py +188 -0
  57. xoscar/driver.py +42 -0
  58. xoscar/errors.py +63 -0
  59. xoscar/libcpp.pxd +31 -0
  60. xoscar/metrics/__init__.py +21 -0
  61. xoscar/metrics/api.py +288 -0
  62. xoscar/metrics/backends/__init__.py +13 -0
  63. xoscar/metrics/backends/console/__init__.py +13 -0
  64. xoscar/metrics/backends/console/console_metric.py +82 -0
  65. xoscar/metrics/backends/metric.py +149 -0
  66. xoscar/metrics/backends/prometheus/__init__.py +13 -0
  67. xoscar/metrics/backends/prometheus/prometheus_metric.py +70 -0
  68. xoscar/nvutils.py +717 -0
  69. xoscar/profiling.py +260 -0
  70. xoscar/serialization/__init__.py +20 -0
  71. xoscar/serialization/aio.py +142 -0
  72. xoscar/serialization/core.cpython-312-darwin.so +0 -0
  73. xoscar/serialization/core.pxd +28 -0
  74. xoscar/serialization/core.pyi +57 -0
  75. xoscar/serialization/core.pyx +944 -0
  76. xoscar/serialization/cuda.py +111 -0
  77. xoscar/serialization/exception.py +48 -0
  78. xoscar/serialization/mlx.py +63 -0
  79. xoscar/serialization/numpy.py +82 -0
  80. xoscar/serialization/pyfury.py +37 -0
  81. xoscar/serialization/scipy.py +72 -0
  82. xoscar/utils.py +517 -0
  83. xoscar/virtualenv/__init__.py +34 -0
  84. xoscar/virtualenv/core.py +52 -0
  85. xoscar/virtualenv/uv.py +91 -0
  86. xoscar-0.7.0.dist-info/METADATA +228 -0
  87. xoscar-0.7.0.dist-info/RECORD +89 -0
  88. xoscar-0.7.0.dist-info/WHEEL +6 -0
  89. xoscar-0.7.0.dist-info/top_level.txt +2 -0
xoscar/api.py ADDED
@@ -0,0 +1,493 @@
1
+ # Copyright 2022-2023 XProbe Inc.
2
+ # derived from copyright 1999-2021 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import functools
20
+ import inspect
21
+ import logging
22
+ import threading
23
+ import uuid
24
+ from collections import defaultdict
25
+ from numbers import Number
26
+ from typing import (
27
+ TYPE_CHECKING,
28
+ Any,
29
+ Dict,
30
+ Generic,
31
+ List,
32
+ Optional,
33
+ Tuple,
34
+ Type,
35
+ TypeVar,
36
+ Union,
37
+ )
38
+ from urllib.parse import urlparse
39
+
40
+ from .aio import AioFileObject
41
+ from .backend import get_backend
42
+ from .context import get_context
43
+ from .core import ActorRef, BufferRef, FileObjectRef, _Actor, _StatelessActor
44
+
45
+ if TYPE_CHECKING:
46
+ from .backends.config import ActorPoolConfig
47
+ from .backends.pool import MainActorPoolType
48
+
49
+ logger = logging.getLogger(__name__)
50
+
51
+
52
+ async def create_actor(
53
+ actor_cls: Type, *args, uid=None, address=None, **kwargs
54
+ ) -> ActorRef:
55
+ # TODO: explain default values.
56
+ """
57
+ Create an actor.
58
+
59
+ Parameters
60
+ ----------
61
+ actor_cls : Actor
62
+ Actor class.
63
+ args : tuple
64
+ Positional arguments for ``actor_cls.__init__``.
65
+ uid : identifier, default=None
66
+ Actor identifier.
67
+ address : str, default=None
68
+ Address to locate the actor.
69
+ kwargs : dict
70
+ Keyword arguments for ``actor_cls.__init__``.
71
+
72
+ Returns
73
+ -------
74
+ ActorRef
75
+ """
76
+
77
+ ctx = get_context()
78
+ return await ctx.create_actor(actor_cls, *args, uid=uid, address=address, **kwargs)
79
+
80
+
81
+ async def has_actor(actor_ref: ActorRef) -> bool:
82
+ """
83
+ Check if the given actor exists.
84
+
85
+ Parameters
86
+ ----------
87
+ actor_ref : ActorRef
88
+ Reference to an actor.
89
+
90
+ Returns
91
+ -------
92
+ bool
93
+ """
94
+ ctx = get_context()
95
+ return await ctx.has_actor(actor_ref)
96
+
97
+
98
+ async def destroy_actor(actor_ref: ActorRef):
99
+ """
100
+ Destroy an actor by its reference.
101
+
102
+ Parameters
103
+ ----------
104
+ actor_ref : ActorRef
105
+ Reference to an actor.
106
+
107
+ Returns
108
+ -------
109
+ bool
110
+ """
111
+ ctx = get_context()
112
+ return await ctx.destroy_actor(actor_ref)
113
+
114
+
115
+ async def actor_ref(*args, **kwargs) -> ActorRef:
116
+ """
117
+ Create a reference to an actor.
118
+
119
+ Returns
120
+ -------
121
+ ActorRef
122
+ """
123
+ # TODO: refine the argument list for better user experience.
124
+ ctx = get_context()
125
+ return await ctx.actor_ref(*args, **kwargs)
126
+
127
+
128
+ async def kill_actor(actor_ref: ActorRef):
129
+ # TODO: explain the meaning of 'kill'
130
+ """
131
+ Forcefully kill an actor.
132
+
133
+ It's important to note that this operation is potentially
134
+ dangerous as it may result in the termination of other
135
+ associated actors. Only proceed if you understand the
136
+ potential impact on associated actors and can handle any
137
+ resulting consequences.
138
+
139
+ Parameters
140
+ ----------
141
+ actor_ref : ActorRef
142
+ Reference to an actor.
143
+
144
+ Returns
145
+ -------
146
+ bool
147
+ """
148
+ ctx = get_context()
149
+ return await ctx.kill_actor(actor_ref)
150
+
151
+
152
+ async def create_actor_pool(
153
+ address: str, n_process: int | None = None, **kwargs
154
+ ) -> "MainActorPoolType":
155
+ # TODO: explain default values.
156
+ """
157
+ Create an actor pool.
158
+
159
+ Parameters
160
+ ----------
161
+ address: str
162
+ Address of the actor pool.
163
+ n_process: Optional[int], default=None
164
+ Number of processes.
165
+ kwargs : dict
166
+ Other keyword arguments for the actor pool.
167
+
168
+ Returns
169
+ -------
170
+ MainActorPoolType
171
+ """
172
+ if address is None:
173
+ raise ValueError("address has to be provided")
174
+ if "://" not in address:
175
+ scheme = None
176
+ else:
177
+ scheme = urlparse(address).scheme or None
178
+
179
+ return await get_backend(scheme).create_actor_pool(
180
+ address, n_process=n_process, **kwargs
181
+ )
182
+
183
+
184
+ def buffer_ref(address: str, buffer: Any) -> BufferRef:
185
+ """
186
+ Init buffer ref according address and buffer.
187
+
188
+ Parameters
189
+ ----------
190
+ address
191
+ The address of the buffer.
192
+ buffer
193
+ CPU / GPU buffer. Need to support for slicing and retrieving the length.
194
+
195
+ Returns
196
+ ----------
197
+ BufferRef obj.
198
+ """
199
+ ctx = get_context()
200
+ return ctx.buffer_ref(address, buffer)
201
+
202
+
203
+ def file_object_ref(address: str, fileobj: AioFileObject) -> FileObjectRef:
204
+ """
205
+ Init file object ref according to address and aio file obj.
206
+
207
+ Parameters
208
+ ----------
209
+ address
210
+ The address of the file obj.
211
+ fileobj
212
+ Aio file object.
213
+
214
+ Returns
215
+ ----------
216
+ FileObjectRef obj.
217
+ """
218
+ ctx = get_context()
219
+ return ctx.file_object_ref(address, fileobj)
220
+
221
+
222
+ async def copy_to(
223
+ local_buffers_or_fileobjs: list,
224
+ remote_refs: List[Union[BufferRef, FileObjectRef]],
225
+ block_size: Optional[int] = None,
226
+ ):
227
+ """
228
+ Copy data from local buffers to remote buffers or copy local file objects to remote file objects.
229
+
230
+ Parameters
231
+ ----------
232
+ local_buffers_or_fileobjs
233
+ Local buffers or file objects.
234
+ remote_refs
235
+ Remote buffer refs or file object refs.
236
+ block_size
237
+ Transfer block size when non-ucx
238
+ """
239
+ ctx = get_context()
240
+ return await ctx.copy_to(local_buffers_or_fileobjs, remote_refs, block_size)
241
+
242
+
243
+ async def wait_actor_pool_recovered(address: str, main_pool_address: str | None = None):
244
+ """
245
+ Wait until the specified actor pool has recovered from failure.
246
+
247
+ Parameters
248
+ ----------
249
+ address: str
250
+ Address of the actor pool.
251
+ main_pool_address: Optional[str], default=None
252
+ Address of corresponding main actor pool.
253
+
254
+ Returns
255
+ -------
256
+ """
257
+ ctx = get_context()
258
+ return await ctx.wait_actor_pool_recovered(address, main_pool_address)
259
+
260
+
261
+ async def get_pool_config(address: str) -> "ActorPoolConfig":
262
+ """
263
+ Get the configuration of specified actor pool.
264
+
265
+ Parameters
266
+ ----------
267
+ address: str
268
+ Address of the actor pool.
269
+
270
+ Returns
271
+ -------
272
+ ActorPoolConfig
273
+ """
274
+ ctx = get_context()
275
+ return await ctx.get_pool_config(address)
276
+
277
+
278
+ def setup_cluster(address_to_resources: Dict[str, Dict[str, Number]]):
279
+ scheme_to_address_resources: defaultdict[str | None, dict] = defaultdict(dict)
280
+ for address, resources in address_to_resources.items():
281
+ if address is None:
282
+ raise ValueError("address has to be provided")
283
+ if "://" not in address:
284
+ scheme = None
285
+ else:
286
+ scheme = urlparse(address).scheme or None
287
+
288
+ scheme_to_address_resources[scheme][address] = resources
289
+ for scheme, address_resources in scheme_to_address_resources.items():
290
+ get_backend(scheme).get_driver_cls().setup_cluster(address_resources)
291
+
292
+
293
+ T = TypeVar("T")
294
+
295
+
296
+ class IteratorWrapper(Generic[T]):
297
+ def __init__(self, uid: str, actor_addr: str, actor_uid: str):
298
+ self._uid = uid
299
+ self._actor_addr = actor_addr
300
+ self._actor_uid = actor_uid
301
+ self._actor_ref = None
302
+ self._gc_destroy = True
303
+
304
+ async def destroy(self):
305
+ if self._actor_ref is None:
306
+ self._actor_ref = await actor_ref(
307
+ address=self._actor_addr, uid=self._actor_uid
308
+ )
309
+ assert self._actor_ref is not None
310
+ return await self._actor_ref.__xoscar_destroy_generator__(self._uid)
311
+
312
+ def __del__(self):
313
+ # It's not a good idea to spawn a new thread and join in __del__,
314
+ # but currently it's the only way to GC the generator.
315
+ # TODO(codingl2k1): This __del__ may hangs if the program is exiting.
316
+ if self._gc_destroy:
317
+ thread = threading.Thread(
318
+ target=asyncio.run, args=(self.destroy(),), daemon=True
319
+ )
320
+ thread.start()
321
+ thread.join()
322
+
323
+ def __aiter__(self):
324
+ return self
325
+
326
+ def __getstate__(self):
327
+ # Transfer gc destroy during serialization.
328
+ state = self.__dict__.copy()
329
+ state["_gc_destroy"] = True
330
+ self._gc_destroy = False
331
+ return state
332
+
333
+ async def __anext__(self) -> T:
334
+ if self._actor_ref is None:
335
+ self._actor_ref = await actor_ref(
336
+ address=self._actor_addr, uid=self._actor_uid
337
+ )
338
+ try:
339
+ assert self._actor_ref is not None
340
+ return await self._actor_ref.__xoscar_next__(self._uid)
341
+ except Exception as e:
342
+ if "StopIteration" in str(e):
343
+ raise StopAsyncIteration
344
+ else:
345
+ raise
346
+
347
+
348
+ class AsyncActorMixin:
349
+ @classmethod
350
+ def default_uid(cls):
351
+ return cls.__name__
352
+
353
+ def __new__(cls, *args, **kwargs):
354
+ try:
355
+ return _actor_implementation[cls](*args, **kwargs)
356
+ except KeyError:
357
+ return super().__new__(cls, *args, **kwargs)
358
+
359
+ def __init__(self, *args, **kwargs) -> None:
360
+ super().__init__()
361
+ self._generators: Dict[str, IteratorWrapper] = {}
362
+
363
+ async def __post_create__(self):
364
+ """
365
+ Method called after actor creation
366
+ """
367
+ return await super().__post_create__()
368
+
369
+ async def __pre_destroy__(self):
370
+ """
371
+ Method called before actor destroy
372
+ """
373
+ return await super().__pre_destroy__()
374
+
375
+ async def __on_receive__(self, message: Tuple[Any]):
376
+ """
377
+ Handle message from other actors and dispatch them to user methods
378
+
379
+ Parameters
380
+ ----------
381
+ message : tuple
382
+ Message shall be (method_name,) + args + (kwargs,)
383
+ """
384
+ return await super().__on_receive__(message) # type: ignore
385
+
386
+ async def __xoscar_next__(self, generator_uid: str) -> Any:
387
+ """
388
+ Iter the next of generator.
389
+
390
+ Parameters
391
+ ----------
392
+ generator_uid: str
393
+ The uid of generator
394
+
395
+ Returns
396
+ -------
397
+ The next value of generator
398
+ """
399
+
400
+ def _wrapper(_gen):
401
+ try:
402
+ return next(_gen)
403
+ except StopIteration:
404
+ return stop
405
+
406
+ async def _async_wrapper(_gen):
407
+ try:
408
+ # anext is only available for Python >= 3.10
409
+ return await _gen.__anext__() # noqa: F821
410
+ except StopAsyncIteration:
411
+ return stop
412
+
413
+ if gen := self._generators.get(generator_uid):
414
+ stop = object()
415
+ try:
416
+ if inspect.isgenerator(gen):
417
+ r = await asyncio.to_thread(_wrapper, gen)
418
+ elif inspect.isasyncgen(gen):
419
+ r = await asyncio.create_task(_async_wrapper(gen))
420
+ else:
421
+ raise Exception(
422
+ f"The generator {generator_uid} should be a generator or an async generator, "
423
+ f"but a {type(gen)} is got."
424
+ )
425
+ except Exception as e:
426
+ logger.exception(
427
+ f"Destroy generator {generator_uid} due to an error encountered."
428
+ )
429
+ await self.__xoscar_destroy_generator__(generator_uid)
430
+ del gen # Avoid exception hold generator reference.
431
+ raise e
432
+ if r is stop:
433
+ await self.__xoscar_destroy_generator__(generator_uid)
434
+ del gen # Avoid exception hold generator reference.
435
+ raise Exception("StopIteration")
436
+ else:
437
+ return r
438
+ else:
439
+ raise RuntimeError(f"No iterator with id: {generator_uid}")
440
+
441
+ async def __xoscar_destroy_generator__(self, generator_uid: str):
442
+ """
443
+ Destroy the generator.
444
+
445
+ Parameters
446
+ ----------
447
+ generator_uid: str
448
+ The uid of generator
449
+ """
450
+ logger.debug("Destroy generator: %s", generator_uid)
451
+ self._generators.pop(generator_uid, None)
452
+
453
+
454
+ def generator(func):
455
+ need_to_thread = not asyncio.iscoroutinefunction(func)
456
+
457
+ @functools.wraps(func)
458
+ async def _wrapper(self, *args, **kwargs):
459
+ if need_to_thread:
460
+ r = await asyncio.to_thread(func, self, *args, **kwargs)
461
+ else:
462
+ r = await func(self, *args, **kwargs)
463
+ if inspect.isgenerator(r) or inspect.isasyncgen(r):
464
+ gen_uid = uuid.uuid1().hex
465
+ logger.debug("Create generator: %s", gen_uid)
466
+ self._generators[gen_uid] = r
467
+ return IteratorWrapper(gen_uid, self.address, self.uid)
468
+ else:
469
+ return r
470
+
471
+ return _wrapper
472
+
473
+
474
+ class Actor(AsyncActorMixin, _Actor):
475
+ pass
476
+
477
+
478
+ class StatelessActor(AsyncActorMixin, _StatelessActor):
479
+ pass
480
+
481
+
482
+ _actor_implementation: Dict[Type[Actor], Type[Actor]] = dict()
483
+
484
+
485
+ def register_actor_implementation(actor_cls: Type[Actor], impl_cls: Type[Actor]):
486
+ _actor_implementation[actor_cls] = impl_cls
487
+
488
+
489
+ def unregister_actor_implementation(actor_cls: Type[Actor]):
490
+ try:
491
+ del _actor_implementation[actor_cls]
492
+ except KeyError:
493
+ pass
xoscar/backend.py ADDED
@@ -0,0 +1,67 @@
1
+ # Copyright 2022-2023 XProbe Inc.
2
+ # derived from copyright 1999-2021 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ from abc import ABC, abstractmethod
19
+ from typing import Dict, Type
20
+
21
+ from .context import register_backend_context
22
+ from .driver import register_backend_driver
23
+
24
+ __all__ = ["BaseActorBackend", "register_backend", "get_backend"]
25
+
26
+
27
+ class BaseActorBackend(ABC):
28
+ @staticmethod
29
+ @abstractmethod
30
+ def name():
31
+ pass
32
+
33
+ @staticmethod
34
+ @abstractmethod
35
+ def get_context_cls():
36
+ pass
37
+
38
+ @classmethod
39
+ async def create_actor_pool(
40
+ cls, address: str, n_process: int | None = None, **kwargs
41
+ ):
42
+ pass
43
+
44
+ @staticmethod
45
+ @abstractmethod
46
+ def get_driver_cls():
47
+ pass
48
+
49
+
50
+ _scheme_to_backend_cls: Dict[str, Type[BaseActorBackend]] = dict()
51
+
52
+
53
+ def register_backend(backend_cls: Type[BaseActorBackend]):
54
+ name = backend_cls.name()
55
+ if isinstance(name, (list, tuple)):
56
+ names = name
57
+ else:
58
+ names = [name]
59
+ for name in names:
60
+ _scheme_to_backend_cls[name] = backend_cls
61
+ register_backend_context(name, backend_cls.get_context_cls())
62
+ register_backend_driver(name, backend_cls.get_driver_cls())
63
+ return backend_cls
64
+
65
+
66
+ def get_backend(name):
67
+ return _scheme_to_backend_cls[name]
@@ -0,0 +1,14 @@
1
+ # Copyright 2022-2023 XProbe Inc.
2
+ # derived from copyright 1999-2021 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.