cunumpy 0.1.1__tar.gz → 0.1.3__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cunumpy
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cunumpy as xp`.
5
5
  Author: Max
6
6
  Project-URL: Source, https://github.com/max-models/cunumpy
@@ -40,18 +40,8 @@ Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cun
40
40
 
41
41
  # Install
42
42
 
43
- Create and activate python environment
44
-
45
- ```
46
- python -m venv env
47
- source env/bin/activate
48
- pip install --upgrade pip
49
- ```
50
-
51
- Install the code and requirements with pip
52
-
53
- ```
54
- pip install -e .
43
+ ```bash
44
+ pip install cunumpy
55
45
  ```
56
46
 
57
47
  Example usage:
@@ -62,10 +52,33 @@ export ARRAY_BACKEND=cupy
62
52
 
63
53
  ```python
64
54
  import cunumpy as xp
65
- arr = xp.array([1,2])
55
+
56
+ arr = xp.array([1, 2])
66
57
 
67
58
  print(type(arr))
68
59
  print(xp.__version__)
60
+
61
+ # Convert to NumPy
62
+ arr_np = xp.to_numpy(arr)
63
+
64
+ # Convert to active backend
65
+ arr_xp = xp.to_cunumpy(arr)
66
+
67
+ # Inspect backend
68
+ print(xp.get_backend(arr))
69
+ print(xp.is_gpu(arr))
70
+ print(xp.is_cpu(arr))
71
+
72
+ # Temporarily switch backend
73
+ with xp.use_backend("numpy"):
74
+ # This code runs on CPU even if ARRAY_BACKEND=cupy
75
+ arr_cpu = xp.zeros(100)
76
+
77
+ # Set backend globally
78
+ xp.set_backend("cupy")
79
+
80
+ # Synchronize GPU operations (no-op on CPU)
81
+ xp.synchronize()
69
82
  ```
70
83
 
71
84
  # Build docs
@@ -0,0 +1,55 @@
1
+ # CuNumpy
2
+
3
+ Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cunumpy as xp`.
4
+
5
+ # Install
6
+
7
+ ```bash
8
+ pip install cunumpy
9
+ ```
10
+
11
+ Example usage:
12
+
13
+ ```
14
+ export ARRAY_BACKEND=cupy
15
+ ```
16
+
17
+ ```python
18
+ import cunumpy as xp
19
+
20
+ arr = xp.array([1, 2])
21
+
22
+ print(type(arr))
23
+ print(xp.__version__)
24
+
25
+ # Convert to NumPy
26
+ arr_np = xp.to_numpy(arr)
27
+
28
+ # Convert to active backend
29
+ arr_xp = xp.to_cunumpy(arr)
30
+
31
+ # Inspect backend
32
+ print(xp.get_backend(arr))
33
+ print(xp.is_gpu(arr))
34
+ print(xp.is_cpu(arr))
35
+
36
+ # Temporarily switch backend
37
+ with xp.use_backend("numpy"):
38
+ # This code runs on CPU even if ARRAY_BACKEND=cupy
39
+ arr_cpu = xp.zeros(100)
40
+
41
+ # Set backend globally
42
+ xp.set_backend("cupy")
43
+
44
+ # Synchronize GPU operations (no-op on CPU)
45
+ xp.synchronize()
46
+ ```
47
+
48
+ # Build docs
49
+
50
+
51
+ ```
52
+ make html
53
+ cd ../
54
+ open docs/_build/html/index.html
55
+ ```
@@ -5,7 +5,7 @@ requires = [ "setuptools", "wheel" ]
5
5
 
6
6
  [project]
7
7
  name = "cunumpy"
8
- version = "0.1.1"
8
+ version = "0.1.3"
9
9
  description = "Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cunumpy as xp`."
10
10
  readme = "README.md"
11
11
  keywords = [ "python" ]
@@ -51,3 +51,6 @@ where = [ "src" ]
51
51
 
52
52
  [tool.setuptools.package-data]
53
53
  cunumpy = [ "py.typed", "*.pyi" ]
