jaxtyping
repository·main·Indexed 22 days ago
https://github.com/patrick-kidger/jaxtypingType annotations and runtime checking for the shape and dtype of arrays and tensors across multiple frameworks, including JAX, PyTorch, NumPy, MLX, and TensorFlow. It allows specifying data types and dimensions using string literals and supports integration with runtime checkers like typeguard and beartype, as well as JAX-specific PyTree annotations.
What's inside jaxtyping
- jaxtyping is a library for providing type annotations and runtime type-checking for the shape and dtype of arrays and tensors. While the name is historical, it supports JAX, PyTorch, NumPy, MLX, and TensorFlow without requiring a JAX dependency.
How jaxtyping interacts with jax.jit
mainjaxtyping is compatible withjax.jit. When usingjax.jit, dtype and shape checking occurs at trace time (when JAX traces the function prior to compilation). The resulting compiled code does not contain any dtype/shape-checking overhead, ensuring performance remains identical to untyped JAX code.Annotate arrays with shape and dtype
mainYou can annotate arrays using the syntax
dtype[array_type, shape]. This allows you to specify both the expected data type and the dimensions of the array in a single type hint.Example:
jaxtyping.Float[torch.Tensor, "batch channels"]specifies a PyTorch tensor where the data is floating-point and the shape has two axes namedbatchandchannels.jaxtyping.Float[torch.Tensor, "batch channels"]Use path-dependent shapes with the '?' prefix
mainYou can use the
?prefix in a shape string (e.g.,?foo) to indicate that an axis size is path-dependent. This means the size of that axis can vary depending on which leaf of the PyTree is being inspected, but it must remain consistent across different PyTrees sharing the same structure template.When you use
?foo,jaxtypinginternally treats each leaf as having a unique version of that dimension (e.g.,0foo,1foo, etc.). This allows you to enforce that corresponding leaves in two different PyTrees have matching dimensions, even if the dimensions differ between leaves within the same tree.def f( x: PyTree[Shaped[jax.Array, "?foo"], "T"], y: PyTree[Shaped[jax.Array, "?foo"], "T"], ): pass # Example of valid usage: x0 = jnp.arange(3) x1 = jnp.arange(5) y0 = jnp.arange(3) + 1 y1 = jnp.arange(5) + 1 # This works because x0 matches y0 and x1 matches y1 f((x0, x1), (y0, y1)) # This fails because x1 (size 5) does not match y0 (size 3) # f((x1, x1), (y0, y1))Compatibility with static type checkers (mypy, pyright, pytype)
mainjaxtyping provides partial support for static type checkers. Annotations like
dtype[array, shape]are treated as justarrayby static checkers, as full dtype/shape checking is currently beyond the scope of static type checking capabilities.- mypy and pyright: Work correctly.
- pytype: Has a known bug where
dtype[array, shape]is sometimes treated asAnyinstead ofarray.
Supported array types and duck-typing
mainThe following frameworks are supported as the
array_typeargument:jax.Array/jax.numpy.ndarraynp.ndarray(NumPy)torch.Tensor(PyTorch)tf.Tensor(TensorFlow)mx.array(MLX)
Duck-typing: You can use any object that has
.shape(returningtuple[int, ...]) and.dtype(returning astr). To use custom dtypes with duck-typed arrays, inherit fromjaxtyping.AbstractDtypeand define thedtypeslist.class MyDuckArray: @property def shape(self) -> tuple[int, ...]: return (3, 4, 5) @property def dtype(self) -> str: return "my_dtype" class MyDtype(jaxtyping.AbstractDtype): dtypes = ["my_dtype"] x = MyDuckArray() assert isinstance(x, MyDtype[MyDuckArray, "3 4 5"])Define array shapes and axis symbols
mainShapes are defined as space-separated strings. Supported symbols include:
int: A fixed-size axis (e.g.,"28 28").str: A variable-size axis name (e.g.,"channels"). These names are used to match axes across different function arguments.- Symbolic expressions: Expressions using other variable-size axes (e.g.,
"dim-1"). These must not contain spaces. ...: Anonymous zero or more axes (equivalent to*_).""(Empty string): Represents a scalar shape.
Note on Symbolic Expressions: To use local variables or class attributes in symbolic expressions, wrap them in curly braces
{}. They are evaluated as f-strings first, then as axis sizes.def full(size: int, fill: float) -> Float[jax.Array, "{size}"]: return jax.numpy.full((size,), fill) class SomeClass: some_value = 5 def full(self, fill: float) -> Float[jax.Array, "{self.some_value}+3"]: return jax.numpy.full((self.some_value + 3,), fill)Runtime type checking with jax.jit
mainRuntime type checking is compatible withjax.jit. All shape checks are performed during the tracing process and do not impact the runtime performance of the compiled function.Configure jaxtyping for Pytest
mainYou can install the jaxtyping import hook at test-time only by using a pytest hook. This is useful for ensuring type safety during your test suite execution without affecting production code.Introspect jaxtyping types
mainIf you are building tools like type hint parsers or decorators, you can detect if a Python object is a
jaxtypingtype usingissubclass.- To check if an object is a dtype: Use
issubclass(x, AbstractDtype). - To check if an object is an array type: Use
issubclass(x, AbstractArray). - To check if an object is a pytree type: Use
issubclass(x, PyTree).
- To check if an object is a dtype: Use
Install jaxtyping
mainInstall
jaxtypingusing pip. Note that it requires Python 3.10 or higher.To enable runtime type-checking of array shapes and dtypes, it is recommended to also install a runtime type-checking library such as
typeguardorbeartype.pip install jaxtypingEnable jaxtyping in IPython or Jupyter Notebooks
mainIn IPython environments like Jupyter or Colab, you can use a magic command to automatically apply the jaxtyping hook to everything defined in the notebook after the magic is run.
import jaxtyping %load_ext jaxtyping %jaxtyping.typechecker beartype.beartype # or any other runtime type checker