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 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
+ ]