Equinox Documentation

repository·main·Indexed 25 days ago

https://github.com/patrick-kidger/equinox

A JAX library for building neural networks and performing PyTree manipulation using a PyTorch-like syntax. Equinox treats models as PyTrees via the `equinox.Module` class, ensuring compatibility with JAX transformations like `jax.jit`, `jax.grad`, and `jax.vmap`. It includes utilities for handling non-array leaves with `eqx.partition` and `eqx.combine`, filtered transformations such as `eqx.filter_jit`, and advanced features in the `equinox.internal` namespace for backpropagating through loops and reducing compile times.

Tokens
34.4K
Snippets
60
Records
201
Agent score
83%

What's inside Equinox

  1. Explore Equinox ecosystem models

    main

    The Equinox ecosystem includes various model implementations across different domains such as LLMs, audio generation, and protein modeling. You can use these as reference implementations or building blocks for your own JAX-based projects.

    Language Models & Transformers:

    • Mistral 7B: Implementation of the Mistral 7B model.
    • PaLM: Implementation of the Pathways Language Model (PaLM).
    • nanoGPT: A minimalist port of nanoGPT (Generative Pre-trained Transformer).
    • PaliGemma 1/2 + OpenPI: VLM support including transfusion and the OpenPi Visual Language Action model.

    Audio & Generative Models:

    • HiFiGAN: Vocoder for mapping mel-spectrograms to audio.
    • VQVAE: Vector Quantized Variational Autoencoder for audio generation.

    Biological & Scientific Models:

    • ESM2quinox: The ESM2 protein language model.
    • esmj: The ESM-C protein language model.
    • joltz: Boltz-1 protein structure prediction model.
    • nequip-eqx: NequIP neural network interatomic potential model.
    • chronos2-jax: Port of the Chronos-2 time series foundation model.
  2. Compare Equinox with PyTorch

    main
    Equinox is built on JAX, which provides a stronger JIT compiler and more advanced features like jit-of-vmap, forward-mode autolinearisation, and autoparallelism compared to PyTorch. For scientific computing, Equinox benefits from the JAX ecosystem (e.g., Diffrax for differential equations). While PyTorch is often considered easier for beginners due to its more standard Pythonic feel, Equinox handles advanced use-cases more cleanly by using a single JIT compiler instead of the multiple, complex compilers found in PyTorch (torch.fx, torch.jit.script, torch.compile, etc.).
  3. Compare Equinox with Flax

    main

    Equinox differs from Flax in several key architectural ways:

    • Abstractions: Flax introduces several new abstractions (e.g., flax.linen.Module, flax.linen.Variable, Module.setup). Equinox avoids adding new abstractions to core JAX; everything in Equinox is always just a PyTree.
    • JAX Compatibility: Flax is a DSL that is generally incompatible with non-Flax code and requires using wrapped operations like flax.linen.vmap or flax.linen.scan. Equinox is compatible with arbitrary JAX code and allows the use of native JAX operations.
    • Bound Methods: Bound methods of eqx.Module are PyTrees, making them easy to pass around. In Flax, passing bound methods can lead to errors or unexpected recompilations.
    • Advanced Features: Equinox provides features like runtime errors and specialized PyTree manipulation (e.g., equinox.tree_at) that are not present in Flax.
  4. Explore Equinox ecosystem libraries

    main

    A wide range of libraries are built on top of or complement Equinox to extend its functionality into specialized domains:

    Core Machine Learning & Optimization:

    • Levanter: Training large language models.
    • Eqxvision: Computer vision (inspired by torchvision).
    • Optimistix: Nonlinear optimisation (root finding, fixed point, minimisation, etc.).
    • Lineax: Linear solvers and operators.
    • Diffrax: Numerical differential equation solvers.
    • Quax: Multiple dispatch over abstract array types.
    • sympy2jax: Converting SymPy expressions to JAX.

    Scientific & Specialized Computing:

    • Haliax: Distributed multi-dimensional arrays.
    • quadax: Numerical quadrature.
    • traceax: Stochastic trace estimation.
    • Dynamiqs: Quantum systems simulation.
    • unxt / coordinax / galax: Physics and astrophysics utilities (units, coordinates, and galactic dynamics).
    • Exponax: Fast & differentiable n-D PDE solvers.

    Probabilistic & Generative Modeling:

    • FlowJAX: Normalizing flows and probabilistic ML.
    • GPJax: Gaussian process models.
    • distreqx: Lightweight probability distributions and bijectors.
    • jumpax: Jump Processes.
    • isax: Ising-like models.

    Utilities & Tooling:

    • statedict2pytree: Converts PyTorch state_dicts into Equinox pytrees.
    • Typinox: Enhanced runtime typechecking for jaxtyping annotated Equinox Modules.
    • Peex: Runtime hooks for Equinox modules.
    • Oncequinox: Micro-library for creating Equinox singletons.
  5. Compare Equinox with Keras

    main
    Equinox and Keras serve different purposes. Keras is a high-level, 'plug-and-play' framework designed for quickly building neural networks (similar to building with Legos). Equinox is a lower-level library designed for more general use-cases, including scientific computing, and requires more proficiency in numerical computing and machine learning software development.
  6. Compare Equinox with Julia

    main

    Equinox (via JAX) and Julia have different strengths:

    • Autodifferentiation: JAX+Equinox uses a single, robust autodifferentiation system. Julia has multiple competing implementations for both forward-mode and reverse-mode autodifferentiation and lacks robust support for higher-order autodifferentiation.
    • Use Cases: Julia is superior for general-purpose programming tasks like branch-and-bound combinatorial optimization. Equinox is optimized specifically for arrays and linear algebra (NumPy-style workloads).
    • Hardware: Equinox supports running on TPUs, whereas Julia generally does not.
  7. Subclass `equinox.Module` to create PyTrees and frozen dataclasses

    main
    The core of Equinox is the equinox.Module class. By subclassing it, your class becomes both a JAX PyTree and a frozen dataclass. This allows your models and components to be compatible with JAX transformations like jax.jit, jax.grad, and jax.vmap while maintaining a clean, frozen state.
  8. Use the semi-public `equinox.internal` namespace for advanced features

    main

    The equinox.internal namespace (often imported as eqxi) contains advanced, undocumented features intended for privileged downstream libraries (like Diffrax) or JAX enthusiasts.

    WARNING: This namespace is 'semi-public'. APIs may change or be removed at any time without stability guarantees. Use with caution.

    import equinox.internal as eqxi
  9. Use non-array modules in scan/cond/while via eqx.partition

    main

    When passing a module containing non-JAX objects (static elements) into JAX control flow primitives like jax.lax.scan, you must use eqx.partition and eqx.combine to separate the arrays from the static parts. This allows the arrays to be part of the scan's carry while the static parts are captured via closure.

    def rollout(mlp, xs):
        # Separate arrays from static elements
        arr, static = eqx.partition(mlp, eqx.is_array)
        
        def step(carry, x):
            # Recombine static elements into the module for use
            mlp = eqx.combine(carry, static)
            val = mlp(x)
            # Partition again to prepare the next carry
            carry, _ = eqx.partition(mlp, eqx.is_array)
            return carry, [val]
    
        _, scan_out = jax.lax.scan(step, arr, xs)
        return scan_out
  10. Create and evaluate model ensembles with `equinox.filter_vmap`

    main

    You can create an ensemble of models by using equinox.filter_vmap on a model initialisation function. Because Equinox models are PyTrees, filter_vmap will vectorise the JAX arrays (weights/biases) across the ensemble while keeping non-JAX arrays (like activation functions) shared.

    Note: An ensembled model cannot be called directly because its parameters have an extra leading batch dimension; it must be called within a vectorised region (like filter_vmap).

    key = jax.random.PRNGKey(0)
    keys = jax.random.split(key, 8)
    
    # Create an ensemble of 8 MLPs
    @eqx.filter_vmap
    def make_ensemble(key):
        return eqx.nn.MLP(2, 2, 2, 2, key=key)
    
    mlp_ensemble = make_ensemble(keys)
    
    # Evaluate each member on the same data
    @eqx.filter_vmap(in_axes=(eqx.if_array(0), None))
    def evaluate_ensemble(model, x):
        return model(x)
    
    # Evaluate each member on different data
    @eqx.filter_vmap
    def evaluate_per_ensemble(model, x):
        return model(x)
  11. Avoid adding new methods to concrete subclasses

    main

    To maintain type safety and predictable return types in functions, the abstract/final pattern discourages adding new methods to a concrete subclass that were not present in the superclass.

    By ensuring that a concrete class only implements what was defined in its abstract ancestors, you avoid ambiguity in type signatures. For example, if a function is typed to return ConcreteArray, you avoid the question of whether it should return a specific subclass like ConcreteArrayTwo when different types are passed as arguments.

    # Avoid this pattern to keep return types predictable
    class ConcreteArray(eqx.Module):
        def some_method(self):
            pass
    
    class ConcreteArrayTwo(ConcreteArray):
        def another_method(self):
            pass
  12. Mark arrays as non-trainable (Buffers)

    main

    To treat an array as a non-trainable buffer (similar to PyTorch buffers), wrap the array in jax.lax.stop_gradient within your model's __call__ method.

    class Model(eqx.Module):
        buffer: Array
        param: Array
    
        def __call__(self, x):
            return self.param * x + jax.lax.stop_gradient(self.buffer)