xoscar 0.9.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.
Files changed (94) hide show
  1. xoscar/__init__.py +61 -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 +527 -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 +352 -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 +515 -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 +1630 -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 +233 -0
  41. xoscar/batch.py +256 -0
  42. xoscar/collective/__init__.py +27 -0
  43. xoscar/collective/backend/__init__.py +13 -0
  44. xoscar/collective/backend/nccl_backend.py +160 -0
  45. xoscar/collective/common.py +102 -0
  46. xoscar/collective/core.py +737 -0
  47. xoscar/collective/process_group.py +687 -0
  48. xoscar/collective/utils.py +41 -0
  49. xoscar/collective/xoscar_pygloo.cpython-312-darwin.so +0 -0
  50. xoscar/collective/xoscar_pygloo.pyi +239 -0
  51. xoscar/constants.py +23 -0
  52. xoscar/context.cpython-312-darwin.so +0 -0
  53. xoscar/context.pxd +21 -0
  54. xoscar/context.pyx +368 -0
  55. xoscar/core.cpython-312-darwin.so +0 -0
  56. xoscar/core.pxd +51 -0
  57. xoscar/core.pyx +664 -0
  58. xoscar/debug.py +188 -0
  59. xoscar/driver.py +42 -0
  60. xoscar/errors.py +63 -0
  61. xoscar/libcpp.pxd +31 -0
  62. xoscar/metrics/__init__.py +21 -0
  63. xoscar/metrics/api.py +288 -0
  64. xoscar/metrics/backends/__init__.py +13 -0
  65. xoscar/metrics/backends/console/__init__.py +13 -0
  66. xoscar/metrics/backends/console/console_metric.py +82 -0
  67. xoscar/metrics/backends/metric.py +149 -0
  68. xoscar/metrics/backends/prometheus/__init__.py +13 -0
  69. xoscar/metrics/backends/prometheus/prometheus_metric.py +70 -0
  70. xoscar/nvutils.py +717 -0
  71. xoscar/profiling.py +260 -0
  72. xoscar/serialization/__init__.py +20 -0
  73. xoscar/serialization/aio.py +141 -0
  74. xoscar/serialization/core.cpython-312-darwin.so +0 -0
  75. xoscar/serialization/core.pxd +28 -0
  76. xoscar/serialization/core.pyi +57 -0
  77. xoscar/serialization/core.pyx +944 -0
  78. xoscar/serialization/cuda.py +111 -0
  79. xoscar/serialization/exception.py +48 -0
  80. xoscar/serialization/mlx.py +67 -0
  81. xoscar/serialization/numpy.py +82 -0
  82. xoscar/serialization/pyfury.py +37 -0
  83. xoscar/serialization/scipy.py +72 -0
  84. xoscar/serialization/torch.py +180 -0
  85. xoscar/utils.py +522 -0
  86. xoscar/virtualenv/__init__.py +34 -0
  87. xoscar/virtualenv/core.py +268 -0
  88. xoscar/virtualenv/platform.py +56 -0
  89. xoscar/virtualenv/utils.py +100 -0
  90. xoscar/virtualenv/uv.py +321 -0
  91. xoscar-0.9.0.dist-info/METADATA +230 -0
  92. xoscar-0.9.0.dist-info/RECORD +94 -0
  93. xoscar-0.9.0.dist-info/WHEEL +6 -0
  94. xoscar-0.9.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,268 @@
