From 94793f9b9e288879786d96f62fdf85e669c5abc7 Mon Sep 17 00:00:00 2001 From: Cristian Garcia Date: Fri, 15 Nov 2024 14:39:32 -0800 Subject: [PATCH] [nnx] add performance guide notebook --- .gitignore | 3 +- docs_nnx/guides/images/performance-graph.png | Bin 0 -> 87827 bytes docs_nnx/guides/index.rst | 1 + docs_nnx/guides/performance.ipynb | 151 +++++++++++++++++++ docs_nnx/guides/performance.md | 110 ++++++++++++++ docs_nnx/nnx_basics.ipynb | 39 +---- docs_nnx/nnx_basics.md | 37 +---- pyproject.toml | 1 + uv.lock | 108 ++++++++++--- 9 files changed, 365 insertions(+), 85 deletions(-) create mode 100644 docs_nnx/guides/images/performance-graph.png create mode 100644 docs_nnx/guides/performance.ipynb create mode 100644 docs_nnx/guides/performance.md diff --git a/.gitignore b/.gitignore index a1de4b1dda..0bc7f3cbe6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,7 @@ flaxlib_src/build flaxlib_src/builddir flaxlib_src/dist flaxlib_src/subprojects -target/ -flaxlib.cpython-* + # used by direnv .envrc diff --git a/docs_nnx/guides/images/performance-graph.png b/docs_nnx/guides/images/performance-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..34fb134e22dfee296ceb0472e66068cd0dc10573 GIT binary patch literal 87827 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

