Skip to main content

Bind any function written in another language to JAX with support for JVP/VJP/batching/jit compilation

Project description

JAXbind: Bind any function to JAX

JAXbind API documentation: nifty-ppl.github.io/JAXbind/ | Found a bug? github.com/NIFTy-PPL/JAXbind/issues | Need help? github.com/NIFTy-PPL/JAXbind/discussions

Summary

The existing interface in JAX for connecting fully differentiable custom code requires deep knowledge of JAX and its C++ backend. The aim of JAXbind is to drastically lower the burden of connecting custom functions implemented in other programming languages to JAX. Specifically, JAXbind provides an easy-to-use Python interface for defining custom, so-called JAX primitives. Via JAXbind, any function callable from Python can be exposed as a JAX primitive. JAXbind allows to interface the JAX function transformation engine with custom derivatives and batching rules, enabling all JAX transformations for the custom primitive. In contrast, the JAX built-in external callback interface also has a Python endpoint but the external callbacks cannot be fully integrated into the JAX transformation engine, as only the Jacobian-vector product or the vector-Jacobian product can be added but not both.

Automatic Differentiation and Code Example

Automatic differentiation is a core feature of JAX and often one of the main reasons for using it. Thus, it is essential that custom functions registered with JAX support automatic differentiation. In the following, we will outline which functions our package respectively JAX requires to enable automatic differentiation. For simplicity, we assume that we want to connect the nonlinear function $f(x_1,x_2) = x_1x_2^2$ to JAX. The JAXbind package expects the Python function for $f$ to take three positional arguments. The first argument, out, is a tuple into which the function results are written. The second argument is also a tuple containing the input to the function, in our case, $x_1$ and $x_2$. Via kwargs_dump, potential keyword arguments given to the later registered Jax primitive can be forwarded to f in serialized form.

import jaxbind

def f(out, args, kwargs_dump):
    kwargs = jaxbind.load_kwargs(kwargs_dump)
    x1, x2 = args
    out[0][()] = x1 * x2**2

JAX's automatic differentiation engine can compute the Jacobian-vector product jvp and vector-Jacobian product vjp of JAX primitives. The Jacobian-vector product in JAX is a function applying the Jacobian of $f$ at a position $x$ to a tangent vector. In mathematical nomenclature this operation is called the pushforward of $f$ and can be denoted as $\partial f(x): T_x X \mapsto T_{f(x)} Y$, with $T_x X$ and $T_{f(x)} Y$ being the tangent spaces of $X$ and $Y$ at the positions $x$ and $f(x)$. As the implementation of $f$ is not JAX native, JAX cannot automatically compute the jvp. Instead, an implementation of the pushforward has to be provided, which JAXbind will register as the jvp of the JAX primitive of $f$. For our example, this Jacobian-vector-product function is given by $\partial f(x_1,x_2)(dx_1,dx_2) = x_2^2dx_1 + 2x_1x_2dx_2$.

def f_jvp(out, args, kwargs_dump):
    kwargs = jaxbind.load_kwargs(kwargs_dump)
    x1, x2, dx1, dx2 = args
    out[0][()] = x2**2 * dx1 + 2 * x1 * x2 * dx2

The vector-Jacobian product vjp in JAX is the linear transpose of the Jacobian-vector product. In mathematical nomenclature this is the pullback $(\partial f(x))^{T}: T_{f(x)}Y \mapsto T_x X$ of $f$. Analogously to the jvp, the user has to implement this function as JAX cannot automatically construct it. For our example function, the vector-Jacobian product is $(\partial f(x_1,x_2))^{T}(dy) = (x_2^2dy, 2x_1x_2dy)$.

def f_vjp(out, args, kwargs_dump):
    kwargs = jaxbind.load_kwargs(kwargs_dump)
    x1, x2, dy = args
    out[0][()] = x2**2 * dy
    out[1][()] = 2 * x1 * x2 * dy

To just-in-time compile the function, JAX needs to abstractly evaluate the code, i.e. it needs to be able to know the shape and dtype of the output of the custom function given only the shape and dtype of the input. We have to provide these abstract evaluation functions returning the output shape and dtype given an input shape and dtype for f as well as for the vjp application. The output shape of the jvp is identical to the output shape of f itself and does not need to be specified again. Due to the internals of JAX the abstract evaluation functions take normal keyword arguments and not serialized keyword arguments.