1
+ # Copyright 2022-2025 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import ast
18
+ import importlib
19
+ import operator
20
+ from abc import ABC, abstractmethod
21
+ from pathlib import Path
22
+
23
+ from packaging.markers import Marker, default_environment
24
+ from packaging.requirements import InvalidRequirement, Requirement
25
+ from packaging.version import Version
26
+
27
+ from .platform import (
28
+ check_cuda_available,
29
+ check_npu_available,
30
+ get_cuda_arch,
31
+ get_cuda_version,
32
+ )
33
+ from .utils import is_vcs_url
34
+
35
+
36
+ class VirtualEnvManager(ABC):
37
+ @classmethod
38
+ @abstractmethod
39
+ def is_available(cls):
40
+ pass
41
+
42
+ def __init__(self, env_path: Path):
43
+ self.env_path = env_path.resolve()
44
+
45
+ @abstractmethod
46
+ def exists_env(self) -> bool:
47
+ pass
48
+
49
+ @abstractmethod
50
+ def create_env(
51
+ self, python_path: Path | None = None, exists: str = "ignore"
52
+ ) -> None:
53
+ pass
54
+
55
+ @abstractmethod
56
+ def install_packages(self, packages: list[str], **kwargs):
57
+ pass
58
+
59
+ @staticmethod
60
+ def process_packages(packages: list[str]) -> list[str]:
61
+ """
62
+ Process a list of package names, replacing placeholders like #system_<package>#
63
+ with the installed version of the corresponding package from the system environment.
64
+
65
+ Example:
66
+ "#system_torch#" -> "torch==2.1.0" (if torch 2.1.0 is installed)
67
+
68
+ Args:
69
+ packages (list[str]): A list of package names, which may include placeholders.
70
+
71
+ Returns:
72
+ list[str]: A new list with resolved package names and versions.
73
+
74
+ Raises:
75
+ RuntimeError: If a specified system package is not found in the environment.
76
+ """
77
+ processed = []
78
+
79
+ for pkg in packages:
80
+ if pkg.startswith("#system_") and pkg.endswith("#"):
81
+ real_pkg = pkg[
82
+ len("#system_") : -1
83
+ ] # Extract actual package name, e.g., "torch"
84
+ try:
85
+ version = importlib.metadata.version(real_pkg)
86
+ # Strip build metadata like "+cpu"
87
+ version = version.split("+")[0]
88
+ except importlib.metadata.PackageNotFoundError:
89
+ raise RuntimeError(
90
+ f"System package '{real_pkg}' not found. Cannot resolve '{pkg}'."
91
+ )
92
+ processed.append(f"{real_pkg}=={version}")
93
+ else:
94
+ processed.append(pkg)
95
+
96
+ # apply extended syntax including:
97
+ # - has_cuda: whether CUDA is available (bool)
98
+ # - cuda_version: CUDA version string, e.g. "12.1" (str)
99
+ # - cuda_arch: CUDA architecture string, e.g. "sm_80" (str)
100
+ # - has_npu: whether an NPU is available (bool)
101
+ processed = filter_requirements(processed)
102
+
103
+ return processed
104
+
105
+ @abstractmethod
106
+ def cancel_install(self):
107
+ pass
108
+
109
+ @abstractmethod
110
+ def get_python_path(self) -> str | None:
111
+ pass
112
+
113
+ @abstractmethod
114
+ def get_lib_path(self) -> str:
115
+ pass
116
+
117
+ @abstractmethod
118
+ def remove_env(self):
119
+ pass
120
+
121
+
122
+ def get_env() -> dict[str, str | bool]:
123
+ env = default_environment().copy()
124
+ # Your custom env vars here, e.g.:
125
+ env.update(
126
+ {
127
+ "has_cuda": check_cuda_available(),
128
+ "cuda_version": get_cuda_version(),
129
+ "cuda_arch": get_cuda_arch(),
130
+ "has_npu": check_npu_available(),
131
+ }
132
+ )
133
+ return env
134
+
135
+
136
+ STANDARD_ENV_VARS = set(default_environment().keys())
137
+
138
+
139
+ def is_custom_marker(marker_str: str) -> bool:
140
+ try:
141
+ marker = Marker(marker_str)
142
+ except Exception:
143
+ return True
144
+
145
+ def traverse_markers(node):
146
+ if isinstance(node, tuple):
147
+ env_var = node[0]
148
+ if env_var not in STANDARD_ENV_VARS:
149
+ return True
150
+ return False
151
+ elif isinstance(node, list):
152
+ return any(traverse_markers(child) for child in node)
153
+ return False
154
+
155
+ return traverse_markers(marker._markers)
156
+
157
+
158
+ def eval_custom_marker(marker_str: str, env: dict) -> bool:
159
+ ops = {
160
+ ast.Eq: operator.eq,
161
+ ast.NotEq: operator.ne,
162
+ ast.Lt: operator.lt,
163
+ ast.LtE: operator.le,
164
+ ast.Gt: operator.gt,
165
+ ast.GtE: operator.ge,
166
+ ast.And: lambda a, b: a and b,
167
+ ast.Or: lambda a, b: a or b,
168
+ ast.Not: operator.not_,
169
+ }
170
+
171
+ def normalize_value(val):
172
+ # Normalize for boolean
173
+ if isinstance(val, str):
174
+ if val.lower() == "true":
175
+ return True
176
+ if val.lower() == "false":
177
+ return False
178
+
179
+ # Normalize for version-like fields
180
+ if isinstance(val, str):
181
+ if val.count(".") >= 1 and all(
182
+ part.isdigit() for part in val.split(".") if part
183
+ ):
184
+ return Version(val)
185
+
186
+ return val
187
+
188
+ def maybe_parse_cuda_arch(val):
189
+ if isinstance(val, str) and val.startswith("sm_"):
190
+ try:
191
+ return int(val[3:])
192
+ except ValueError:
193
+ return val
194
+ return val
195
+
196
+ def _eval(node):
197
+ if isinstance(node, ast.BoolOp):
198
+ left = _eval(node.values[0])
199
+ for right_node in node.values[1:]:
200
+ right = _eval(right_node)
201
+ op = ops[type(node.op)]
202
+ left = op(left, right)
203
+ return left
204
+
205
+ elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
206
+ return not _eval(node.operand)
207
+
208
+ elif isinstance(node, ast.Compare):
209
+ left = _eval(node.left)
210
+ left = maybe_parse_cuda_arch(normalize_value(left))
211
+
212
+ for op_node, right_expr in zip(node.ops, node.comparators):
213
+ right = _eval(right_expr)
214
+ right = maybe_parse_cuda_arch(normalize_value(right))
215
+
216
+ op_func = ops[type(op_node)]
217
+ if not op_func(left, right):
218
+ return False
219
+ left = right # for chained comparisons
220
+
221
+ return True
222
+
223
+ elif isinstance(node, ast.Name):
224
+ return normalize_value(env.get(node.id))
225
+
226
+ elif isinstance(node, ast.Constant):
227
+ return node.value
228
+
229
+ elif isinstance(node, ast.Str): # Python <3.8
230
+ return node.s
231
+
232
+ else:
233
+ raise ValueError(f"Unsupported expression: {ast.dump(node)}")
234
+
235
+ tree = ast.parse(marker_str, mode="eval")
236
+ return _eval(tree.body)
237
+
238
+
239
+ def filter_requirements(requirements: list[str]) -> list[str]:
240
+ """
241
+ Filter requirements by evaluating markers in given env.
242
+ If env is None, use get_env().
243
+ """
244
+ env = get_env()
245
+ result = []
246
+ for req_str in requirements:
247
+ if is_vcs_url(req_str):
248
+ result.append(req_str)
249
+ elif ";" in req_str:
250
+ req_part, marker_part = req_str.split(";", 1)
251
+ marker_part = marker_part.strip()
252
+ try:
253
+ req = Requirement(req_str)
254
+ if req.marker is None or req.marker.evaluate(env):
255
+ result.append(f"{req.name}{req.specifier}")
256
+ continue
257
+ except InvalidRequirement:
258
+ if is_custom_marker(marker_part):
259
+ if eval_custom_marker(marker_part, env):
260
+ req = Requirement(req_part.strip())
261
+ result.append(str(req))
262
+ else:
263
+ raise
264
+ else:
265
+ req = Requirement(req_str.strip())
266
+ result.append(str(req))
267
+
268
+ return result
@@ -0,0 +1,56 @@
1
+ # Copyright 2022-2025 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Optional
16
+
17
+
18
+ def check_cuda_available() -> bool:
19
+ try:
20
+ import torch
21
+
22
+ return torch.cuda.is_available()
23
+ except (ImportError, AttributeError):
24
+ return False
25
+
26
+
27
+ def get_cuda_version() -> Optional[str]:
28
+ try:
29
+ import torch
30
+
31
+ return torch.version.cuda # e.g. '12.1'
32
+ except (ImportError, AttributeError):
33
+ return None
34
+
35
+
36
+ def get_cuda_arch() -> Optional[str]:
37
+ try:
38
+ import torch
39
+
40
+ major, minor = torch.cuda.get_device_capability()
41
+ return f"sm_{major}{minor}" # e.g. 'sm_80'
42
+ except (ImportError, AttributeError, AssertionError, RuntimeError):
43
+ # If no cuda available,
44
+ # AssertionError("Torch not compiled with CUDA enabled")
45
+ # will be raised
46
+ return None
47
+
48
+
49
+ def check_npu_available() -> bool:
50
+ try:
51
+ import torch
52
+ import torch_npu # noqa: F401
53
+
54
+ return torch.npu.is_available()
55
+ except ImportError:
56
+ return False
@@ -0,0 +1,100 @@
1
+ # Copyright 2022-2025 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import logging
16
+ import re
17
+ import subprocess
18
+ import sys
19
+ import threading
20
+ from contextlib import contextmanager
21
+ from typing import BinaryIO, Callable, Iterator, List, Optional, TextIO, Union
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ ansi_escape = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")
26
+
27
+
28
+ def clean_ansi(text: str) -> str:
29
+ """Remove ANSI escape sequences from text."""
30
+ return ansi_escape.sub("", text)
31
+
32
+
33
+ def stream_reader(
34
+ stream: BinaryIO, log_func: Callable[[str], None], output_stream: TextIO
35
+ ) -> None:
36
+ """
37
+ Read from the stream, write to logger, and also write to the terminal.
38
+ """
39
+ for line in iter(stream.readline, b""):
40
+ decoded = line.decode(errors="replace")
41
+ output_stream.write(decoded)
42
+ output_stream.flush()
43
+ log_func(clean_ansi(decoded.rstrip("\n")))
44
+
45
+
46
+ @contextmanager
47
+ def run_subprocess_with_logger(
48
+ cmd: Union[str, List[str]], cwd: Optional[str] = None, env: Optional[dict] = None
49
+ ) -> Iterator[subprocess.Popen]:
50
+ """
51
+ Run a subprocess, redirect stdout to logger.info and stderr to logger.error.
52
+ Returns the Popen object as a context manager.
53
+
54
+ :param cmd: Command to execute
55
+ :param kwargs: Additional arguments passed to subprocess.Popen
56
+ :yield: The subprocess.Popen object
57
+ """
58
+
59
+ process = subprocess.Popen(
60
+ cmd,
61
+ stdout=subprocess.PIPE,
62
+ stderr=subprocess.PIPE,
63
+ cwd=cwd,
64
+ env=env,
65
+ bufsize=1,
66
+ )
67
+
68
+ threads = [
69
+ threading.Thread(
70
+ target=stream_reader, args=(process.stdout, logger.info, sys.stdout)
71
+ ),
72
+ threading.Thread(
73
+ target=stream_reader, args=(process.stderr, logger.error, sys.stderr)
74
+ ),
75
+ ]
76
+ for t in threads:
77
+ t.start()
78
+
79
+ try:
80
+ yield process
81
+ finally:
82
+ process.wait()
83
+ for t in threads:
84
+ t.join()
85
+
86
+
87
+ def is_vcs_url(spec_str: str) -> bool:
88
+ """
89
+ Check if the given spec string is a VCS URL.
90
+
91
+ Supports common VCS schemes like git+, svn+, hg+, bzr+, and HTTP/HTTPS URLs.
92
+
93
+ Args:
94
+ spec_str (str): The package spec string.
95
+
96
+ Returns:
97
+ bool: True if it's a VCS URL, False otherwise.
98
+ """
99
+ vcs_prefixes = ("git+", "http://", "https://", "svn+", "hg+", "bzr+")
100
+ return spec_str.startswith(vcs_prefixes)