JAX AI Stack

repository·main·Indexed 43 days ago

https://github.com/jax-ml/jax-ai-stack

A curated suite of Google-developed libraries for implementing and deploying generative AI models. It provides a verified, integrated installation of core packages including JAX, Flax, Optax, Orbax, Chex, Grain, and ml_dtypes, with optional support for TensorFlow and TFDS.

Tokens
45.1K
Snippets
132
Records
141
Agent score
54%

What's inside JAX AI Stack

  1. How Grain's data loading paradigm works

    main

    Grain follows a source-sampler-loader paradigm for handling input data:

    1. DataSource: Implements the grain.RandomAccessDataSource interface. It defines how to access individual records via __getitem__ and provides the total length via __len__.
    2. Sampler: Determines the order in which records are processed. For example, grain.IndexSampler can be used to shuffle data or iterate through specific epochs.
    3. DataLoader: Orchestrates the loading process. It takes a DataSource and a Sampler, and applies operations (like grain.Batch) to produce batches. It supports multiprocessing via worker_count to parallelize data transformations.
  2. Understand the JAX AI Stack ecosystem

    main

    The JAX AI stack is a collection of domain-specific tools built on top of JAX, a Python package for accelerator-oriented array computation and program transformation. When building AI models with this stack, you will primarily interact with:

    • Flax NNX: A machine learning library used for defining and building scalable neural networks.
    • Optax: A high-performance function optimization library that provides built-in optimizers and loss functions.

    This ecosystem is designed for users familiar with NumPy and the general machine learning lifecycle (defining, training, and evaluating models).

  3. Use the JAX NumPy interface for array computing

    main

    JAX provides a subpackage jax.numpy (often imported as jnp) that offers a NumPy-compatible API for array computing. jax.Array objects behave similarly to NumPy arrays regarding attributes, indexing, and broadcasting semantics. This allows you to use familiar functions like jnp.where, jnp.exp, and jnp.arange to build mathematical operations that run on JAX backends.

    import jax.numpy as jnp
    
    def selu(x, alpha=1.67, lam=1.05):
      return lam * jnp.where(x > 0, x, alpha * jnp.exp(x) - alpha)
    
    x = jnp.arange(5.0)
    print(selu(x))
  4. UNETR Decoder Components: UpBlocks and PrUpBlocks

    main

    The UNETR decoder relies on two specific types of upsampling modules to bridge the gap between transformer sequence representations and spatial feature maps:

    1. UnetrPrUpBlock (Projection Upsampling): Used to create skip connections from the intermediate feature maps provided by the ViT encoder. It uses nnx.ConvTranspose to project and upsample the transformer's sequence representations into a spatial format.
    2. UnetrUpBlock: The standard upsampling module used in the decoder path. It performs a transposed convolution on the current feature map and then concatenates the result with a skip connection from the encoder before passing it through a UnetResBlock.

    UnetrUpBlock Signature: __call__(self, x: jax.Array, skip: jax.Array) -> jax.Array

    • x: The upsampled feature map from the previous decoder layer.
    • skip: The feature map from the encoder skip connection.
  5. Implement auto-batching with vmap

    main

    To process batches of data efficiently, use jax.vmap to vectorize a function designed for a single example. By specifying in_axes, you can tell JAX which arguments should be treated as batches (e.g., in_axes=(None, 0) means the first argument is a constant and the second is a batch along axis 0).

    from jax import vmap
    
    def predict(params, image):
      # ... per-example prediction logic ...
      return logits
    
    # Create a batched version of the predict function
    batched_predict = vmap(predict, in_axes=(None, 0))
  6. Download Oxford Pets dataset

    main

    The example uses the Oxford Pets dataset. You can download and extract the images and annotations to /tmp/data/oxford_pets using the following commands:

    !rm -rf /tmp/data/oxford_pets
    !mkdir -p /tmp/data/oxford_pets
    !wget https://thor.robots.ox.ac.uk/datasets/pets/images.tar.gz -O /tmp/data/oxford_pets/images.tar.gz
    !wget https://thor.robots.ox.ac.uk/datasets/pets/annotations.tar.gz -O /tmp/data/oxford_pets/annotations.tar.gz
    
    !cd /tmp/data/oxford_pets && tar -xf images.tar.gz
    !cd /tmp/data/oxford_pets && tar -xf annotations.tar.gz
  7. Save model checkpoints using Orbax

    main

    To save the best models during training (e.g., based on validation Mean IoU), use orbax.checkpoint.

    When saving an nnx.Module, you must extract its state using nnx.state(model). Note that for compatibility with certain layers like Dropout, you may need to map jax._src.prng.PRNGKeyArray to a serializable format using jax.random.key_data(x) before passing the state to mngr.save().

    import orbax.checkpoint as ocp
    
    # Setup manager
    path = ocp.test_utils.erase_and_create_empty("/tmp/output-oxford-model/")
    options = ocp.CheckpointManagerOptions(max_to_keep=2)
    mngr = ocp.CheckpointManager(path, options=options)
    
    def save_model(epoch):
        state = nnx.state(model)
        # Convert PRNGKeyArray to serializable format
        def get_key_data(x):
            if isinstance(x, jax._src.prng.PRNGKeyArray):
                if isinstance(x.dtype, jax._src.prng.KeyTy):
                    return jax.random.key_data(x)
            return x
        
        serializable_state = jax.tree.map(get_key_data, state)
        mngr.save(epoch, args=ocp.args.StandardSave(serializable_state))
        mngr.wait_until_finished()
  8. Prepare the Food 101 dataset for training

    main

    To prepare the Food 101 dataset for training a Vision Transformer, use datasets.load_dataset to download the data. You can reduce training time by selecting a subset of classes. For efficient data loading, use grain alongside TorchVision for image transformations.

    Key steps include:

    1. Loading the dataset from HuggingFace.
    2. Creating a label mapping to normalize class indices.
    3. Applying image transformations (resizing, normalization, and augmentation) using torchvision.transforms.v2.
    4. Using grain.DataLoader with a grain.IndexSampler for efficient, parallelized data loading.
    from datasets import load_dataset
    import grain.python as grain
    from torchvision.transforms import v2 as T
    
    # 1. Load subset
    train_dataset = load_dataset("food101", split="train[:15000]")
    
    # 2. Define transforms
    tv_train_transforms = T.Compose([
        T.RandomResizedCrop((224, 224), scale=(0.7, 1.0)),
        T.RandomHorizontalFlip(),
        T.Lambda(lambda x: np.asarray(x.convert("RGB"))),
        T.Lambda(normalize),
    ])
    
    # 3. Setup Grain Loader
    train_sampler = grain.IndexSampler(
        len(train_dataset),
        shuffle=True,
        seed=12,
        shard_options=grain.NoSharding(),
        num_epochs=1,
    )
    
    train_loader = grain.DataLoader(
        data_source=train_dataset,
        sampler=train_sampler,
        worker_count=4,
        operations=[grain.Batch(32, drop_remainder=True)],
    )
  9. Install and use Grain for data loading

    main

    Grain is a data-loading library that can be used with JAX. To use it, install it via pip, define a custom Dataset class (e.g., wrapping torchvision.datasets.MNIST), and initialize a pygrain.DataLoader using a pygrain.SequentialSampler for sequential batch sampling.

    !pip install grain
    import numpy as np
    import grain.python as pygrain
    from torchvision.datasets import MNIST
    
    class Dataset:
        def __init__(self, data_dir, train=True):
            self.data_dir = data_dir
            self.train = train
            self.load_data()
    
        def load_data(self):
            self.dataset = MNIST(self.data_dir, download=True, train=self.train)
    
        def __len__(self):
            return len(self.dataset)
    
        def __getitem__(self, index):
            img, label = self.dataset[index]
            return np.ravel(np.array(img, dtype=np.float32)), label
    
    # Initialize
    mnist_dataset = Dataset(data_dir)
    
    # Setup DataLoader
    sampler = pygrain.SequentialSampler(
        num_records=len(mnist_dataset),
        shard_options=pygrain.NoSharding())
    
    def pygrain_training_generator():
        return pygrain.DataLoader(
            data_source=mnist_dataset,
            sampler=sampler,
            operations=[pygrain.Batch(batch_size)],
        )
  10. Build the documentation locally

    main

    To preview the documentation locally, use sphinx-build to generate HTML files from the source directory. Run this command from the root of the repository.

    ```bash
    # from the root of the repository
    sphinx-build -b html docs/source docs/_build/html

    After building, open docs/_build/html/index.html in your browser to view the site.