V?nq8j@#X2C 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 literal 0 HcmV?d00001 diff --git a/docs_nnx/guides/index.rst b/docs_nnx/guides/index.rst index 5d917be3e3..58c9e80f12 100644 --- a/docs_nnx/guides/index.rst +++ b/docs_nnx/guides/index.rst @@ -8,6 +8,7 @@ Guides flax_gspmd filters_guide randomness + performance linen_to_nnx bridge_guide surgery diff --git a/docs_nnx/guides/performance.ipynb b/docs_nnx/guides/performance.ipynb new file mode 100644 index 0000000000..a745167114 --- /dev/null +++ b/docs_nnx/guides/performance.ipynb @@ -0,0 +1,151 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Performance Considerations\n", + "Currently `nnx.jit` traverses the object graph in pure Python, this is slow and adds overhead. To solve this in general we will be developing a Rust extension called `flaxlib` (see first steps in #4196) to speedup some of the traversal logic in `graph.py`, similar to how JAX solved the same issue with `jaxlib` for standard pytrees. However, there's two things to consider:\n", + "\n", + "* The overhead is only relevant for small models. See [Asynchronous dispatch](#asynchronous-dispatch).\n", + "* You can remove the overhead by using `jax.jit` + `nnx.split` / `nnx.merge` to stage out the traversal logic. See [Lowering the Python Overhead](#lowering-the-python-overhead).\n", + "\n", + "\n", + "## Asynchronous dispatch\n", + "In [benchmarks/nnx_simple_training.py](https://github.com/google/flax/blob/main/benchmarks/nnx_simple_training.py) we are increasing the layer width (features per layer) and measuring the total training time for the same model trained both with `nnx.jit` and `jax.jit`. As you can see in the graph below, after a certain model size the time spent in the traversal is completely absorbed by async dispatch. This happens when Python is able to finish the current for loop step, and reach the next `train_step` and JAX is still not done with the previous `train_step`. \n", + "\n", + "![performance-graph](images/performance-graph.png)\n", + "\n", + "This means that you only need to worry about the `nnx.jit` overhead for small models. If you are working with a small model, check out the next section to see how you can remove the overhead.\n", + "\n", + "## Lowering the Python Overhead\n", + "To remove the python overhead you can use regular `jax.jit` in combination with `nnx.split` and `nnx.merge` to stage out the traversal logic. To learn how to do this, lets first create this simple model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from flax import nnx\n", + "import jax\n", + "import jax.numpy as jnp\n", + "import optax\n", + "\n", + "class Model(nnx.Module):\n", + " def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n", + " self.linear = nnx.Linear(din, dmid, rngs=rngs)\n", + " self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n", + " self.dropout = nnx.Dropout(0.2, rngs=rngs)\n", + " self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n", + "\n", + " def __call__(self, x):\n", + " x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n", + " return self.linear_out(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lets say we have this `train_step` function that is using `nnx.jit` and takes in a `model`, `optimizer`, and `metrics`, all of which are Flax NNX objects:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = Model(2, 64, 3, rngs=nnx.Rngs(0)) # eager initialization\n", + "optimizer = nnx.Optimizer(model, optax.adam(1e-3)) # reference sharing\n", + "metrics = nnx.MultiMetric(\n", + " loss=nnx.metrics.Average('loss'),\n", + ")\n", + "\n", + "@nnx.jit # <== currently slow\n", + "def train_step(model, optimizer, metrics, x, y):\n", + " def loss_fn(model):\n", + " y_pred = model(x) # call methods directly\n", + " return ((y_pred - y) ** 2).mean()\n", + "\n", + " loss, grads = nnx.value_and_grad(loss_fn)(model)\n", + " optimizer.update(grads) # in-place updates\n", + " metrics.update(loss=loss)\n", + "\n", + " return loss\n", + " \n", + "for _ in range(10):\n", + " x, y = jnp.ones((32, 2)), jnp.zeros((32, 3))\n", + " loss = train_step(model, optimizer, metrics, x, y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To speed it up, before starting the training loop we can use `nnx.split` over the all the Flax NNX objects that are inputs to `train_step` to create a `graphdef` and `state` pytrees that are fast to traverse. Next we change `train_step` so accept `graphdef` and `state` and use `nnx.merge` and `nnx.split` at the beginning and end of `train_step` to switch back and forth between the objects and their pytree representations. Even though `nnx.split` and `nnx.merge` are slow it doesn't matter because they will only run once during tracing. With this in place, we can change the `train_step` function to use `jax.jit` instead of `nnx.jit`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = Model(2, 64, 3, rngs=nnx.Rngs(0)) # eager initialization\n", + "optimizer = nnx.Optimizer(model, optax.adamw(1e-3)) # reference sharing\n", + "metrics = nnx.MultiMetric(\n", + " loss=nnx.metrics.Average('loss'),\n", + ")\n", + "# split before training loop\n", + "graphdef, state = nnx.split((model, optimizer, metrics))\n", + "\n", + "@jax.jit # regular JAX\n", + "def train_step(graphdef, state, x, y):\n", + " # merge at the beginning of the function\n", + " model, optimizer, metrics = nnx.merge(graphdef, state)\n", + "\n", + " def loss_fn(model):\n", + " y_pred = model(x) # call methods directly\n", + " return ((y_pred - y) ** 2).mean()\n", + "\n", + " loss, grads = nnx.value_and_grad(loss_fn)(model)\n", + " optimizer.update(grads)\n", + " metrics.update(loss=loss)\n", + "\n", + " # split at the end of the function\n", + " _, state = nnx.split((model, optimizer, metrics))\n", + "\n", + " # return new state\n", + " return state, loss\n", + "\n", + "for _ in range(10):\n", + " x, y = jnp.ones((32, 2)), jnp.zeros((32, 3))\n", + " state, loss = train_step(graphdef, state, x, y)\n", + "\n", + "# update objects after training\n", + "nnx.update((model, optimizer, metrics), state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that we only do this for `jit`, you can still use other transforms like `nnx.value_and_grad` shown in the example since their overhead is already absorbed by the outer `jit`. Also, after the training loop is done (or whenever need) `nnx.update` can be used to update Flax NNX objects like `model`, `optimizer`, and `metrics` to a new `state`." + ] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,md:myst" + }, + "language_info": { + "name": "python", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs_nnx/guides/performance.md b/docs_nnx/guides/performance.md new file mode 100644 index 0000000000..5d13b54b50 --- /dev/null +++ b/docs_nnx/guides/performance.md @@ -0,0 +1,110 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.13.8 +--- + +# Performance Considerations +Currently `nnx.jit` traverses the object graph in pure Python, this is slow and adds overhead. To solve this in general we will be developing a Rust extension called `flaxlib` (see first steps in #4196) to speedup some of the traversal logic in `graph.py`, similar to how JAX solved the same issue with `jaxlib` for standard pytrees. However, there's two things to consider: + +* The overhead is only relevant for small models. See [Asynchronous dispatch](#asynchronous-dispatch). +* You can remove the overhead by using `jax.jit` + `nnx.split` / `nnx.merge` to stage out the traversal logic. See [Lowering the Python Overhead](#lowering-the-python-overhead). + + +## Asynchronous dispatch +In [benchmarks/nnx_simple_training.py](https://github.com/google/flax/blob/main/benchmarks/nnx_simple_training.py) we are increasing the layer width (features per layer) and measuring the total training time for the same model trained both with `nnx.jit` and `jax.jit`. As you can see in the graph below, after a certain model size the time spent in the traversal is completely absorbed by async dispatch. This happens when Python is able to finish the current for loop step, and reach the next `train_step` and JAX is still not done with the previous `train_step`. + +![performance-graph](images/performance-graph.png) + +This means that you only need to worry about the `nnx.jit` overhead for small models. If you are working with a small model, check out the next section to see how you can remove the overhead. + +## Lowering the Python Overhead +To remove the python overhead you can use regular `jax.jit` in combination with `nnx.split` and `nnx.merge` to stage out the traversal logic. To learn how to do this, lets first create this simple model: + +```{code-cell} +from flax import nnx +import jax +import jax.numpy as jnp +import optax + +class Model(nnx.Module): + def __init__(self, din, dmid, dout, rngs: nnx.Rngs): + self.linear = nnx.Linear(din, dmid, rngs=rngs) + self.bn = nnx.BatchNorm(dmid, rngs=rngs) + self.dropout = nnx.Dropout(0.2, rngs=rngs) + self.linear_out = nnx.Linear(dmid, dout, rngs=rngs) + + def __call__(self, x): + x = nnx.relu(self.dropout(self.bn(self.linear(x)))) + return self.linear_out(x) +``` + +Lets say we have this `train_step` function that is using `nnx.jit` and takes in a `model`, `optimizer`, and `metrics`, all of which are Flax NNX objects: + +```{code-cell} +model = Model(2, 64, 3, rngs=nnx.Rngs(0)) # eager initialization +optimizer = nnx.Optimizer(model, optax.adam(1e-3)) # reference sharing +metrics = nnx.MultiMetric( + loss=nnx.metrics.Average('loss'), +) + +@nnx.jit # <== currently slow +def train_step(model, optimizer, metrics, x, y): + def loss_fn(model): + y_pred = model(x) # call methods directly + return ((y_pred - y) ** 2).mean() + + loss, grads = nnx.value_and_grad(loss_fn)(model) + optimizer.update(grads) # in-place updates + metrics.update(loss=loss) + + return loss + +for _ in range(10): + x, y = jnp.ones((32, 2)), jnp.zeros((32, 3)) + loss = train_step(model, optimizer, metrics, x, y) +``` + +To speed it up, before starting the training loop we can use `nnx.split` over the all the Flax NNX objects that are inputs to `train_step` to create a `graphdef` and `state` pytrees that are fast to traverse. Next we change `train_step` so accept `graphdef` and `state` and use `nnx.merge` and `nnx.split` at the beginning and end of `train_step` to switch back and forth between the objects and their pytree representations. Even though `nnx.split` and `nnx.merge` are slow it doesn't matter because they will only run once during tracing. With this in place, we can change the `train_step` function to use `jax.jit` instead of `nnx.jit`: + +```{code-cell} +model = Model(2, 64, 3, rngs=nnx.Rngs(0)) # eager initialization +optimizer = nnx.Optimizer(model, optax.adamw(1e-3)) # reference sharing +metrics = nnx.MultiMetric( + loss=nnx.metrics.Average('loss'), +) +# split before training loop +graphdef, state = nnx.split((model, optimizer, metrics)) + +@jax.jit # regular JAX +def train_step(graphdef, state, x, y): + # merge at the beginning of the function + model, optimizer, metrics = nnx.merge(graphdef, state) + + def loss_fn(model): + y_pred = model(x) # call methods directly + return ((y_pred - y) ** 2).mean() + + loss, grads = nnx.value_and_grad(loss_fn)(model) + optimizer.update(grads) + metrics.update(loss=loss) + + # split at the end of the function + _, state = nnx.split((model, optimizer, metrics)) + + # return new state + return state, loss + +for _ in range(10): + x, y = jnp.ones((32, 2)), jnp.zeros((32, 3)) + state, loss = train_step(graphdef, state, x, y) + +# update objects after training +nnx.update((model, optimizer, metrics), state) +``` + +Notice that we only do this for `jit`, you can still use other transforms like `nnx.value_and_grad` shown in the example since their overhead is already absorbed by the outer `jit`. Also, after the training loop is done (or whenever need) `nnx.update` can be used to update Flax NNX objects like `model`, `optimizer`, and `metrics` to a new `state`. diff --git a/docs_nnx/nnx_basics.ipynb b/docs_nnx/nnx_basics.ipynb index d333db5dbc..30c2dcb499 100644 --- a/docs_nnx/nnx_basics.ipynb +++ b/docs_nnx/nnx_basics.ipynb @@ -8,27 +8,12 @@ "\n", "Flax NNX is a new simplified API that is designed to make it easier to create, inspect, debug, and analyze neural networks in [JAX](https://jax.readthedocs.io/). It achieves this by adding first class support for Python reference semantics. This allows users to express their models using regular Python objects, which are modeled as PyGraphs (instead of pytrees), enabling reference sharing and mutability. Such API design should make PyTorch or Keras users feel at home.\n", "\n", - "In this guide you will learn about:\n", - "\n", - "- The Flax [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) system: An example of creating and initializing a custom `Linear` layer.\n", - " - Stateful computation: An example of creating a Flax [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable) and updating its value (such as state updates needed during the forward pass).\n", - " - Nested [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html)s: An MLP example with `Linear`, [`nnx.Dropout`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/stochastic.html#flax.nnx.Dropout), and [`nnx.BatchNorm`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/normalization.html#flax.nnx.BatchNorm) layers.\n", - " - Model surgery: An example of replacing custom `Linear` layers inside a model with custom `LoraLinear` layers.\n", - "- Flax transformations: An example of using [`nnx.jit`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.jit) for automatic state management.\n", - " - [`nnx.scan`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.scan) over layers.\n", - "- The Flax NNX Functional API: An example of a custom `StatefulLinear` layer with [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param)s with fine-grained control over the state.\n", - " - [`State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) and [`GraphDef`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.GraphDef).\n", - " - [`split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split), [`merge`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.merge), and `update`\n", - " - Fine-grained [`State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) control: An example of using [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable) type `Filter`s to split into multiple [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State)s.\n", - "\n", - "## Setup\n", - "\n", - "Install Flax with `pip` and import necessary dependencies:" + "To begin, install Flax with `pip` and import necessary dependencies:" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "tags": [ "skip-execution" @@ -36,7 +21,7 @@ }, "outputs": [], "source": [ - "# ! pip install -U flax treescope" + "# ! pip install -U flax" ] }, { @@ -56,17 +41,9 @@ "source": [ "## The Flax NNX Module system\n", "\n", - "The main difference between the Flax[`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) and other `Module` systems in [Flax Linen](https://flax-linen.readthedocs.io/en/latest/api_reference/flax.linen/module.html) or [Haiku](https://dm-haiku.readthedocs.io/en/latest/notebooks/basics.html#Built-in-Haiku-nets-and-nested-modules) is that in NNX everything is **explicit**. This means, among other things, that:\n", - "\n", - "1) The [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) itself holds the state (such as parameters) directly.\n", - "2) The [PRNG](https://jax.readthedocs.io/en/latest/random-numbers.html) state is threaded by the user.\n", - "3) All shape information must be provided on initialization (no shape inference).\n", + "The main difference between the Flax[`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) and other `Module` systems in [Flax Linen](https://flax-linen.readthedocs.io/en/latest/api_reference/flax.linen/module.html) or [Haiku](https://dm-haiku.readthedocs.io/en/latest/notebooks/basics.html#Built-in-Haiku-nets-and-nested-modules) is that in NNX everything is **explicit**. This means, among other things, that the [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) itself holds the state (such as parameters) directly, the [PRNG](https://jax.readthedocs.io/en/latest/random-numbers.html) state is threaded by the user, and all shape information must be provided on initialization (no shape inference).\n", "\n", - "Let's begin by creating a `Linear` [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html). The following code shows that:\n", - "\n", - "- Dynamic state is usually stored in [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param)s, and static state (all types not handled by NNX), such as integers or strings are stored directly.\n", - "- Attributes of type [`jax.Array`](https://jax.readthedocs.io/en/latest/_autosummary/jax.Array.html) and `numpy.ndarray` are also treated as dynamic states, although storing them inside [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable)s, such as `Param`, is preferred.\n", - "- The [`nnx.Rngs`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/rnglib.html#flax.nnx.Rngs) object can be used to get new unique keys based on a root PRNG key passed to the constructor." + "Let's begin by creating a `Linear` [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html). As shown next, dynamic state is usually stored in `nnx.Param`s, and static state (all types not handled by NNX) such as integers or strings are stored directly. Attributes of type `jax.Array` and `numpy.ndarray` are also treated as dynamic states, although storing them inside `nnx.Variable`s, such as `Param`, is preferred. Also the [`nnx.Rngs`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/rnglib.html#flax.nnx.Rngs) object can be used to get new unique keys based on a root PRNG key passed to the constructor." ] }, { @@ -90,9 +67,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Also note that:\n", - "\n", - "- The inner values of [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable) can be accessed using the `value` property, but for convenience they implement all numeric operators and can be used directly in arithmetic expressions (as shown in the code above).\n", + "Also note that the inner values of `nnx.Variable`s can be accessed using the `value` property, but for convenience they implement all numeric operators and can be used directly in arithmetic expressions (as shown in the code above).\n", "\n", "To initialize a Flax [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html), you just call the constructor, and all the parameters of a `Module` are usually created eagerly. Since [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html)s hold their own state methods, you can call them directly without the need for a separate `apply` method.\n", "This can be very convenient for debugging, allowing you to directly inspect the entire structure of the model." @@ -355,6 +330,8 @@ "1. The updates to each of the [`nnx.BatchNorm`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/normalization.html#flax.nnx.BatchNorm) and [`nnx.Dropout`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/stochastic.html#flax.nnx.Dropout) layer's state is automatically propagated from within `loss_fn` to `train_step` all the way to the `model` reference outside.\n", "2. The `optimizer` holds a mutable reference to the `model` - this relationship is preserved inside the `train_step` function making it possible to update the model's parameters using the optimizer alone.\n", "\n", + "> **Note**
`nnx.jit` has performance overhead for small models, check the [Performance Considerations](https://flax.readthedocs.io/en/latest/guides/performance.html) guide for more information.\n", + "\n", "### Scan over layers\n", "\n", "The next example uses Flax [`nnx.vmap`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.vmap) to create a stack of multiple MLP layers and [`nnx.scan`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.scan) to iteratively apply each layer of the stack to the input.\n", diff --git a/docs_nnx/nnx_basics.md b/docs_nnx/nnx_basics.md index e836ef53d1..fbf9be0a26 100644 --- a/docs_nnx/nnx_basics.md +++ b/docs_nnx/nnx_basics.md @@ -12,27 +12,12 @@ jupytext: Flax NNX is a new simplified API that is designed to make it easier to create, inspect, debug, and analyze neural networks in [JAX](https://jax.readthedocs.io/). It achieves this by adding first class support for Python reference semantics. This allows users to express their models using regular Python objects, which are modeled as PyGraphs (instead of pytrees), enabling reference sharing and mutability. Such API design should make PyTorch or Keras users feel at home. -In this guide you will learn about: - -- The Flax [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) system: An example of creating and initializing a custom `Linear` layer. - - Stateful computation: An example of creating a Flax [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable) and updating its value (such as state updates needed during the forward pass). - - Nested [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html)s: An MLP example with `Linear`, [`nnx.Dropout`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/stochastic.html#flax.nnx.Dropout), and [`nnx.BatchNorm`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/normalization.html#flax.nnx.BatchNorm) layers. - - Model surgery: An example of replacing custom `Linear` layers inside a model with custom `LoraLinear` layers. -- Flax transformations: An example of using [`nnx.jit`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.jit) for automatic state management. - - [`nnx.scan`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.scan) over layers. -- The Flax NNX Functional API: An example of a custom `StatefulLinear` layer with [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param)s with fine-grained control over the state. - - [`State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) and [`GraphDef`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.GraphDef). - - [`split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split), [`merge`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.merge), and `update` - - Fine-grained [`State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) control: An example of using [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable) type `Filter`s to split into multiple [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State)s. - -## Setup - -Install Flax with `pip` and import necessary dependencies: +To begin, install Flax with `pip` and import necessary dependencies: ```{code-cell} ipython3 :tags: [skip-execution] -# ! pip install -U flax treescope +# ! pip install -U flax ``` ```{code-cell} ipython3 @@ -43,17 +28,9 @@ import jax.numpy as jnp ## The Flax NNX Module system -The main difference between the Flax[`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) and other `Module` systems in [Flax Linen](https://flax-linen.readthedocs.io/en/latest/api_reference/flax.linen/module.html) or [Haiku](https://dm-haiku.readthedocs.io/en/latest/notebooks/basics.html#Built-in-Haiku-nets-and-nested-modules) is that in NNX everything is **explicit**. This means, among other things, that: - -1) The [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) itself holds the state (such as parameters) directly. -2) The [PRNG](https://jax.readthedocs.io/en/latest/random-numbers.html) state is threaded by the user. -3) All shape information must be provided on initialization (no shape inference). +The main difference between the Flax[`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) and other `Module` systems in [Flax Linen](https://flax-linen.readthedocs.io/en/latest/api_reference/flax.linen/module.html) or [Haiku](https://dm-haiku.readthedocs.io/en/latest/notebooks/basics.html#Built-in-Haiku-nets-and-nested-modules) is that in NNX everything is **explicit**. This means, among other things, that the [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) itself holds the state (such as parameters) directly, the [PRNG](https://jax.readthedocs.io/en/latest/random-numbers.html) state is threaded by the user, and all shape information must be provided on initialization (no shape inference). -Let's begin by creating a `Linear` [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html). The following code shows that: - -- Dynamic state is usually stored in [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param)s, and static state (all types not handled by NNX), such as integers or strings are stored directly. -- Attributes of type [`jax.Array`](https://jax.readthedocs.io/en/latest/_autosummary/jax.Array.html) and `numpy.ndarray` are also treated as dynamic states, although storing them inside [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable)s, such as `Param`, is preferred. -- The [`nnx.Rngs`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/rnglib.html#flax.nnx.Rngs) object can be used to get new unique keys based on a root PRNG key passed to the constructor. +Let's begin by creating a `Linear` [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html). As shown next, dynamic state is usually stored in `nnx.Param`s, and static state (all types not handled by NNX) such as integers or strings are stored directly. Attributes of type `jax.Array` and `numpy.ndarray` are also treated as dynamic states, although storing them inside `nnx.Variable`s, such as `Param`, is preferred. Also the [`nnx.Rngs`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/rnglib.html#flax.nnx.Rngs) object can be used to get new unique keys based on a root PRNG key passed to the constructor. ```{code-cell} ipython3 class Linear(nnx.Module): @@ -67,9 +44,7 @@ class Linear(nnx.Module): return x @ self.w + self.b ``` -Also note that: - -- The inner values of [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable) can be accessed using the `value` property, but for convenience they implement all numeric operators and can be used directly in arithmetic expressions (as shown in the code above). +Also note that the inner values of `nnx.Variable`s can be accessed using the `value` property, but for convenience they implement all numeric operators and can be used directly in arithmetic expressions (as shown in the code above). To initialize a Flax [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html), you just call the constructor, and all the parameters of a `Module` are usually created eagerly. Since [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html)s hold their own state methods, you can call them directly without the need for a separate `apply` method. This can be very convenient for debugging, allowing you to directly inspect the entire structure of the model. @@ -209,6 +184,8 @@ There are two things happening in this example that are worth mentioning: 1. The updates to each of the [`nnx.BatchNorm`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/normalization.html#flax.nnx.BatchNorm) and [`nnx.Dropout`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/stochastic.html#flax.nnx.Dropout) layer's state is automatically propagated from within `loss_fn` to `train_step` all the way to the `model` reference outside. 2. The `optimizer` holds a mutable reference to the `model` - this relationship is preserved inside the `train_step` function making it possible to update the model's parameters using the optimizer alone. +> **Note**
`nnx.jit` has performance overhead for small models, check the [Performance Considerations](https://flax.readthedocs.io/en/latest/guides/performance.html) guide for more information. + ### Scan over layers The next example uses Flax [`nnx.vmap`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.vmap) to create a stack of multiple MLP layers and [`nnx.scan`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.scan) to iteratively apply each layer of the stack to the input. diff --git a/pyproject.toml b/pyproject.toml index 6c67a21cc2..658b2f15d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ "rich>=11.1", "typing_extensions>=4.2", "PyYAML>=5.4.1", + "treescope>=0.1.2", ] classifiers = [ "Development Status :: 3 - Alpha", diff --git a/uv.lock b/uv.lock index b4a0aaa65d..0d68a86b72 100644 --- a/uv.lock +++ b/uv.lock @@ -773,7 +773,7 @@ wheels = [ [[package]] name = "flax" -version = "0.10.0" +version = "0.10.1" source = { editable = "." } dependencies = [ { name = "jax" }, @@ -784,6 +784,7 @@ dependencies = [ { name = "pyyaml" }, { name = "rich" }, { name = "tensorstore" }, + { name = "treescope" }, { name = "typing-extensions" }, ] @@ -890,6 +891,7 @@ requires-dist = [ { name = "tensorflow-text", marker = "platform_system != 'Darwin' and extra == 'testing'", specifier = ">=2.11.0" }, { name = "tensorstore" }, { name = "torch", marker = "extra == 'testing'" }, + { name = "treescope", specifier = ">=0.1.2" }, { name = "treescope", marker = "python_full_version >= '3.10' and extra == 'testing'", specifier = ">=0.1.1" }, { name = "typing-extensions", specifier = ">=4.2" }, ] @@ -2261,7 +2263,7 @@ wheels = [ [[package]] name = "orbax-checkpoint" -version = "0.9.1" +version = "0.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "absl-py" }, @@ -2275,12 +2277,13 @@ dependencies = [ { name = "protobuf", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "protobuf", version = "4.25.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pyyaml" }, + { name = "simplejson" }, { name = "tensorstore" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/ce/4a3386e9e4bc95ab638c2779e259a231ffc2ae1fc2b67a68f4d6d8a794f2/orbax_checkpoint-0.9.1.tar.gz", hash = "sha256:edf76d8fc482a9a0296645522f6d13ee09c499af0ef2f9369899cbfee31c7f88", size = 212247 } +sdist = { url = "https://files.pythonhosted.org/packages/07/24/f13f75810a00873f779625b4fff9419d09f95a56bedb01453ac2b4990ce8/orbax_checkpoint-0.10.1.tar.gz", hash = "sha256:aaf44f5a10ced74badc7fcaf8a2396e9047a20a61487ad5e8514e539d7992cd8", size = 230081 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/41/19de59b4a8581ad364970b2429481838cea8d190f50bc8f672ca329b64e0/orbax_checkpoint-0.9.1-py3-none-any.whl", hash = "sha256:d33e23e63b7ffcf66de3fb5daa3d11bf24a34e66d133eb4a037788f22155857e", size = 296514 }, + { url = "https://files.pythonhosted.org/packages/b3/67/a175072cd7e5a215b12f39f4d9d891881a6220d75e30ae6480d05647bdf4/orbax_checkpoint-0.10.1-py3-none-any.whl", hash = "sha256:b4d7ae295d89a329c39109f945ff690d47c1db04eac644fa5316b2f42b5fa9e5", size = 328311 }, ] [[package]] @@ -3100,6 +3103,67 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/12/c657047c11a47e1c3e51bdc26bd6f2661a268fd0384bd8ed56b227530486/simple_parsing-0.1.5-py3-none-any.whl", hash = "sha256:46f35ed7002f9bb25dca3a49eac491cc78d2140e4adcbe156225ae643c2874ea", size = 113568 }, ] +[[package]] +name = "simplejson" +version = "3.19.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/29/085111f19717f865eceaf0d4397bf3e76b08d60428b076b64e2a1903706d/simplejson-3.19.3.tar.gz", hash = "sha256:8e086896c36210ab6050f2f9f095a5f1e03c83fa0e7f296d6cba425411364680", size = 85237 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/24/260ad03435ce8ef2436031951134659c7161776ec3a78094b35b9375ceea/simplejson-3.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:50d8b742d74c449c4dcac570d08ce0f21f6a149d2d9cf7652dbf2ba9a1bc729a", size = 93660 }, + { url = "https://files.pythonhosted.org/packages/63/a1/dee207f357bcd6b106f2ca5129ee916c24993ba08b7dfbf9a37c22442ea9/simplejson-3.19.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd011fc3c1d88b779645495fdb8189fb318a26981eebcce14109460e062f209b", size = 75546 }, + { url = "https://files.pythonhosted.org/packages/80/7b/45ef1da43f54d209ce2ef59b7356cda13f810186c381f38ae23a4d2b1337/simplejson-3.19.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:637c4d4b81825c1f4d651e56210bd35b5604034b192b02d2d8f17f7ce8c18f42", size = 75602 }, + { url = "https://files.pythonhosted.org/packages/7f/4b/9a132382982f8127bc7ce5212a5585d83c174707c9dd698d0cb6a0d41882/simplejson-3.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f56eb03bc9e432bb81adc8ecff2486d39feb371abb442964ffb44f6db23b332", size = 138632 }, + { url = "https://files.pythonhosted.org/packages/76/37/012f5ad2f38afa28f8a6ad9da01dc0b64492ffbaf2a3f2f8a0e1fddf9c1d/simplejson-3.19.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef59a53be400c1fad2c914b8d74c9d42384fed5174f9321dd021b7017fd40270", size = 146740 }, + { url = "https://files.pythonhosted.org/packages/69/b3/89640bd676e26ea2315b5aaf80712a6fbbb4338e4caf872d91448502a19b/simplejson-3.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72e8abbc86fcac83629a030888b45fed3a404d54161118be52cb491cd6975d3e", size = 134440 }, + { url = "https://files.pythonhosted.org/packages/61/20/0035a288deaff05397d6cc0145b33f3dd2429b99cdc880de4c5eca41ca72/simplejson-3.19.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8efb03ca77bd7725dfacc9254df00d73e6f43013cf39bd37ef1a8ed0ebb5165", size = 137949 }, + { url = "https://files.pythonhosted.org/packages/5d/de/5b03fafe3003e32d179588953d38183af6c3747e95c7dcc668c4f9eb886a/simplejson-3.19.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:add8850db04b98507a8b62d248a326ecc8561e6d24336d1ca5c605bbfaab4cad", size = 139992 }, + { url = "https://files.pythonhosted.org/packages/d1/ce/e493116ff49fd215f7baa25195b8f684c91e65c153e2a57e04dc3f3a466b/simplejson-3.19.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fc3dc9fb413fc34c396f52f4c87de18d0bd5023804afa8ab5cc224deeb6a9900", size = 140320 }, + { url = "https://files.pythonhosted.org/packages/86/f3/a18b98a7a27548829f672754dd3940fb637a27981399838128d3e560087f/simplejson-3.19.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dfa420bb9225dd33b6efdabde7c6a671b51150b9b1d9c4e5cd74d3b420b3fe1", size = 148625 }, + { url = "https://files.pythonhosted.org/packages/0f/55/d3da33ee3e708133da079b9d537693d7fef281e6f0d27921cc7e5b3ec523/simplejson-3.19.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7b5c472099b39b274dcde27f1113db8d818c9aa3ba8f78cbb8ad04a4c1ac2118", size = 141287 }, + { url = "https://files.pythonhosted.org/packages/17/e8/56184ab4d66bb64a6ff569f069b3796dfd943f9b961268fe0d403526fc17/simplejson-3.19.3-cp310-cp310-win32.whl", hash = "sha256:817abad79241ed4a507b3caf4d3f2be5079f39d35d4c550a061988986bffd2ec", size = 74143 }, + { url = "https://files.pythonhosted.org/packages/be/8f/a0089eff060f10a925f08b0a0f50854321484f1ac54b1895bbf4c9213dfe/simplejson-3.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:dd5b9b1783e14803e362a558680d88939e830db2466f3fa22df5c9319f8eea94", size = 75643 }, + { url = "https://files.pythonhosted.org/packages/8c/bb/9ee3959e6929d228cf669b3f13f0edd43c5261b6cd69598640748b19ca35/simplejson-3.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e88abff510dcff903a18d11c2a75f9964e768d99c8d147839913886144b2065e", size = 91930 }, + { url = "https://files.pythonhosted.org/packages/ac/ae/a06523928af3a6783e2638cd4f6035c3e32de1c1063d563d9060c8d2f1ad/simplejson-3.19.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:934a50a614fb831614db5dbfba35127ee277624dda4d15895c957d2f5d48610c", size = 74787 }, + { url = "https://files.pythonhosted.org/packages/c3/58/fea732e48a7540035fe46d39e6fd77679f5810311d31da8661ce7a18210a/simplejson-3.19.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:212fce86a22188b0c7f53533b0f693ea9605c1a0f02c84c475a30616f55a744d", size = 74612 }, + { url = "https://files.pythonhosted.org/packages/ab/4d/15718f20cb0e3875b8af9597d6bb3bfbcf1383834b82b6385ee9ac0b72a9/simplejson-3.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d9e8f836688a8fabe6a6b41b334aa550a6823f7b4ac3d3712fc0ad8655be9a8", size = 143550 }, + { url = "https://files.pythonhosted.org/packages/93/44/815a4343774760f7a82459c8f6a4d8268b4b6d23f81e7b922a5e2ca79171/simplejson-3.19.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23228037dc5d41c36666384062904d74409a62f52283d9858fa12f4c22cffad1", size = 153284 }, + { url = "https://files.pythonhosted.org/packages/9d/52/d3202d9bba95444090d1c98e43da3c10907875babf63ed3c134d1b9437e3/simplejson-3.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0791f64fed7d4abad639491f8a6b1ba56d3c604eb94b50f8697359b92d983f36", size = 141518 }, + { url = "https://files.pythonhosted.org/packages/b7/d4/850948bcbcfe0b4a6c69dfde10e245d3a1ea45252f16a1e2308a3b06b1da/simplejson-3.19.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f614581b61a26fbbba232a1391f6cee82bc26f2abbb6a0b44a9bba25c56a1c", size = 144688 }, + { url = "https://files.pythonhosted.org/packages/58/d2/b8dcb0a07d9cd54c47f9fe8733dbb83891d1efe4fc786d9dfc8781cc04f9/simplejson-3.19.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1df0aaf1cb787fdf34484ed4a1f0c545efd8811f6028623290fef1a53694e597", size = 144534 }, + { url = "https://files.pythonhosted.org/packages/a9/95/1e92d99039041f596e0923ec4f9153244acaf3830944dc69a7c11b23ceaa/simplejson-3.19.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:951095be8d4451a7182403354c22ec2de3e513e0cc40408b689af08d02611588", size = 146565 }, + { url = "https://files.pythonhosted.org/packages/21/04/c96aeb3a74031255e4cbcc0ca1b6ebfb5549902f0a065f06d65ce8447c0c/simplejson-3.19.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a954b30810988feeabde843e3263bf187697e0eb5037396276db3612434049b", size = 155014 }, + { url = "https://files.pythonhosted.org/packages/b7/41/e28a28593afc4a75d8999d057bfb7c73a103e35f927e66f4bb92571787ae/simplejson-3.19.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c40df31a75de98db2cdfead6074d4449cd009e79f54c1ebe5e5f1f153c68ad20", size = 148092 }, + { url = "https://files.pythonhosted.org/packages/2b/82/1c81a3af06f937afb6d2e9d74a465c0e0ae6db444d1bf2a436ea26de1965/simplejson-3.19.3-cp311-cp311-win32.whl", hash = "sha256:7e2a098c21ad8924076a12b6c178965d88a0ad75d1de67e1afa0a66878f277a5", size = 73942 }, + { url = "https://files.pythonhosted.org/packages/65/be/d8ab9717f471be3c114f16abd8be21d9a6a0a09b9b49177d93d64d3717d9/simplejson-3.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:c9bedebdc5fdad48af8783022bae307746d54006b783007d1d3c38e10872a2c6", size = 75469 }, + { url = "https://files.pythonhosted.org/packages/20/15/513fea93fafbdd4993eacfcb762965b2ff3d29e618c029e2956174d68c4b/simplejson-3.19.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:66a0399e21c2112acacfebf3d832ebe2884f823b1c7e6d1363f2944f1db31a99", size = 92921 }, + { url = "https://files.pythonhosted.org/packages/a4/4f/998a907ae1a6c104dc0ee48aa248c2478490152808d34d8e07af57f396c3/simplejson-3.19.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6ef9383c5e05f445be60f1735c1816163c874c0b1ede8bb4390aff2ced34f333", size = 75311 }, + { url = "https://files.pythonhosted.org/packages/db/44/acd6122201e927451869d45952b9ab1d3025cdb5e61548d286d08fbccc08/simplejson-3.19.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42e5acf80d4d971238d4df97811286a044d720693092b20a56d5e56b7dcc5d09", size = 74964 }, + { url = "https://files.pythonhosted.org/packages/27/ca/d0a1e8f16e1bbdc0b8c6d88166f45f565ed7285f53928cfef3b6ce78f14d/simplejson-3.19.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0b0efc7279d768db7c74d3d07f0b5c81280d16ae3fb14e9081dc903e8360771", size = 150106 }, + { url = "https://files.pythonhosted.org/packages/63/59/0554b78cf26c98e2b9cae3f44723bd72c2394e2afec1a14eedc6211f7187/simplejson-3.19.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0552eb06e7234da892e1d02365cd2b7b2b1f8233aa5aabdb2981587b7cc92ea0", size = 158347 }, + { url = "https://files.pythonhosted.org/packages/b2/fe/9f30890352e431e8508cc569912d3322147d3e7e4f321e48c0adfcb4c97d/simplejson-3.19.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf6a3b9a7d7191471b464fe38f684df10eb491ec9ea454003edb45a011ab187", size = 148456 }, + { url = "https://files.pythonhosted.org/packages/37/e3/663a09542ee021d4131162f7a164cb2e7f04ef48433a67591738afbf12ea/simplejson-3.19.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7017329ca8d4dca94ad5e59f496e5fc77630aecfc39df381ffc1d37fb6b25832", size = 152190 }, + { url = "https://files.pythonhosted.org/packages/31/20/4e0c4d35e10ff6465003bec304316d822a559a1c38c66ef6892ca199c207/simplejson-3.19.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:67a20641afebf4cfbcff50061f07daad1eace6e7b31d7622b6fa2c40d43900ba", size = 149846 }, + { url = "https://files.pythonhosted.org/packages/08/7a/46e2e072cac3987cbb05946f25167f0ad2fe536748e7405953fd6661a486/simplejson-3.19.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd6a7dabcc4c32daf601bc45e01b79175dde4b52548becea4f9545b0a4428169", size = 151714 }, + { url = "https://files.pythonhosted.org/packages/7f/7d/dbeeac10eb61d5d8858d0bb51121a21050d281dc83af4c557f86da28746c/simplejson-3.19.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:08f9b443a94e72dd02c87098c96886d35790e79e46b24e67accafbf13b73d43b", size = 158777 }, + { url = "https://files.pythonhosted.org/packages/fc/8f/a98bdbb799c6a4a884b5823db31785a96ba895b4b0f4d8ac345d6fe98bbf/simplejson-3.19.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa97278ae6614346b5ca41a45a911f37a3261b57dbe4a00602048652c862c28b", size = 154230 }, + { url = "https://files.pythonhosted.org/packages/b1/db/852eebceb85f969ae40e06babed1a93d3bacb536f187d7a80ff5823a5979/simplejson-3.19.3-cp312-cp312-win32.whl", hash = "sha256:ef28c3b328d29b5e2756903aed888960bc5df39b4c2eab157ae212f70ed5bf74", size = 74002 }, + { url = "https://files.pythonhosted.org/packages/fe/68/9f0e5df0651cb79ef83cba1378765a00ee8038e6201cc82b8e7178a7778e/simplejson-3.19.3-cp312-cp312-win_amd64.whl", hash = "sha256:1e662336db50ad665777e6548b5076329a94a0c3d4a0472971c588b3ef27de3a", size = 75596 }, + { url = "https://files.pythonhosted.org/packages/93/3a/5896821ed543899fcb9c4256c7e71bb110048047349a00f42bc8b8fb379f/simplejson-3.19.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0959e6cb62e3994b5a40e31047ff97ef5c4138875fae31659bead691bed55896", size = 92931 }, + { url = "https://files.pythonhosted.org/packages/39/15/5d33d269440912ee40d856db0c8be2b91aba7a219690ab01f86cb0edd590/simplejson-3.19.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a7bfad839c624e139a4863007233a3f194e7c51551081f9789cba52e4da5167", size = 75318 }, + { url = "https://files.pythonhosted.org/packages/2a/8d/2e7483a2bf7ec53acf7e012bafbda79d7b34f90471dda8e424544a59d484/simplejson-3.19.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afab2f7f2486a866ff04d6d905e9386ca6a231379181a3838abce1f32fbdcc37", size = 74971 }, + { url = "https://files.pythonhosted.org/packages/4d/9d/9bdf34437c8834a7cf7246f85e9d5122e30579f512c10a0c2560e994294f/simplejson-3.19.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00313681015ac498e1736b304446ee6d1c72c5b287cd196996dad84369998f7", size = 150112 }, + { url = "https://files.pythonhosted.org/packages/a7/e2/1f2ae2d89eaf85f6163c82150180aae5eaa18085cfaf892f8a57d4c51cbd/simplejson-3.19.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d936ae682d5b878af9d9eb4d8bb1fdd5e41275c8eb59ceddb0aeed857bb264a2", size = 158354 }, + { url = "https://files.pythonhosted.org/packages/60/83/26f610adf234c8492b3f30501e12f2271e67790f946c6898fe0c58aefe99/simplejson-3.19.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c6657485393f2e9b8177c77a7634f13ebe70d5e6de150aae1677d91516ce6b", size = 148455 }, + { url = "https://files.pythonhosted.org/packages/b5/4b/109af50006af77133653c55b5b91b4bd2d579ff8254ce11216c0b75f911b/simplejson-3.19.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a6a750d3c7461b1c47cfc6bba8d9e57a455e7c5f80057d2a82f738040dd1129", size = 152191 }, + { url = "https://files.pythonhosted.org/packages/75/dc/108872a8825cbd99ae6f4334e0490ff1580367baf12198bcaf988f6820ba/simplejson-3.19.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ea7a4a998c87c5674a27089e022110a1a08a7753f21af3baf09efe9915c23c3c", size = 149954 }, + { url = "https://files.pythonhosted.org/packages/eb/be/deec1d947a5d0472276ab4a4d1a9378dc5ee27f3dc9e54d4f62ffbad7a08/simplejson-3.19.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6300680d83a399be2b8f3b0ef7ef90b35d2a29fe6e9c21438097e0938bbc1564", size = 151812 }, + { url = "https://files.pythonhosted.org/packages/e9/58/4ee130702d36b1551ef66e7587eefe56651f3669255bf748cd71691e2434/simplejson-3.19.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ab69f811a660c362651ae395eba8ce84f84c944cea0df5718ea0ba9d1e4e7252", size = 158880 }, + { url = "https://files.pythonhosted.org/packages/0f/e1/59cc6a371b60f89e3498d9f4c8109f6b7359094d453f5fe80b2677b777b0/simplejson-3.19.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:256e09d0f94d9c3d177d9e95fd27a68c875a4baa2046633df387b86b652f5747", size = 154344 }, + { url = "https://files.pythonhosted.org/packages/79/45/1b36044670016f5cb25ebd92497427d2d1711ecb454d00f71eb9a00b77cc/simplejson-3.19.3-cp313-cp313-win32.whl", hash = "sha256:2c78293470313aefa9cfc5e3f75ca0635721fb016fb1121c1c5b0cb8cc74712a", size = 74002 }, + { url = "https://files.pythonhosted.org/packages/e2/58/b06226e6b0612f2b1fa13d5273551da259f894566b1eef32249ddfdcce44/simplejson-3.19.3-cp313-cp313-win_amd64.whl", hash = "sha256:3bbcdc438dc1683b35f7a8dc100960c721f922f9ede8127f63bed7dfded4c64c", size = 75599 }, + { url = "https://files.pythonhosted.org/packages/0d/e7/f9fafbd4f39793a20cc52e77bbd766f7384312526d402c382928dc7667f6/simplejson-3.19.3-py3-none-any.whl", hash = "sha256:49cc4c7b940d43bd12bf87ec63f28cbc4964fc4e12c031cc8cd01650f43eb94e", size = 57004 }, +] + [[package]] name = "six" version = "1.16.0" @@ -3458,29 +3522,29 @@ wheels = [ [[package]] name = "tensorstore" -version = "0.1.64" +version = "0.1.68" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ml-dtypes" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/b7/04d19901451da377f03a6e1ae3d9edf0b43af93309f558abf28b2e5aaceb/tensorstore-0.1.64.tar.gz", hash = "sha256:7fa89e90876fb5377efc54f3f37326a6fb83ec9e1326565819a75a4e80949886", size = 6510000 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/a8/63876bab9ca44d0b57bca6893927df90b08ff0123697216fe7b297036015/tensorstore-0.1.64-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:c369088c74c0dda30398290724513a0289f25ccc01865ed5aec62e57f1930709", size = 15366638 }, - { url = "https://files.pythonhosted.org/packages/90/3d/28b0ee2d792842d2e27be9fea5c541a77d1f8f4d4c1a3a981306acb69818/tensorstore-0.1.64-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40cae39aca2992fdac0ed5fbcef71f72cd38a759b1a61c37d95ad395606697b4", size = 13563010 }, - { url = "https://files.pythonhosted.org/packages/b8/26/40a8cc7ffcc4abeacd196560f8d54ca2e24d2bb8ca540360bf4c7b1b5e70/tensorstore-0.1.64-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cf64ee03c7cd62a0dde2f4d1f3f8784d50aea3a2e85a65686be0fe33ea18ed5", size = 13650288 }, - { url = "https://files.pythonhosted.org/packages/f1/3b/9e539c9d22f4eda48a9e5788d76e761f0627f249c3018d396bcdf17c7a54/tensorstore-0.1.64-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a78aedbddccc09ea283b145496da03dbc7eb8693ae4e01074ed791d72b7eac2", size = 14926295 }, - { url = "https://files.pythonhosted.org/packages/66/f4/fb0bab70e472ce78f290222b5b1631c589a8fe9043148c0882150b28b527/tensorstore-0.1.64-cp310-cp310-win_amd64.whl", hash = "sha256:72517af8c5f9c49d0343acb7c6b0cc250f8077ca989285d471d3a64dbbfcc36b", size = 11523913 }, - { url = "https://files.pythonhosted.org/packages/4d/9c/e1ef8f867de64f36c2ec3a1cb803693736a4dcb91d5afd0741c8e11e71df/tensorstore-0.1.64-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2b0a1e3294d2e690a9c269ea50d62f2f60f7935ca507243d8b56b2871b0e201f", size = 15367232 }, - { url = "https://files.pythonhosted.org/packages/46/a7/e6adff4ec3f622bd28a79bfa339aea3dc9d66508e87bc739f730b970098e/tensorstore-0.1.64-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3da6fa00ddf312e1b502d2ee9de39b858a78a02b396114201c67c01bc03fc382", size = 13567261 }, - { url = "https://files.pythonhosted.org/packages/19/c4/e74f4c288b429221fd2f128eb57bed62ebf4bf69739970e404d8a5b63712/tensorstore-0.1.64-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c32976f5a0e881a097b52a488fb16d33a1d94a86393115098da87894fc9c5abf", size = 13652088 }, - { url = "https://files.pythonhosted.org/packages/c8/5a/2df005251df903de0fda4d8da7e7a5081a6854d40b62b8eeaf88a86a1c7a/tensorstore-0.1.64-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55af5ec5bd78056e4df18f4af107bac7ea84d2bdc34ff6ab6642b3a036f99390", size = 14926070 }, - { url = "https://files.pythonhosted.org/packages/e5/68/07d792f014fc3ad886a2498ebbfdaf5d6807c09c65fad5534969620846b4/tensorstore-0.1.64-cp311-cp311-win_amd64.whl", hash = "sha256:24a4cebaf9d0e75d494342948f68edc971d6bb90e23192ddf8d98397fb1ff3cb", size = 11523737 }, - { url = "https://files.pythonhosted.org/packages/00/32/e9b22f4c05ae910940fbc6c304b6570b8cf8d35b1d2e8600d8118c42a80d/tensorstore-0.1.64-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:80c510024cc31c4dee7f478ea67a0b4b4cacf5a6bffe8c4e446188fdbe2d7b4c", size = 15404886 }, - { url = "https://files.pythonhosted.org/packages/df/9d/01e43143ac82cdc7b87e55818f0052a63b3414bd9f731a2c991dd68ca4ba/tensorstore-0.1.64-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c90d38b552c79f0d688cc3d502a9023e3dee9821881d6727d8aa06482ccdc0c1", size = 13594439 }, - { url = "https://files.pythonhosted.org/packages/44/7e/1522b9092e396d64d84ea799ef1f9c1d7e7da3514277fa8b908e1d8d26d1/tensorstore-0.1.64-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9968f9a9b9cd7c669bfae5244307e105c006038e8dd156eebbf2146f771ba369", size = 13646074 }, - { url = "https://files.pythonhosted.org/packages/0a/eb/09210bb4a8afc991eb9cb794269ff276a62f15936aef2b64335b61412f7a/tensorstore-0.1.64-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:806774968ee4cc8809114281730e9fad5970a94a7ef9104bc54fa35a32068b2f", size = 14923761 }, - { url = "https://files.pythonhosted.org/packages/c7/70/27281fb67817d69dddc5eec9827513f8e341e3a52cb85f066a84e9274a47/tensorstore-0.1.64-cp312-cp312-win_amd64.whl", hash = "sha256:cc315029f49c0f294f0721462c221e0ef4c15360a526cc34392ac81565fd63b8", size = 11523992 }, +sdist = { url = "https://files.pythonhosted.org/packages/93/c4/477fc183721128feb97a3427940457ace4c4f063da6f6f7a0b374f6b6d4c/tensorstore-0.1.68.tar.gz", hash = "sha256:6e13d3e3c8fb6ed67712835a343821536b38d6bdb517db554d41cebfe5947ab7", size = 6585632 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/22/ec1298e7b01ff2a8dbcfa5f5cffbcc323c7f040ef511bd343a25ee1e2511/tensorstore-0.1.68-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:c9ca5a5dc1e13760f024c3607219e60c3b8338f1b4f7413e1a13115a132ac7d9", size = 13996437 }, + { url = "https://files.pythonhosted.org/packages/c8/44/a50067d4964c5106f72752d3b778839edf7156695ece64bc51aefe5b5476/tensorstore-0.1.68-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:425c56cdd7f76af8be0c056933da9bf8b8812c00e4fef08888465e2f126d53eb", size = 12230834 }, + { url = "https://files.pythonhosted.org/packages/99/9e/cbffcabdfc5a471d4ddadddccac32d028f7b76375c1c3204edaeb44cef6a/tensorstore-0.1.68-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1348768a5aae514b440212eedb50d246a1a4b39f8e74d275ef0bead688c562b", size = 13983781 }, + { url = "https://files.pythonhosted.org/packages/18/35/1b4f581767982c539b5558b6bcd14e885cd1d6f5589b614fbae7bf2846d4/tensorstore-0.1.68-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5fa0e47b42eb58ddea81763cb0de4a92c4ab0da530d2a27f1928539980a781a", size = 15320730 }, + { url = "https://files.pythonhosted.org/packages/5e/eb/db4df6fbd35fd8dc2312f5cdb4832505864f6ecb1bdbcc55d8b44a17e979/tensorstore-0.1.68-cp310-cp310-win_amd64.whl", hash = "sha256:76ebad6762d226c9621d256d8703381963e407d0361cd33f0f89409a31acb57e", size = 11996126 }, + { url = "https://files.pythonhosted.org/packages/28/f1/4e0212dd514f8aabf739ad4e599e73e28c9dd3fe69431ba52b0e06e89894/tensorstore-0.1.68-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:23dc88d5188267529beb72012f72ce892ee25d40daf9dd533413bfc818b1d030", size = 13999180 }, + { url = "https://files.pythonhosted.org/packages/38/d9/7f60328b202cc3a263fe1975e9de77d7f2f0f5b9ba0e4c8eedc85d60fe46/tensorstore-0.1.68-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9d62c4288e68b4640de878f8393a5779440b2de8e84cf7b717f91a01a4e6b4be", size = 12231469 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/3028f63cdbe8c421301e54b6bf6d4f806906b0ed63493667c930e12185bc/tensorstore-0.1.68-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6e51188a82c93563440c805bd501b12f0dc30267667f664091b3a2b8b108017", size = 13985840 }, + { url = "https://files.pythonhosted.org/packages/57/fc/00412d0acf5e51d17e14d39da30a91d53cec83b5a717064af781021f65db/tensorstore-0.1.68-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:889900ee6a9ffba4635f44f663b41f5b43f67b1e74bd507fa4a30f0f02704c80", size = 15319741 }, + { url = "https://files.pythonhosted.org/packages/b3/8b/e2e8f4b2a8e682d3cbd25a32df2da06441a4091d7cf262cf7f10237627af/tensorstore-0.1.68-cp311-cp311-win_amd64.whl", hash = "sha256:c65460ac90f8db49ad35779964ea5983332fe63e60b4d94ba66640c68ef73091", size = 11997197 }, + { url = "https://files.pythonhosted.org/packages/f7/e5/047b97ac501a0bc48928897f8e3516856bc7488301df4af462750acff37d/tensorstore-0.1.68-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:d80f9b48b057fda9aea0407e576324354b054aae02fa08fc0a8e6b11acf7ae3a", size = 14038026 }, + { url = "https://files.pythonhosted.org/packages/a2/1e/3f368ed9e1dd09e022851be2a1ffdc2e410ababdd16333332bcbf4eda1de/tensorstore-0.1.68-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5902d7c36e6119b761d02260b68646585b315202397e2a6c016e3f5d81d39a43", size = 12260517 }, + { url = "https://files.pythonhosted.org/packages/36/32/26afb8fd2dd5cff758a01708e10a3b59e1756d72e5f00c1fe91f0c971057/tensorstore-0.1.68-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a93fe05708acb9d9e3813f7f7ecd807c8ff34ec3fa30e2baa37e9270d128dcf0", size = 13972573 }, + { url = "https://files.pythonhosted.org/packages/93/e9/9674f5d59161325f350acc51b19db749a1b2b381b09417843ff09eba4d67/tensorstore-0.1.68-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6672b2047df3f772350ac75d6780f31201a82383c5b7c0c1986903b88e6f341a", size = 15312725 }, + { url = "https://files.pythonhosted.org/packages/7b/78/4d855796274f90bd869e2f79f20b84d12ff50b9b612970a128815f7b375e/tensorstore-0.1.68-cp312-cp312-win_amd64.whl", hash = "sha256:172420ec1c4e925a8ec3c386e31b4f81eae403bdca71b6258e7f775a69c3bfb3", size = 11998145 }, ] [[package]]