54
+
55
+ [tool.isort]
56
+ profile = "black"
@@ -0,0 +1,49 @@
1
+ # cunumpy/__init__.py
2
+ from importlib.metadata import PackageNotFoundError, version
3
+
4
+ from . import xp
5
+ from .xp import (
6
+ cupy_available,
7
+ get_backend,
8
+ is_cpu,
9
+ is_gpu,
10
+ set_backend,
11
+ set_device,
12
+ synchronize,
13
+ to_cunumpy,
14
+ to_cupy,
15
+ to_numpy,
16
+ use_backend,
17
+ )
18
+
19
+ try:
20
+ __version__ = version("cunumpy")
21
+ except PackageNotFoundError:
22
+ __version__ = "0.0.0+unknown"
23
+
24
+ __all__ = [
25
+ "__version__",
26
+ "cupy_available",
27
+ "cupy_backend",
28
+ "get_backend",
29
+ "is_cpu",
30
+ "is_gpu",
31
+ "numpy_backend",
32
+ "set_backend",
33
+ "set_device",
34
+ "synchronize",
35
+ "to_cunumpy",
36
+ "to_cupy",
37
+ "to_numpy",
38
+ "use_backend",
39
+ "xp",
40
+ ]
41
+
42
+
43
+ def __getattr__(name: str):
44
+ """Set cunumpy.<name> to cunumpy.xp.<name> (NumPy/CuPy)."""
45
+ if name == "numpy_backend":
46
+ return xp.numpy_backend
47
+ if name == "cupy_backend":
48
+ return xp.cupy_backend
49
+ return getattr(xp.xp, name)
@@ -0,0 +1,27 @@
1
+ # Stub file for Pylance/mypy: exposes all numpy symbols so that
2
+ # `import cunumpy as xp` followed by `xp.<Tab>` shows numpy completions.
3
+ # At runtime the real __init__.py dispatches to numpy or cupy via __getattr__.
4
+ from contextlib import contextmanager
5
+ from typing import Any, Generator
6
+
7
+ import numpy as np
8
+ from numpy import *
9
+
10
+ from . import xp as xp
11
+
12
+ def to_numpy(array: Any) -> np.ndarray: ...
13
+ def to_cupy(array: Any) -> Any: ...
14
+ def to_cunumpy(array: Any) -> Any: ...
15
+ def cupy_available() -> bool: ...
16
+ def get_backend(array: Any) -> str: ...
17
+ def is_gpu(array: Any) -> bool: ...
18
+ def is_cpu(array: Any) -> bool: ...
19
+ @contextmanager
20
+ def use_backend(backend: str) -> Generator[None]: ...
21
+ def set_backend(backend: str) -> None: ...
22
+ def set_device(device_id: int) -> None: ...
23
+ def synchronize() -> None: ...
24
+
25
+ numpy_backend: bool
26
+ cupy_backend: bool
27
+ __version__: str
@@ -0,0 +1,210 @@
1
+ import os
2
+ import warnings
3
+ from contextlib import contextmanager
4
+ from types import ModuleType
5
+ from typing import TYPE_CHECKING, Any, Generator, Literal
6
+
7
+ import numpy as np
8
+
9
+ BackendType = Literal["numpy", "cupy"]
10
+
11
+
12
+ _CUPY_AVAILABLE_CACHE = None
13
+
14
+
15
+ def cupy_available() -> bool:
16
+ """Check if CuPy is available and functional."""
17
+ global _CUPY_AVAILABLE_CACHE
18
+ if _CUPY_AVAILABLE_CACHE is not None:
19
+ return _CUPY_AVAILABLE_CACHE
20
+
21
+ try:
22
+ import cupy as cp
23
+
24
+ # Check if a GPU is available
25
+ _CUPY_AVAILABLE_CACHE = cp.is_available()
26
+ return _CUPY_AVAILABLE_CACHE
27
+ except Exception: # noqa: BLE001 - tolerate any driver/runtime failure
28
+ _CUPY_AVAILABLE_CACHE = False
29
+ return False
30
+
31
+
32
+ class ArrayBackend:
33
+ """Holds the process-wide active backend (NumPy or CuPy).
34
+
35
+ Not thread-safe: `set_backend`/`use_backend` mutate this single shared
36
+ instance in place, so concurrent code (threads, async tasks) switching
37
+ backends independently will race. Safe for the typical single-threaded
38
+ script/notebook usage this library targets.
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ backend: BackendType = "numpy",
44
+ verbose: bool = False,
45
+ ) -> None:
46
+ if backend.lower() not in ("numpy", "cupy"):
47
+ raise ValueError("Array backend must be either 'numpy' or 'cupy'.")
48
+
49
+ self._backend: BackendType = "cupy" if backend.lower() == "cupy" else "numpy"
50
+ self._xp: ModuleType = np # Placeholder
51
+
52
+ # Import numpy/cupy
53
+ self._xp = self._load_backend(self._backend, verbose)
54
+
55
+ def _load_backend(self, backend: BackendType, verbose: bool = False) -> ModuleType:
56
+ if backend == "cupy":
57
+ if cupy_available():
58
+ import cupy as cp
59
+
60
+ self._backend = "cupy"
61
+ return cp
62
+ else:
63
+ if verbose:
64
+ print(
65
+ "CuPy not available or not functional. Falling back to NumPy."
66
+ )
67
+ self._backend = "numpy"
68
+ return np
69
+ import numpy as np_mod
70
+
71
+ self._backend = "numpy"
72
+ return np_mod
73
+
74
+ def __repr__(self) -> str:
75
+ return f"ArrayBackend(backend={self._backend!r}, module={self._xp.__name__!r})"
76
+
77
+ @property
78
+ def backend(self) -> BackendType:
79
+ return self._backend
80
+
81
+ @property
82
+ def xp(self) -> ModuleType:
83
+ return self._xp
84
+
85
+ @contextmanager
86
+ def use_backend(self, backend: BackendType) -> Generator[None, None, None]:
87
+ """Temporarily change the backend."""
88
+ old_backend = self._backend
89
+ old_xp = self._xp
90
+
91
+ self._backend = backend
92
+ self._xp = self._load_backend(backend)
93
+
94
+ try:
95
+ yield
96
+ finally:
97
+ self._backend = old_backend
98
+ self._xp = old_xp
99
+
100
+
101
+ array_backend = ArrayBackend(
102
+ backend=(
103
+ "cupy" if os.getenv("ARRAY_BACKEND", "numpy").lower() == "cupy" else "numpy"
104
+ ),
105
+ verbose=False,
106
+ )
107
+
108
+
109
+ def use_backend(backend: BackendType) -> Generator[None, None, None]:
110
+ """Temporarily change the backend."""
111
+ return array_backend.use_backend(backend)
112
+
113
+
114
+ def set_backend(backend: BackendType) -> None:
115
+ """Set the backend globally."""
116
+ array_backend._backend = backend
117
+ array_backend._xp = array_backend._load_backend(backend)
118
+
119
+
120
+ def _cupy_backend() -> bool:
121
+ """Check if the active global backend is CuPy."""
122
+ return array_backend.backend == "cupy"
123
+
124
+
125
+ def _numpy_backend() -> bool:
126
+ """Check if the active global backend is NumPy."""
127
+ return array_backend.backend == "numpy"
128
+
129
+
130
+ def set_device(device_id: int) -> None:
131
+ """Select the active CUDA device for the current process (no-op on NumPy)."""
132
+ if array_backend.backend == "cupy":
133
+ import cupy as cp
134
+
135
+ cp.cuda.Device(device_id).use()
136
+
137
+
138
+ def synchronize() -> None:
139
+ """Wait for all kernels in all streams on current device to complete."""
140
+ if array_backend.backend == "cupy":
141
+ try:
142
+ import cupy as cp
143
+
144
+ cp.cuda.Device().synchronize()
145
+ except ImportError:
146
+ pass
147
+ except AttributeError as e:
148
+ warnings.warn(
149
+ f"CuPy synchronize() failed unexpectedly, this may indicate a "
150
+ f"CuPy API mismatch: {e}",
151
+ RuntimeWarning,
152
+ stacklevel=2,
153
+ )
154
+
155
+
156
+ def to_numpy(array: Any) -> np.ndarray:
157
+ """Convert an array to a NumPy array."""
158
+ if get_backend(array) == "cupy":
159
+ return array.get()
160
+
161
+ return np.asarray(array)
162
+
163
+
164
+ def to_cupy(array: Any) -> Any:
165
+ """Convert an array to a CuPy array."""
166
+ if not cupy_available():
167
+ raise ImportError("CuPy is not available or not functional.")
168
+
169
+ import cupy as cp
170
+
171
+ return cp.asarray(array)
172
+
173
+
174
+ def to_cunumpy(array: Any) -> Any:
175
+ """Convert an array to the currently active backend."""
176
+ if array_backend.backend == "cupy" and cupy_available():
177
+ return to_cupy(array)
178
+ return to_numpy(array)
179
+
180
+
181
+ def get_backend(array: Any) -> BackendType:
182
+ """Return 'cupy' or 'numpy' depending on the array type."""
183
+ module = getattr(type(array), "__module__", "")
184
+ return "cupy" if "cupy" in module else "numpy"
185
+
186
+
187
+ def is_gpu(array: Any) -> bool:
188
+ """Check if the array is stored on a GPU (CuPy)."""
189
+ return get_backend(array) == "cupy"
190
+
191
+
192
+ def is_cpu(array: Any) -> bool:
193
+ """Check if the array is stored on a CPU (NumPy)."""
194
+ return get_backend(array) == "numpy"
195
+
196
+
197
+ # TYPE_CHECKING is True when type checking (e.g., mypy), but False at runtime.
198
+ # This allows us to use autocompletion for xp (i.e., numpy/cupy) as if numpy was imported.
199
+ if TYPE_CHECKING:
200
+ import numpy as xp # noqa: F401 - type-checker-only alias for autocompletion
201
+ else:
202
+ # Use module-level __getattr__ for dynamic xp (Python 3.7+)
203
+ def __getattr__(name):
204
+ if name == "xp":
205
+ return array_backend.xp
206
+ if name == "numpy_backend":
207
+ return _numpy_backend()
208
+ if name == "cupy_backend":
209
+ return _cupy_backend()
210
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cunumpy
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cunumpy as xp`.
5
5
  Author: Max
6
6
  Project-URL: Source, https://github.com/max-models/cunumpy
@@ -40,18 +40,8 @@ Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cun
40
40
 
41
41
  # Install
42
42
 
43
- Create and activate python environment
44
-
45
- ```
46
- python -m venv env
47
- source env/bin/activate
48
- pip install --upgrade pip
49
- ```
50
-
51
- Install the code and requirements with pip
52
-
53
- ```
54
- pip install -e .
43
+ ```bash
44
+ pip install cunumpy
55
45
  ```
56
46
 
57
47
  Example usage:
@@ -62,10 +52,33 @@ export ARRAY_BACKEND=cupy
62
52
 
63
53
  ```python
64
54
  import cunumpy as xp
65
- arr = xp.array([1,2])
55
+
56
+ arr = xp.array([1, 2])
66
57
 
67
58
  print(type(arr))
68
59
  print(xp.__version__)
60
+
61
+ # Convert to NumPy
62
+ arr_np = xp.to_numpy(arr)
63
+
64
+ # Convert to active backend
65
+ arr_xp = xp.to_cunumpy(arr)
66
+
67
+ # Inspect backend
68
+ print(xp.get_backend(arr))
69
+ print(xp.is_gpu(arr))
70
+ print(xp.is_cpu(arr))
71
+
72
+ # Temporarily switch backend
73
+ with xp.use_backend("numpy"):
74
+ # This code runs on CPU even if ARRAY_BACKEND=cupy
75
+ arr_cpu = xp.zeros(100)
76
+
77
+ # Set backend globally
78
+ xp.set_backend("cupy")
79
+
80
+ # Synchronize GPU operations (no-op on CPU)
81
+ xp.synchronize()
69
82
  ```
70
83
 
71
84
  # Build docs
cunumpy-0.1.1/README.md DELETED
@@ -1,42 +0,0 @@
1
- # CuNumpy
2
-
3
- Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cunumpy as xp`.
4
-
5
- # Install
6
-
7
- Create and activate python environment
8
-
9
- ```
10
- python -m venv env
11
- source env/bin/activate
12
- pip install --upgrade pip
13
- ```
14
-
15
- Install the code and requirements with pip
16
-
17
- ```
18
- pip install -e .
19
- ```
20
-
21
- Example usage:
22
-
23
- ```
24
- export ARRAY_BACKEND=cupy
25
- ```
26
-
27
- ```python
28
- import cunumpy as xp
29
- arr = xp.array([1,2])
30
-
31
- print(type(arr))
32
- print(xp.__version__)
33
- ```
34
-
35
- # Build docs
36
-
37
-
38
- ```
39
- make html
40
- cd ../
41
- open docs/_build/html/index.html
42
- ```
@@ -1,9 +0,0 @@
1
- # cunumpy/__init__.py
2
- from . import xp
3
-
4
- __all__ = ["xp"]
5
-
6
-
7
- def __getattr__(name: str):
8
- """Set cunumpy.<name> to cunumpy.xp.<name> (NumPy/CuPy)."""
9
- return getattr(xp.xp, name)
@@ -1,7 +0,0 @@
1
- # Stub file for Pylance/mypy: exposes all numpy symbols so that
2
- # `import cunumpy as xp` followed by `xp.<Tab>` shows numpy completions.
3
- # At runtime the real __init__.py dispatches to numpy or cupy via __getattr__.
4
- from numpy import *
5
- from numpy import __config__, __version__
6
-
7
- from . import xp
@@ -1,64 +0,0 @@
1
- import os
2
- from types import ModuleType
3
- from typing import TYPE_CHECKING, Literal
4
-
5
- BackendType = Literal["numpy", "cupy"]
6
-
7
-
8
- class ArrayBackend:
9
- def __init__(
10
- self,
11
- backend: BackendType = "numpy",
12
- verbose: bool = False,
13
- ) -> None:
14
- assert backend.lower() in [
15
- "numpy",
16
- "cupy",
17
- ], "Array backend must be either 'numpy' or 'cupy'."
18
-
19
- self._backend: BackendType = "cupy" if backend.lower() == "cupy" else "numpy"
20
-
21
- # Import numpy/cupy
22
- if self.backend == "cupy":
23
- try:
24
- import cupy as cp
25
-
26
- self._xp = cp
27
- except ImportError:
28
- if verbose:
29
- print("CuPy not available.")
30
- self._backend = "numpy"
31
-
32
- if self.backend == "numpy":
33
- import numpy as np
34
-
35
- self._xp = np
36
-
37
- assert isinstance(self.xp, ModuleType)
38
-
39
- if verbose:
40
- print(f"Using {self.xp.__name__} backend.")
41
-
42
- @property
43
- def backend(self) -> BackendType:
44
- return self._backend
45
-
46
- @property
47
- def xp(self) -> ModuleType:
48
- return self._xp
49
-
50
-
51
- # TODO: Make this configurable via environment variable or config file.
52
- array_backend = ArrayBackend(
53
- backend=(
54
- "cupy" if os.getenv("ARRAY_BACKEND", "numpy").lower() == "cupy" else "numpy"
55
- ),
56
- verbose=False,
57
- )
58
-
59
- # TYPE_CHECKING is True when type checking (e.g., mypy), but False at runtime.
60
- # This allows us to use autocompletion for xp (i.e., numpy/cupy) as if numpy was imported.
61
- if TYPE_CHECKING:
62
- import numpy as xp
63
- else:
64
- xp = array_backend.xp
File without changes
File without changes
File without changes