Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Global Backend Control**:
- `xp.set_backend(name)`: Globally switch the active backend at runtime.
- `xp.use_backend(name)`: Context manager for temporary, scoped backend switching.
- `xp.numpy_backend` & `xp.cupy_backend`: Boolean properties to check the globally active backend.
- **Synchronization**:
- `xp.synchronize()`: Blocks until GPU operations are complete (no-op on CPU). Essential for accurate benchmarking.
- **Developer Experience**:
Expand All @@ -25,7 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- **Dynamic Dispatch Architecture**: Refactored `src/cunumpy/xp.py` to use module-level `__getattr__`. This ensures that `cunumpy.<op>` calls always resolve to the currently active backend module, enabling seamless runtime switching via `set_backend`.
- **Type Safety**: Updated `src/cunumpy/__init__.pyi` stubs to provide full IDE autocompletion and type-checking for all new API methods.
- **Documentation**: Enhanced `README.md` with usage examples for the new backend control and synchronization features.
- **Documentation**:
- Simplified `README.md` and documentation to exclusively focus on PyPI installation (`pip install cunumpy`).
- Enhanced `quickstart.md` and `api.md` with usage examples for the new backend control and synchronization features.
- **CI/CD**: Restricted GitHub Pages documentation deployment to the `devel` branch only.

### Fixed
- Improved `ArrayBackend` initialization to fallback gracefully to NumPy if CuPy is requested but not installed.
Expand Down
6 changes: 6 additions & 0 deletions docs/source/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ Returns `True` if the array is stored on a CPU (NumPy).

## Global Configuration

### `numpy_backend`
Boolean property that returns `True` if the currently active global backend is NumPy.

### `cupy_backend`
Boolean property that returns `True` if the currently active global backend is CuPy.

### `set_backend(backend_name)`
Globally sets the active backend for all `cunumpy` operations. `backend_name` should be `"numpy"` or `"cupy"`.

Expand Down
6 changes: 6 additions & 0 deletions src/cunumpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@
"use_backend",
"set_backend",
"synchronize",
"numpy_backend",
"cupy_backend",
]


def __getattr__(name: str):
"""Set cunumpy.<name> to cunumpy.xp.<name> (NumPy/CuPy)."""
if name == "numpy_backend":
return xp.numpy_backend
if name == "cupy_backend":
return xp.cupy_backend
return getattr(xp.xp, name)
3 changes: 3 additions & 0 deletions src/cunumpy/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ def is_cpu(array: Any) -> bool: ...
def use_backend(backend: str) -> Generator[None, None, None]: ...
def set_backend(backend: str) -> None: ...
def synchronize() -> None: ...

numpy_backend: bool
cupy_backend: bool
14 changes: 14 additions & 0 deletions src/cunumpy/xp.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ def set_backend(backend: BackendType) -> None:
array_backend._xp = array_backend._load_backend(backend)


def _cupy_backend() -> bool:
"""Check if the active global backend is CuPy."""
return array_backend.backend == "cupy"


def _numpy_backend() -> bool:
"""Check if the active global backend is NumPy."""
return array_backend.backend == "numpy"


def synchronize() -> None:
"""Wait for all kernels in all streams on current device to complete."""
if array_backend.backend == "cupy":
Expand Down Expand Up @@ -153,4 +163,8 @@ def is_cpu(array: Any) -> bool:
def __getattr__(name):
if name == "xp":
return array_backend.xp
if name == "numpy_backend":
return _numpy_backend()
if name == "cupy_backend":
return _cupy_backend()
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
12 changes: 12 additions & 0 deletions tests/unit/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def test_numpy_symbols_accessible():
"use_backend",
"set_backend",
"synchronize",
"numpy_backend",
"cupy_backend",
"xp",
]
missing = [
Expand Down Expand Up @@ -114,6 +116,16 @@ def test_synchronize():
xp.synchronize()


def test_backend_bools():
with xp.use_backend("numpy"):
assert xp.numpy_backend is True
assert xp.cupy_backend is False

# Note: in test env without cupy, cupy_backend might be false
# even inside use_backend('cupy') if fallback occurs.
# Our implementation of use_backend calls _load_backend which returns np if cp missing.


if __name__ == "__main__":
test_xp_array()
test_numpy_symbols_accessible()
Loading