haliax 1.4.dev336__tar.gz → 1.4.dev339__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.dev336 → haliax-1.4.dev339}/PKG-INFO +1 -1
- {haliax-1.4.dev336 → haliax-1.4.dev339}/mkdocs.yml +0 -1
- haliax-1.4.dev339/src/haliax/__about__.py +1 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/__init__.py +2 -2
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/jax_utils.py +19 -27
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/__init__.py +4 -1
- haliax-1.4.dev339/src/haliax/nn/scan.py +425 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/quantization.py +6 -1
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/random.py +1 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_scan.py +3 -56
- haliax-1.4.dev336/src/haliax/__about__.py +0 -1
- haliax-1.4.dev336/src/haliax/nn/scan.py +0 -684
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.coveragerc +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.flake8 +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.gitignore +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/LICENSE +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/README.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/api.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/css/material.css +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/faq.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/fp8.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/hof.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/index.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/indexing.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/matmul.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/nn.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/partitioning.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/rearrange.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/requirements.txt +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/stacked.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/state-dict.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/tutorial.md +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/pyproject.toml +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/state_dict.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/core.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/embedding.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/linear.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/normalization.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/types.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/util.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/core_test.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_attention.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_axis.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_conv.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_debug.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_dot.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_hof.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_int8.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_nn.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_ops.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_pool.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_random.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev339
|
|
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/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.dev339"
|
|
@@ -61,7 +61,7 @@ from .core import (
|
|
|
61
61
|
updated_slice,
|
|
62
62
|
)
|
|
63
63
|
from .hof import fold, map, scan, vmap
|
|
64
|
-
from .jax_utils import
|
|
64
|
+
from .jax_utils import filter_checkpoint
|
|
65
65
|
from .ops import clip, isclose, pad_left, trace, tril, triu, where
|
|
66
66
|
from .partitioning import auto_sharded, axis_mapping, fsdp, named_jit, shard, shard_with_axis_mapping
|
|
67
67
|
from .specialized_fns import top_k
|
|
@@ -887,6 +887,7 @@ def true_divide(x1: NamedOrNumeric, x2: NamedOrNumeric, /) -> NamedOrNumeric:
|
|
|
887
887
|
# deprecated name
|
|
888
888
|
concat_axis_specs = concat_axes
|
|
889
889
|
|
|
890
|
+
|
|
890
891
|
__all__ = [
|
|
891
892
|
"debug",
|
|
892
893
|
"random",
|
|
@@ -1070,5 +1071,4 @@ __all__ = [
|
|
|
1070
1071
|
"ravel",
|
|
1071
1072
|
"flatten",
|
|
1072
1073
|
"is_named_array",
|
|
1073
|
-
"tree_checkpoint_name",
|
|
1074
1074
|
]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import functools as ft
|
|
2
2
|
import typing
|
|
3
|
-
import warnings
|
|
4
3
|
from typing import Any, Callable, Optional, Sequence, Union
|
|
5
4
|
|
|
6
5
|
import equinox as eqx
|
|
@@ -9,8 +8,8 @@ import numpy as np
|
|
|
9
8
|
from jax import Array
|
|
10
9
|
from jax import numpy as jnp
|
|
11
10
|
from jax import random as jrandom
|
|
12
|
-
from jax.
|
|
13
|
-
from jax.typing import DTypeLike
|
|
11
|
+
from jax._src.numpy import lax_numpy
|
|
12
|
+
from jax._src.typing import DTypeLike
|
|
14
13
|
from jaxtyping import PRNGKeyArray
|
|
15
14
|
|
|
16
15
|
import haliax
|
|
@@ -28,7 +27,6 @@ except ImportError:
|
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
F = typing.TypeVar("F", bound=Callable[..., Any])
|
|
31
|
-
T = typing.TypeVar("T")
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
class Static(eqx.Module):
|
|
@@ -72,9 +70,23 @@ def filter_eval_shape(*args, **kwargs):
|
|
|
72
70
|
def filter_checkpoint(fun: Callable, *, prevent_cse: bool = True, policy: Optional[Callable[..., bool]] = None):
|
|
73
71
|
"""As `jax.checkpoint`, but allows any Python object as inputs and outputs"""
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
@ft.wraps(fun)
|
|
74
|
+
def _fn(_static, _dynamic):
|
|
75
|
+
_args, _kwargs = eqx.combine(_static, _dynamic)
|
|
76
|
+
_out = fun(*_args, **_kwargs)
|
|
77
|
+
_dynamic_out, _static_out = eqx.partition(_out, is_jax_array_like)
|
|
78
|
+
return _dynamic_out, Static(_static_out)
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
checkpointed_fun = jax.checkpoint(_fn, prevent_cse=prevent_cse, policy=policy, static_argnums=(0,))
|
|
81
|
+
|
|
82
|
+
@ft.wraps(fun)
|
|
83
|
+
def wrapper(*args, **kwargs):
|
|
84
|
+
dynamic, static = eqx.partition((args, kwargs), is_jax_array_like)
|
|
85
|
+
dynamic_out, static_out = checkpointed_fun(static, dynamic)
|
|
86
|
+
|
|
87
|
+
return eqx.combine(dynamic_out, static_out.value)
|
|
88
|
+
|
|
89
|
+
return wrapper
|
|
78
90
|
|
|
79
91
|
|
|
80
92
|
def is_jax_array_like(x):
|
|
@@ -190,7 +202,7 @@ def _jittable_dg_einsum(
|
|
|
190
202
|
contract_path = opt_einsum.contract_path
|
|
191
203
|
else:
|
|
192
204
|
ty = next(iter(non_constant_dim_types))
|
|
193
|
-
contract_path =
|
|
205
|
+
contract_path = lax_numpy._poly_einsum_handlers.get(ty, lax_numpy._default_poly_einsum_handler)
|
|
194
206
|
# using einsum_call=True here is an internal api for opt_einsum... sorry
|
|
195
207
|
operands, contractions = contract_path(*operands, einsum_call=True, use_blas=True, optimize=optimize)
|
|
196
208
|
|
|
@@ -200,23 +212,3 @@ def _jittable_dg_einsum(
|
|
|
200
212
|
if spec is not None:
|
|
201
213
|
einsum = jax.named_call(einsum, name=spec)
|
|
202
214
|
return einsum(operands, contractions, precision, preferred_element_type, _dot_general) # type: ignore[operator]
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def tree_checkpoint_name(x: T, name: str) -> T:
|
|
206
|
-
"""
|
|
207
|
-
Checkpoint a tree of arrays with a given name. This is useful for gradient checkpointing.
|
|
208
|
-
This is equivalent to calling [jax.ad_checkpoint.checkpoint_name][]
|
|
209
|
-
except that it works for any PyTree, not just arrays.
|
|
210
|
-
|
|
211
|
-
See Also:
|
|
212
|
-
* [jax.ad_checkpoint.checkpoint_name][]
|
|
213
|
-
* [haliax.nn.StackedCheckpointPolicy][]
|
|
214
|
-
"""
|
|
215
|
-
|
|
216
|
-
def _checkpoint_leaf(x):
|
|
217
|
-
if is_jax_array_like(x):
|
|
218
|
-
return checkpoint_name(x, name)
|
|
219
|
-
else:
|
|
220
|
-
return x
|
|
221
|
-
|
|
222
|
-
return jax.tree.map(_checkpoint_leaf, x)
|
|
@@ -38,7 +38,10 @@ from .loss import binary_cross_entropy_loss, cross_entropy_loss, cross_entropy_l
|
|
|
38
38
|
from .mlp import MLP
|
|
39
39
|
from .normalization import LayerNorm, log_softmax, logsumexp, softmax, standardize
|
|
40
40
|
from .pool import max_pool, mean_pool, min_pool
|
|
41
|
-
from .scan import BlockSeq, Stacked
|
|
41
|
+
from .scan import BlockSeq, Stacked
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# TODO: support where in softmax, etc
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
def one_hot(x: NamedArray | int, class_axis: Axis, *, dtype=None) -> NamedArray:
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import re
|
|
3
|
+
from typing import Any, Dict, Generic, Optional, Protocol, Sequence, Type, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
import equinox as eqx
|
|
6
|
+
import jax
|
|
7
|
+
from jax import numpy as jnp
|
|
8
|
+
|
|
9
|
+
import haliax
|
|
10
|
+
import haliax.util
|
|
11
|
+
from haliax.jax_utils import filter_checkpoint, is_jax_array_like
|
|
12
|
+
from haliax.util import is_jax_or_hax_array_like
|
|
13
|
+
|
|
14
|
+
from .._src.state_dict import ModuleWithStateDictSerialization, StateDict, with_prefix
|
|
15
|
+
from ..axis import Axis
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
M = TypeVar("M", bound=eqx.Module)
|
|
19
|
+
M_co = TypeVar("M_co", bound=eqx.Module, covariant=True)
|
|
20
|
+
S = TypeVar("S", bound=eqx.Module)
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ModuleInit(Protocol[M_co]):
|
|
25
|
+
def __call__(self, *args, **kwargs) -> M_co:
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BlockFoldable(Protocol[M]):
|
|
30
|
+
"""
|
|
31
|
+
A superclass for [haliax.nn.Stacked][] and [haliax.nn.BlockSeq][] that exposes the fold and scan methods, as
|
|
32
|
+
well as a few other methods that are useful for these modules.
|
|
33
|
+
|
|
34
|
+
This is a protocol, so you can use it as a type hint for a function that takes a Stacked or BlockSeq.
|
|
35
|
+
Equinox modules can't directly inherit from Protocols, but you can use it as a type hint.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
Block: Axis
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def init(
|
|
42
|
+
cls: Type[S], Block: Axis, module: Type[M], *, gradient_checkpointing: bool = False, prevent_cse: bool = False
|
|
43
|
+
) -> ModuleInit[S]:
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
def scan(self, init: T, *extra_args, **extra_kwargs):
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def fold(self, init: T, *args, **kwargs) -> T:
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def unstacked(self) -> Sequence[M]:
|
|
53
|
+
"""
|
|
54
|
+
Returns the unstacked version of this module. This is useful for logging or saving checkpoints.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
61
|
+
"""
|
|
62
|
+
A "BlockSeq" wraps another module and produces a "sequential" version of it, where an input is applied
|
|
63
|
+
to each instance of the sequential module in sequence. This is useful for e.g. transformers
|
|
64
|
+
where you have multiple instances of the same transformer block and the input is applied in a fold/for loop
|
|
65
|
+
in sequence.
|
|
66
|
+
|
|
67
|
+
It's similar in spirit to an [equinox.nn.Sequential][]. Unlike [equinox.nn.Sequential][], BlockSeq does not need to be
|
|
68
|
+
homogeneous (though the init method assumes that it is).
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
blocks: Sequence[M]
|
|
72
|
+
Block: Axis = eqx.static_field()
|
|
73
|
+
gradient_checkpointing: bool = eqx.static_field()
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def init(
|
|
77
|
+
cls: Type[S], Block: Axis, module: Type[M], *, gradient_checkpointing: bool = False, prevent_cse: bool = False
|
|
78
|
+
) -> ModuleInit[S]:
|
|
79
|
+
"""
|
|
80
|
+
This is a curried init method that takes the Block and module and returns a function that takes
|
|
81
|
+
the arguments to the module's init method. Any NamedArrays in the arguments will be sliced along the
|
|
82
|
+
Block axis (if it exists). JAX arrays will be sliced along the first axis.
|
|
83
|
+
"""
|
|
84
|
+
del prevent_cse # not needed, but kept for compat with Stacked
|
|
85
|
+
|
|
86
|
+
@functools.wraps(module)
|
|
87
|
+
def fn(*args, **kwargs):
|
|
88
|
+
# The only complexity here is that the args and kwargs might have a Block axis in them,
|
|
89
|
+
# in which case we need to loop over them them to slice them out.
|
|
90
|
+
|
|
91
|
+
def init_block(i):
|
|
92
|
+
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
93
|
+
functools.partial(BlockSeq._slice_out, Block, i), (args, kwargs)
|
|
94
|
+
)
|
|
95
|
+
return module.init(*block_args, **block_kwargs)
|
|
96
|
+
|
|
97
|
+
seq = [init_block(i) for i in range(Block.size)]
|
|
98
|
+
|
|
99
|
+
return BlockSeq(seq, Block, gradient_checkpointing)
|
|
100
|
+
|
|
101
|
+
return fn
|
|
102
|
+
|
|
103
|
+
def scan(self, init: T, *extra_args, **extra_kwargs):
|
|
104
|
+
out = []
|
|
105
|
+
carry = init
|
|
106
|
+
|
|
107
|
+
for i, block in enumerate(self.blocks):
|
|
108
|
+
if self.gradient_checkpointing:
|
|
109
|
+
block = filter_checkpoint(block)
|
|
110
|
+
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
111
|
+
functools.partial(BlockSeq._slice_out, self.Block, i), (extra_args, extra_kwargs)
|
|
112
|
+
)
|
|
113
|
+
block_result = block(carry, *block_args, **block_kwargs)
|
|
114
|
+
if not isinstance(block_result, (tuple, list)) or len(block_result) != 2:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"BlockSeq.scan expects the block to return a pair of (carry, extra), got {block_result}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
carry, extra = block_result
|
|
120
|
+
|
|
121
|
+
out.append(extra)
|
|
122
|
+
|
|
123
|
+
# TODO: do we want to stack the outputs?
|
|
124
|
+
return carry, out
|
|
125
|
+
|
|
126
|
+
def fold(self, init: T, *args, **kwargs) -> T:
|
|
127
|
+
carry = init
|
|
128
|
+
for i, block in enumerate(self.blocks):
|
|
129
|
+
if self.gradient_checkpointing:
|
|
130
|
+
block = filter_checkpoint(block)
|
|
131
|
+
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
132
|
+
functools.partial(BlockSeq._slice_out, self.Block, i), (args, kwargs)
|
|
133
|
+
)
|
|
134
|
+
carry = block(carry, *block_args, **block_kwargs)
|
|
135
|
+
return carry
|
|
136
|
+
|
|
137
|
+
def unstacked(self) -> Sequence[M]:
|
|
138
|
+
return self.blocks
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _slice_out(Block, i, x):
|
|
142
|
+
if haliax.is_named_array(x):
|
|
143
|
+
if haliax.selects_axis(x.axes, Block):
|
|
144
|
+
return x[Block, i]
|
|
145
|
+
else:
|
|
146
|
+
return x
|
|
147
|
+
elif haliax.jax_utils.is_jax_array_like(x):
|
|
148
|
+
return x[i]
|
|
149
|
+
else:
|
|
150
|
+
return x
|
|
151
|
+
|
|
152
|
+
def _state_dict_key_map(self) -> Dict[str, Optional[str]]:
|
|
153
|
+
return {"blocks": None}
|
|
154
|
+
|
|
155
|
+
def from_state_dict(self: M, state_dict: StateDict, prefix: Optional[str] = None) -> M:
|
|
156
|
+
out_blocks = []
|
|
157
|
+
for i, block in enumerate(self.blocks):
|
|
158
|
+
my_prefix = with_prefix(prefix, str(i))
|
|
159
|
+
block = block.from_state_dict(state_dict, my_prefix)
|
|
160
|
+
out_blocks.append(block)
|
|
161
|
+
|
|
162
|
+
return eqx.tree_at(lambda m: m.blocks, self, out_blocks)
|
|
163
|
+
|
|
164
|
+
def to_state_dict(self, prefix: Optional[str] = None) -> StateDict:
|
|
165
|
+
"""
|
|
166
|
+
Returns the unstacked format of the module, which is compatible with torch.nn.Sequential, with keys of the form (...). The stacked/vectorized format is required for haliax.nn.Stacked and vectorizes all such tensors into a single shared key.".
|
|
167
|
+
"""
|
|
168
|
+
state_dict: StateDict = {}
|
|
169
|
+
for i, block in enumerate(self.blocks):
|
|
170
|
+
my_prefix = with_prefix(prefix, str(i))
|
|
171
|
+
# we can't assume to_state_dict is implemented, so we have to do it manually
|
|
172
|
+
block_dict = haliax.state_dict.to_state_dict(block, my_prefix)
|
|
173
|
+
state_dict.update(block_dict)
|
|
174
|
+
|
|
175
|
+
return state_dict
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
179
|
+
"""
|
|
180
|
+
A "Stacked" wraps another module and produces a "stacked" version of it, where an input is applied
|
|
181
|
+
to each instance of the stacked module in sequence. This is useful for e.g. transformers
|
|
182
|
+
where you have multiple instances of the same transformer block and the input is applied in a fold/for loop
|
|
183
|
+
in sequence.
|
|
184
|
+
|
|
185
|
+
It's similar in spirit to an [equinox.nn.Sequential], but it must be homogeneous. In Jax, this is much cheaper to
|
|
186
|
+
compile than a sequential (or moral equivalent), because Jax compiles the module's method once, instead of unrolling
|
|
187
|
+
the sequential and compiling everything as a giant graph. In Jax, this pattern is often called "scan layers" or
|
|
188
|
+
"scan over layers".
|
|
189
|
+
|
|
190
|
+
A further constraint is that the elements of the stack must have the same Python control flow. This is because
|
|
191
|
+
Jax's scan primitive requires that the function you pass to it is pure, and the only way to do that is to ensure
|
|
192
|
+
that the function has the same control flow for every element of the stack.
|
|
193
|
+
|
|
194
|
+
Stacked supports both "fold" and "scan" semantics. "fold" is the same as a for loop that accumulates a single
|
|
195
|
+
output, while "scan" is the same as a for loop that accumulates a list of intermediates as well as the final output.
|
|
196
|
+
|
|
197
|
+
Stacked also supports gradient checkpointing, which is useful for very large models that don't fit in memory.
|
|
198
|
+
|
|
199
|
+
Typically only one of "fold" or "scan" can be used with a given Stacked module, depending on the what the module
|
|
200
|
+
returns: if the module returns a single output, use "fold"; if the module returns a sequence of intermediates and
|
|
201
|
+
an output to be passed to the next layer, use "scan". More concretely, for a transformer, you would use "scan" if
|
|
202
|
+
you wanted to return a kv cache (or the attention matrix) as well as the output of the transformer. If you just
|
|
203
|
+
wanted the output of the transformer, you would use "fold".
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
```python
|
|
208
|
+
>>> import equinox as eqx
|
|
209
|
+
>>> import haliax as hax
|
|
210
|
+
>>> import haliax.nn as hnn
|
|
211
|
+
>>> class MyModule(eqx.Module):
|
|
212
|
+
... def __init__(self, num_layers: int, hidden: hax.Axis, *, key):
|
|
213
|
+
... self.axis = hax.Axis("layer", num_layers)
|
|
214
|
+
... split_key = jax.random.split(key, num_layers)
|
|
215
|
+
... self.layers = Stacked.init(self.axis, hnn.Linear)(In=hidden, Out=hidden, key=split_key)
|
|
216
|
+
...
|
|
217
|
+
... def __call__(self, x):
|
|
218
|
+
... return self.layers.fold(x) # applies each layer in sequence
|
|
219
|
+
...
|
|
220
|
+
>>> Hidden = hax.Axis("hidden", 10)
|
|
221
|
+
>>> mod = MyModule(5, Hidden)
|
|
222
|
+
>>> mod(hax.ones(Hidden))
|
|
223
|
+
```
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
# TODO: we can probably make this module support pipeline parallelism, but that's a whole project in itself
|
|
227
|
+
|
|
228
|
+
stacked: M
|
|
229
|
+
Block: Axis = eqx.static_field()
|
|
230
|
+
# TODO: support fancier gradient checkpointing
|
|
231
|
+
gradient_checkpointing: bool = eqx.static_field()
|
|
232
|
+
prevent_cse: bool = eqx.static_field()
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
def init(
|
|
236
|
+
cls, Block: Axis, module: Type[M], *, gradient_checkpointing: bool = False, prevent_cse: bool = False
|
|
237
|
+
) -> ModuleInit["Stacked[M]"]:
|
|
238
|
+
"""
|
|
239
|
+
Initialize a Stacked module. This method is curried: you can pass in the Block and module, and it will return
|
|
240
|
+
a function that takes (batched) arguments to the vmapped module's init method.
|
|
241
|
+
:param Block:
|
|
242
|
+
:param module:
|
|
243
|
+
:param gradient_checkpointing:
|
|
244
|
+
:param prevent_cse:
|
|
245
|
+
:return:
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
@functools.wraps(module)
|
|
249
|
+
def fn(*args, **kwargs):
|
|
250
|
+
stacked = haliax.vmap(module.init, Block)(*args, **kwargs)
|
|
251
|
+
return Stacked(stacked, Block, gradient_checkpointing, prevent_cse)
|
|
252
|
+
|
|
253
|
+
return fn
|
|
254
|
+
|
|
255
|
+
def scan(self, init, *extra_args, **extra_kwargs):
|
|
256
|
+
"""
|
|
257
|
+
Scan over the stacked module. This is the same as a for loop that applies each instance of the module in sequence
|
|
258
|
+
to the input, passing the output of one instance to the next instance. It returns a stack of intermediates as
|
|
259
|
+
well as the final output.
|
|
260
|
+
|
|
261
|
+
That is, it behaves similarly to the following Python code:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
carry = init
|
|
265
|
+
intermediates = []
|
|
266
|
+
|
|
267
|
+
for block in self.stacked:
|
|
268
|
+
carry, extra = block(carry)
|
|
269
|
+
intermediates.append(extra)
|
|
270
|
+
|
|
271
|
+
return carry, hax.stack(Block, intermediates)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
init:
|
|
276
|
+
*extra_args:
|
|
277
|
+
**extra_kwargs:
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
|
|
281
|
+
"""
|
|
282
|
+
if self.gradient_checkpointing:
|
|
283
|
+
do_block = filter_checkpoint(self._do_block, prevent_cse=self.prevent_cse)
|
|
284
|
+
else:
|
|
285
|
+
do_block = self._do_block
|
|
286
|
+
return haliax.scan(do_block, self.Block)(init, self.stacked, *extra_args, **extra_kwargs)
|
|
287
|
+
|
|
288
|
+
def fold(self, init, *args, **kwargs):
|
|
289
|
+
"""
|
|
290
|
+
Fold over the stacked module. This is the same as a for loop that applies each instance of the module in sequence
|
|
291
|
+
to the input, passing the output of one instance to the next instance.
|
|
292
|
+
That is, it behaves similarly to the following Python code:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
carry = init
|
|
296
|
+
for block in self.stacked:
|
|
297
|
+
carry = block(carry)
|
|
298
|
+
|
|
299
|
+
return carry
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
init:
|
|
304
|
+
*args:
|
|
305
|
+
**kwargs:
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
if self.gradient_checkpointing:
|
|
311
|
+
do_block = filter_checkpoint(self._do_block, prevent_cse=self.prevent_cse)
|
|
312
|
+
else:
|
|
313
|
+
do_block = self._do_block
|
|
314
|
+
|
|
315
|
+
return haliax.fold(do_block, self.Block)(init, self.stacked, *args, **kwargs)
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _do_block(carry, block, *extra_args, **extra_kwargs):
|
|
319
|
+
return block(carry, *extra_args, **extra_kwargs)
|
|
320
|
+
|
|
321
|
+
# TODO: this is for logic that's in levanter. We should move that logic to haliax I guess?
|
|
322
|
+
def _state_dict_key_map(self) -> Dict[str, Optional[str]]:
|
|
323
|
+
return {"stacked": None}
|
|
324
|
+
|
|
325
|
+
def unstacked(self) -> Sequence[M]:
|
|
326
|
+
"""
|
|
327
|
+
Returns the unstacked version of this module. This is useful for logging or saving checkpoints.
|
|
328
|
+
Returns:
|
|
329
|
+
A sequence of modules, one for each element of the stack
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
def unbatch_leaf(x):
|
|
333
|
+
if isinstance(x, haliax.core.NamedArray):
|
|
334
|
+
if haliax.selects_axis(x.axes, self.Block):
|
|
335
|
+
return haliax.unbind(x, self.Block)
|
|
336
|
+
else:
|
|
337
|
+
return tuple(x for _ in range(self.Block.size))
|
|
338
|
+
elif haliax.jax_utils.is_jax_array_like(x):
|
|
339
|
+
assert (
|
|
340
|
+
x.shape[0] == self.Block.size
|
|
341
|
+
), f"Expected first dimension to be {self.Block.size}, got {x.shape[0]}"
|
|
342
|
+
return tuple(x[i] for i in range(self.Block.size))
|
|
343
|
+
else:
|
|
344
|
+
return tuple(x for _ in range(self.Block.size))
|
|
345
|
+
|
|
346
|
+
leaves, structure = jax.tree_util.tree_flatten(self.stacked, is_leaf=haliax.is_named_array)
|
|
347
|
+
unstacked_leaves = tuple(map(unbatch_leaf, leaves))
|
|
348
|
+
# now we need to transpose the leaves
|
|
349
|
+
unstacked_leaves = tuple(zip(*unstacked_leaves))
|
|
350
|
+
return tuple(map(lambda x: jax.tree_util.tree_unflatten(structure, x), unstacked_leaves))
|
|
351
|
+
|
|
352
|
+
def to_state_dict(self, prefix: Optional[str] = None) -> StateDict:
|
|
353
|
+
# this method needs to "devectorize" the blocks, so that we have a list of blocks h.0.FOO, h.1.FOO, etc.
|
|
354
|
+
# first just do the normal thing with our own dict, which we'll post-process
|
|
355
|
+
state_dict: StateDict = super().to_state_dict(prefix)
|
|
356
|
+
|
|
357
|
+
return _unstack_state_dict(state_dict, prefix)
|
|
358
|
+
|
|
359
|
+
def from_state_dict(self: M, state_dict: StateDict, prefix: Optional[str] = None) -> M:
|
|
360
|
+
# this method needs to "vectorize" the blocks, so that we have a single block h.FOO
|
|
361
|
+
# first just do the normal thing with our own dict, which we'll post-process
|
|
362
|
+
stacked = _stack_state_dict(state_dict, prefix=prefix)
|
|
363
|
+
out = super().from_state_dict(stacked, prefix=prefix) # type: ignore
|
|
364
|
+
return out
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _stack_state_dict(state_dict: StateDict, prefix: Optional[str] = None) -> StateDict:
|
|
368
|
+
"""
|
|
369
|
+
Stack all keys matching prefix in a new state dict, returning a state dict that has all keys matching
|
|
370
|
+
prefix stacked, but otherwise the same.
|
|
371
|
+
|
|
372
|
+
Stacked in this case means roughly "compatible with a torch.nn.Sequential", which means that the
|
|
373
|
+
keys are of the form "<prefix>.0.<key>", "<prefix>.1.<key>", etc.
|
|
374
|
+
|
|
375
|
+
Mostly for use with [haliax.nn.Stacked][].
|
|
376
|
+
"""
|
|
377
|
+
vectorized_dict: StateDict = {}
|
|
378
|
+
|
|
379
|
+
tensors_to_vectorize: dict[str, list[Optional[Any]]] = {}
|
|
380
|
+
if prefix is not None:
|
|
381
|
+
prefix_for_pat = re.escape(prefix + ".")
|
|
382
|
+
else:
|
|
383
|
+
prefix_for_pat = ""
|
|
384
|
+
pattern = re.compile(rf"{prefix_for_pat}(\d+)\.(.*)")
|
|
385
|
+
|
|
386
|
+
for k, v in state_dict.items():
|
|
387
|
+
match = pattern.match(k)
|
|
388
|
+
if match:
|
|
389
|
+
block_idx = int(match.group(1))
|
|
390
|
+
block_key = match.group(2)
|
|
391
|
+
tensors = tensors_to_vectorize.setdefault(block_key, [])
|
|
392
|
+
if len(tensors) <= block_idx:
|
|
393
|
+
tensors.extend([None] * (block_idx - len(tensors) + 1))
|
|
394
|
+
assert tensors[block_idx] is None, f"Duplicate key {k}"
|
|
395
|
+
tensors[block_idx] = v
|
|
396
|
+
else:
|
|
397
|
+
vectorized_dict[k] = v
|
|
398
|
+
|
|
399
|
+
# now we have to vectorize the tensors
|
|
400
|
+
for k, tensors in tensors_to_vectorize.items():
|
|
401
|
+
vectorized_dict[cast(str, with_prefix(prefix, k))] = jnp.stack(tensors, axis=0)
|
|
402
|
+
|
|
403
|
+
return vectorized_dict
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _unstack_state_dict(state_dict: StateDict, prefix: Optional[str] = None) -> StateDict:
|
|
407
|
+
"""
|
|
408
|
+
Unstack all keys matching prefix in a new state dict, returning a state dict that has all keys matching
|
|
409
|
+
prefix unstacked, but otherwise the same. Mostly for use with [haliax.nn.Stacked][].
|
|
410
|
+
|
|
411
|
+
Unstacked in this case means roughly "compatible with a torch.nn.Sequential", which means that the
|
|
412
|
+
keys are of the form "<prefix>.0.<key>", "<prefix>.1.<key>", etc.
|
|
413
|
+
"""
|
|
414
|
+
new_dict: StateDict = {}
|
|
415
|
+
prefix = with_prefix(prefix, "")
|
|
416
|
+
assert prefix is not None
|
|
417
|
+
|
|
418
|
+
for k, v in state_dict.items():
|
|
419
|
+
if k.startswith(prefix) and is_jax_or_hax_array_like(v):
|
|
420
|
+
for i, v_i in enumerate(v):
|
|
421
|
+
new_dict[f"{prefix}{i}.{k[len(prefix):]}"] = v_i
|
|
422
|
+
else:
|
|
423
|
+
new_dict[k] = v
|
|
424
|
+
|
|
425
|
+
return new_dict
|
|
@@ -14,9 +14,10 @@ import jax.random as jrandom
|
|
|
14
14
|
from aqt.jax.v2.aqt_dot_general import DotGeneral
|
|
15
15
|
from jax import numpy as jnp
|
|
16
16
|
from jax.tree_util import DictKey, FlattenedIndexKey, GetAttrKey, SequenceKey
|
|
17
|
-
from
|
|
17
|
+
from jaxtyping import DTypeLike, PyTree
|
|
18
18
|
|
|
19
19
|
import haliax.nn as hnn
|
|
20
|
+
from haliax.state_dict import StateDict
|
|
20
21
|
from haliax.types import PrecisionLike
|
|
21
22
|
|
|
22
23
|
from ._src.fp8 import dot_general_with_precision, in_qdq, out_qdq
|
|
@@ -206,6 +207,10 @@ class Int8DotGeneralOp(OverwriteWithGradient):
|
|
|
206
207
|
cfg = aqt_config.set_context(self.cfg, jrandom.PRNGKey(42), train_step=None)
|
|
207
208
|
return cfg(lhs, rhs, dimension_numbers, precision, preferred_element_type)
|
|
208
209
|
|
|
210
|
+
def to_state_dict(tree: PyTree, prefix: Optional[str] = None) -> StateDict:
|
|
211
|
+
warnings.warn("Ignore all int8 states (if any) for now.")
|
|
212
|
+
return {}
|
|
213
|
+
|
|
209
214
|
|
|
210
215
|
@dataclass(frozen=True)
|
|
211
216
|
class QuantizationConfig:
|
|
@@ -24,6 +24,7 @@ def uniform(
|
|
|
24
24
|
minval = broadcast_to(minval, shape).array
|
|
25
25
|
maxval = broadcast_to(maxval, shape).array
|
|
26
26
|
jax_shape = _to_jax_shape(shape)
|
|
27
|
+
print(jax_shape, minval, maxval)
|
|
27
28
|
jax_array = jrandom.uniform(key=key, shape=jax_shape, dtype=dtype, minval=minval, maxval=maxval)
|
|
28
29
|
return haliax.auto_sharded(NamedArray(jax_array, shape))
|
|
29
30
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import equinox as eqx
|
|
2
2
|
import jax
|
|
3
3
|
import pytest
|
|
4
|
-
from equinox import filter_value_and_grad
|
|
5
4
|
|
|
6
5
|
import haliax as hax
|
|
7
|
-
from haliax.
|
|
8
|
-
from haliax.nn.scan import BlockSeq, Stacked, StackedCheckpointPolicy
|
|
6
|
+
from haliax.nn.scan import BlockSeq, Stacked
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
def test_unstacked():
|
|
@@ -132,6 +130,8 @@ def test_scan_with_aux_named_args():
|
|
|
132
130
|
z_seq, z_seq_scan = m_seq.scan(x, initial_y, key=jax.random.split(jax.random.PRNGKey(2), Block.size))
|
|
133
131
|
assert hax.all(hax.isclose(z, z_seq, atol=1e-5))
|
|
134
132
|
|
|
133
|
+
z_seq_scan = hax.stack(Block, z_seq_scan)
|
|
134
|
+
|
|
135
135
|
assert hax.all(hax.isclose(z_scan, z_seq_scan, atol=1e-5))
|
|
136
136
|
|
|
137
137
|
|
|
@@ -164,56 +164,3 @@ def test_stacked_to_state_dict():
|
|
|
164
164
|
y2 = m2.fold(input, key=key)
|
|
165
165
|
|
|
166
166
|
assert hax.all(hax.equal(y, y2))
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def test_checkpoint_carries():
|
|
170
|
-
class Module(eqx.Module):
|
|
171
|
-
named: hax.NamedArray
|
|
172
|
-
|
|
173
|
-
def __call__(self, x):
|
|
174
|
-
y = tree_checkpoint_name(hax.sin(x + self.named), "sin")
|
|
175
|
-
y = tree_checkpoint_name(hax.cos(y + x), "cos")
|
|
176
|
-
return y + x
|
|
177
|
-
|
|
178
|
-
@staticmethod
|
|
179
|
-
def init(named):
|
|
180
|
-
return Module(named=named)
|
|
181
|
-
|
|
182
|
-
Block = hax.Axis("block", 4)
|
|
183
|
-
E = hax.Axis("E", 10)
|
|
184
|
-
|
|
185
|
-
initial_named = hax.random.uniform(jax.random.PRNGKey(0), (Block, E))
|
|
186
|
-
|
|
187
|
-
carry_policy = StackedCheckpointPolicy(save_carries=True, save_outputs=False, save_block_internals=False)
|
|
188
|
-
save_nothing = StackedCheckpointPolicy(save_carries=False, save_outputs=False, save_block_internals=False)
|
|
189
|
-
save_everything = StackedCheckpointPolicy(save_carries=True, save_outputs=True, save_block_internals=True)
|
|
190
|
-
save_internals = StackedCheckpointPolicy(save_carries=False, save_outputs=False, save_block_internals=True)
|
|
191
|
-
save_cos = StackedCheckpointPolicy(save_carries=False, save_outputs=False, save_block_internals=["cos"])
|
|
192
|
-
save_sin_carry = StackedCheckpointPolicy(save_carries=True, save_outputs=False, save_block_internals=["sin"])
|
|
193
|
-
|
|
194
|
-
for name, (policy, expected_scan_shapes) in {
|
|
195
|
-
"carry": (carry_policy, [(E.size,), (Block.size, E.size)]),
|
|
196
|
-
"nothing": (save_nothing, [(E.size,)]),
|
|
197
|
-
"everything": (save_everything, [(E.size,), (Block.size, E.size), (Block.size, E.size)]),
|
|
198
|
-
"internals": (save_internals, [(E.size,), (Block.size, E.size), (Block.size, E.size)]),
|
|
199
|
-
"cos": (save_cos, [(E.size,), (Block.size, E.size)]),
|
|
200
|
-
"sin": (save_sin_carry, [(E.size,), (Block.size, E.size), (Block.size, E.size)]),
|
|
201
|
-
}.items():
|
|
202
|
-
m = Stacked.init(
|
|
203
|
-
Block,
|
|
204
|
-
Module,
|
|
205
|
-
gradient_checkpointing=policy,
|
|
206
|
-
)(named=initial_named)
|
|
207
|
-
|
|
208
|
-
def loss_fn(m, x):
|
|
209
|
-
y = m.fold(x)
|
|
210
|
-
return hax.sum(y).scalar()
|
|
211
|
-
|
|
212
|
-
grad_fn = filter_value_and_grad(loss_fn)
|
|
213
|
-
|
|
214
|
-
jaxpr = jax.make_jaxpr(grad_fn)(m, hax.random.uniform(jax.random.PRNGKey(1), (E,)))
|
|
215
|
-
closed_call = next(eqn for eqn in jaxpr.jaxpr.eqns if eqn.primitive == jax.core.closed_call_p)
|
|
216
|
-
out_shapes = [out.aval.shape for out in closed_call.outvars]
|
|
217
|
-
|
|
218
|
-
# saved_residuals doesn't give me sensible results, so I'm doing this by hand
|
|
219
|
-
assert out_shapes == expected_scan_shapes, f"{name}: Expected {expected_scan_shapes}, got {out_shapes}"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.4.dev336"
|