haliax 1.4.dev375__tar.gz → 1.4.dev379__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.dev375 → haliax-1.4.dev379}/.github/workflows/run_quick_levanter_tests.yaml +3 -2
- {haliax-1.4.dev375 → haliax-1.4.dev379}/PKG-INFO +1 -1
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/scan.md +3 -1
- haliax-1.4.dev379/src/haliax/__about__.py +1 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/core.py +19 -9
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/attention.py +13 -2
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/dropout.py +2 -2
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/embedding.py +2 -2
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/linear.py +5 -5
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/normalization.py +2 -2
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/scan.py +173 -47
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/ops.py +2 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/wrap.py +26 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/core_test.py +4 -4
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_dot.py +1 -1
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_hof.py +1 -1
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_ops.py +4 -4
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_scan.py +398 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_tree_util.py +1 -1
- haliax-1.4.dev375/src/haliax/__about__.py +0 -1
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.coveragerc +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.flake8 +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.gitignore +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.playbooks/add-types.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/AGENTS.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/LICENSE +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/README.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/api.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/css/material.css +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/faq.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/fp8.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/index.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/indexing.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/matmul.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/nn.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/partitioning.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/rearrange.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/requirements.txt +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/state-dict.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/tutorial.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/typing.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/docs/vmap.md +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/mkdocs.yml +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/pyproject.toml +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/__init__.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/scan.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/state_dict.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/haxtyping.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/jax_utils.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/random.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/types.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/src/haliax/util.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_attention.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_axis.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_conv.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_debug.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_dtype_typing.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_int8.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_namedarray_typing.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_nn.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_pool.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_random.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_scatter_gather.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/tests/test_utils.py +0 -0
- {haliax-1.4.dev375 → haliax-1.4.dev379}/uv.lock +0 -0
|
@@ -9,7 +9,7 @@ jobs:
|
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
10
|
strategy:
|
|
11
11
|
matrix:
|
|
12
|
-
python-version: [3.10, 3.11]
|
|
12
|
+
python-version: ["3.10", "3.11"]
|
|
13
13
|
steps:
|
|
14
14
|
- name: Checkout repository
|
|
15
15
|
uses: actions/checkout@v3
|
|
@@ -39,10 +39,11 @@ jobs:
|
|
|
39
39
|
run: |
|
|
40
40
|
uv pip install -e .[tests]
|
|
41
41
|
uv pip install -e ../haliax
|
|
42
|
+
uv pip install pytest
|
|
42
43
|
|
|
43
44
|
- name: Run pytest suite
|
|
44
45
|
env:
|
|
45
46
|
XLA_FLAGS: --xla_force_host_platform_device_count=8
|
|
46
47
|
VIRTUAL_ENV: ${{ github.workspace }}/.venv
|
|
47
48
|
run: |
|
|
48
|
-
pytest tests -m "not entry and not slow"
|
|
49
|
+
uv run pytest tests -m "not entry and not slow"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev379
|
|
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/
|
|
@@ -458,7 +458,9 @@ We also provide a way to create a sequence of layers that can be applied to a se
|
|
|
458
458
|
same interface as [haliax.nn.Stacked][], but with a different implementation. This is the [haliax.nn.BlockSeq][] module.
|
|
459
459
|
BlockSeq implements those for loops directly, rather than using [haliax.fold][] or [haliax.scan][].
|
|
460
460
|
|
|
461
|
-
[haliax.nn.scan.BlockFoldable][] is an interface that both [haliax.nn.Stacked][] and [haliax.nn.BlockSeq][] implement.
|
|
461
|
+
[haliax.nn.scan.BlockFoldable][] is an interface that both [haliax.nn.Stacked][] and [haliax.nn.BlockSeq][] implement. It
|
|
462
|
+
exposes the usual ``fold`` and ``scan`` methods as well as helpers ``fold_via`` and ``scan_via`` which return
|
|
463
|
+
callables that perform the respective operations using a custom block function.
|
|
462
464
|
|
|
463
465
|
## API
|
|
464
466
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.dev379"
|
|
@@ -241,15 +241,15 @@ class NamedArray(metaclass=NamedArrayMeta):
|
|
|
241
241
|
raise ValueError(f"Expected scalar, got {self.array.ndim}-dimensional array")
|
|
242
242
|
return self.array
|
|
243
243
|
|
|
244
|
-
def __jax_array__(self):
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
244
|
+
# def __jax_array__(self):
|
|
245
|
+
# if self.ndim == 0:
|
|
246
|
+
# return self.array
|
|
247
|
+
# else:
|
|
248
|
+
# raise ValueError(
|
|
249
|
+
# "Only scalar NamedArrays can be implicitly converted to jax arrays, but "
|
|
250
|
+
# f"got {self.shape} array. This error typically occurs when you pass a "
|
|
251
|
+
# "NamedArray to a plain jax.numpy function. Please use `x.array` instead."
|
|
252
|
+
# )
|
|
253
253
|
|
|
254
254
|
@ft.cached_property
|
|
255
255
|
def shape(self) -> Dict[str, int]:
|
|
@@ -1787,12 +1787,22 @@ def broadcast_arrays_and_return_axes(
|
|
|
1787
1787
|
"""
|
|
1788
1788
|
if len(arrays) == 0:
|
|
1789
1789
|
return ((), ())
|
|
1790
|
+
elif len(arrays) == 1:
|
|
1791
|
+
a = arrays[0]
|
|
1792
|
+
if a is None:
|
|
1793
|
+
return (None,), ()
|
|
1794
|
+
if isinstance(a, NamedArray):
|
|
1795
|
+
return (a,), a.axes
|
|
1796
|
+
return (named(jnp.asarray(a), ()), ), ()
|
|
1790
1797
|
|
|
1791
1798
|
# sort the arrays by size, so that we use the biggest ones to broadcast the others
|
|
1792
1799
|
# need to hold on to the order so we can return the arrays in the same order
|
|
1793
1800
|
actual_arrays = [x for x in arrays if isinstance(x, NamedArray)]
|
|
1794
1801
|
size_order = sorted(range(len(actual_arrays)), key=lambda i: actual_arrays[i].size, reverse=True)
|
|
1795
1802
|
all_axes = [actual_arrays[i].axes for i in size_order]
|
|
1803
|
+
if len(all_axes) == 0:
|
|
1804
|
+
return (arrays, ())
|
|
1805
|
+
|
|
1796
1806
|
full_axes = ft.reduce(lambda a, b: _broadcast_axes(a, b, require_subset) if a is not None else None, all_axes) # type: ignore
|
|
1797
1807
|
if full_axes is None:
|
|
1798
1808
|
raise ValueError(f"Cannot broadcast arrays {arrays}: no subset relationship")
|
|
@@ -155,7 +155,7 @@ def combine_masks_or(mask1: Optional[NamedArray], mask2: Optional[NamedArray]) -
|
|
|
155
155
|
return mask1 | mask2.broadcast_axis(mask1.axes)
|
|
156
156
|
|
|
157
157
|
|
|
158
|
-
def causal_mask(QPos: Axis, KPos: Axis, q_start: int = 0, k_start: int = 0) -> NamedArray:
|
|
158
|
+
def causal_mask(QPos: Axis, KPos: Axis, q_start: int | NamedArray = 0, k_start: int | NamedArray= 0) -> NamedArray:
|
|
159
159
|
"""
|
|
160
160
|
Creates a materialized causal mask for attention.
|
|
161
161
|
|
|
@@ -163,7 +163,18 @@ def causal_mask(QPos: Axis, KPos: Axis, q_start: int = 0, k_start: int = 0) -> N
|
|
|
163
163
|
:param KPos: Axis of key sequence length
|
|
164
164
|
:return: NamedArray of shape (QPos, KPos)
|
|
165
165
|
"""
|
|
166
|
-
|
|
166
|
+
# if q_start is a named array, we vmap the arange
|
|
167
|
+
if isinstance(q_start, NamedArray):
|
|
168
|
+
q_range = haliax.vmap(haliax.arange, q_start.axes)(QPos, start=q_start)
|
|
169
|
+
else:
|
|
170
|
+
q_range = haliax.arange(QPos, start=q_start)
|
|
171
|
+
|
|
172
|
+
if isinstance(k_start, NamedArray):
|
|
173
|
+
k_range = haliax.vmap(haliax.arange, k_start.axes)(KPos, start=k_start)
|
|
174
|
+
else:
|
|
175
|
+
k_range = haliax.arange(KPos, start=k_start)
|
|
176
|
+
|
|
177
|
+
return q_range >= k_range.broadcast_axis(QPos)
|
|
167
178
|
|
|
168
179
|
|
|
169
180
|
def prefix_lm_mask(QSeqLen: Axis, KSeqLen: Axis, prefix_len: int, q_start: int = 0, k_start: int = 0) -> NamedArray:
|
|
@@ -61,8 +61,8 @@ class Dropout(eqx.Module):
|
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
63
|
# key difference from equinox: these are static fields
|
|
64
|
-
pdrop: float = eqx.
|
|
65
|
-
broadcast_axes: Optional[AxisSpec] = eqx.
|
|
64
|
+
pdrop: float = eqx.field(static=True)
|
|
65
|
+
broadcast_axes: Optional[AxisSpec] = eqx.field(static=True)
|
|
66
66
|
inference: bool = False # note: not static
|
|
67
67
|
|
|
68
68
|
def __init__(
|
|
@@ -17,8 +17,8 @@ class Embedding(eqx.Module):
|
|
|
17
17
|
weight: NamedArray
|
|
18
18
|
|
|
19
19
|
# axes
|
|
20
|
-
Vocab: Axis = eqx.
|
|
21
|
-
Embed: AxisSpec = eqx.
|
|
20
|
+
Vocab: Axis = eqx.field(static=True)
|
|
21
|
+
Embed: AxisSpec = eqx.field(static=True)
|
|
22
22
|
|
|
23
23
|
@staticmethod
|
|
24
24
|
def init(Vocab: Axis, Embed: AxisSpec, *, init_scale: float = 1, key, initializer_range: Optional[float] = None):
|
|
@@ -28,8 +28,8 @@ class Linear(ModuleWithStateDictSerialization):
|
|
|
28
28
|
weight: NamedArray
|
|
29
29
|
bias: Optional[NamedArray]
|
|
30
30
|
|
|
31
|
-
In: AxisSpec = eqx.
|
|
32
|
-
Out: AxisSpec = eqx.
|
|
31
|
+
In: AxisSpec = eqx.field(static=True)
|
|
32
|
+
Out: AxisSpec = eqx.field(static=True)
|
|
33
33
|
dot_general: DotGeneralOp = eqx.field(default_factory=DotGeneralOp.default)
|
|
34
34
|
|
|
35
35
|
@staticmethod
|
|
@@ -140,9 +140,9 @@ class MoELinear(eqx.Module):
|
|
|
140
140
|
weight: NamedArray
|
|
141
141
|
bias: Optional[NamedArray]
|
|
142
142
|
|
|
143
|
-
Experts: AxisSpec = eqx.
|
|
144
|
-
In: Axis = eqx.
|
|
145
|
-
Out: Axis = eqx.
|
|
143
|
+
Experts: AxisSpec = eqx.field(static=True)
|
|
144
|
+
In: Axis = eqx.field(static=True)
|
|
145
|
+
Out: Axis = eqx.field(static=True)
|
|
146
146
|
# TODO: support quanitization for ragged_dot?
|
|
147
147
|
# dot_general: DotGeneralOp = eqx.field(default_factory=DotGeneralOp.default)
|
|
148
148
|
|
|
@@ -20,10 +20,10 @@ A = TypeVar("A", Scalar, NamedArray, jnp.ndarray)
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class LayerNormBase(ModuleWithStateDictSerialization):
|
|
23
|
-
axis: AxisSpec = eqx.
|
|
23
|
+
axis: AxisSpec = eqx.field(static=True)
|
|
24
24
|
weight: Optional[NamedArray]
|
|
25
25
|
bias: Optional[NamedArray]
|
|
26
|
-
eps: float = eqx.
|
|
26
|
+
eps: float = eqx.field(default=1e-5, static=True)
|
|
27
27
|
dtype: Optional[jnp.dtype] = eqx.field(default=None, static=True)
|
|
28
28
|
|
|
29
29
|
@abstractmethod
|
|
@@ -2,7 +2,8 @@ import dataclasses
|
|
|
2
2
|
import functools
|
|
3
3
|
import re
|
|
4
4
|
import warnings
|
|
5
|
-
from typing import Any, Callable, Dict, Generic, Optional, Protocol, Sequence, Type, TypeVar, cast,
|
|
5
|
+
from typing import Any, Callable, Concatenate, Dict, Generic, Optional, Protocol, Sequence, Type, TypeVar, cast, \
|
|
6
|
+
overload, ParamSpec
|
|
6
7
|
|
|
7
8
|
import equinox as eqx
|
|
8
9
|
import jax
|
|
@@ -27,13 +28,18 @@ CarryT = TypeVar("CarryT")
|
|
|
27
28
|
OutputT_co = TypeVar("OutputT_co", covariant=True)
|
|
28
29
|
P = ParamSpec("P")
|
|
29
30
|
|
|
30
|
-
class FoldFunction(Protocol[M_contra, CarryT]):
|
|
31
|
-
def __call__(self, module: M_contra, carry: CarryT) -> CarryT:
|
|
31
|
+
class FoldFunction(Protocol[M_contra, P, CarryT]):
|
|
32
|
+
def __call__(self, module: M_contra, carry: CarryT, *args: P.args, **kwargs: P.kwargs) -> CarryT:
|
|
32
33
|
...
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
class ScanFunction(Protocol[M_contra, CarryT, OutputT_co]):
|
|
36
|
-
def __call__(self, module: M_contra, carry: CarryT) -> tuple[CarryT, OutputT_co]:
|
|
36
|
+
class ScanFunction(Protocol[M_contra, CarryT, P, OutputT_co]):
|
|
37
|
+
def __call__(self, module: M_contra, carry: CarryT, *args: P.args, **kwargs: P.kwargs) -> tuple[CarryT, OutputT_co]:
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class VmapFunction(Protocol[M_contra, P, OutputT_co]):
|
|
42
|
+
def __call__(self, module: M_contra, *args: P.args, **kwargs: P.kwargs) -> OutputT_co:
|
|
37
43
|
...
|
|
38
44
|
|
|
39
45
|
|
|
@@ -43,12 +49,13 @@ class ModuleInit(Protocol[M_co]):
|
|
|
43
49
|
|
|
44
50
|
|
|
45
51
|
class BlockFoldable(Protocol[M]):
|
|
46
|
-
"""
|
|
47
|
-
A superclass for [haliax.nn.Stacked][] and [haliax.nn.BlockSeq][] that exposes the fold and scan methods, as
|
|
48
|
-
well as a few other methods that are useful for these modules.
|
|
52
|
+
"""Common interface for :class:`~haliax.nn.Stacked` and :class:`~haliax.nn.BlockSeq`.
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
The interface exposes the :py:meth:`fold` and :py:meth:`scan` methods along with the helper
|
|
55
|
+
methods :py:meth:`fold_via`, :py:meth:`scan_via`, and :py:meth:`vmap_via`.
|
|
56
|
+
|
|
57
|
+
This is a protocol, so you can use it as a type hint for a function that takes a ``Stacked`` or ``BlockSeq``.
|
|
58
|
+
Equinox modules can't directly inherit from ``Protocol`` classes, but you can use it as a type hint.
|
|
52
59
|
"""
|
|
53
60
|
|
|
54
61
|
Block: Axis
|
|
@@ -70,6 +77,39 @@ class BlockFoldable(Protocol[M]):
|
|
|
70
77
|
def fold(self, init: T, *args, **kwargs) -> T:
|
|
71
78
|
...
|
|
72
79
|
|
|
80
|
+
@overload
|
|
81
|
+
def fold_via(self, fn: FoldFunction[M, P, CarryT]) -> Callable[Concatenate[CarryT, P], CarryT]:
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
@overload
|
|
85
|
+
def fold_via(self, fn: Callable[[M, CarryT], CarryT]) -> Callable[[CarryT], CarryT]:
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
def fold_via(self, fn: Callable[..., CarryT]) -> Callable[Concatenate[CarryT, P], CarryT]:
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
@overload
|
|
92
|
+
def scan_via(self, fn: ScanFunction[M, CarryT, P, OutputT_co]) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]:
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
@overload
|
|
96
|
+
def scan_via(self, fn: Callable[[M, CarryT], tuple[CarryT, OutputT_co]]) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]:
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]]) -> Callable[P, tuple[CarryT, OutputT_co]]:
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
@overload
|
|
103
|
+
def vmap_via(self, fn: VmapFunction[M, P, OutputT_co]) -> Callable[P, OutputT_co]:
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
@overload
|
|
107
|
+
def vmap_via(self, fn: Callable[[M], OutputT_co]) -> Callable[[], OutputT_co]:
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
def vmap_via(self, fn: Callable[..., OutputT_co]) -> Callable[..., OutputT_co]:
|
|
111
|
+
...
|
|
112
|
+
|
|
73
113
|
def unstacked(self) -> Sequence[M]:
|
|
74
114
|
"""
|
|
75
115
|
Returns the unstacked version of this module. This is useful for logging or saving checkpoints.
|
|
@@ -90,8 +130,8 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
90
130
|
"""
|
|
91
131
|
|
|
92
132
|
blocks: Sequence[M]
|
|
93
|
-
Block: Axis = eqx.
|
|
94
|
-
gradient_checkpointing: ScanCheckpointPolicy = eqx.
|
|
133
|
+
Block: Axis = eqx.field(static=True)
|
|
134
|
+
gradient_checkpointing: ScanCheckpointPolicy = eqx.field(static=True)
|
|
95
135
|
|
|
96
136
|
@classmethod
|
|
97
137
|
def init(
|
|
@@ -177,6 +217,87 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
177
217
|
|
|
178
218
|
return do_fold(init, *args, **kwargs)
|
|
179
219
|
|
|
220
|
+
@overload
|
|
221
|
+
def fold_via(self, fn: FoldFunction[M, P, CarryT]) -> Callable[Concatenate[CarryT, P], CarryT]:
|
|
222
|
+
...
|
|
223
|
+
|
|
224
|
+
@overload
|
|
225
|
+
def fold_via(self, fn: Callable[[M, CarryT], CarryT]) -> Callable[[CarryT], CarryT]:
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
def fold_via(self, fn: Callable[..., CarryT]):
|
|
229
|
+
"""Return a function that folds over the sequence using ``fn``.
|
|
230
|
+
|
|
231
|
+
``fn`` should take a block and a carry and return a new carry. The
|
|
232
|
+
returned function mirrors :func:`haliax.fold` over the block axis.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def do_fold(init: CarryT, *args, **kwargs) -> CarryT:
|
|
236
|
+
carry = init
|
|
237
|
+
for block in self.blocks:
|
|
238
|
+
carry = fn(block, carry, *args, **kwargs)
|
|
239
|
+
carry = tree_checkpoint_name(carry, self._carry_ckpt_name)
|
|
240
|
+
return carry
|
|
241
|
+
|
|
242
|
+
return do_fold
|
|
243
|
+
|
|
244
|
+
@overload
|
|
245
|
+
def scan_via(self, fn: ScanFunction[M, CarryT, P, OutputT_co]) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]:
|
|
246
|
+
...
|
|
247
|
+
|
|
248
|
+
@overload
|
|
249
|
+
def scan_via(self, fn: Callable[[M, CarryT], tuple[CarryT, OutputT_co]]) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]:
|
|
250
|
+
...
|
|
251
|
+
|
|
252
|
+
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]]):
|
|
253
|
+
"""Return a function that scans over the sequence using ``fn``.
|
|
254
|
+
|
|
255
|
+
``fn`` should take a block and a carry and return ``(carry, output)``.
|
|
256
|
+
Semantics match :func:`haliax.scan` over the block axis.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
def do_scan(init: CarryT, *args, **kwargs) -> tuple[CarryT, OutputT_co]:
|
|
260
|
+
out = []
|
|
261
|
+
carry = init
|
|
262
|
+
for block in self.blocks:
|
|
263
|
+
carry, extra = fn(block, carry, *args, **kwargs)
|
|
264
|
+
carry = tree_checkpoint_name(carry, self._carry_ckpt_name)
|
|
265
|
+
extra = tree_checkpoint_name(extra, self._output_ckpt_name)
|
|
266
|
+
out.append(extra)
|
|
267
|
+
|
|
268
|
+
stacked_out = haliax.tree_util.tree_map(lambda *x: haliax.stack(self.Block, x), *out)
|
|
269
|
+
return carry, stacked_out
|
|
270
|
+
|
|
271
|
+
return do_scan
|
|
272
|
+
|
|
273
|
+
@overload
|
|
274
|
+
def vmap_via(self, fn: VmapFunction[M, P, OutputT_co]) -> Callable[P, OutputT_co]:
|
|
275
|
+
...
|
|
276
|
+
|
|
277
|
+
@overload
|
|
278
|
+
def vmap_via(self, fn: Callable[[M], OutputT_co]) -> Callable[[], OutputT_co]:
|
|
279
|
+
...
|
|
280
|
+
|
|
281
|
+
def vmap_via(self, fn: Callable[..., OutputT_co]) -> Callable[..., OutputT_co]:
|
|
282
|
+
"""Return a function that applies each block independently using ``fn``.
|
|
283
|
+
|
|
284
|
+
``fn`` should take a block and a carry and return an output. The
|
|
285
|
+
returned function mirrors :func:`haliax.vmap` over the block axis.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def do_vmap(init: CarryT, *args, **kwargs) -> OutputT_co:
|
|
289
|
+
# Apply fn to each block independently
|
|
290
|
+
outputs = []
|
|
291
|
+
for block in self.blocks:
|
|
292
|
+
output = fn(block, init, *args, **kwargs)
|
|
293
|
+
outputs.append(output)
|
|
294
|
+
|
|
295
|
+
# Stack the outputs
|
|
296
|
+
stacked_out = haliax.tree_util.tree_map(lambda *x: haliax.stack(self.Block, x), *outputs)
|
|
297
|
+
return stacked_out
|
|
298
|
+
|
|
299
|
+
return do_vmap
|
|
300
|
+
|
|
180
301
|
def unstacked(self) -> Sequence[M]:
|
|
181
302
|
return self.blocks
|
|
182
303
|
|
|
@@ -279,8 +400,8 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
279
400
|
# TODO: we can probably make this module support pipeline parallelism, but that's a whole project in itself
|
|
280
401
|
|
|
281
402
|
stacked: M
|
|
282
|
-
Block: Axis = eqx.
|
|
283
|
-
gradient_checkpointing: ScanCheckpointPolicy = eqx.
|
|
403
|
+
Block: Axis = eqx.field(static=True)
|
|
404
|
+
gradient_checkpointing: ScanCheckpointPolicy = eqx.field(static=True)
|
|
284
405
|
|
|
285
406
|
@classmethod
|
|
286
407
|
def init(
|
|
@@ -398,7 +519,7 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
398
519
|
return do_fold(init, *args, **kwargs)
|
|
399
520
|
|
|
400
521
|
@overload
|
|
401
|
-
def fold_via(self, fn: FoldFunction[M, CarryT]) -> Callable[[CarryT], CarryT]:
|
|
522
|
+
def fold_via(self, fn: FoldFunction[M, P, CarryT]) -> Callable[Concatenate[CarryT, P], CarryT]:
|
|
402
523
|
...
|
|
403
524
|
|
|
404
525
|
@overload
|
|
@@ -412,16 +533,16 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
412
533
|
returned function mirrors :func:`haliax.fold` over the block axis.
|
|
413
534
|
"""
|
|
414
535
|
|
|
415
|
-
def do_block(carry: CarryT, block: M) -> CarryT:
|
|
416
|
-
return fn(block, carry)
|
|
536
|
+
def do_block(carry: CarryT, block: M, *args, **kwargs) -> CarryT:
|
|
537
|
+
return fn(block, carry, *args, **kwargs)
|
|
417
538
|
|
|
418
|
-
def do_fold(init: CarryT) -> CarryT:
|
|
419
|
-
return haliax.fold(do_block, self.Block, remat=self.gradient_checkpointing)(init, self.stacked)
|
|
539
|
+
def do_fold(init: CarryT, *args, **kwargs) -> CarryT:
|
|
540
|
+
return haliax.fold(do_block, self.Block, remat=self.gradient_checkpointing)(init, self.stacked, *args, **kwargs)
|
|
420
541
|
|
|
421
542
|
return do_fold
|
|
422
543
|
|
|
423
544
|
@overload
|
|
424
|
-
def scan_via(self, fn: ScanFunction[M, CarryT, OutputT_co]) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]:
|
|
545
|
+
def scan_via(self, fn: ScanFunction[M, CarryT, P, OutputT_co]) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]:
|
|
425
546
|
...
|
|
426
547
|
|
|
427
548
|
@overload
|
|
@@ -435,42 +556,47 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
435
556
|
Semantics match :func:`haliax.scan` over the block axis.
|
|
436
557
|
"""
|
|
437
558
|
|
|
438
|
-
def do_block(carry: CarryT, block: M) -> tuple[CarryT, OutputT_co]:
|
|
439
|
-
|
|
559
|
+
def do_block(carry: CarryT, block: M, *args, **kwargs) -> tuple[CarryT, OutputT_co]:
|
|
560
|
+
carry, output = fn(block, carry, *args, **kwargs)
|
|
561
|
+
return carry, output
|
|
440
562
|
|
|
441
|
-
|
|
442
|
-
|
|
563
|
+
|
|
564
|
+
def do_scan(init: CarryT, *args, **kwargs) -> tuple[CarryT, OutputT_co]:
|
|
565
|
+
return haliax.scan(do_block, self.Block, remat=self.gradient_checkpointing)(init, self.stacked, *args, **kwargs)
|
|
443
566
|
|
|
444
567
|
return do_scan
|
|
445
568
|
|
|
446
|
-
|
|
447
|
-
|
|
569
|
+
@overload
|
|
570
|
+
def vmap_via(self, fn: VmapFunction[M, P, OutputT_co]) -> Callable[P, OutputT_co]:
|
|
571
|
+
...
|
|
572
|
+
|
|
573
|
+
@overload
|
|
574
|
+
def vmap_via(self, fn: Callable[[M], OutputT_co]) -> Callable[[], OutputT_co]:
|
|
575
|
+
...
|
|
576
|
+
|
|
577
|
+
def vmap_via(self, fn: Callable[..., OutputT_co]) -> Callable[..., OutputT_co]:
|
|
578
|
+
"""Return a function that applies each block independently using ``fn``.
|
|
579
|
+
|
|
580
|
+
``fn`` should take a block and a carry and return an output. The
|
|
581
|
+
returned function mirrors :func:`haliax.vmap` over the block axis.
|
|
582
|
+
"""
|
|
583
|
+
|
|
584
|
+
def do_vmap(*args, **kwargs) -> OutputT_co:
|
|
585
|
+
# Create a function that captures the additional arguments
|
|
586
|
+
def do_block_with_args(block: M, *args, **kwargs) -> OutputT_co:
|
|
587
|
+
return fn(block, *args, **kwargs)
|
|
588
|
+
|
|
589
|
+
return haliax.vmap(do_block_with_args, self.Block)(self.stacked, *args, **kwargs)
|
|
448
590
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
591
|
+
return do_vmap
|
|
592
|
+
|
|
593
|
+
def vmap(self, *extra_args, **extra_kwargs):
|
|
594
|
+
"""Apply each block independently using :func:`haliax.vmap`.
|
|
452
595
|
|
|
453
596
|
Returns the stacked outputs of each block.
|
|
454
597
|
"""
|
|
455
598
|
|
|
456
|
-
|
|
457
|
-
init = init.broadcast_axis(self.Block)
|
|
458
|
-
elif haliax.jax_utils.is_jax_array_like(init):
|
|
459
|
-
init = jnp.broadcast_to(init, (self.Block.size,) + init.shape)
|
|
460
|
-
else:
|
|
461
|
-
init = tuple(init for _ in range(self.Block.size))
|
|
462
|
-
|
|
463
|
-
arg_spec = (0, 0) + (0,) * len(extra_args)
|
|
464
|
-
kwarg_spec = {k: 0 for k in extra_kwargs}
|
|
465
|
-
|
|
466
|
-
do_vmap = haliax.vmap(
|
|
467
|
-
Stacked._do_block,
|
|
468
|
-
self.Block,
|
|
469
|
-
default=0,
|
|
470
|
-
args=arg_spec,
|
|
471
|
-
kwargs=kwarg_spec,
|
|
472
|
-
)
|
|
473
|
-
return do_vmap(init, self.stacked, *extra_args, **extra_kwargs)
|
|
599
|
+
return haliax.vmap(type(self.stacked).__call__, self.Block)(self.stacked, *extra_args, **extra_kwargs)
|
|
474
600
|
|
|
475
601
|
@staticmethod
|
|
476
602
|
def _do_block(carry, block, *extra_args, **extra_kwargs):
|
|
@@ -85,6 +85,8 @@ def where(
|
|
|
85
85
|
raise ValueError("x must be a NamedArray or scalar if y is a NamedArray")
|
|
86
86
|
x = named(x, ())
|
|
87
87
|
x, y = broadcast_arrays(x, y)
|
|
88
|
+
if isinstance(condition, NamedArray):
|
|
89
|
+
condition = condition.scalar()
|
|
88
90
|
return jax.lax.cond(condition, lambda _: x, lambda _: y, None)
|
|
89
91
|
|
|
90
92
|
condition, x, y = broadcast_arrays(condition, x, y) # type: ignore
|
|
@@ -5,6 +5,7 @@ import jax
|
|
|
5
5
|
from haliax.core import NamedArray, _broadcast_order, broadcast_to
|
|
6
6
|
|
|
7
7
|
from .axis import AxisSelection, AxisSelector, axis_spec_to_shape_dict, eliminate_axes
|
|
8
|
+
from .jax_utils import is_scalarish
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def wrap_elemwise_unary(f, a, *args, **kwargs):
|
|
@@ -94,8 +95,33 @@ def wrap_elemwise_binary(op):
|
|
|
94
95
|
b = broadcast_to(b, axes)
|
|
95
96
|
return NamedArray(op(a.array, b.array), axes)
|
|
96
97
|
elif isinstance(a, NamedArray):
|
|
98
|
+
# b isn't named.
|
|
99
|
+
if not is_scalarish(a):
|
|
100
|
+
if not is_scalarish(b):
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Cannot apply {op.__name__} to a NamedArray and a non-scalar {type(b)}. "
|
|
103
|
+
"Either both must be NamedArrays or b must be a scalar."
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
if is_scalarish(b):
|
|
107
|
+
return NamedArray(op(a.array, b), a.axes)
|
|
108
|
+
a = a.scalar()
|
|
109
|
+
return op(a, b)
|
|
110
|
+
|
|
97
111
|
return NamedArray(op(a.array, b), a.axes)
|
|
98
112
|
elif isinstance(b, NamedArray):
|
|
113
|
+
if not is_scalarish(b):
|
|
114
|
+
if not is_scalarish(a):
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"Cannot apply {op.__name__} to a non-scalar {type(a)} and a NamedArray. "
|
|
117
|
+
"Either both must be NamedArrays or a must be a scalar."
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
if is_scalarish(a):
|
|
121
|
+
return NamedArray(op(a, b.array), b.axes)
|
|
122
|
+
b = b.scalar()
|
|
123
|
+
return op(a, b)
|
|
124
|
+
|
|
99
125
|
return NamedArray(op(a, b.array), b.axes)
|
|
100
126
|
else:
|
|
101
127
|
return op(a, b)
|
|
@@ -34,7 +34,7 @@ def test_reduction_functions():
|
|
|
34
34
|
m1 = NamedArray(rand_m, (Height, Width, Depth))
|
|
35
35
|
|
|
36
36
|
# sum out everything
|
|
37
|
-
assert jnp.all(jnp.equal(hax.sum(m1), jnp.sum(m1.array)))
|
|
37
|
+
assert jnp.all(jnp.equal(hax.sum(m1).array, jnp.sum(m1.array)))
|
|
38
38
|
# ensure it's a scalar
|
|
39
39
|
|
|
40
40
|
assert jnp.all(jnp.equal(hax.sum(m1, axis=Height).array, jnp.sum(m1.array, axis=0)))
|
|
@@ -60,7 +60,7 @@ def test_reduction_functions():
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
# argmax
|
|
63
|
-
assert jnp.all(jnp.equal(hax.argmax(m1, axis=None), jnp.argmax(m1.array)))
|
|
63
|
+
assert jnp.all(jnp.equal(hax.argmax(m1, axis=None).array, jnp.argmax(m1.array)))
|
|
64
64
|
assert jnp.all(jnp.equal(hax.argmax(m1, axis=Height).array, jnp.argmax(m1.array, axis=0)))
|
|
65
65
|
|
|
66
66
|
|
|
@@ -75,7 +75,7 @@ def test_reduction_functions_with_where():
|
|
|
75
75
|
jmask = m1.array > 0.5
|
|
76
76
|
|
|
77
77
|
# sum out everything
|
|
78
|
-
assert jnp.all(jnp.equal(hax.sum(m1, where=mask), jnp.sum(rand_m, where=jmask)))
|
|
78
|
+
assert jnp.all(jnp.equal(hax.sum(m1, where=mask).array, jnp.sum(rand_m, where=jmask)))
|
|
79
79
|
# ensure it's a scalar
|
|
80
80
|
|
|
81
81
|
assert jnp.all(jnp.equal(hax.sum(m1, axis=H, where=mask).array, jnp.sum(rand_m, axis=0, where=jmask)))
|
|
@@ -507,7 +507,7 @@ def test_index():
|
|
|
507
507
|
assert named1[{"H": slice(0, 10, 2), "W": 0}].axes == (Axis("H", 5), D)
|
|
508
508
|
|
|
509
509
|
# try indexing with 3 integers: returns scalar ndarray
|
|
510
|
-
assert jnp.all(jnp.equal(named1[{"H": 0, "W": 0, "D": 0}], named1.array[0, 0, 0]))
|
|
510
|
+
assert jnp.all(jnp.equal(named1[{"H": 0, "W": 0, "D": 0}].array, named1.array[0, 0, 0]))
|
|
511
511
|
|
|
512
512
|
|
|
513
513
|
def test_index_with_tracer():
|
|
@@ -79,7 +79,7 @@ def test_scan_doesnt_scan_scalars():
|
|
|
79
79
|
named1 = hax.random.uniform(PRNGKey(0), (Height,))
|
|
80
80
|
|
|
81
81
|
def scan_fun(acc, z, x):
|
|
82
|
-
return acc + z * x, x * z
|
|
82
|
+
return (acc + z * x).scalar(), x * z
|
|
83
83
|
|
|
84
84
|
total, selected = hax.scan(scan_fun, Height)(0.0, 4.0, named1)
|
|
85
85
|
|
|
@@ -215,14 +215,14 @@ def test_mean_respects_where():
|
|
|
215
215
|
named1 = hax.random.uniform(PRNGKey(0), (Height, Width))
|
|
216
216
|
where = hax.random.uniform(PRNGKey(1), (Height, Width)) > 0.5
|
|
217
217
|
|
|
218
|
-
assert not
|
|
219
|
-
assert jnp.all(jnp.isclose(hax.mean(named1, where=where), jnp.mean(named1.array, where=where.array)))
|
|
218
|
+
assert not hax.all(hax.isclose(hax.mean(named1), hax.mean(named1, where=where)))
|
|
219
|
+
assert jnp.all(jnp.isclose(hax.mean(named1, where=where).array, jnp.mean(named1.array, where=where.array)))
|
|
220
220
|
|
|
221
221
|
# check broadcasting
|
|
222
222
|
where = hax.random.uniform(PRNGKey(2), (Height,)) > 0.5
|
|
223
|
-
assert not jnp.all(jnp.isclose(hax.mean(named1), hax.mean(named1, where=where)))
|
|
223
|
+
assert not jnp.all(jnp.isclose(hax.mean(named1).array, hax.mean(named1, where=where).array))
|
|
224
224
|
assert jnp.all(
|
|
225
|
-
jnp.isclose(hax.mean(named1, where=where), jnp.mean(named1.array, where=where.array.reshape((-1, 1))))
|
|
225
|
+
jnp.isclose(hax.mean(named1, where=where).array, jnp.mean(named1.array, where=where.array.reshape((-1, 1))))
|
|
226
226
|
)
|
|
227
227
|
|
|
228
228
|
|