haliax 1.4.dev289__tar.gz → 1.4.dev290__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {haliax-1.4.dev289 → haliax-1.4.dev290}/PKG-INFO +1 -1
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/fp8.md +3 -2
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/indexing.md +90 -3
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/rearrange.md +1 -0
- haliax-1.4.dev290/src/haliax/__about__.py +1 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/core.py +326 -59
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/types.py +3 -1
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/core_test.py +107 -0
- haliax-1.4.dev289/src/haliax/__about__.py +0 -1
- {haliax-1.4.dev289 → haliax-1.4.dev290}/.coveragerc +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/.flake8 +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/.gitignore +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/LICENSE +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/README.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/api.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/css/material.css +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/faq.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/hof.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/index.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/matmul.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/nn.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/partitioning.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/requirements.txt +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/docs/tutorial.md +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/mkdocs.yml +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/pyproject.toml +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/__init__.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/jax_utils.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/embedding.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/linear.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/normalization.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/nn/scan.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/random.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/util.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_attention.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_axis.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_conv.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_debug.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_dot.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_hof.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_nn.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_ops.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_pool.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_random.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_scan.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev289 → haliax-1.4.dev290}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev290
|
|
4
4
|
Summary: Named Tensors for Legible Deep Learning in JAX
|
|
5
5
|
Project-URL: Homepage, https://github.com/stanford-crfm/haliax
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/stanford-crfm/haliax/issues/
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# FP8 Training
|
|
2
2
|
|
|
3
3
|
!!! warning
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
FP8 training in Haliax is currently experimental and may change in the future.
|
|
5
6
|
|
|
6
7
|
FP8 refers to 8-bit floating point numbers. FP8 is a massively reduced precision compared to the 32-bit floating point numbers
|
|
7
8
|
or 16-bit floating point numbers that are typically used in deep learning: there are only 256 possible values in FP8, compared to
|
|
@@ -123,7 +124,7 @@ you will get the gradient computation as normal, but you'll also get the updated
|
|
|
123
124
|
This updated state needs to directly replace the state in the module (rather than be used for a gradient step), which is
|
|
124
125
|
why you need to use the `partition_for_grad_overwrite`
|
|
125
126
|
|
|
126
|
-
The FP8 `dot_general` module is implemented in [haliax.quantization.
|
|
127
|
+
The FP8 `dot_general` module is implemented in [haliax.quantization.Fp8DotGeneralOp][]. It's actually not that complicated:
|
|
127
128
|
|
|
128
129
|
1) It holds a scaling factor and history of maximum values for each of (lhs, rhs, output) and updates them based on the
|
|
129
130
|
gradients.
|
|
@@ -37,12 +37,42 @@ Otherwise, the idea is pretty straightforward: any unspecified axes are treated
|
|
|
37
37
|
slices are kept in reduced dimensions, and integers eliminate dimensions. If all dimensions are eliminated, a scalar
|
|
38
38
|
JAX ndarray is returned.
|
|
39
39
|
|
|
40
|
+
The following types are supported for indexing:
|
|
41
|
+
|
|
42
|
+
* Integers, including scalar JAX arrays
|
|
43
|
+
* Slices
|
|
44
|
+
* [haliax.dslice][] objects (See [Dynamic Slices](#dynamic-slices) below.)
|
|
45
|
+
* Lists of integers
|
|
46
|
+
* Named arrays (See [Advanced Indexing](#advanced-indexing) below.)
|
|
47
|
+
* 1-D JAX Arrays of integers
|
|
48
|
+
|
|
49
|
+
1-D JAX Arrays are interpreted as NamedArrays with a single axis with the same name as
|
|
50
|
+
the one they are slicing. That is:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import haliax as hax
|
|
54
|
+
import jax
|
|
55
|
+
import jax.numpy as jnp
|
|
56
|
+
|
|
57
|
+
X = hax.Axis("X", 10)
|
|
58
|
+
Y = hax.Axis("Y", 20)
|
|
59
|
+
|
|
60
|
+
a = hax.random.uniform(jax.random.PRNGKey(0), (X, Y))
|
|
61
|
+
|
|
62
|
+
sliced = a["X", jnp.array([1, 2, 3])]
|
|
63
|
+
|
|
64
|
+
# same as
|
|
65
|
+
a.array[jnp.array([1, 2, 3]), :]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Note that boolean arrays are not supported, as JAX does not support them in JIT-compiled code. You
|
|
69
|
+
can use [haliax.where][] for most of the same functionality, though.
|
|
40
70
|
|
|
41
71
|
### Shapes in JAX
|
|
42
72
|
|
|
43
|
-
Before we continue note on shapes in JAX. Most JAX code will be used inside `jit`, which means that the sizes of all
|
|
44
|
-
must be determined at compile time (i.e. when JAX interprets your functions abstractly). This is a hard
|
|
45
|
-
XLA.
|
|
73
|
+
Before we continue, a note on shapes in JAX. Most JAX code will be used inside `jit`, which means that the sizes of all
|
|
74
|
+
arrays must be determined at compile time (i.e. when JAX interprets your functions abstractly). This is a hard
|
|
75
|
+
requirement in XLA. It might worked around one day, but it's the way things are for now.
|
|
46
76
|
|
|
47
77
|
A consequence of this restriction is that certain indexing patterns aren't allowed in `jit`-ed JAX code:
|
|
48
78
|
|
|
@@ -177,3 +207,60 @@ a[{"Y": ind1}] # error, "X" is not eliminated by the indexing operation
|
|
|
177
207
|
|
|
178
208
|
a[{"X": ind2, "Y": ind1}] # ok, because X and Y are eliminated by the indexing operation
|
|
179
209
|
```
|
|
210
|
+
|
|
211
|
+
## Index Update
|
|
212
|
+
|
|
213
|
+
JAX is a functional version of NumPy, so it doesn't directly support in-place updates. It does
|
|
214
|
+
however [provide an `at` syntax](https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.ndarray.at.html#jax.numpy.ndarray.at)
|
|
215
|
+
to express the same logic (and that will typically be optimized to be as efficient as an in-place update). Haliax
|
|
216
|
+
provides a similar syntax for updating arrays.
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import haliax as hax
|
|
220
|
+
|
|
221
|
+
X = hax.Axis("X", 10)
|
|
222
|
+
Y = hax.Axis("Y", 20)
|
|
223
|
+
Z = hax.Axis("Z", 30)
|
|
224
|
+
|
|
225
|
+
a = hax.zeros((X, Y, Z))
|
|
226
|
+
|
|
227
|
+
a.at[{"X": 1, "Y": 2, "Z": 3}].set(1.0) # sets a[1, 2, 3] to 1.0
|
|
228
|
+
a.at["X", 1].set(2.0) # sets a[1, :, :] to 2.0
|
|
229
|
+
|
|
230
|
+
a.at[{"X": 1, "Y": hax.ds(3, 5), "Z": 3}].add(1.0) # adds 1.0 to a[1, 3:8, 3]
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Haliax supports the same `at` functionality as JAX, just with named arrays and additionally dslices. A summary of the
|
|
234
|
+
`at` syntax is as follows:
|
|
235
|
+
|
|
236
|
+
| Alternate Syntax | Equivalent In-Place Operation |
|
|
237
|
+
|------------------------------|-------------------------------|
|
|
238
|
+
| `x = x.at[idx].set(y)` | `x[idx] = y` |
|
|
239
|
+
| `x = x.at[idx].add(y)` | `x[idx] += y` |
|
|
240
|
+
| `x = x.at[idx].multiply(y)` | `x[idx] *= y` |
|
|
241
|
+
| `x = x.at[idx].divide(y)` | `x[idx] /= y` |
|
|
242
|
+
| `x = x.at[idx].power(y)` | `x[idx] **= y` |
|
|
243
|
+
| `x = x.at[idx].min(y)` | `x[idx] = minimum(x[idx], y)` |
|
|
244
|
+
| `x = x.at[idx].max(y)` | `x[idx] = maximum(x[idx], y)` |
|
|
245
|
+
| `x = x.at[idx].apply(ufunc)` | `ufunc.at(x, idx)` |
|
|
246
|
+
| `x = x.at[idx].get()` | `x = x[idx]` |
|
|
247
|
+
|
|
248
|
+
These methods also have options to control out-of-bounds behavior, as well as allowing you
|
|
249
|
+
to specify that the indices are sorted or unique. (If they are, XLA can sometimes optimize the
|
|
250
|
+
operation more effectively.)
|
|
251
|
+
|
|
252
|
+
!!! note
|
|
253
|
+
|
|
254
|
+
These named arguments are not passed to `at`, but to the next method in the chain.
|
|
255
|
+
|
|
256
|
+
(This is copied from the JAX documentation:)
|
|
257
|
+
|
|
258
|
+
* `mode`: One of `"promise_in_bounds"`, `"clip"`, `"drop"`, or `"fill"`. See [JAX's documentation](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.GatherScatterMode.html#jax.lax.GatherScatterMode) for more details.
|
|
259
|
+
* `indices_are_sorted`: If `True`, the implementation will assume that the indices passed to `at` are sorted in ascending order, which can lead to more efficient execution on some backends.
|
|
260
|
+
* `unique_indices`: If `True`, the implementation will assume that the indices passed to `at` are unique, which can result in more efficient execution on some backends.
|
|
261
|
+
* `fill_value`: Only applies to the `get()` method: the fill value to return for out-of-bounds slices when mode is 'fill'. Ignored otherwise. Defaults to NaN for inexact types, the largest negative value for signed types, the largest positive value for unsigned types, and True for booleans.
|
|
262
|
+
|
|
263
|
+
!!! tip
|
|
264
|
+
|
|
265
|
+
It's worth emphasizing that these functions are typically compiled to scatter-add and friends (as appropriate).
|
|
266
|
+
This is the preferred way to do scatter/gather operations in JAX, as well as in Haliax.
|
|
@@ -54,6 +54,7 @@ If you're used to einops, the syntax should be familiar, with the main differenc
|
|
|
54
54
|
and the additional "unordered" syntax for selecting dimensions by name.
|
|
55
55
|
|
|
56
56
|
!!! warning
|
|
57
|
+
|
|
57
58
|
This syntax is fairly new. It is pretty well-tested, but it is possible that there are bugs.
|
|
58
59
|
|
|
59
60
|
### Examples
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.dev290"
|
|
@@ -9,7 +9,6 @@ from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple
|
|
|
9
9
|
|
|
10
10
|
import jax
|
|
11
11
|
import jax.numpy as jnp
|
|
12
|
-
import numpy
|
|
13
12
|
import numpy as np
|
|
14
13
|
|
|
15
14
|
import haliax
|
|
@@ -18,22 +17,21 @@ from haliax.jax_utils import is_jax_array_like, is_pallas_dslice
|
|
|
18
17
|
from haliax.util import ensure_tuple
|
|
19
18
|
|
|
20
19
|
from ._src.util import index_where, py_slice, slice_t
|
|
21
|
-
from .axis import
|
|
22
|
-
|
|
23
|
-
AxisSelection,
|
|
24
|
-
AxisSelector,
|
|
25
|
-
AxisSpec,
|
|
26
|
-
axis_name,
|
|
27
|
-
dslice,
|
|
28
|
-
eliminate_axes,
|
|
29
|
-
selects_axis,
|
|
30
|
-
union_axes,
|
|
31
|
-
)
|
|
32
|
-
from .types import DTypeLike, IntScalar, PrecisionLike, Scalar
|
|
20
|
+
from .axis import Axis, AxisSelection, AxisSelector, AxisSpec, axis_name, dslice, eliminate_axes, selects_axis
|
|
21
|
+
from .types import GatherScatterModeStr, IntScalar, PrecisionLike, Scalar
|
|
33
22
|
|
|
34
23
|
|
|
35
24
|
NamedOrNumeric = Union[Scalar, "NamedArray"]
|
|
36
|
-
NamedIndex = Union[int, slice_t, "NamedArray", dslice]
|
|
25
|
+
NamedIndex = Union[int, slice_t, "NamedArray", dslice, list[int], jnp.ndarray]
|
|
26
|
+
|
|
27
|
+
SliceSpec = Union[
|
|
28
|
+
tuple[AxisSelector, NamedIndex],
|
|
29
|
+
tuple[AxisSelector, NamedIndex, AxisSelector, NamedIndex],
|
|
30
|
+
tuple[AxisSelector, NamedIndex, AxisSelector, NamedIndex, AxisSelector, NamedIndex],
|
|
31
|
+
tuple[AxisSelector | NamedOrNumeric, ...],
|
|
32
|
+
Mapping[AxisSelector, NamedIndex],
|
|
33
|
+
]
|
|
34
|
+
|
|
37
35
|
|
|
38
36
|
_ENABLE_SHAPE_CHECKS = True
|
|
39
37
|
|
|
@@ -330,19 +328,47 @@ class NamedArray:
|
|
|
330
328
|
def take(self, axis: AxisSelector, index: Union[int, "NamedArray"]) -> "NamedArray":
|
|
331
329
|
return haliax.take(self, axis=axis, index=index)
|
|
332
330
|
|
|
333
|
-
@
|
|
334
|
-
def
|
|
335
|
-
|
|
331
|
+
@property
|
|
332
|
+
def at(self) -> "_NamedIndexUpdateHelper":
|
|
333
|
+
"""
|
|
334
|
+
Named analog of [jax's at method](https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.ndarray.at.html).
|
|
336
335
|
|
|
337
|
-
|
|
338
|
-
def __getitem__(self, item: Tuple[AxisSelector, NamedIndex, AxisSelector, NamedIndex]) -> "NamedArray":
|
|
339
|
-
...
|
|
336
|
+
Docs from the JAX docs:
|
|
340
337
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
338
|
+
The at property provides a functionally pure equivalent of in-place array modifications.
|
|
339
|
+
|
|
340
|
+
In particular:
|
|
341
|
+
|
|
342
|
+
| Alternate syntax | Equivalent In-place expression |
|
|
343
|
+
|------------------|-------------------------------|
|
|
344
|
+
| `x = x.at[idx].set(y)` | `x[idx] = y` |
|
|
345
|
+
| `x[idx] = y` | `x = x.at[idx].set(y)` |
|
|
346
|
+
| `x = x.at[idx].add(y)` | `x[idx] += y`|
|
|
347
|
+
| `x = x.at[idx].multiply(y)` | `x[idx] *= y`|
|
|
348
|
+
| `x = x.at[idx].divide(y)` | `x[idx] /= y`|
|
|
349
|
+
| `x = x.at[idx].power(y)` | `x[idx] **= y`|
|
|
350
|
+
| `x = x.at[idx].min(y)` | `x[idx] = minimum(x[idx], y)`|
|
|
351
|
+
| `x = x.at[idx].max(y)` | `x[idx] = maximum(x[idx], y)`|
|
|
352
|
+
| `x = x.at[idx].apply(ufunc)` | `ufunc.at(x, idx)`|
|
|
353
|
+
|
|
354
|
+
x = x.at[idx].get()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
x = x[idx]
|
|
358
|
+
|
|
359
|
+
None of the x.at expressions modify the original x; instead they return a modified copy of x. However, inside a jit() compiled function, expressions like x = x.at[idx].set(y) are guaranteed to be applied in-place.
|
|
360
|
+
|
|
361
|
+
Unlike NumPy in-place operations such as x[idx] += y, if multiple indices refer to the same location, all updates will be applied (NumPy would only apply the last update, rather than applying all updates.) The order in which conflicting updates are applied is implementation-defined and may be nondeterministic (e.g., due to concurrency on some hardware platforms).
|
|
362
|
+
|
|
363
|
+
By default, JAX assumes that all indices are in-bounds. Alternative out-of-bound index semantics can be specified via the mode parameter (see below).
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
344
367
|
|
|
345
|
-
|
|
368
|
+
"""
|
|
369
|
+
return _NamedIndexUpdateHelper(self)
|
|
370
|
+
|
|
371
|
+
def __getitem__(self, idx: SliceSpec) -> "NamedArray":
|
|
346
372
|
"""Syntactic sugar for [haliax.index][], which is the actual implementation.
|
|
347
373
|
|
|
348
374
|
Supports indexing like:
|
|
@@ -366,17 +392,8 @@ class NamedArray:
|
|
|
366
392
|
|
|
367
393
|
This returns a NamedArray if any axes remain, or a scalar (0-dimensional) jnp.ndarray if all axes are indexed out.
|
|
368
394
|
"""
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
idx = idx[0]
|
|
372
|
-
else:
|
|
373
|
-
if len(idx) % 2 != 0:
|
|
374
|
-
raise ValueError(
|
|
375
|
-
"Must provide an even number of arguments to __getitem__ when using the shorthand syntax."
|
|
376
|
-
)
|
|
377
|
-
idx = {idx[i]: idx[i + 1] for i in range(0, len(idx), 2)}
|
|
378
|
-
|
|
379
|
-
return index(self, idx)
|
|
395
|
+
idx_dict = _convert_index_expr_to_dict(idx)
|
|
396
|
+
return index(self, idx_dict)
|
|
380
397
|
|
|
381
398
|
# np.ndarray methods:
|
|
382
399
|
def all(self, axis: Optional[AxisSelection] = None, *, where: Optional["NamedArray"] = None) -> "NamedArray":
|
|
@@ -886,28 +903,60 @@ def index(array: NamedArray, slices: Mapping[AxisSelector, NamedIndex]) -> Named
|
|
|
886
903
|
you might use `array[{"batch": slice(0, 10)}]` or `array["batch", 0:10]` to select the first 10 elements
|
|
887
904
|
of the 'batch' axis.
|
|
888
905
|
|
|
906
|
+
See Also:
|
|
907
|
+
* [haliax.NamedArray.at][] for a functional equivalent of in-place array modifications.
|
|
908
|
+
|
|
889
909
|
Returns:
|
|
890
910
|
NamedArray or jnp.ndarray: A NamedArray is returned if there are any axes remaining after selection,
|
|
891
911
|
otherwise a scalar (0-dimensional) jnp.ndarray is returned if all axes are indexed out.
|
|
892
912
|
"""
|
|
893
913
|
# indices where we have array args
|
|
894
|
-
|
|
895
|
-
|
|
914
|
+
new_axes, ordered_slices = _compute_new_axes_and_slices_for_index(array, slices)
|
|
915
|
+
sliced, ordered_slices = _handle_dynamic_slices(array.array, ordered_slices)
|
|
916
|
+
sliced = sliced[tuple(ordered_slices)]
|
|
917
|
+
|
|
918
|
+
return haliax.named(sliced, new_axes)
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def _compute_new_axes_and_slices_for_index(
|
|
922
|
+
array, slices
|
|
923
|
+
) -> tuple[AxisSpec, list[py_slice | dslice | jnp.ndarray | int | list[int]]]:
|
|
896
924
|
ordered_slices: list = [py_slice(None, None, None)] * len(array.axes) # type: ignore
|
|
897
925
|
kept_axes = [True] * len(array.axes)
|
|
926
|
+
array_slice_indices = []
|
|
927
|
+
|
|
898
928
|
for axis, slice_ in slices.items():
|
|
899
929
|
axis_index = array._lookup_indices(axis)
|
|
900
930
|
if axis_index is None:
|
|
901
931
|
raise ValueError(f"axis {axis} not found in {array}")
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
932
|
+
if isinstance(slice_, py_slice) or isinstance(slice_, dslice) or is_pallas_dslice(slice_):
|
|
933
|
+
ordered_slices[axis_index] = slice_
|
|
934
|
+
kept_axes[axis_index] = True
|
|
935
|
+
elif isinstance(slice_, int):
|
|
936
|
+
ordered_slices[axis_index] = slice_
|
|
937
|
+
kept_axes[axis_index] = False
|
|
938
|
+
elif isinstance(slice_, NamedArray):
|
|
939
|
+
ordered_slices[axis_index] = slice_
|
|
907
940
|
array_slice_indices.append(axis_index)
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
941
|
+
kept_axes[axis_index] = False
|
|
942
|
+
elif isinstance(slice_, list):
|
|
943
|
+
# we'll let JAX complain if this is wrong
|
|
944
|
+
ordered_slices[axis_index] = slice_
|
|
945
|
+
elif isinstance(slice_, jnp.ndarray):
|
|
946
|
+
# we allow this if it's a 0-d or 1-d array
|
|
947
|
+
if slice_.ndim == 0:
|
|
948
|
+
ordered_slices[axis_index] = slice_
|
|
949
|
+
elif slice_.ndim == 1:
|
|
950
|
+
# we allow this if it's a 1-d array, in which case we treat it as sugar for NamedArray(slice_, sliced-axis)
|
|
951
|
+
ordered_slices[axis_index] = haliax.named(slice_, axis_name(axis))
|
|
952
|
+
kept_axes[axis_index] = False
|
|
953
|
+
array_slice_indices.append(axis_index)
|
|
954
|
+
else:
|
|
955
|
+
raise ValueError(
|
|
956
|
+
f"Only 0-d or 1-d unnamed arrays can be used for indexing. Got {slice_} for axis {axis}"
|
|
957
|
+
)
|
|
958
|
+
else:
|
|
959
|
+
raise ValueError(f"Only NamedArrays can be used for advanced indexing. Got {slice_} for axis {axis}")
|
|
911
960
|
|
|
912
961
|
# advanced indexing
|
|
913
962
|
if len(array_slice_indices) > 0:
|
|
@@ -958,23 +1007,39 @@ def index(array: NamedArray, slices: Mapping[AxisSelector, NamedIndex]) -> Named
|
|
|
958
1007
|
# the advanced indices are not contiguous, so we need to insert the new axes at the front
|
|
959
1008
|
new_axes = broadcasted_axes + tuple(ax for i, ax in enumerate(array.axes) if kept_axes[i])
|
|
960
1009
|
else:
|
|
961
|
-
new_axes = tuple(axis
|
|
1010
|
+
new_axes = tuple(axis for axis, keep in zip(array.axes, kept_axes) if keep)
|
|
962
1011
|
|
|
963
|
-
|
|
1012
|
+
new_axes = tuple(axis_name(ax) for ax in new_axes)
|
|
1013
|
+
return new_axes, ordered_slices
|
|
964
1014
|
|
|
965
|
-
if len(dslice_indices) > 0:
|
|
966
|
-
# dynamic slice out the dslices
|
|
967
|
-
indices = [0] * len(array.axes)
|
|
968
|
-
lengths = [ax.size for ax in array.axes]
|
|
969
|
-
for i in dslice_indices:
|
|
970
|
-
indices[i] = ordered_slices[i].start
|
|
971
|
-
lengths[i] = ordered_slices[i].size
|
|
972
|
-
sliced = jax.lax.dynamic_slice(sliced, indices, lengths)
|
|
973
|
-
for i in dslice_indices:
|
|
974
|
-
ordered_slices[i] = py_slice(None, None, None)
|
|
975
1015
|
|
|
976
|
-
|
|
977
|
-
|
|
1016
|
+
def _handle_dynamic_slices(array: jnp.ndarray, slices):
|
|
1017
|
+
"""
|
|
1018
|
+
Helper function to handle dynamic slices in the array. These have to be handled with jax.lax.dynamic_slice,
|
|
1019
|
+
which is for when the start index is not known at compile time. (Sizes must always be known at compile time.)
|
|
1020
|
+
|
|
1021
|
+
Notes:
|
|
1022
|
+
**MUTATES `slices` IN PLACE**
|
|
1023
|
+
|
|
1024
|
+
Returns:
|
|
1025
|
+
array.array: the sliced array
|
|
1026
|
+
|
|
1027
|
+
"""
|
|
1028
|
+
indices_for_dslice = [0] * array.ndim
|
|
1029
|
+
lengths_for_dslice = list(array.shape)
|
|
1030
|
+
dslice_indices = []
|
|
1031
|
+
need_to_slice = False
|
|
1032
|
+
for axis_index, slice_ in enumerate(slices):
|
|
1033
|
+
if isinstance(slice_, dslice) or is_pallas_dslice(slice_):
|
|
1034
|
+
dslice_indices.append(axis_index)
|
|
1035
|
+
indices_for_dslice[axis_index] = slice_.start
|
|
1036
|
+
lengths_for_dslice[axis_index] = slice_.size
|
|
1037
|
+
need_to_slice = True
|
|
1038
|
+
if need_to_slice:
|
|
1039
|
+
array = jax.lax.dynamic_slice(array, indices_for_dslice, lengths_for_dslice)
|
|
1040
|
+
for i in dslice_indices:
|
|
1041
|
+
slices[i] = py_slice(None, None, None)
|
|
1042
|
+
return array, slices
|
|
978
1043
|
|
|
979
1044
|
|
|
980
1045
|
def split(a: NamedArray, axis: AxisSelector, new_axes: Sequence[Axis]) -> Sequence[NamedArray]:
|
|
@@ -1445,6 +1510,208 @@ def flatten_all_axes_but(
|
|
|
1445
1510
|
return result, unflatten
|
|
1446
1511
|
|
|
1447
1512
|
|
|
1513
|
+
class _NamedIndexUpdateHelper:
|
|
1514
|
+
def __init__(self, array: NamedArray):
|
|
1515
|
+
self.array = array
|
|
1516
|
+
|
|
1517
|
+
def __getitem__(self, slices: SliceSpec) -> "_NamedIndexUpdateRef":
|
|
1518
|
+
return _NamedIndexUpdateRef(self.array, _convert_index_expr_to_dict(slices))
|
|
1519
|
+
|
|
1520
|
+
|
|
1521
|
+
class _NamedIndexUpdateRef:
|
|
1522
|
+
def __init__(self, array: NamedArray, slices: SliceSpec):
|
|
1523
|
+
self._array = array
|
|
1524
|
+
self._slices = slices
|
|
1525
|
+
|
|
1526
|
+
def get(
|
|
1527
|
+
self,
|
|
1528
|
+
*,
|
|
1529
|
+
indices_are_sorted: bool = False,
|
|
1530
|
+
unique_indices: bool = False,
|
|
1531
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1532
|
+
fill_value: Optional[Scalar] = None,
|
|
1533
|
+
) -> NamedArray:
|
|
1534
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1535
|
+
new_array = self._array.array.at[tuple(slices)].get(
|
|
1536
|
+
indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode, fill_value=fill_value
|
|
1537
|
+
)
|
|
1538
|
+
return NamedArray(new_array, sliced_axes)
|
|
1539
|
+
|
|
1540
|
+
def set(
|
|
1541
|
+
self,
|
|
1542
|
+
update: NamedOrNumeric,
|
|
1543
|
+
*,
|
|
1544
|
+
indices_are_sorted: bool = False,
|
|
1545
|
+
unique_indices: bool = False,
|
|
1546
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1547
|
+
) -> NamedArray:
|
|
1548
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1549
|
+
update = haliax.broadcast_to(update, sliced_axes, enforce_no_extra_axes=True)
|
|
1550
|
+
new_array = self._array.array.at[tuple(slices)].set(
|
|
1551
|
+
update.array, indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode
|
|
1552
|
+
)
|
|
1553
|
+
return NamedArray(new_array, self._array.axes)
|
|
1554
|
+
|
|
1555
|
+
def add(
|
|
1556
|
+
self,
|
|
1557
|
+
update: NamedOrNumeric,
|
|
1558
|
+
*,
|
|
1559
|
+
indices_are_sorted: bool = False,
|
|
1560
|
+
unique_indices: bool = False,
|
|
1561
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1562
|
+
) -> NamedArray:
|
|
1563
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1564
|
+
update = haliax.broadcast_to(update, sliced_axes, enforce_no_extra_axes=True)
|
|
1565
|
+
new_array = self._array.array.at[tuple(slices)].add(
|
|
1566
|
+
update.array, indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode
|
|
1567
|
+
)
|
|
1568
|
+
return NamedArray(new_array, self._array.axes)
|
|
1569
|
+
|
|
1570
|
+
def multiply(
|
|
1571
|
+
self,
|
|
1572
|
+
update: NamedOrNumeric,
|
|
1573
|
+
*,
|
|
1574
|
+
indices_are_sorted: bool = False,
|
|
1575
|
+
unique_indices: bool = False,
|
|
1576
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1577
|
+
) -> NamedArray:
|
|
1578
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1579
|
+
update = haliax.broadcast_to(update, sliced_axes, enforce_no_extra_axes=True)
|
|
1580
|
+
new_array = self._array.array.at[tuple(slices)].multiply(
|
|
1581
|
+
update.array, indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode
|
|
1582
|
+
)
|
|
1583
|
+
return NamedArray(new_array, self._array.axes)
|
|
1584
|
+
|
|
1585
|
+
def divide(
|
|
1586
|
+
self,
|
|
1587
|
+
update: NamedOrNumeric,
|
|
1588
|
+
*,
|
|
1589
|
+
indices_are_sorted: bool = False,
|
|
1590
|
+
unique_indices: bool = False,
|
|
1591
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1592
|
+
) -> NamedArray:
|
|
1593
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1594
|
+
update = haliax.broadcast_to(update, sliced_axes, enforce_no_extra_axes=True)
|
|
1595
|
+
new_array = self._array.array.at[tuple(slices)].divide(
|
|
1596
|
+
update.array, indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode
|
|
1597
|
+
)
|
|
1598
|
+
return NamedArray(new_array, self._array.axes)
|
|
1599
|
+
|
|
1600
|
+
def max(
|
|
1601
|
+
self,
|
|
1602
|
+
update: NamedOrNumeric,
|
|
1603
|
+
*,
|
|
1604
|
+
indices_are_sorted: bool = False,
|
|
1605
|
+
unique_indices: bool = False,
|
|
1606
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1607
|
+
) -> NamedArray:
|
|
1608
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1609
|
+
update = haliax.broadcast_to(update, sliced_axes, enforce_no_extra_axes=True)
|
|
1610
|
+
new_array = self._array.array.at[tuple(slices)].max(
|
|
1611
|
+
update.array, indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode
|
|
1612
|
+
)
|
|
1613
|
+
return NamedArray(new_array, self._array.axes)
|
|
1614
|
+
|
|
1615
|
+
def min(
|
|
1616
|
+
self,
|
|
1617
|
+
update: NamedOrNumeric,
|
|
1618
|
+
*,
|
|
1619
|
+
indices_are_sorted: bool = False,
|
|
1620
|
+
unique_indices: bool = False,
|
|
1621
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1622
|
+
) -> NamedArray:
|
|
1623
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1624
|
+
update = haliax.broadcast_to(update, sliced_axes, enforce_no_extra_axes=True)
|
|
1625
|
+
new_array = self._array.array.at[tuple(slices)].min(
|
|
1626
|
+
update.array, indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode
|
|
1627
|
+
)
|
|
1628
|
+
return NamedArray(new_array, self._array.axes)
|
|
1629
|
+
|
|
1630
|
+
def power(
|
|
1631
|
+
self,
|
|
1632
|
+
update: NamedOrNumeric,
|
|
1633
|
+
*,
|
|
1634
|
+
indices_are_sorted: bool = False,
|
|
1635
|
+
unique_indices: bool = False,
|
|
1636
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1637
|
+
) -> NamedArray:
|
|
1638
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1639
|
+
update = haliax.broadcast_to(update, sliced_axes, enforce_no_extra_axes=True)
|
|
1640
|
+
new_array = self._array.array.at[tuple(slices)].power(
|
|
1641
|
+
update.array, indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode
|
|
1642
|
+
)
|
|
1643
|
+
return NamedArray(new_array, self._array.axes)
|
|
1644
|
+
|
|
1645
|
+
def apply(
|
|
1646
|
+
self,
|
|
1647
|
+
func,
|
|
1648
|
+
*,
|
|
1649
|
+
indices_are_sorted: bool = False,
|
|
1650
|
+
unique_indices: bool = False,
|
|
1651
|
+
mode: Optional[GatherScatterModeStr] = None,
|
|
1652
|
+
) -> NamedArray:
|
|
1653
|
+
# It's not really documented, but func can be any callable that takes a scalar array and returns a scalar array
|
|
1654
|
+
slices, sliced_axes = _raw_indices_for_at(self._array, self._slices)
|
|
1655
|
+
new_array = self._array.array.at[tuple(slices)].apply(
|
|
1656
|
+
func, indices_are_sorted=indices_are_sorted, unique_indices=unique_indices, mode=mode
|
|
1657
|
+
)
|
|
1658
|
+
return NamedArray(new_array, self._array.axes)
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
def _raw_indices_for_at(array, indexes):
|
|
1662
|
+
sliced_axes, ordered_slices = _compute_new_axes_and_slices_for_index(array, indexes)
|
|
1663
|
+
del sliced_axes
|
|
1664
|
+
# this isn't the fastest (it does the _compute_new_axes_and_slices_for_index twice)
|
|
1665
|
+
# but it's easy
|
|
1666
|
+
_sliced = index(array, indexes)
|
|
1667
|
+
# we have to handle dslices differently than for normal indexing, because we can't use
|
|
1668
|
+
# extra dynamic_slices...
|
|
1669
|
+
# we'd like to just replace these with iota, but we have account for broadcasting semantics
|
|
1670
|
+
# for the other arrays
|
|
1671
|
+
dslice_sizes = tuple(x.size for x in ordered_slices if isinstance(x, dslice) or is_pallas_dslice(x)) # type: ignore
|
|
1672
|
+
current_array_slice_shape = next((x.shape for x in ordered_slices if is_jax_array_like(x)), None) # type: ignore
|
|
1673
|
+
dims_to_expand = list(range(len(dslice_sizes)))
|
|
1674
|
+
if current_array_slice_shape is not None:
|
|
1675
|
+
iota_shape = dslice_sizes + current_array_slice_shape
|
|
1676
|
+
else:
|
|
1677
|
+
iota_shape = dslice_sizes
|
|
1678
|
+
|
|
1679
|
+
def iota_for_dslice(dslice, cur_dynamic_slice):
|
|
1680
|
+
return jax.lax.broadcasted_iota(int, iota_shape, cur_dynamic_slice) + dslice.start
|
|
1681
|
+
|
|
1682
|
+
if len(dslice_sizes) > 0:
|
|
1683
|
+
cur_dynamic_slice = 0
|
|
1684
|
+
for i in range(len(ordered_slices)):
|
|
1685
|
+
if isinstance(ordered_slices[i], dslice) or is_pallas_dslice(ordered_slices[i]):
|
|
1686
|
+
ordered_slices[i] = iota_for_dslice(ordered_slices[i], cur_dynamic_slice)
|
|
1687
|
+
cur_dynamic_slice += 1
|
|
1688
|
+
elif is_jax_array_like(ordered_slices[i]):
|
|
1689
|
+
# prepend array slices with one 1 for each dynamic slice
|
|
1690
|
+
ordered_slices[i] = jnp.expand_dims(ordered_slices[i], axis=dims_to_expand)
|
|
1691
|
+
|
|
1692
|
+
assert cur_dynamic_slice == len(dslice_sizes)
|
|
1693
|
+
|
|
1694
|
+
# ok the ordered slices are now correct
|
|
1695
|
+
return ordered_slices, _sliced.axes
|
|
1696
|
+
|
|
1697
|
+
|
|
1698
|
+
def _convert_index_expr_to_dict(idx) -> dict[AxisSelector, NamedIndex]:
|
|
1699
|
+
if isinstance(idx, tuple):
|
|
1700
|
+
if len(idx) == 1:
|
|
1701
|
+
idx = idx[0]
|
|
1702
|
+
else:
|
|
1703
|
+
if len(idx) % 2 != 0:
|
|
1704
|
+
raise ValueError(
|
|
1705
|
+
"Must provide an even number of arguments to __getitem__ when using the shorthand syntax."
|
|
1706
|
+
)
|
|
1707
|
+
idx = {idx[i]: idx[i + 1] for i in range(0, len(idx), 2)}
|
|
1708
|
+
elif isinstance(idx, dict):
|
|
1709
|
+
pass
|
|
1710
|
+
else:
|
|
1711
|
+
raise ValueError(f"Invalid index type {type(idx)}")
|
|
1712
|
+
return idx
|
|
1713
|
+
|
|
1714
|
+
|
|
1448
1715
|
__all__ = [
|
|
1449
1716
|
"NamedArray",
|
|
1450
1717
|
"named",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Protocol, Tuple, TypeAlias, Union
|
|
1
|
+
from typing import Any, Literal, Protocol, Tuple, TypeAlias, Union
|
|
2
2
|
|
|
3
3
|
import jax.numpy as jnp
|
|
4
4
|
import numpy as np
|
|
@@ -28,3 +28,5 @@ Scalar = Union[float, int, jnp.ndarray] # ndarray b/c array(1) is a scalar
|
|
|
28
28
|
IntScalar = Union[int, jnp.ndarray]
|
|
29
29
|
|
|
30
30
|
PrecisionLike = Union[None, str, Precision, Tuple[str, str], Tuple[Precision, Precision]]
|
|
31
|
+
|
|
32
|
+
GatherScatterModeStr = Literal["promise_in_bounds", "clip", "drop", "fill"]
|
|
@@ -649,6 +649,34 @@ def test_slice_nd_array_present_dims():
|
|
|
649
649
|
assert jnp.all(jnp.equal(named1[{"H": index2}].array, named1.array[index2.array, :, :]))
|
|
650
650
|
|
|
651
651
|
|
|
652
|
+
def test_slice_nd_array_unnamed_slice():
|
|
653
|
+
# tests slicing with arrays that are already present in the named array, which is sometimes ok
|
|
654
|
+
H = Axis("H", 10)
|
|
655
|
+
W = Axis("W", 20)
|
|
656
|
+
D = Axis("D", 30)
|
|
657
|
+
|
|
658
|
+
named1 = hax.random.uniform(PRNGKey(0), (H, W, D))
|
|
659
|
+
|
|
660
|
+
index1 = jax.random.randint(PRNGKey(1), (4,), 0, H.size)
|
|
661
|
+
assert jnp.all(jnp.equal(named1[{"H": index1}].array, named1.array[index1, :, :]))
|
|
662
|
+
|
|
663
|
+
# hidden behavior: if we also pass in an H index to e.g. D, it is zipped together
|
|
664
|
+
index2 = hax.random.randint(PRNGKey(2), Axis("H", 4), 0, D.size)
|
|
665
|
+
assert jnp.all(jnp.equal(named1[{"H": index1, "D": index2}].array, named1.array[index1, :, index2.array]))
|
|
666
|
+
|
|
667
|
+
# this is different though:
|
|
668
|
+
index2r = index2.array
|
|
669
|
+
assert jnp.all(
|
|
670
|
+
jnp.equal(
|
|
671
|
+
named1[{"H": index1, "D": index2r}].array, named1.array[index1.reshape(1, -1), :, index2r.reshape(-1, 1)]
|
|
672
|
+
)
|
|
673
|
+
)
|
|
674
|
+
assert named1[{"H": index1, "D": index2r}].shape != named1[{"H": index1, "D": index2}].shape
|
|
675
|
+
|
|
676
|
+
index1 = list(index1)
|
|
677
|
+
assert jnp.all(jnp.equal(named1[{"H": index1}].array, named1.array[index1, :, :]))
|
|
678
|
+
|
|
679
|
+
|
|
652
680
|
def test_full_indexing_returns_named_array():
|
|
653
681
|
H = Axis("H", 10)
|
|
654
682
|
W = Axis("W", 20)
|
|
@@ -847,3 +875,82 @@ def test_named_arrays_work_in_eqxi_while_loop():
|
|
|
847
875
|
grad_fun = eqx.filter_value_and_grad(loss_fun)
|
|
848
876
|
|
|
849
877
|
grad_fun(named1)
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def test_at_for_in_placeish():
|
|
881
|
+
H = Axis("H", 10)
|
|
882
|
+
W = Axis("W", 20)
|
|
883
|
+
|
|
884
|
+
named1 = hax.random.uniform(PRNGKey(0), (H, W))
|
|
885
|
+
|
|
886
|
+
named1_at = named1.at[H, 0].set(0)
|
|
887
|
+
|
|
888
|
+
assert jnp.all(jnp.equal(named1_at[H, 0].array, 0))
|
|
889
|
+
assert jnp.all(named1_at[H, 1:].array == named1[H, 1:].array)
|
|
890
|
+
|
|
891
|
+
# test add, multiply, power, etc.
|
|
892
|
+
named1_at = named1.at[H, 0].add(1)
|
|
893
|
+
assert jnp.all(named1_at.array == named1.array.at[0].add(1))
|
|
894
|
+
|
|
895
|
+
named1_at = named1.at[H, 0].multiply(2)
|
|
896
|
+
assert jnp.all(named1_at.array == named1.array.at[0].multiply(2))
|
|
897
|
+
|
|
898
|
+
named1_at = named1.at[H, 0].power(2)
|
|
899
|
+
assert jnp.all(named1_at.array == named1.array.at[0].power(2))
|
|
900
|
+
|
|
901
|
+
named1_at = named1.at[H, 0].divide(2)
|
|
902
|
+
assert jnp.all(named1_at.array == named1.array.at[0].divide(2))
|
|
903
|
+
|
|
904
|
+
named1_at = named1.at[H, 0].apply(hax.square)
|
|
905
|
+
assert jnp.all(named1_at.array == named1.array.at[0].apply(jnp.square))
|
|
906
|
+
|
|
907
|
+
named1_at = named1.at[H, 0].max(0.5)
|
|
908
|
+
assert jnp.all(named1_at.array == named1.array.at[0].max(0.5))
|
|
909
|
+
|
|
910
|
+
named1_at = named1.at[H, 0].min(0.5)
|
|
911
|
+
assert jnp.all(named1_at.array == named1.array.at[0].min(0.5))
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
def test_at_with_fancy_indexing():
|
|
915
|
+
H = Axis("H", 10)
|
|
916
|
+
W = Axis("W", 20)
|
|
917
|
+
I0 = Axis("I0", 5)
|
|
918
|
+
I1 = Axis("I1", 5)
|
|
919
|
+
|
|
920
|
+
named1 = hax.random.uniform(PRNGKey(0), (H, W))
|
|
921
|
+
ind1 = hax.random.randint(PRNGKey(0), (I0,), 0, H.size)
|
|
922
|
+
ind2 = hax.random.randint(PRNGKey(0), (I1,), 0, W.size)
|
|
923
|
+
|
|
924
|
+
named1_at = named1.at[H, ind1].set(0)
|
|
925
|
+
assert jnp.all(named1_at.array == named1.array.at[ind1.array].set(0))
|
|
926
|
+
|
|
927
|
+
named1_at = named1.at[H, ind1].add(1, mode="clip")
|
|
928
|
+
assert jnp.all(named1_at.array == named1.array.at[ind1.array].add(1, mode="clip"))
|
|
929
|
+
|
|
930
|
+
named1_at = named1.at[H, ind1, W, ind2].set(0)
|
|
931
|
+
assert jnp.all(named1_at.array == named1.array.at[ind1.array.reshape(-1, 1), ind2.array.reshape(1, -1)].set(0))
|
|
932
|
+
|
|
933
|
+
# dslices
|
|
934
|
+
from haliax import ds
|
|
935
|
+
|
|
936
|
+
named1_at = named1.at[H, ds(3, 5)].set(0)
|
|
937
|
+
assert jnp.all(named1_at.array == named1.array.at[3:8].set(0))
|
|
938
|
+
|
|
939
|
+
named1_at = named1.at[H, ds(3, 5), W, ind2].power(2)
|
|
940
|
+
assert jnp.all(named1_at.array == named1.array.at[3:8, ind2.array].power(2))
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
def test_slice_dslice_and_array():
|
|
944
|
+
H = Axis("H", 10)
|
|
945
|
+
W = Axis("W", 20)
|
|
946
|
+
I0 = Axis("I0", 5)
|
|
947
|
+
|
|
948
|
+
named1 = hax.random.uniform(PRNGKey(0), (H, W))
|
|
949
|
+
ind2 = hax.random.randint(PRNGKey(0), (I0,), 0, W.size)
|
|
950
|
+
|
|
951
|
+
from haliax import ds
|
|
952
|
+
|
|
953
|
+
named1.array.at[3:8, ind2.array].add(jnp.full((5, 5), 2))
|
|
954
|
+
|
|
955
|
+
named1_at = named1.at[H, ds(3, 5), W, ind2].add(2)
|
|
956
|
+
assert jnp.all(named1_at.array == named1.array.at[3:8, ind2.array].add(2))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.4.dev289"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|