-
Notifications
You must be signed in to change notification settings - Fork 256
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Experimental Feature - Accelerated NumPy
- Loading branch information
Showing
8 changed files
with
819 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,3 +18,4 @@ dist/ | |
doc/build/ | ||
doc/source/**/generated/ | ||
arch/univariate/recursions.c | ||
**/.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from .engine import ( | ||
backend, | ||
set_backend, | ||
use_backend, | ||
NumpyEngine, | ||
LinAlgEngine, | ||
numpy, | ||
linalg, | ||
fori_loop, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
from contextlib import contextmanager | ||
from typing import Any | ||
|
||
_BACKEND_ENGINE = "numpy" | ||
|
||
|
||
def backend(): | ||
return _BACKEND_ENGINE | ||
|
||
|
||
def set_backend(library_name): | ||
""" | ||
Set backend engine. | ||
The function sets the backend engine in global level. | ||
Parameters | ||
---------- | ||
library_name : str | ||
Library name. Default is `numpy`. Options are `numpy`, `tensorflow`, | ||
`cupy` and `jax`. | ||
""" | ||
assert library_name.lower() in ["numpy", "tensorflow", "cupy", "jax"], ( | ||
"Only `numpy`, `tensorflow`, `cupy` and `jax` are supported, but not " | ||
f"{library_name}" | ||
) | ||
global _BACKEND_ENGINE | ||
_BACKEND_ENGINE = library_name | ||
|
||
|
||
@contextmanager | ||
def use_backend(library_name="numpy"): | ||
""" | ||
NumPy engine selection. | ||
The function is a context manager to enable users to switch to a | ||
specific library as a replacement of NumPy in CPU. | ||
Parameters | ||
---------- | ||
library_name : str | ||
Library name. Default is `numpy`. Options are `numpy`, `tensorflow`, | ||
`cupy` and `jax`. | ||
""" | ||
assert library_name.lower() in ["numpy", "tensorflow", "cupy", "jax"], ( | ||
"Only `numpy`, `tensorflow`, `cupy` and `jax` are supported, but not " | ||
f"{library_name}" | ||
) | ||
global _BACKEND_ENGINE | ||
_original = _BACKEND_ENGINE | ||
try: | ||
_BACKEND_ENGINE = library_name | ||
if _BACKEND_ENGINE == "tensorflow": | ||
import tensorflow.experimental.numpy as np | ||
|
||
np.experimental_enable_numpy_behavior() | ||
yield | ||
finally: | ||
_BACKEND_ENGINE = _original | ||
|
||
|
||
class NumpyEngine: | ||
""" | ||
NumPy engine. | ||
""" | ||
|
||
@property | ||
def name(self): | ||
""" | ||
Get engine name. | ||
""" | ||
global _BACKEND_ENGINE | ||
return _BACKEND_ENGINE | ||
|
||
def __getattribute__(self, __name: str) -> Any: | ||
global _BACKEND_ENGINE | ||
try: | ||
if _BACKEND_ENGINE == "numpy": | ||
import numpy as anp | ||
elif _BACKEND_ENGINE == "tensorflow": | ||
import tensorflow.experimental.numpy as anp | ||
elif _BACKEND_ENGINE == "cupy": | ||
import cupy as anp | ||
elif _BACKEND_ENGINE == "jax": | ||
import jax.numpy as anp | ||
else: | ||
raise ValueError(f"Cannot recognize backend {_BACKEND_ENGINE}") | ||
except ImportError: | ||
raise ImportError( | ||
"Library `numpy` cannot be imported from backend engine " | ||
f"{_BACKEND_ENGINE}. Please make sure to install the library " | ||
f"via `pip install {_BACKEND_ENGINE}`." | ||
) | ||
|
||
try: | ||
return getattr(anp, __name) | ||
except AttributeError: | ||
raise AttributeError( | ||
"Cannot get attribute / function from numpy library in " | ||
f"backend engine {_BACKEND_ENGINE}" | ||
) | ||
|
||
|
||
class LinAlgEngine: | ||
""" | ||
Linear algebra engine. | ||
""" | ||
|
||
@property | ||
def name(self): | ||
""" | ||
Get engine name. | ||
""" | ||
global _BACKEND_ENGINE | ||
return _BACKEND_ENGINE | ||
|
||
def __getattribute__(self, __name: str) -> Any: | ||
global _BACKEND_ENGINE | ||
try: | ||
if _BACKEND_ENGINE == "numpy": | ||
import numpy.linalg as alinalg | ||
elif _BACKEND_ENGINE == "tensorflow": | ||
import tensorflow.linalg as alinalg | ||
elif _BACKEND_ENGINE == "cupy": | ||
import cupy.linalg as alinalg | ||
elif _BACKEND_ENGINE == "jax": | ||
import jax.numpy.linalg as alinalg | ||
else: | ||
raise ValueError(f"Cannot recognize backend {_BACKEND_ENGINE}") | ||
except ImportError: | ||
raise ImportError( | ||
"Library `linalg` cannot be imported from backend engine " | ||
f"{_BACKEND_ENGINE}. Please make sure to install the library " | ||
f"via `pip install {_BACKEND_ENGINE}`." | ||
) | ||
|
||
try: | ||
return getattr(alinalg, __name) | ||
except AttributeError: | ||
raise AttributeError( | ||
"Cannot get attribute / function from linalg library in " | ||
f"backend engine {_BACKEND_ENGINE}" | ||
) | ||
|
||
|
||
def fori_loop(lower, upper, body_fun, init_val=None): | ||
global _BACKEND_ENGINE | ||
if _BACKEND_ENGINE in ["numpy", "cupy"]: | ||
val = init_val | ||
for i in range(lower, upper): | ||
val = body_fun(i, val) | ||
return val | ||
elif _BACKEND_ENGINE == "jax": | ||
import jax.lax | ||
|
||
return jax.lax.fori_loop(lower, upper, body_fun, init_val) | ||
elif _BACKEND_ENGINE == "tensorflow": | ||
import tensorflow as tf | ||
|
||
i = tf.constant(lower) | ||
while_condition = lambda i: tf.less(i, upper) | ||
|
||
def body(i, val): | ||
return [tf.add(i, 1), body_fun(val)] | ||
|
||
return tf.while_loop(while_condition, body, [i, init_val]) | ||
|
||
raise ImportError(f"Cannot recognize backend {_BACKEND_ENGINE}") | ||
|
||
|
||
numpy = NumpyEngine() | ||
linalg = LinAlgEngine() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
.. module:: arch.experimental.engine | ||
:noindex: | ||
.. currentmodule:: arch.experimental.engine | ||
|
||
Accelerated NumPy | ||
================= | ||
|
||
The feature is to allow users to choose alternative NumPy-like engine | ||
to run on CPU and GPU. Currently, the following engine are supported in | ||
CPU and GPU runtime | ||
|
||
|
||
* `JAX <https://jax.readthedocs.io/en/latest/index.html#>`_ | ||
|
||
* `TensorFlow <https://www.tensorflow.org/guide/tf_numpy>`_ | ||
|
||
* `CuPy <https://docs.cupy.dev/en/stable/index.html>`_ | ||
|
||
There are two options users can switch the backend engine. | ||
|
||
1. Context Manager | ||
|
||
Users can use function ``use_backend`` in a ``with`` statement to temporarily | ||
switch the NumPy engine. | ||
|
||
In the below example, assume that ``data`` object is a timeseries in NumPy array. | ||
The covariance estimation (NeweyWest) is computed in TensorFlow. Since the | ||
output is in TensorFlow Tensor type, the last line convert the long term | ||
covariance from Tensor to NumPy array type. | ||
|
||
.. code-block:: python | ||
import numpy as np | ||
from arch.experimental import use_backend | ||
from arch.covariance.kernel import NeweyWest | ||
with use_backend("tensorflow"): | ||
cov = NeweyWest(data).cov | ||
long_term_cov = np.asarray(cov.long_term) | ||
2. Global | ||
|
||
Users can also configure the backend engine in global level with function | ||
``set_backend``. | ||
|
||
.. code-block:: python | ||
from arch.experimental import set_backend | ||
set_backend("tensorflow") | ||
# Output is already in TensorFlow Tensor type | ||
long_term_cov = NeweyWest(data).cov.long_term | ||
For further examples, please refer to the example | ||
`notebook <experimental_accelerated_numpy.ipynb>`_. | ||
|
||
Configure | ||
--------- | ||
|
||
.. autosummary:: | ||
:toctree: generated/ | ||
|
||
use_backend | ||
set_backend | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.