def f_abstract(*args, **kwargs):
    assert args[0].shape == args[1].shape
    return ((args[0].shape, args[0].dtype),)

def f_abstract_T(*args, **kwargs):
    return (
        (args[0].shape, args[0].dtype),
        (args[0].shape, args[0].dtype),
    )

We have now defined all ingredients necessary to register a JAX primitive for our function $f$ using the JAXbind package.

f_jax = jaxbind.get_nonlinear_call(
    f, (f_jvp, f_vjp), f_abstract, f_abstract_T
)

f_jax is a JAX primitive registered via the JAXbind package supporting all JAX transformations. We can now compute the jvp and vjp of the new JAX primitive and even jit-compile and batch it.

import jax
import jax.numpy as jnp

inp = (jnp.full((4,3), 4.), jnp.full((4,3), 2.))
tan = (jnp.full((4,3), 1.), jnp.full((4,3), 1.))
res, res_tan = jax.jvp(f_jax, inp, tan)

cotan = [jnp.full((4,3), 6.)]
res, f_vjp = jax.vjp(f_jax, *inp)
res_cotan = f_vjp(cotan)

f_jax_jit = jax.jit(f_jax)
res = f_jax_jit(*inp)

Higher Order Derivatives and Linear Functions

JAX supports higher order derivatives and can differentiate a jvp or vjp with respect to the position at which the Jacobian was taken. Similar to first derivatives, JAX can not automatically compute higher derivatives of a general function $f$ that is not natively implemented in JAX. Higher order derivatives would again need to be provided by the user. For many algorithms, first derivatives are sufficient, and higher order derivatives are often not implemented by the high-performance codes. Therefore, the current interface of JAXbind is, for simplicity, restricted to first derivatives. In the future, the interface could be easily expanded if specific use cases require higher order derivatives.

In scientific computing, linear functions such as, e.g., spherical harmonic transforms are widespread. If the function $f$ is linear, differentiation becomes trivial. Specifically for a linear function $f$, the pushforward respectively the jvp of $f$ is identical to $f$ itself and independent of the position at which it is computed. Expressed in formulas, $\partial f(x)(dx) = f(dx)$ if $f$ is linear in $x$. Analogously, the pullback respectively the vjp becomes independent of the initial position and is given by the linear transpose of $f$, thus $(\partial f(x))^{T}(dy) = f^T(dy)$. Also, all higher order derivatives can be expressed in terms of $f$ and its transpose. To make use of these simplifications, JAXbind provides a special interface for linear functions, supporting higher order derivatives, only requiring an implementation of the function and its transpose.

Demos and Documentation

Additional demos can be found in the demos folder. Specifically, there is a basic demo 01_linear_function.py showcasing the interface for linear functions and custom batching rules. 02_multilinear_function.py binds a multi-linear function as a JAX primitive. Finally, 03_nonlinear_function.py demonstrates the interface for non-linear functions and shows how to deal with fixed arguments, which cannot be differentiated. JAXbind provides bindings to parts of the functionality of the DUCC package. The DUCC bindings are also exposed as a webpage to showcase a real-world example of the usage of JAXbind. The documentation of the JAXbind API is available here.

Platforms

Currently, JAXbind only has CPU but no GPU support. With some expertise on Python bindings for GPU kernels adding GPU support should be fairly simple. The interfacing with the JAX automatic differentiation engine is identical for CPU and GPU. Contributions are welcome!

Installation

Binary wheels for JAXbind can be obtained and installed from PyPI via:

pip install jaxbind

To install JAXbind from source, clone the repository and install the package via pip.

git clone https://github.com/NIFTy-PPL/jaxbind.git
cd jaxbind
pip install .

Contributing

Contributions are highly appreciated! Please open an issue first if you think your PR changes current code substantially. Please format your code using black. PRs affecting the public API, including adding new features, should update the public documentation. If possible, add appropriate tests to your PR. Feel free to open a PR early on in the development process, we are happy to help in the development process and provide feedback along the way.

Licensing terms

All source code in this package is released under the 2-clause BSD license. All of JAXbind is distributed without any warranty.

Citing JAXbind

To cite JAXbind, please use the citation provided below.

