haliax 1.4.dev443__tar.gz → 1.4.dev445__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.dev443 → haliax-1.4.dev445}/PKG-INFO +1 -1
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/__about__.py +1 -1
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/state_dict.py +20 -7
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/jax_utils.py +82 -3
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/scan.py +102 -40
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_scan.py +239 -2
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.agents/projects/api_parity.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.coveragerc +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.flake8 +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.gitignore +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.playbooks/add-types.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.playbooks/wrap-non-named.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/AGENTS.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/AUTHORS.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/CONTRIBUTORS.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/LICENSE +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/README.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/api.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/css/material.css +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/faq.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/fp8.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/index.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/indexing.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/matmul.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/nn.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/partitioning.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/primer.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/rearrange.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/requirements.txt +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/scan.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/state-dict.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/tutorial.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/typing.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/docs/vmap.md +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/etc/license_header.txt +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/mkdocs.yml +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/pyproject.toml +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/__init__.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/scan.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/core.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/fft.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/field.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/haxtyping.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/embedding.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/linear.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/mup.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/normalization.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/poly.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/random.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/tree.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/types.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/util.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/core_test.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_attention.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_axis.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_bitwise_ops.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_conv.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_debug.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_dot.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_dtype_typing.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_fft.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_field.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_hof.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_int8.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_moe_linear.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_mup_coordinate_check.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_mup_embedding.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_mup_linear.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_namedarray_typing.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_nan_reductions.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_nn.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_ops.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_poly_ops.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_pool.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_random.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_scatter_gather.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_utils.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/tests/test_visualize_sharding.py +0 -0
- {haliax-1.4.dev443 → haliax-1.4.dev445}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev445
|
|
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/
|
|
@@ -13,7 +13,7 @@ import jax
|
|
|
13
13
|
import jax.numpy as jnp
|
|
14
14
|
import numpy as np
|
|
15
15
|
from jax import ShapeDtypeStruct
|
|
16
|
-
from
|
|
16
|
+
from haliax.jax_utils import sync_global_devices
|
|
17
17
|
from jax.sharding import Mesh, NamedSharding, PartitionSpec
|
|
18
18
|
from jax.tree_util import DictKey, FlattenedIndexKey, GetAttrKey, SequenceKey
|
|
19
19
|
from jaxtyping import PyTree
|
|
@@ -68,7 +68,11 @@ def flatten_modules_for_export(t: T) -> T:
|
|
|
68
68
|
)
|
|
69
69
|
return module
|
|
70
70
|
|
|
71
|
-
return scan_aware_tree_map(
|
|
71
|
+
return scan_aware_tree_map(
|
|
72
|
+
_flatten_module,
|
|
73
|
+
t,
|
|
74
|
+
is_leaf=lambda x: isinstance(x, ModuleWithStateDictSerialization),
|
|
75
|
+
)
|
|
72
76
|
|
|
73
77
|
|
|
74
78
|
def unflatten_modules_from_export(t: T, template: T) -> T:
|
|
@@ -88,7 +92,10 @@ def unflatten_modules_from_export(t: T, template: T) -> T:
|
|
|
88
92
|
return module
|
|
89
93
|
|
|
90
94
|
return scan_aware_tree_map(
|
|
91
|
-
_unflatten_module,
|
|
95
|
+
_unflatten_module,
|
|
96
|
+
t,
|
|
97
|
+
template,
|
|
98
|
+
is_leaf=lambda x: isinstance(x, ModuleWithStateDictSerialization),
|
|
92
99
|
)
|
|
93
100
|
|
|
94
101
|
|
|
@@ -187,9 +194,13 @@ def from_state_dict(tree: T, state_dict: StateDict, prefix: str | None = None) -
|
|
|
187
194
|
else:
|
|
188
195
|
return default_eqx_module_from_state_dict(tree, state_dict, prefix)
|
|
189
196
|
elif isinstance(tree, list):
|
|
190
|
-
return [
|
|
197
|
+
return [
|
|
198
|
+
from_state_dict(item, state_dict, with_prefix(prefix, str(i))) for i, item in enumerate(tree)
|
|
199
|
+
] # type: ignore
|
|
191
200
|
elif isinstance(tree, dict):
|
|
192
|
-
return {
|
|
201
|
+
return {
|
|
202
|
+
k: from_state_dict(v, state_dict, prefix=with_prefix(prefix, k)) for k, v in tree.items()
|
|
203
|
+
} # type: ignore
|
|
193
204
|
elif isinstance(tree, NamedArray):
|
|
194
205
|
if prefix is None:
|
|
195
206
|
raise ValueError("Cannot extract a leaf value from a torch dict without a prefix")
|
|
@@ -383,7 +394,8 @@ def to_numpy_state_dict(model, prefix: str | None = None) -> StateDict:
|
|
|
383
394
|
# TODO: ensure that this mesh arranges devices correctly
|
|
384
395
|
# (jax seems to do this internally itself, so we should be fine?)
|
|
385
396
|
process_mesh = Mesh(
|
|
386
|
-
np.array(jax.devices()).reshape((jax.process_count(), -1)),
|
|
397
|
+
np.array(jax.devices()).reshape((jax.process_count(), -1)),
|
|
398
|
+
("process", "device"),
|
|
387
399
|
)
|
|
388
400
|
|
|
389
401
|
# now we need to find an axis along which we can shard the array.
|
|
@@ -391,7 +403,8 @@ def to_numpy_state_dict(model, prefix: str | None = None) -> StateDict:
|
|
|
391
403
|
|
|
392
404
|
try:
|
|
393
405
|
axis_to_shard = index_where(
|
|
394
|
-
lambda axis_size: axis_size % process_mesh.devices.size == 0,
|
|
406
|
+
lambda axis_size: axis_size % process_mesh.devices.size == 0,
|
|
407
|
+
arr.shape,
|
|
395
408
|
)
|
|
396
409
|
except ValueError:
|
|
397
410
|
return np.array(arr)
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import functools as ft
|
|
7
7
|
import typing
|
|
8
8
|
import warnings
|
|
9
|
+
import zlib
|
|
9
10
|
from typing import Any, Callable, Sequence
|
|
10
11
|
|
|
11
12
|
import equinox as eqx
|
|
@@ -14,6 +15,8 @@ import numpy as np
|
|
|
14
15
|
from jax import Array
|
|
15
16
|
from jax import numpy as jnp
|
|
16
17
|
from jax import random as jrandom
|
|
18
|
+
from jax.experimental.multihost_utils import host_local_array_to_global_array
|
|
19
|
+
from jax.sharding import PartitionSpec
|
|
17
20
|
from jax.ad_checkpoint import checkpoint_name
|
|
18
21
|
from jax.typing import DTypeLike
|
|
19
22
|
from jaxtyping import PRNGKeyArray
|
|
@@ -39,6 +42,71 @@ class Static(eqx.Module):
|
|
|
39
42
|
value: Any = eqx.field(static=True)
|
|
40
43
|
|
|
41
44
|
|
|
45
|
+
# Non-busted version of broadcast_one_to_all from jax.multihost_utils. (The issue is that if you use a non-contiguous
|
|
46
|
+
# mesh, their utility blows up because it makes a contiguous mesh.)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _psum(xs: Any) -> Any:
|
|
50
|
+
return jax.tree.map(lambda x: jnp.sum(x, dtype=x.dtype, axis=0), xs)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def broadcast_one_to_all(in_tree: Any, is_source: bool | None = None) -> Any:
|
|
54
|
+
"""Broadcast data from a source host (host 0 by default) to all other hosts.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
in_tree: pytree of arrays - each array *must* have the same shape across the
|
|
58
|
+
hosts.
|
|
59
|
+
is_source: optional bool denoting whether the caller is the source. Only
|
|
60
|
+
'source host' will contribute the data for the broadcast. If None, then
|
|
61
|
+
host 0 is used.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
A pytree matching in_tree where the leaves now all contain the data from the
|
|
65
|
+
first host.
|
|
66
|
+
"""
|
|
67
|
+
if jax.process_count() == 1:
|
|
68
|
+
return jax.tree.map(np.asarray, in_tree)
|
|
69
|
+
|
|
70
|
+
if is_source is None:
|
|
71
|
+
is_source = jax.process_index() == 0
|
|
72
|
+
|
|
73
|
+
devices: np.ndarray = np.array(jax.devices()).reshape(jax.process_count(), jax.local_device_count())
|
|
74
|
+
global_mesh = jax.sharding.Mesh(devices, ("processes", "local_devices"))
|
|
75
|
+
pspec = PartitionSpec("processes")
|
|
76
|
+
|
|
77
|
+
def pre_jit(x):
|
|
78
|
+
if is_source:
|
|
79
|
+
inp = x
|
|
80
|
+
else:
|
|
81
|
+
inp = np.zeros_like(x)
|
|
82
|
+
inp = np.expand_dims(inp, axis=0)
|
|
83
|
+
return host_local_array_to_global_array(inp, global_mesh, pspec)
|
|
84
|
+
|
|
85
|
+
def post_jit(x):
|
|
86
|
+
return jax.device_get(x.addressable_data(0))
|
|
87
|
+
|
|
88
|
+
with haliax.partitioning.set_mesh(global_mesh):
|
|
89
|
+
in_tree = jax.tree.map(pre_jit, in_tree)
|
|
90
|
+
out_tree = jax.jit(
|
|
91
|
+
_psum,
|
|
92
|
+
out_shardings=jax.sharding.NamedSharding(global_mesh, PartitionSpec()),
|
|
93
|
+
)(in_tree)
|
|
94
|
+
return jax.tree.map(post_jit, out_tree)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def assert_equal(in_tree, fail_message: str = ""):
|
|
98
|
+
"""Verifies that all the hosts have the same tree of values."""
|
|
99
|
+
expected = broadcast_one_to_all(in_tree)
|
|
100
|
+
if not jax.tree_util.tree_all(jax.tree_util.tree_map(lambda *x: np.all(np.equal(*x)), in_tree, expected)):
|
|
101
|
+
raise AssertionError(f"{fail_message} Expected: {expected}; got: {in_tree}.")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def sync_global_devices(name: str):
|
|
105
|
+
"""Creates a barrier across all hosts/devices."""
|
|
106
|
+
h = np.uint32(zlib.crc32(name.encode()))
|
|
107
|
+
assert_equal(h, f"sync_global_devices name mismatch ('{name}')")
|
|
108
|
+
|
|
109
|
+
|
|
42
110
|
def shaped_rng_split(key, split_shape: int | Sequence[int] = 2) -> PRNGKeyArray:
|
|
43
111
|
if isinstance(split_shape, int):
|
|
44
112
|
num_splits = split_shape
|
|
@@ -69,14 +137,25 @@ def maybe_rng_split(key: PRNGKeyArray | None, num: int = 2):
|
|
|
69
137
|
def filter_eval_shape(*args, **kwargs):
|
|
70
138
|
import warnings
|
|
71
139
|
|
|
72
|
-
warnings.warn(
|
|
140
|
+
warnings.warn(
|
|
141
|
+
"filter_eval_shape is deprecated, use eqx.filter_eval_shape instead",
|
|
142
|
+
DeprecationWarning,
|
|
143
|
+
)
|
|
73
144
|
return eqx.filter_eval_shape(*args, **kwargs)
|
|
74
145
|
|
|
75
146
|
|
|
76
|
-
def filter_checkpoint(
|
|
147
|
+
def filter_checkpoint(
|
|
148
|
+
fun: Callable,
|
|
149
|
+
*,
|
|
150
|
+
prevent_cse: bool = True,
|
|
151
|
+
policy: Callable[..., bool] | None = None,
|
|
152
|
+
):
|
|
77
153
|
"""As `jax.checkpoint`, but allows any Python object as inputs and outputs"""
|
|
78
154
|
|
|
79
|
-
warnings.warn(
|
|
155
|
+
warnings.warn(
|
|
156
|
+
"filter_checkpoint is deprecated, use eqx.filter_checkpoint instead",
|
|
157
|
+
DeprecationWarning,
|
|
158
|
+
)
|
|
80
159
|
|
|
81
160
|
return eqx.filter_checkpoint(fun, prevent_cse=prevent_cse, policy=policy)
|
|
82
161
|
|
|
@@ -62,6 +62,22 @@ class ModuleInit(Protocol[M_co]):
|
|
|
62
62
|
def __call__(self, *args, **kwargs) -> M_co: ...
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
def _normalize_unroll(unroll: int | bool | None, block_size: int) -> int | bool:
|
|
66
|
+
"""Convert user-provided ``unroll`` values into something understood by ``jax.lax.scan``."""
|
|
67
|
+
|
|
68
|
+
if unroll is None:
|
|
69
|
+
return 1
|
|
70
|
+
|
|
71
|
+
if isinstance(unroll, bool):
|
|
72
|
+
return unroll
|
|
73
|
+
|
|
74
|
+
resolved = int(unroll)
|
|
75
|
+
if resolved < 1:
|
|
76
|
+
raise ValueError(f"unroll must be >= 1; got {resolved}.")
|
|
77
|
+
|
|
78
|
+
return resolved
|
|
79
|
+
|
|
80
|
+
|
|
65
81
|
class BlockFoldable(Protocol[M]):
|
|
66
82
|
"""Common interface for :class:`~haliax.nn.Stacked` and :class:`~haliax.nn.BlockSeq`.
|
|
67
83
|
|
|
@@ -84,29 +100,40 @@ class BlockFoldable(Protocol[M]):
|
|
|
84
100
|
prevent_cse: bool = False,
|
|
85
101
|
) -> ModuleInit[S]: ...
|
|
86
102
|
|
|
87
|
-
def scan(self, init: T, *extra_args, **extra_kwargs): ...
|
|
103
|
+
def scan(self, init: T, *extra_args, unroll: int | bool | None = None, **extra_kwargs): ...
|
|
88
104
|
|
|
89
|
-
def fold(self, init: T, *args, **kwargs) -> T: ...
|
|
105
|
+
def fold(self, init: T, *args, unroll: int | bool | None = None, **kwargs) -> T: ...
|
|
90
106
|
|
|
91
107
|
@overload
|
|
92
|
-
def fold_via(
|
|
108
|
+
def fold_via(
|
|
109
|
+
self, fn: FoldFunction[M, P, CarryT], *, unroll: int | bool | None = None
|
|
110
|
+
) -> Callable[Concatenate[CarryT, P], CarryT]: ...
|
|
93
111
|
|
|
94
112
|
@overload
|
|
95
|
-
def fold_via(
|
|
113
|
+
def fold_via(
|
|
114
|
+
self, fn: Callable[[M, CarryT], CarryT], *, unroll: int | bool | None = None
|
|
115
|
+
) -> Callable[[CarryT], CarryT]: ...
|
|
96
116
|
|
|
97
|
-
def fold_via(
|
|
117
|
+
def fold_via(
|
|
118
|
+
self, fn: Callable[..., CarryT], *, unroll: int | bool | None = None
|
|
119
|
+
) -> Callable[Concatenate[CarryT, P], CarryT]: ...
|
|
98
120
|
|
|
99
121
|
@overload
|
|
100
122
|
def scan_via(
|
|
101
|
-
self, fn: ScanFunction[M, CarryT, P, OutputT_co]
|
|
123
|
+
self, fn: ScanFunction[M, CarryT, P, OutputT_co], *, unroll: int | bool | None = None
|
|
102
124
|
) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]: ...
|
|
103
125
|
|
|
104
126
|
@overload
|
|
105
127
|
def scan_via(
|
|
106
|
-
self,
|
|
128
|
+
self,
|
|
129
|
+
fn: Callable[[M, CarryT], tuple[CarryT, OutputT_co]],
|
|
130
|
+
*,
|
|
131
|
+
unroll: int | bool | None = None,
|
|
107
132
|
) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]: ...
|
|
108
133
|
|
|
109
|
-
def scan_via(
|
|
134
|
+
def scan_via(
|
|
135
|
+
self, fn: Callable[..., tuple[CarryT, OutputT_co]], *, unroll: int | bool | None = None
|
|
136
|
+
) -> Callable[P, tuple[CarryT, OutputT_co]]: ...
|
|
110
137
|
|
|
111
138
|
@overload
|
|
112
139
|
def vmap_via(self, fn: VmapFunction[M, P, OutputT_co]) -> Callable[P, OutputT_co]: ...
|
|
@@ -181,15 +208,15 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
181
208
|
|
|
182
209
|
return fn
|
|
183
210
|
|
|
184
|
-
def scan(self, init: T, *extra_args, **extra_kwargs):
|
|
211
|
+
def scan(self, init: T, *extra_args, unroll: int | bool | None = None, **extra_kwargs):
|
|
185
212
|
def do_scan(init, *extra_args, **extra_kwargs):
|
|
186
213
|
out = []
|
|
187
214
|
carry = init
|
|
188
215
|
|
|
189
216
|
for i, block in enumerate(self.blocks):
|
|
190
|
-
|
|
191
217
|
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
192
|
-
functools.partial(BlockSeq._slice_out, self.Block, i),
|
|
218
|
+
functools.partial(BlockSeq._slice_out, self.Block, i),
|
|
219
|
+
(extra_args, extra_kwargs),
|
|
193
220
|
)
|
|
194
221
|
|
|
195
222
|
block_result = block(carry, *block_args, **block_kwargs)
|
|
@@ -210,12 +237,13 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
210
237
|
|
|
211
238
|
return do_scan(init, *extra_args, **extra_kwargs)
|
|
212
239
|
|
|
213
|
-
def fold(self, init: T, *args, **kwargs) -> T:
|
|
240
|
+
def fold(self, init: T, *args, unroll: int | bool | None = None, **kwargs) -> T:
|
|
214
241
|
def do_fold(init, *args, **kwargs):
|
|
215
242
|
carry = init
|
|
216
243
|
for i, block in enumerate(self.blocks):
|
|
217
244
|
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
218
|
-
functools.partial(BlockSeq._slice_out, self.Block, i),
|
|
245
|
+
functools.partial(BlockSeq._slice_out, self.Block, i),
|
|
246
|
+
(args, kwargs),
|
|
219
247
|
)
|
|
220
248
|
carry = block(carry, *block_args, **block_kwargs)
|
|
221
249
|
carry = tree_checkpoint_name(carry, self._carry_ckpt_name)
|
|
@@ -224,12 +252,16 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
224
252
|
return do_fold(init, *args, **kwargs)
|
|
225
253
|
|
|
226
254
|
@overload
|
|
227
|
-
def fold_via(
|
|
255
|
+
def fold_via(
|
|
256
|
+
self, fn: FoldFunction[M, P, CarryT], *, unroll: int | bool | None = None
|
|
257
|
+
) -> Callable[Concatenate[CarryT, P], CarryT]: ...
|
|
228
258
|
|
|
229
259
|
@overload
|
|
230
|
-
def fold_via(
|
|
260
|
+
def fold_via(
|
|
261
|
+
self, fn: Callable[[M, CarryT], CarryT], *, unroll: int | bool | None = None
|
|
262
|
+
) -> Callable[[CarryT], CarryT]: ...
|
|
231
263
|
|
|
232
|
-
def fold_via(self, fn: Callable[..., CarryT]):
|
|
264
|
+
def fold_via(self, fn: Callable[..., CarryT], *, unroll: int | bool | None = None):
|
|
233
265
|
"""Return a function that folds over the sequence using ``fn``.
|
|
234
266
|
|
|
235
267
|
``fn`` should take a block and a carry and return a new carry. The
|
|
@@ -247,15 +279,18 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
247
279
|
|
|
248
280
|
@overload
|
|
249
281
|
def scan_via(
|
|
250
|
-
self, fn: ScanFunction[M, CarryT, P, OutputT_co]
|
|
282
|
+
self, fn: ScanFunction[M, CarryT, P, OutputT_co], *, unroll: int | bool | None = None
|
|
251
283
|
) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]: ...
|
|
252
284
|
|
|
253
285
|
@overload
|
|
254
286
|
def scan_via(
|
|
255
|
-
self,
|
|
287
|
+
self,
|
|
288
|
+
fn: Callable[[M, CarryT], tuple[CarryT, OutputT_co]],
|
|
289
|
+
*,
|
|
290
|
+
unroll: int | bool | None = None,
|
|
256
291
|
) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]: ...
|
|
257
292
|
|
|
258
|
-
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]]):
|
|
293
|
+
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]], *, unroll: int | bool | None = None):
|
|
259
294
|
"""Return a function that scans over the sequence using ``fn``.
|
|
260
295
|
|
|
261
296
|
``fn`` should take a block and a carry and return ``(carry, output)``.
|
|
@@ -447,7 +482,7 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
447
482
|
|
|
448
483
|
return fn
|
|
449
484
|
|
|
450
|
-
def scan(self, init, *extra_args, **extra_kwargs):
|
|
485
|
+
def scan(self, init, *extra_args, unroll: int | bool | None = None, **extra_kwargs):
|
|
451
486
|
"""
|
|
452
487
|
Scan over the stacked module. This is the same as a for loop that applies each instance of the module in sequence
|
|
453
488
|
to the input, passing the output of one instance to the next instance. It returns a stack of outputs as
|
|
@@ -475,19 +510,24 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
475
510
|
|
|
476
511
|
"""
|
|
477
512
|
|
|
513
|
+
resolved_unroll = _normalize_unroll(unroll, self.Block.size)
|
|
514
|
+
|
|
478
515
|
def do_block(carry, block, *args, **kwargs):
|
|
479
516
|
carry, out = block(carry, *args, **kwargs)
|
|
480
517
|
return carry, out
|
|
481
518
|
|
|
482
519
|
def do_scan(init, *extra_args, **extra_kwargs):
|
|
483
|
-
carry, out = haliax.scan(
|
|
484
|
-
|
|
485
|
-
|
|
520
|
+
carry, out = haliax.scan(
|
|
521
|
+
do_block,
|
|
522
|
+
self.Block,
|
|
523
|
+
remat=self.gradient_checkpointing,
|
|
524
|
+
unroll=resolved_unroll,
|
|
525
|
+
)(init, self.stacked, *extra_args, **extra_kwargs)
|
|
486
526
|
return carry, out
|
|
487
527
|
|
|
488
528
|
return do_scan(init, *extra_args, **extra_kwargs)
|
|
489
529
|
|
|
490
|
-
def fold(self, init, *args, **kwargs):
|
|
530
|
+
def fold(self, init, *args, unroll: int | bool | None = None, **kwargs):
|
|
491
531
|
"""
|
|
492
532
|
Fold over the stacked module. This is the same as a for loop that applies each instance of the module in sequence
|
|
493
533
|
to the input, passing the output of one instance to the next instance.
|
|
@@ -510,66 +550,88 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
510
550
|
|
|
511
551
|
"""
|
|
512
552
|
|
|
553
|
+
resolved_unroll = _normalize_unroll(unroll, self.Block.size)
|
|
554
|
+
|
|
513
555
|
def do_block(carry, block, *args, **kwargs):
|
|
514
556
|
carry = block(carry, *args, **kwargs)
|
|
515
557
|
return carry
|
|
516
558
|
|
|
517
559
|
def do_fold(init, *extra_args, **extra_kwargs):
|
|
518
|
-
carry = haliax.fold(
|
|
519
|
-
|
|
520
|
-
|
|
560
|
+
carry = haliax.fold(
|
|
561
|
+
do_block,
|
|
562
|
+
self.Block,
|
|
563
|
+
remat=self.gradient_checkpointing,
|
|
564
|
+
unroll=resolved_unroll,
|
|
565
|
+
)(init, self.stacked, *extra_args, **extra_kwargs)
|
|
521
566
|
return carry
|
|
522
567
|
|
|
523
568
|
return do_fold(init, *args, **kwargs)
|
|
524
569
|
|
|
525
570
|
@overload
|
|
526
|
-
def fold_via(
|
|
571
|
+
def fold_via(
|
|
572
|
+
self, fn: FoldFunction[M, P, CarryT], *, unroll: int | bool | None = None
|
|
573
|
+
) -> Callable[Concatenate[CarryT, P], CarryT]: ...
|
|
527
574
|
|
|
528
575
|
@overload
|
|
529
|
-
def fold_via(
|
|
576
|
+
def fold_via(
|
|
577
|
+
self, fn: Callable[[M, CarryT], CarryT], *, unroll: int | bool | None = None
|
|
578
|
+
) -> Callable[[CarryT], CarryT]: ...
|
|
530
579
|
|
|
531
|
-
def fold_via(self, fn: Callable[..., CarryT]):
|
|
580
|
+
def fold_via(self, fn: Callable[..., CarryT], *, unroll: int | bool | None = None):
|
|
532
581
|
"""Return a function that folds over the stack using ``fn``.
|
|
533
582
|
|
|
534
583
|
``fn`` should take a block and a carry and return a new carry. The
|
|
535
584
|
returned function mirrors :func:`haliax.fold` over the block axis.
|
|
536
585
|
"""
|
|
537
586
|
|
|
587
|
+
resolved_unroll = _normalize_unroll(unroll, self.Block.size)
|
|
588
|
+
|
|
538
589
|
def do_block(carry: CarryT, block: M, *args, **kwargs) -> CarryT:
|
|
539
590
|
return fn(block, carry, *args, **kwargs)
|
|
540
591
|
|
|
541
592
|
def do_fold(init: CarryT, *args, **kwargs) -> CarryT:
|
|
542
|
-
return haliax.fold(
|
|
543
|
-
|
|
544
|
-
|
|
593
|
+
return haliax.fold(
|
|
594
|
+
do_block,
|
|
595
|
+
self.Block,
|
|
596
|
+
remat=self.gradient_checkpointing,
|
|
597
|
+
unroll=resolved_unroll,
|
|
598
|
+
)(init, self.stacked, *args, **kwargs)
|
|
545
599
|
|
|
546
600
|
return do_fold
|
|
547
601
|
|
|
548
602
|
@overload
|
|
549
603
|
def scan_via(
|
|
550
|
-
self, fn: ScanFunction[M, CarryT, P, OutputT_co]
|
|
604
|
+
self, fn: ScanFunction[M, CarryT, P, OutputT_co], *, unroll: int | bool | None = None
|
|
551
605
|
) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]: ...
|
|
552
606
|
|
|
553
607
|
@overload
|
|
554
608
|
def scan_via(
|
|
555
|
-
self,
|
|
609
|
+
self,
|
|
610
|
+
fn: Callable[[M, CarryT], tuple[CarryT, OutputT_co]],
|
|
611
|
+
*,
|
|
612
|
+
unroll: int | bool | None = None,
|
|
556
613
|
) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]: ...
|
|
557
614
|
|
|
558
|
-
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]]):
|
|
615
|
+
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]], *, unroll: int | bool | None = None):
|
|
559
616
|
"""Return a function that scans over the stack using ``fn``.
|
|
560
617
|
|
|
561
618
|
``fn`` should take a block and a carry and return ``(carry, output)``.
|
|
562
619
|
Semantics match :func:`haliax.scan` over the block axis.
|
|
563
620
|
"""
|
|
564
621
|
|
|
622
|
+
resolved_unroll = _normalize_unroll(unroll, self.Block.size)
|
|
623
|
+
|
|
565
624
|
def do_block(carry: CarryT, block: M, *args, **kwargs) -> tuple[CarryT, OutputT_co]:
|
|
566
625
|
carry, output = fn(block, carry, *args, **kwargs)
|
|
567
626
|
return carry, output
|
|
568
627
|
|
|
569
628
|
def do_scan(init: CarryT, *args, **kwargs) -> tuple[CarryT, OutputT_co]:
|
|
570
|
-
return haliax.scan(
|
|
571
|
-
|
|
572
|
-
|
|
629
|
+
return haliax.scan(
|
|
630
|
+
do_block,
|
|
631
|
+
self.Block,
|
|
632
|
+
remat=self.gradient_checkpointing,
|
|
633
|
+
unroll=resolved_unroll,
|
|
634
|
+
)(init, self.stacked, *args, **kwargs)
|
|
573
635
|
|
|
574
636
|
return do_scan
|
|
575
637
|
|
|
@@ -707,7 +769,7 @@ def _unstack_state_dict(state_dict: StateDict, prefix: str | None = None) -> Sta
|
|
|
707
769
|
for k, v in state_dict.items():
|
|
708
770
|
if k.startswith(prefix) and is_jax_or_hax_array_like(v):
|
|
709
771
|
for i, v_i in enumerate(v):
|
|
710
|
-
new_dict[f"{prefix}{i}.{k[len(prefix):]}"] = v_i
|
|
772
|
+
new_dict[f"{prefix}{i}.{k[len(prefix) :]}"] = v_i
|
|
711
773
|
else:
|
|
712
774
|
new_dict[k] = v
|
|
713
775
|
|