folx 0.2__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.
- folx-0.2/LICENSE +21 -0
- folx-0.2/PKG-INFO +216 -0
- folx-0.2/README.md +181 -0
- folx-0.2/folx/__init__.py +24 -0
- folx-0.2/folx/api.py +374 -0
- folx-0.2/folx/custom_hessian.py +59 -0
- folx-0.2/folx/hessian.py +346 -0
- folx-0.2/folx/interpreter.py +221 -0
- folx-0.2/folx/jvp.py +438 -0
- folx-0.2/folx/operators.py +72 -0
- folx-0.2/folx/tree_utils.py +81 -0
- folx-0.2/folx/utils.py +519 -0
- folx-0.2/folx/vmap.py +67 -0
- folx-0.2/folx/wrapped_functions.py +303 -0
- folx-0.2/folx/wrapper.py +130 -0
- folx-0.2/pyproject.toml +43 -0
folx-0.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|
folx-0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: folx
|
|
3
|
+
Version: 0.2
|
|
4
|
+
Summary: forward mode laplacian for jax
|
|
5
|
+
Home-page: https://github.com/microsoft/folx
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: jax,laplacian,numeric
|
|
8
|
+
Author: Nicholas Gao
|
|
9
|
+
Author-email: n.gao@tum.de
|
|
10
|
+
Maintainer: Nicholas Gao
|
|
11
|
+
Maintainer-email: n.gao@tum.de
|
|
12
|
+
Requires-Python: >=3.10,<4.0
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Environment :: GPU :: NVIDIA CUDA
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
27
|
+
Requires-Dist: jax
|
|
28
|
+
Requires-Dist: jaxlib
|
|
29
|
+
Requires-Dist: jaxtyping
|
|
30
|
+
Requires-Dist: numpy
|
|
31
|
+
Requires-Dist: pytest
|
|
32
|
+
Project-URL: Repository, https://github.com/microsoft/folx
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# `folx` - Forward Laplacian for JAX
|
|
36
|
+
|
|
37
|
+
This submodule implements the forward laplacian from https://arxiv.org/abs/2307.08214. It is implemented as a [custom interpreter for Jaxprs](https://jax.readthedocs.io/en/latest/notebooks/Writing_custom_interpreters_in_Jax.html).
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
Either clone repo and install locally via
|
|
42
|
+
```bash
|
|
43
|
+
poetry install
|
|
44
|
+
```
|
|
45
|
+
or
|
|
46
|
+
```bash
|
|
47
|
+
pip install .
|
|
48
|
+
```
|
|
49
|
+
or install via `pip` package manager via
|
|
50
|
+
```bash
|
|
51
|
+
pip install folx
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Example
|
|
55
|
+
For simple usage, one can decorate any function with `forward_laplacian`.
|
|
56
|
+
```python
|
|
57
|
+
import numpy as np
|
|
58
|
+
from folx import forward_laplacian
|
|
59
|
+
|
|
60
|
+
def f(x):
|
|
61
|
+
return (x**2).sum()
|
|
62
|
+
|
|
63
|
+
fwd_f = forward_laplacian(f)
|
|
64
|
+
result = fwd_f(np.arange(3, dtype=float))
|
|
65
|
+
result.x # f(x) 3
|
|
66
|
+
result.jacobian.dense_array # J_f(x) [0, 2, 4]
|
|
67
|
+
result.laplacian # tr(H_f(x)) 6
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Introduction
|
|
71
|
+
To avoid custom wrappers for all of JAX's commands, the forward laplacian is implemented as custom interpreter for Jaxpr.
|
|
72
|
+
This means if you have a function
|
|
73
|
+
```python
|
|
74
|
+
class Fn(Protocol):
|
|
75
|
+
def __call__(self, *args: PyTree[Array]) -> PyTree[Array]:
|
|
76
|
+
...
|
|
77
|
+
```
|
|
78
|
+
the resulting function will have the signature:
|
|
79
|
+
```python
|
|
80
|
+
class LaplacianFn(Protocol):
|
|
81
|
+
def __call__(self, *args: PyTree[Array]) -> PyTree[FwdLaplArray]:
|
|
82
|
+
...
|
|
83
|
+
```
|
|
84
|
+
where `FwdLaplArray` is a triplet of
|
|
85
|
+
```python
|
|
86
|
+
FwdLaplArray.x # jax.Array f(x) f(x).shape
|
|
87
|
+
FwdLaplArray.jacobian # FwdJacobian J_f(x)
|
|
88
|
+
FwdLaplArray.laplacian # jax.Array tr(H_f(x)) f(x).shape
|
|
89
|
+
```
|
|
90
|
+
The jacobian is implemented by a custom class as the forward laplacian supports automatic sparsity. To get the full jacobian:
|
|
91
|
+
```python
|
|
92
|
+
FwdLaplArray.jacobian.dense_array # jax.Array (*f(x).shape, x.size)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Implementation idea
|
|
96
|
+
The idea is to rely on the original function and autodifferentiation to propagate `FwdLaplArray` forward instead of the regular `jax.Array`. The rules for updating `FwdLaplArray` are described by the pseudocode:
|
|
97
|
+
```python
|
|
98
|
+
x # FwdLaplArray
|
|
99
|
+
y = FwdLaplArray(
|
|
100
|
+
x=f(x.x),
|
|
101
|
+
jacobian=jvp(f, (x.x,), (x.jacobian)),
|
|
102
|
+
laplacian=tr_vhv(f, x.jacobian) + jvp(f, (x.x,), (x.laplacian,))
|
|
103
|
+
)
|
|
104
|
+
# tr_vhv is tr(J_f H_f J_f^T)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Implementation
|
|
108
|
+
|
|
109
|
+
When you call the function returned by `forward_laplacian(fn)`, we first use `jax.make_jaxpr` to obtain the jaxpr for `fn`.
|
|
110
|
+
But instead of using the [standard evaluation pipeline](https://github.com/google/jax/blob/776baba0a3fca15a909cb7d108eea830cbe3fc1d/jax/_src/core.py#L436), we use a custom interpreter that replaces all operations to propate `FwdLaplArray` forward instead of regular `jax.Array`.
|
|
111
|
+
|
|
112
|
+
### Package structure
|
|
113
|
+
The general structure of the package is
|
|
114
|
+
* `interpreter.py` contains the evaluation of jaxpr and exported function decorator.
|
|
115
|
+
* `wrapper.py` contains subfunction decorator that maps a function that takes `jax.Array`s to a function that accepts `FwdLaplArray`s instead.
|
|
116
|
+
* `wrapped_functions.py` contains a registry of predefined functions as well as utility functions to add new functions to the registry.
|
|
117
|
+
* `jvp.py` contains logic for jacobian vector products.
|
|
118
|
+
* `hessian.py` contains logic for tr(JHJ^T).
|
|
119
|
+
* `custom_hessian.py` contains special treatment logic for tr(JHJ^T).
|
|
120
|
+
* `api.py` contains general interfaces shared in the package.
|
|
121
|
+
* `operators.py` contains a forward laplacian operator as well as alternatives.
|
|
122
|
+
* `utils.py` contains several small utility functions.
|
|
123
|
+
* `tree_utils.py` contains several utility functions for PyTrees.
|
|
124
|
+
* `vmap.py` contains a batched vmap implementation to reduce memory usage by going through a batch sequentially in chunks.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
### Function Annotations
|
|
128
|
+
There is a default interpreter that will simply apply the rules outlined above but if additional information about a function is available, e.g., that it applies elementwise like `jnp.tanh`, we can do better.
|
|
129
|
+
These additional annotations are available in `wrapped_functions.py`'s `_LAPLACE_FN_REGISTRY`.
|
|
130
|
+
Specifically, to augment a function `fn` to accept `FwdLaplArray` instead of regular `jax.Array`, we wrap it with `wrap_forward_laplacian` from `fwd_laplacian.py`:
|
|
131
|
+
```python
|
|
132
|
+
wrap_forward_laplacian(jnp.tanh, in_axes=())
|
|
133
|
+
```
|
|
134
|
+
In this case, we annotate the function to be applied elementwise, i.e., `()` indicates that none of the axes are relevant for the function.
|
|
135
|
+
|
|
136
|
+
If we know nothing about which axes might be essential, one must pass `None` (the default value) to mark all axes as imporatnt, e.g.,
|
|
137
|
+
```python
|
|
138
|
+
wrap_forward_laplacian(jnp.sum, in_axes=None, flags=FunctionFlags.LINEAR)
|
|
139
|
+
```
|
|
140
|
+
However, in this case we know that a summation is a linear operation. This information is useful for fast hessian computations.
|
|
141
|
+
|
|
142
|
+
If you want rules to a function and add it to the registry you can do the following
|
|
143
|
+
```python
|
|
144
|
+
import jax
|
|
145
|
+
from folx import register_function, wrap_forward_laplacian
|
|
146
|
+
|
|
147
|
+
register_function(jax.lax.cos_p, wrap_forward_laplacian(f, in_axes=()))
|
|
148
|
+
# Now the tracer is aware that the cosine function is applied elementwise.
|
|
149
|
+
```
|
|
150
|
+
We can do even more by defining custom rules:
|
|
151
|
+
```python
|
|
152
|
+
import jax
|
|
153
|
+
from folx import register_function, wrap_forward_laplacian
|
|
154
|
+
|
|
155
|
+
# the jit is important
|
|
156
|
+
@jax.jit
|
|
157
|
+
def f(x):
|
|
158
|
+
return x
|
|
159
|
+
|
|
160
|
+
# define a custom jacobian hessian jacobian product rule
|
|
161
|
+
def custom_jac_hessian_jac(args, extra_args, merge, materialize_idx):
|
|
162
|
+
return jtu.tree_map(lambda x: jnp.full_like(x, 10), args.x)
|
|
163
|
+
|
|
164
|
+
# make sure to use the same name here as above
|
|
165
|
+
register_function("f", wrap_forward_laplacian(f, custom_jac_hessian_jac=custom_jac_hessian_jac))
|
|
166
|
+
|
|
167
|
+
@forward_laplacian
|
|
168
|
+
def g(x):
|
|
169
|
+
return f(x)
|
|
170
|
+
|
|
171
|
+
g(jnp.ones(())).laplacian # 10
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
### Sparsity
|
|
176
|
+
Sparsity is detected at compile time, this has the advantage of avoiding expensive index computations at runtime and enables efficient reductions. However, it completely prohibits dynamic indexing, i.e., if indices are data-dependent we will simply default to full jacobians.
|
|
177
|
+
|
|
178
|
+
As we know a lot about the sparsity structure apriori, e.g., that we are only sparse in one dimension, we use a custom sparsity operations that are more efficient than relying on JAX's default `BCOO` (further, at the time of writing, the support for `jax.experimental.sparse` is quite bad).
|
|
179
|
+
So, the sparsity data format is implemented in `FwdJacobian` in `api.py`. Instead of storing a dense array `(m, n)` for a function `f:R^n -> R^m`, we store only the non-zero data in a `(m,d)` array where `d<n` is the maximum number of non-zero inputs any output depends on.
|
|
180
|
+
To be able to recreate the larger `(m,n)` array from the `(m,d)` array, we additional keep track of the indices in the last dimension in a mask `(m,d)` dimensional array of integers `0<mask_ij<n`.
|
|
181
|
+
|
|
182
|
+
Masks are treated as compile time static and will be traced automatically. If the tracing is not possible, e.g., due to data dependent indexing, we will fall back to a dense implementation. These propagation rules are implemented in `jvp.py`.
|
|
183
|
+
|
|
184
|
+
##### Omnistaging
|
|
185
|
+
If arrays do not depend on the initial input, they are typically still traced to better optimize the final program. This is called [omnistaging](https://github.com/google/jax/pull/3370). While this generally is beneficial, it does not allow us to perform indexing as tracer hide the actual data.
|
|
186
|
+
So, if we use sparsity we want to compute all arrays that do not explicitly depend on the input such that we could use them for index operations.
|
|
187
|
+
While this is not documented, it can be accomplished by overwriting the global trace via:
|
|
188
|
+
```python
|
|
189
|
+
from jax import core
|
|
190
|
+
|
|
191
|
+
with core.new_main(core.EvalTrace, dynamic=True):
|
|
192
|
+
...
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
|
|
197
|
+
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
|
198
|
+
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
|
199
|
+
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
|
200
|
+
|
|
201
|
+
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
|
202
|
+
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
|
203
|
+
provided by the bot. You will only need to do this once across all repos using our CLA.
|
|
204
|
+
|
|
205
|
+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
206
|
+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
|
207
|
+
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
208
|
+
|
|
209
|
+
## Trademarks
|
|
210
|
+
|
|
211
|
+
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
|
|
212
|
+
trademarks or logos is subject to and must follow
|
|
213
|
+
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
|
214
|
+
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
|
215
|
+
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
|
216
|
+
|
folx-0.2/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# `folx` - Forward Laplacian for JAX
|
|
2
|
+
|
|
3
|
+
This submodule implements the forward laplacian from https://arxiv.org/abs/2307.08214. It is implemented as a [custom interpreter for Jaxprs](https://jax.readthedocs.io/en/latest/notebooks/Writing_custom_interpreters_in_Jax.html).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Either clone repo and install locally via
|
|
8
|
+
```bash
|
|
9
|
+
poetry install
|
|
10
|
+
```
|
|
11
|
+
or
|
|
12
|
+
```bash
|
|
13
|
+
pip install .
|
|
14
|
+
```
|
|
15
|
+
or install via `pip` package manager via
|
|
16
|
+
```bash
|
|
17
|
+
pip install folx
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Example
|
|
21
|
+
For simple usage, one can decorate any function with `forward_laplacian`.
|
|
22
|
+
```python
|
|
23
|
+
import numpy as np
|
|
24
|
+
from folx import forward_laplacian
|
|
25
|
+
|
|
26
|
+
def f(x):
|
|
27
|
+
return (x**2).sum()
|
|
28
|
+
|
|
29
|
+
fwd_f = forward_laplacian(f)
|
|
30
|
+
result = fwd_f(np.arange(3, dtype=float))
|
|
31
|
+
result.x # f(x) 3
|
|
32
|
+
result.jacobian.dense_array # J_f(x) [0, 2, 4]
|
|
33
|
+
result.laplacian # tr(H_f(x)) 6
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Introduction
|
|
37
|
+
To avoid custom wrappers for all of JAX's commands, the forward laplacian is implemented as custom interpreter for Jaxpr.
|
|
38
|
+
This means if you have a function
|
|
39
|
+
```python
|
|
40
|
+
class Fn(Protocol):
|
|
41
|
+
def __call__(self, *args: PyTree[Array]) -> PyTree[Array]:
|
|
42
|
+
...
|
|
43
|
+
```
|
|
44
|
+
the resulting function will have the signature:
|
|
45
|
+
```python
|
|
46
|
+
class LaplacianFn(Protocol):
|
|
47
|
+
def __call__(self, *args: PyTree[Array]) -> PyTree[FwdLaplArray]:
|
|
48
|
+
...
|
|
49
|
+
```
|
|
50
|
+
where `FwdLaplArray` is a triplet of
|
|
51
|
+
```python
|
|
52
|
+
FwdLaplArray.x # jax.Array f(x) f(x).shape
|
|
53
|
+
FwdLaplArray.jacobian # FwdJacobian J_f(x)
|
|
54
|
+
FwdLaplArray.laplacian # jax.Array tr(H_f(x)) f(x).shape
|
|
55
|
+
```
|
|
56
|
+
The jacobian is implemented by a custom class as the forward laplacian supports automatic sparsity. To get the full jacobian:
|
|
57
|
+
```python
|
|
58
|
+
FwdLaplArray.jacobian.dense_array # jax.Array (*f(x).shape, x.size)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Implementation idea
|
|
62
|
+
The idea is to rely on the original function and autodifferentiation to propagate `FwdLaplArray` forward instead of the regular `jax.Array`. The rules for updating `FwdLaplArray` are described by the pseudocode:
|
|
63
|
+
```python
|
|
64
|
+
x # FwdLaplArray
|
|
65
|
+
y = FwdLaplArray(
|
|
66
|
+
x=f(x.x),
|
|
67
|
+
jacobian=jvp(f, (x.x,), (x.jacobian)),
|
|
68
|
+
laplacian=tr_vhv(f, x.jacobian) + jvp(f, (x.x,), (x.laplacian,))
|
|
69
|
+
)
|
|
70
|
+
# tr_vhv is tr(J_f H_f J_f^T)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Implementation
|
|
74
|
+
|
|
75
|
+
When you call the function returned by `forward_laplacian(fn)`, we first use `jax.make_jaxpr` to obtain the jaxpr for `fn`.
|
|
76
|
+
But instead of using the [standard evaluation pipeline](https://github.com/google/jax/blob/776baba0a3fca15a909cb7d108eea830cbe3fc1d/jax/_src/core.py#L436), we use a custom interpreter that replaces all operations to propate `FwdLaplArray` forward instead of regular `jax.Array`.
|
|
77
|
+
|
|
78
|
+
### Package structure
|
|
79
|
+
The general structure of the package is
|
|
80
|
+
* `interpreter.py` contains the evaluation of jaxpr and exported function decorator.
|
|
81
|
+
* `wrapper.py` contains subfunction decorator that maps a function that takes `jax.Array`s to a function that accepts `FwdLaplArray`s instead.
|
|
82
|
+
* `wrapped_functions.py` contains a registry of predefined functions as well as utility functions to add new functions to the registry.
|
|
83
|
+
* `jvp.py` contains logic for jacobian vector products.
|
|
84
|
+
* `hessian.py` contains logic for tr(JHJ^T).
|
|
85
|
+
* `custom_hessian.py` contains special treatment logic for tr(JHJ^T).
|
|
86
|
+
* `api.py` contains general interfaces shared in the package.
|
|
87
|
+
* `operators.py` contains a forward laplacian operator as well as alternatives.
|
|
88
|
+
* `utils.py` contains several small utility functions.
|
|
89
|
+
* `tree_utils.py` contains several utility functions for PyTrees.
|
|
90
|
+
* `vmap.py` contains a batched vmap implementation to reduce memory usage by going through a batch sequentially in chunks.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
### Function Annotations
|
|
94
|
+
There is a default interpreter that will simply apply the rules outlined above but if additional information about a function is available, e.g., that it applies elementwise like `jnp.tanh`, we can do better.
|
|
95
|
+
These additional annotations are available in `wrapped_functions.py`'s `_LAPLACE_FN_REGISTRY`.
|
|
96
|
+
Specifically, to augment a function `fn` to accept `FwdLaplArray` instead of regular `jax.Array`, we wrap it with `wrap_forward_laplacian` from `fwd_laplacian.py`:
|
|
97
|
+
```python
|
|
98
|
+
wrap_forward_laplacian(jnp.tanh, in_axes=())
|
|
99
|
+
```
|
|
100
|
+
In this case, we annotate the function to be applied elementwise, i.e., `()` indicates that none of the axes are relevant for the function.
|
|
101
|
+
|
|
102
|
+
If we know nothing about which axes might be essential, one must pass `None` (the default value) to mark all axes as imporatnt, e.g.,
|
|
103
|
+
```python
|
|
104
|
+
wrap_forward_laplacian(jnp.sum, in_axes=None, flags=FunctionFlags.LINEAR)
|
|
105
|
+
```
|
|
106
|
+
However, in this case we know that a summation is a linear operation. This information is useful for fast hessian computations.
|
|
107
|
+
|
|
108
|
+
If you want rules to a function and add it to the registry you can do the following
|
|
109
|
+
```python
|
|
110
|
+
import jax
|
|
111
|
+
from folx import register_function, wrap_forward_laplacian
|
|
112
|
+
|
|
113
|
+
register_function(jax.lax.cos_p, wrap_forward_laplacian(f, in_axes=()))
|
|
114
|
+
# Now the tracer is aware that the cosine function is applied elementwise.
|
|
115
|
+
```
|
|
116
|
+
We can do even more by defining custom rules:
|
|
117
|
+
```python
|
|
118
|
+
import jax
|
|
119
|
+
from folx import register_function, wrap_forward_laplacian
|
|
120
|
+
|
|
121
|
+
# the jit is important
|
|
122
|
+
@jax.jit
|
|
123
|
+
def f(x):
|
|
124
|
+
return x
|
|
125
|
+
|
|
126
|
+
# define a custom jacobian hessian jacobian product rule
|
|
127
|
+
def custom_jac_hessian_jac(args, extra_args, merge, materialize_idx):
|
|
128
|
+
return jtu.tree_map(lambda x: jnp.full_like(x, 10), args.x)
|
|
129
|
+
|
|
130
|
+
# make sure to use the same name here as above
|
|
131
|
+
register_function("f", wrap_forward_laplacian(f, custom_jac_hessian_jac=custom_jac_hessian_jac))
|
|
132
|
+
|
|
133
|
+
@forward_laplacian
|
|
134
|
+
def g(x):
|
|
135
|
+
return f(x)
|
|
136
|
+
|
|
137
|
+
g(jnp.ones(())).laplacian # 10
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
### Sparsity
|
|
142
|
+
Sparsity is detected at compile time, this has the advantage of avoiding expensive index computations at runtime and enables efficient reductions. However, it completely prohibits dynamic indexing, i.e., if indices are data-dependent we will simply default to full jacobians.
|
|
143
|
+
|
|
144
|
+
As we know a lot about the sparsity structure apriori, e.g., that we are only sparse in one dimension, we use a custom sparsity operations that are more efficient than relying on JAX's default `BCOO` (further, at the time of writing, the support for `jax.experimental.sparse` is quite bad).
|
|
145
|
+
So, the sparsity data format is implemented in `FwdJacobian` in `api.py`. Instead of storing a dense array `(m, n)` for a function `f:R^n -> R^m`, we store only the non-zero data in a `(m,d)` array where `d<n` is the maximum number of non-zero inputs any output depends on.
|
|
146
|
+
To be able to recreate the larger `(m,n)` array from the `(m,d)` array, we additional keep track of the indices in the last dimension in a mask `(m,d)` dimensional array of integers `0<mask_ij<n`.
|
|
147
|
+
|
|
148
|
+
Masks are treated as compile time static and will be traced automatically. If the tracing is not possible, e.g., due to data dependent indexing, we will fall back to a dense implementation. These propagation rules are implemented in `jvp.py`.
|
|
149
|
+
|
|
150
|
+
##### Omnistaging
|
|
151
|
+
If arrays do not depend on the initial input, they are typically still traced to better optimize the final program. This is called [omnistaging](https://github.com/google/jax/pull/3370). While this generally is beneficial, it does not allow us to perform indexing as tracer hide the actual data.
|
|
152
|
+
So, if we use sparsity we want to compute all arrays that do not explicitly depend on the input such that we could use them for index operations.
|
|
153
|
+
While this is not documented, it can be accomplished by overwriting the global trace via:
|
|
154
|
+
```python
|
|
155
|
+
from jax import core
|
|
156
|
+
|
|
157
|
+
with core.new_main(core.EvalTrace, dynamic=True):
|
|
158
|
+
...
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Contributing
|
|
162
|
+
|
|
163
|
+
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
|
164
|
+
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
|
165
|
+
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
|
166
|
+
|
|
167
|
+
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
|
168
|
+
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
|
169
|
+
provided by the bot. You will only need to do this once across all repos using our CLA.
|
|
170
|
+
|
|
171
|
+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
172
|
+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
|
173
|
+
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
174
|
+
|
|
175
|
+
## Trademarks
|
|
176
|
+
|
|
177
|
+
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
|
|
178
|
+
trademarks or logos is subject to and must follow
|
|
179
|
+
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
|
180
|
+
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
|
181
|
+
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .interpreter import forward_laplacian
|
|
2
|
+
from .operators import (
|
|
3
|
+
ForwardLaplacianOperator,
|
|
4
|
+
LaplacianOperator,
|
|
5
|
+
LoopLaplacianOperator,
|
|
6
|
+
ParallelLaplacianOperator,
|
|
7
|
+
)
|
|
8
|
+
from .vmap import batched_vmap
|
|
9
|
+
from .wrapper import wrap_forward_laplacian, warp_without_fwd_laplacian
|
|
10
|
+
from .wrapped_functions import deregister_function, register_function
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"batched_vmap",
|
|
15
|
+
"forward_laplacian",
|
|
16
|
+
"ForwardLaplacianOperator",
|
|
17
|
+
"LaplacianOperator",
|
|
18
|
+
"LoopLaplacianOperator",
|
|
19
|
+
"ParallelLaplacianOperator",
|
|
20
|
+
"wrap_forward_laplacian",
|
|
21
|
+
"warp_without_fwd_laplacian",
|
|
22
|
+
"deregister_function",
|
|
23
|
+
"register_function",
|
|
24
|
+
]
|