@article{jaxbind,
    title = {JAXbind: Bind any function to JAX},
    author = {Jakob Roth and Martin Reinecke and Gordian Edenhofer},
    year = {2024},
    journal = {Journal of Open Source Software},
    publisher = {The Open Journal},
    volume = {9},
    number = {98},
    pages = {6532},
    doi = {10.21105/joss.06532},
    url = {https://doi.org/10.21105/joss.06532},
}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jaxbind-1.3.1.tar.gz (48.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

jaxbind-1.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (85.3 kB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

jaxbind-1.3.1-cp314-cp314t-macosx_11_0_arm64.whl (91.7 kB view details)

Uploaded CPython 3.14tmacOS 11.0+ ARM64

jaxbind-1.3.1-cp314-cp314-win_amd64.whl (73.9 kB view details)

Uploaded CPython 3.14Windows x86-64

jaxbind-1.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (83.7 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

jaxbind-1.3.1-cp314-cp314-macosx_11_0_arm64.whl (88.8 kB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

jaxbind-1.3.1-cp313-cp313-win_amd64.whl (72.5 kB view details)

Uploaded CPython 3.13Windows x86-64

jaxbind-1.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (83.7 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

jaxbind-1.3.1-cp313-cp313-macosx_11_0_arm64.whl (88.6 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

jaxbind-1.3.1-cp312-cp312-win_amd64.whl (72.5 kB view details)

Uploaded CPython 3.12Windows x86-64

jaxbind-1.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (83.7 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

jaxbind-1.3.1-cp312-cp312-macosx_11_0_arm64.whl (88.6 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

jaxbind-1.3.1-cp311-cp311-win_amd64.whl (72.9 kB view details)

Uploaded CPython 3.11Windows x86-64

jaxbind-1.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (83.9 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

jaxbind-1.3.1-cp311-cp311-macosx_11_0_arm64.whl (89.9 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

jaxbind-1.3.1-cp310-cp310-win_amd64.whl (72.8 kB view details)

Uploaded CPython 3.10Windows x86-64

jaxbind-1.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (83.8 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

jaxbind-1.3.1-cp310-cp310-macosx_11_0_arm64.whl (90.0 kB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

Details for the file jaxbind-1.3.1.tar.gz.

File metadata

  • Download URL: jaxbind-1.3.1.tar.gz
  • Upload date:
  • Size: 48.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for jaxbind-1.3.1.tar.gz
Algorithm Hash digest
SHA256 8f423ccdc8c86d7088aaf07a1da7ea9db8551150ecb8e01974c18a7dc312dcc5
MD5 e8e851a1935b1cf470637e46e58c5bb3
BLAKE2b-256 dc3db34b05583dedc6826a6fc1bbca56cec8a1981975ff6d7a278a0e3685dff5

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 94cd276aee187aeb5bd583189f65c52ffd8a981446e523d946d363355952c799
MD5 3be44fef561ef2f91872577899d94f5b
BLAKE2b-256 958dce2b35b775053cfdb54615dd9e26c85e2e57e50b1d42e6c0fd51498ecc7a

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp314-cp314t-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp314-cp314t-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 fa6b02934917ccaa99433d0efe6c7436d98de6d302b98501dc36d6a4dee7a55e
MD5 dbf396b4a82d5c5e05b61da4950596ff
BLAKE2b-256 74013a0ce4517b70fb0bcf64680d019ee6aa234e4a147d8fef61e07ab5f4b9b2

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: jaxbind-1.3.1-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 73.9 kB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for jaxbind-1.3.1-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 3c9f5ad9ab215b1ebe91273075b1bb44c6972b6a802b106a0f3ce1d11165b19f
MD5 6d2b3b94388bdccad6a88d045b142881
BLAKE2b-256 28ab42e725f0f236f7dd31d35cc60aa453858a48d58ec953dc4ac3ffa4015c60

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 3ae34bdcc0c2ad6ca125fb7719b992244f605c9ff72782123598b5e27c42350c
MD5 e9ab8c87fb01348f3b2c5d7c6b6bc4cb
BLAKE2b-256 2e62addc57ccd34e46a3febdb17c08a53db0f63f16781814aa02259e7c503570

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9453c658ef9a90f7ac0560d581da6fa95b6d53f68a8d50af000a3e24ef53b733
MD5 85460b01d9fe5778034b4a4bf5f81c59
BLAKE2b-256 df33168d97c44fa62fb48975f3848d8bac51e235dee98356bf5b5e7a5d0ebe21

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: jaxbind-1.3.1-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 72.5 kB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for jaxbind-1.3.1-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 098d7133f4ac2ca30fb86ef14f2f601c6d4c739190240c89ddfa1efb3630f727
MD5 d891b7979a4e359946506f31ed806f97
BLAKE2b-256 254982c95ae0baaf82aec2c4fb2626945a7e4487b6293df55121ddecbe336859

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 49b5bb01570477f798ec1ceb50cbd816a69c574e5720771020fed10c1c318b6c
MD5 c089b3ef4b43ccca42a27434931e5f5a
BLAKE2b-256 d4252fee3e91cfed79089003a6fdd928468e3d020cdaacb38534abd35d8597c0

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1331b89a1156c9d8ed2b74710abf71a64008b5e418d4d6396f49ae08aef8c866
MD5 a6a861321eb2c14bed9dcaddbef2a647
BLAKE2b-256 7a5ccd78a328bbfe820c81ad174f32d67ee5cca4505e2503088e67d44ac1356e

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: jaxbind-1.3.1-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 72.5 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for jaxbind-1.3.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 a4f11f02c3d16443e74d622cbb6de8d884d594b0a2389eb03f94131fbbdfda43
MD5 53524484406d183f41bbcc3c3ef4f7ee
BLAKE2b-256 1c9ae41fdf3907177bdc03942d3a65b90de8b7a4332d76c801d09253cab4f142

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ec6428fbb0532a727e982486da8592f1b80cfc311621dbb26c66ad958a4111a5
MD5 884f054082e49860c7e66cefc2c3f0b0
BLAKE2b-256 9839cacf85ab6a3630e3b16c60d2e42d6854d87389155c3c7dddfa07fc121c05

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 945e73586201c197e24884077b648473bddc55658611ce20f5d96f6c1f61c787
MD5 44ec41ce1343bb9313e4d3b0b9fd6233
BLAKE2b-256 8a565ffcb0b1b8a707bc338aa8a35d176179b95425f12b82518c42dd37ad0491

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: jaxbind-1.3.1-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 72.9 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for jaxbind-1.3.1-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 44bf0a3661e2e497794d4b10e95e848cf395d49a7c4abfff4fd51c1b03410d0a
MD5 3be4bc731c1b01f7e74aded3907763ac
BLAKE2b-256 2227d932fb50dd2548de4f00f97bdbbf9cb21d60ac7ed840c6673a0a59697bf9

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 013816384ab9fae48c6fbfb6e9062d3a535b0996c641ee3a3e0fddc2f90f2feb
MD5 96ab87279309beb5911ae1bbabd5b9b8
BLAKE2b-256 a25a5cdfe0a241924fd02626204761ea318f54889101ac29814b05f1b55c6a3d

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a225293ac060fc1ad212b9523c500c5cd0dcec237a105041fe79dec84dd6bd97
MD5 58c2795b8d7dfd56d1cb7539e1cf0f92
BLAKE2b-256 3dba60d13a80333d3e15d0cecc84dcf16cd54e76c1a59465aa6c22d6e6c07c5f

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: jaxbind-1.3.1-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 72.8 kB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for jaxbind-1.3.1-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 5dad4d41a1383f279951863dd55f836d08a2284ab7fd9bdf475a20e8223faa9e
MD5 504cfed9f04420ed141fd010f07de002
BLAKE2b-256 c82215c2e619b3ae1b1ceb8b26148811553a16195fc4118780066943fde4bc8a

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f608bf02d7beded2460505f5d59c402242a6dc56a4af32711c92aa1f0b210196
MD5 75a80747f0deecd43a4744acf2309cdb
BLAKE2b-256 0f240c7c8854b966fcefd3aaba7072cace681c8fde51110c6df77400933f3675

See more details on using hashes here.

File details

Details for the file jaxbind-1.3.1-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jaxbind-1.3.1-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d91acc09c2f7fcbd6cfce44d1077a9c28d1ea5986b3101ebf0db6cb04ddf3a05
MD5 49ef9cd72f3c2fa3cd3c6a0e08c80641
BLAKE2b-256 e0535c561e9884f9e14da50d2699b0eb421a3fd17aaae2ec8bcb549fa01a89ba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page