haliax 1.4.dev330__py3-none-any.whl → 1.4.dev332__py3-none-any.whl
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/__about__.py +1 -1
- haliax/__init__.py +2 -2
- haliax/jax_utils.py +27 -19
- haliax/nn/__init__.py +1 -4
- haliax/nn/scan.py +320 -61
- haliax/random.py +0 -1
- {haliax-1.4.dev330.dist-info → haliax-1.4.dev332.dist-info}/METADATA +3 -3
- {haliax-1.4.dev330.dist-info → haliax-1.4.dev332.dist-info}/RECORD +10 -10
- {haliax-1.4.dev330.dist-info → haliax-1.4.dev332.dist-info}/WHEEL +0 -0
- {haliax-1.4.dev330.dist-info → haliax-1.4.dev332.dist-info}/licenses/LICENSE +0 -0
haliax/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.4.
|
|
1
|
+
__version__ = "1.4.dev332"
|
haliax/__init__.py
CHANGED
|
@@ -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 tree_checkpoint_name
|
|
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,7 +887,6 @@ def true_divide(x1: NamedOrNumeric, x2: NamedOrNumeric, /) -> NamedOrNumeric:
|
|
|
887
887
|
# deprecated name
|
|
888
888
|
concat_axis_specs = concat_axes
|
|
889
889
|
|
|
890
|
-
|
|
891
890
|
__all__ = [
|
|
892
891
|
"debug",
|
|
893
892
|
"random",
|
|
@@ -1071,4 +1070,5 @@ __all__ = [
|
|
|
1071
1070
|
"ravel",
|
|
1072
1071
|
"flatten",
|
|
1073
1072
|
"is_named_array",
|
|
1073
|
+
"tree_checkpoint_name",
|
|
1074
1074
|
]
|
haliax/jax_utils.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import functools as ft
|
|
2
2
|
import typing
|
|
3
|
+
import warnings
|
|
3
4
|
from typing import Any, Callable, Optional, Sequence, Union
|
|
4
5
|
|
|
5
6
|
import equinox as eqx
|
|
@@ -8,8 +9,8 @@ import numpy as np
|
|
|
8
9
|
from jax import Array
|
|
9
10
|
from jax import numpy as jnp
|
|
10
11
|
from jax import random as jrandom
|
|
11
|
-
from jax.
|
|
12
|
-
from jax.
|
|
12
|
+
from jax.ad_checkpoint import checkpoint_name
|
|
13
|
+
from jax.typing import DTypeLike
|
|
13
14
|
from jaxtyping import PRNGKeyArray
|
|
14
15
|
|
|
15
16
|
import haliax
|
|
@@ -27,6 +28,7 @@ except ImportError:
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
F = typing.TypeVar("F", bound=Callable[..., Any])
|
|
31
|
+
T = typing.TypeVar("T")
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class Static(eqx.Module):
|
|
@@ -70,23 +72,9 @@ def filter_eval_shape(*args, **kwargs):
|
|
|
70
72
|
def filter_checkpoint(fun: Callable, *, prevent_cse: bool = True, policy: Optional[Callable[..., bool]] = None):
|
|
71
73
|
"""As `jax.checkpoint`, but allows any Python object as inputs and outputs"""
|
|
72
74
|
|
|
73
|
-
|
|
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)
|
|
75
|
+
warnings.warn("filter_checkpoint is deprecated, use eqx.filter_checkpoint instead", DeprecationWarning)
|
|
79
76
|
|
|
80
|
-
|
|
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
|
|
77
|
+
return eqx.filter_checkpoint(fun, prevent_cse=prevent_cse, policy=policy)
|
|
90
78
|
|
|
91
79
|
|
|
92
80
|
def is_jax_array_like(x):
|
|
@@ -202,7 +190,7 @@ def _jittable_dg_einsum(
|
|
|
202
190
|
contract_path = opt_einsum.contract_path
|
|
203
191
|
else:
|
|
204
192
|
ty = next(iter(non_constant_dim_types))
|
|
205
|
-
contract_path =
|
|
193
|
+
contract_path = jax_einsum._poly_einsum_handlers.get(ty, jax_einsum._default_poly_einsum_handler)
|
|
206
194
|
# using einsum_call=True here is an internal api for opt_einsum... sorry
|
|
207
195
|
operands, contractions = contract_path(*operands, einsum_call=True, use_blas=True, optimize=optimize)
|
|
208
196
|
|
|
@@ -212,3 +200,23 @@ def _jittable_dg_einsum(
|
|
|
212
200
|
if spec is not None:
|
|
213
201
|
einsum = jax.named_call(einsum, name=spec)
|
|
214
202
|
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)
|
haliax/nn/__init__.py
CHANGED
|
@@ -38,10 +38,7 @@ 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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# TODO: support where in softmax, etc
|
|
41
|
+
from .scan import BlockSeq, Stacked, StackedCheckpointPolicy
|
|
45
42
|
|
|
46
43
|
|
|
47
44
|
def one_hot(x: NamedArray | int, class_axis: Axis, *, dtype=None) -> NamedArray:
|
haliax/nn/scan.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
import functools
|
|
2
3
|
import re
|
|
3
|
-
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Any, Dict, Generic, Literal, Optional, Protocol, Sequence, Type, TypeVar, Union, cast
|
|
4
6
|
|
|
5
7
|
import equinox as eqx
|
|
6
8
|
import jax
|
|
@@ -8,7 +10,7 @@ from jax import numpy as jnp
|
|
|
8
10
|
|
|
9
11
|
import haliax
|
|
10
12
|
import haliax.util
|
|
11
|
-
from haliax.jax_utils import
|
|
13
|
+
from haliax.jax_utils import tree_checkpoint_name
|
|
12
14
|
from haliax.util import is_jax_or_hax_array_like
|
|
13
15
|
|
|
14
16
|
from .._src.state_dict import ModuleWithStateDictSerialization, StateDict, with_prefix
|
|
@@ -26,6 +28,179 @@ class ModuleInit(Protocol[M_co]):
|
|
|
26
28
|
...
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
@dataclasses.dataclass
|
|
32
|
+
class StackedCheckpointPolicy:
|
|
33
|
+
"""
|
|
34
|
+
A class that represents a gradient checkpoint policy for blocks in a Stacked module. This is used to control
|
|
35
|
+
gradient checkpointing in [haliax.nn.Stacked][] and [haliax.nn.BlockSeq][].
|
|
36
|
+
|
|
37
|
+
Gradient checkpointing is a technique for reducing memory usage in training large models. It works by saving only a
|
|
38
|
+
subset of the forward pass and recomputing the rest in the backward pass. (By doing parts of the forward pass again)
|
|
39
|
+
JAX suggests that this usually isn't necessary when not using scan-over-layers (i.e. Stacked), so this is mostly
|
|
40
|
+
useful for Stacked modules.
|
|
41
|
+
|
|
42
|
+
A scan block takes a "carry" and some extra arguments, and returns a "carry" and an "output". The "carry" is passed
|
|
43
|
+
to the next block, and the "output" is concatenated into a final result (sort of like an RNN).
|
|
44
|
+
|
|
45
|
+
Schematically it might look like this:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
I I I I
|
|
49
|
+
| | | |
|
|
50
|
+
C -> B -C-> B -C-> B -C-> B --> C
|
|
51
|
+
| | | |
|
|
52
|
+
O O O O
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
where "C" is the carry and "O" is the output. A block will typically do some computation (e.g. a Transformer block)
|
|
56
|
+
as well, which might require saving or recomputing in the backward pass.
|
|
57
|
+
|
|
58
|
+
Imagine we save just the carries, then during the backward pass, we can recompute the outputs using the carries
|
|
59
|
+
and the inputs (and the blocks), and then compute the gradient as usual. This requires O(N) memory and O(N) time,
|
|
60
|
+
where N is the number of blocks. This is the default behavior in Haliax and works well for most models.
|
|
61
|
+
|
|
62
|
+
Alternatively, we could only save the initial and final carry. (This corresponds to
|
|
63
|
+
`StackedCheckpointPolicy(save_carries=False, save_outputs=False)` or `"recompute"`)
|
|
64
|
+
Then, during the backward pass, for each block we
|
|
65
|
+
can compute all blocks up to that point (to get its input carry) and then compute the block itself.
|
|
66
|
+
This requires O(1) memory and O(N^2) time.
|
|
67
|
+
|
|
68
|
+
Intermediate approaches exist (including O(sqrt(N)) memory and O(N) time), but we don't support them yet.
|
|
69
|
+
|
|
70
|
+
Another choice is to "offload" carries and outputs to the host, which can reduce memory usage on the device.
|
|
71
|
+
We support offloading carries and outputs to the host, but not internals.
|
|
72
|
+
|
|
73
|
+
See Also:
|
|
74
|
+
* [JAX docs on gradient checkpointing](https://docs.jax.dev/en/latest/gradient-checkpointing.html)
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
save_carries: bool | Literal["offload"] = True
|
|
78
|
+
"""
|
|
79
|
+
Whether to save all carries in the forward pass. If True, carries are saved in the forward pass and used in the
|
|
80
|
+
backward pass. If "offload", carries are saved in the forward pass and offloaded to the host
|
|
81
|
+
"""
|
|
82
|
+
save_outputs: bool | Literal["offload"] = True
|
|
83
|
+
"""
|
|
84
|
+
Whether to save scan outputs in the forward pass. If True, outputs are saved in the forward pass and
|
|
85
|
+
used in the backward pass. If "offload", outputs are saved in the forward pass and offloaded to the host
|
|
86
|
+
"""
|
|
87
|
+
save_block_internals: bool | list[str] = True
|
|
88
|
+
"""
|
|
89
|
+
Whether to save internal state of blocks. If a list, only the listed names are saved, as
|
|
90
|
+
with [jax.checkpoint_policies.save_only_these_names][].
|
|
91
|
+
|
|
92
|
+
See Also: https://docs.jax.dev/en/latest/gradient-checkpointing.html#custom-policies-for-offload
|
|
93
|
+
"""
|
|
94
|
+
prevent_cse: bool = False
|
|
95
|
+
"""
|
|
96
|
+
Whether to prevent common subexpression elimination in the checkpointed function.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
disable: bool = False
|
|
100
|
+
"""
|
|
101
|
+
Whether to disable gradient checkpointing entirely. This is useful for debugging.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def from_bool_or_str(remat_policy: bool | str):
|
|
106
|
+
"""
|
|
107
|
+
Convert a boolean or string into a BlockCheckpointPolicy. This is useful for converting user input
|
|
108
|
+
into a BlockCheckpointPolicy.
|
|
109
|
+
|
|
110
|
+
Choices:
|
|
111
|
+
* True: save outputs, don't save block internals. This is the classic Haliax behavior.
|
|
112
|
+
* False: save everything.
|
|
113
|
+
* "offload": offload outputs to the host, don't save block internals.
|
|
114
|
+
* "recompute" or "full": don't save outputs or block internals.
|
|
115
|
+
* "save_all": save outputs and block internals. Equivalent to False
|
|
116
|
+
"""
|
|
117
|
+
if remat_policy == "offload":
|
|
118
|
+
return StackedCheckpointPolicy(save_carries="offload", save_outputs="offload", save_block_internals=False)
|
|
119
|
+
elif remat_policy == "recompute" or remat_policy == "full":
|
|
120
|
+
return StackedCheckpointPolicy(save_carries=False, save_outputs=False, save_block_internals=False)
|
|
121
|
+
elif remat_policy == "save_all":
|
|
122
|
+
return StackedCheckpointPolicy(save_carries=True, save_outputs=True, save_block_internals=True)
|
|
123
|
+
elif remat_policy is True:
|
|
124
|
+
return StackedCheckpointPolicy(save_carries=True, save_outputs=True, save_block_internals=False)
|
|
125
|
+
elif remat_policy is False:
|
|
126
|
+
return StackedCheckpointPolicy(save_carries=True, save_outputs=True, save_block_internals=True)
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError(f"Invalid checkpoint policy {remat_policy}")
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _mk(remat_policy: Union[bool, str, "StackedCheckpointPolicy"]) -> "StackedCheckpointPolicy":
|
|
132
|
+
if isinstance(remat_policy, StackedCheckpointPolicy):
|
|
133
|
+
return remat_policy
|
|
134
|
+
else:
|
|
135
|
+
return StackedCheckpointPolicy.from_bool_or_str(remat_policy)
|
|
136
|
+
|
|
137
|
+
def checkpoint(self, carry_name: str, output_name: str, callable):
|
|
138
|
+
if self.disable:
|
|
139
|
+
return callable
|
|
140
|
+
policy = self._to_jax_policy(carry_name, output_name)
|
|
141
|
+
if policy is None:
|
|
142
|
+
return callable
|
|
143
|
+
else:
|
|
144
|
+
return eqx.filter_checkpoint(callable, policy=policy, prevent_cse=self.prevent_cse)
|
|
145
|
+
|
|
146
|
+
def _to_jax_policy(self, carry_name: str, output_name: str):
|
|
147
|
+
our_names_to_save = []
|
|
148
|
+
our_names_to_offload = []
|
|
149
|
+
our_names_to_remat = []
|
|
150
|
+
|
|
151
|
+
if self.save_outputs is True:
|
|
152
|
+
our_names_to_save.append(output_name)
|
|
153
|
+
elif self.save_outputs == "offload":
|
|
154
|
+
our_names_to_offload.append(output_name)
|
|
155
|
+
else:
|
|
156
|
+
assert self.save_outputs is False, f"Invalid save_outputs {self.save_outputs}"
|
|
157
|
+
our_names_to_remat.append(output_name)
|
|
158
|
+
|
|
159
|
+
if self.save_carries is True:
|
|
160
|
+
our_names_to_save.append(carry_name)
|
|
161
|
+
elif self.save_carries == "offload":
|
|
162
|
+
our_names_to_offload.append(carry_name)
|
|
163
|
+
else:
|
|
164
|
+
assert self.save_carries is False, f"Invalid save_carries {self.save_carries}"
|
|
165
|
+
our_names_to_remat.append(carry_name)
|
|
166
|
+
|
|
167
|
+
if isinstance(self.save_block_internals, Sequence):
|
|
168
|
+
our_names_to_save.extend(self.save_block_internals)
|
|
169
|
+
|
|
170
|
+
if len(our_names_to_offload) > 0:
|
|
171
|
+
if self.save_block_internals is True:
|
|
172
|
+
raise ValueError("Can't save all block internals and offload outputs. Use a list of names instead.")
|
|
173
|
+
|
|
174
|
+
return jax.checkpoint_policies.save_and_offload_only_these_names(
|
|
175
|
+
names_which_can_be_saved=our_names_to_save,
|
|
176
|
+
names_which_can_be_offloaded=our_names_to_offload,
|
|
177
|
+
offload_src="device",
|
|
178
|
+
offload_dst="pinned_host",
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
if len(our_names_to_remat) > 0:
|
|
182
|
+
if self.save_block_internals is True:
|
|
183
|
+
p1 = jax.checkpoint_policies.save_anything_except_these_names(*our_names_to_remat)
|
|
184
|
+
if len(our_names_to_save) > 0:
|
|
185
|
+
p2 = jax.checkpoint_policies.save_only_these_names(*our_names_to_save)
|
|
186
|
+
return jax.checkpoint_policies.save_from_both_policies(p1, p2)
|
|
187
|
+
else:
|
|
188
|
+
return p1
|
|
189
|
+
else:
|
|
190
|
+
return jax.checkpoint_policies.save_only_these_names(*our_names_to_save)
|
|
191
|
+
elif len(our_names_to_save) > 0:
|
|
192
|
+
p1 = jax.checkpoint_policies.save_only_these_names(*our_names_to_save)
|
|
193
|
+
if self.save_block_internals is True:
|
|
194
|
+
p2 = jax.checkpoint_policies.save_anything_except_these_names(*our_names_to_remat)
|
|
195
|
+
return jax.checkpoint_policies.save_from_both_policies(p1, p2)
|
|
196
|
+
else:
|
|
197
|
+
return p1
|
|
198
|
+
elif self.save_block_internals is True:
|
|
199
|
+
return jax.checkpoint_policies.save_anything_except_these_names(*our_names_to_remat)
|
|
200
|
+
else:
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
29
204
|
class BlockFoldable(Protocol[M]):
|
|
30
205
|
"""
|
|
31
206
|
A superclass for [haliax.nn.Stacked][] and [haliax.nn.BlockSeq][] that exposes the fold and scan methods, as
|
|
@@ -39,7 +214,12 @@ class BlockFoldable(Protocol[M]):
|
|
|
39
214
|
|
|
40
215
|
@classmethod
|
|
41
216
|
def init(
|
|
42
|
-
cls: Type[S],
|
|
217
|
+
cls: Type[S],
|
|
218
|
+
Block: Axis,
|
|
219
|
+
module: Type[M],
|
|
220
|
+
*,
|
|
221
|
+
gradient_checkpointing: bool | StackedCheckpointPolicy = False,
|
|
222
|
+
prevent_cse: bool = False,
|
|
43
223
|
) -> ModuleInit[S]:
|
|
44
224
|
...
|
|
45
225
|
|
|
@@ -70,23 +250,37 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
70
250
|
|
|
71
251
|
blocks: Sequence[M]
|
|
72
252
|
Block: Axis = eqx.static_field()
|
|
73
|
-
gradient_checkpointing:
|
|
253
|
+
gradient_checkpointing: StackedCheckpointPolicy = eqx.static_field()
|
|
74
254
|
|
|
75
255
|
@classmethod
|
|
76
256
|
def init(
|
|
77
|
-
cls: Type[S],
|
|
257
|
+
cls: Type[S],
|
|
258
|
+
Block: Axis,
|
|
259
|
+
module: Type[M],
|
|
260
|
+
*,
|
|
261
|
+
gradient_checkpointing: bool | StackedCheckpointPolicy = False,
|
|
262
|
+
prevent_cse: bool | None = None,
|
|
78
263
|
) -> ModuleInit[S]:
|
|
79
264
|
"""
|
|
80
265
|
This is a curried init method that takes the Block and module and returns a function that takes
|
|
81
266
|
the arguments to the module's init method. Any NamedArrays in the arguments will be sliced along the
|
|
82
267
|
Block axis (if it exists). JAX arrays will be sliced along the first axis.
|
|
83
268
|
"""
|
|
84
|
-
|
|
269
|
+
|
|
270
|
+
gradient_checkpointing = StackedCheckpointPolicy._mk(gradient_checkpointing)
|
|
271
|
+
|
|
272
|
+
if prevent_cse is not None:
|
|
273
|
+
warnings.warn(
|
|
274
|
+
"The prevent_cse argument is deprecated and will be removed in a future version of Haliax. Use the"
|
|
275
|
+
" StackedCheckpointPolicy instead.",
|
|
276
|
+
DeprecationWarning,
|
|
277
|
+
)
|
|
278
|
+
gradient_checkpointing = dataclasses.replace(gradient_checkpointing, prevent_cse=prevent_cse)
|
|
85
279
|
|
|
86
280
|
@functools.wraps(module)
|
|
87
281
|
def fn(*args, **kwargs):
|
|
88
282
|
# 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
|
|
283
|
+
# in which case we need to loop over them to slice them out.
|
|
90
284
|
|
|
91
285
|
def init_block(i):
|
|
92
286
|
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
@@ -101,38 +295,50 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
101
295
|
return fn
|
|
102
296
|
|
|
103
297
|
def scan(self, init: T, *extra_args, **extra_kwargs):
|
|
104
|
-
|
|
105
|
-
|
|
298
|
+
def do_scan(init, *extra_args, **extra_kwargs):
|
|
299
|
+
out = []
|
|
300
|
+
carry = init
|
|
106
301
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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}"
|
|
302
|
+
for i, block in enumerate(self.blocks):
|
|
303
|
+
|
|
304
|
+
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
305
|
+
functools.partial(BlockSeq._slice_out, self.Block, i), (extra_args, extra_kwargs)
|
|
117
306
|
)
|
|
118
307
|
|
|
119
|
-
|
|
308
|
+
block_result = block(carry, *block_args, **block_kwargs)
|
|
120
309
|
|
|
121
|
-
|
|
310
|
+
if not isinstance(block_result, (tuple, list)) or len(block_result) != 2:
|
|
311
|
+
raise ValueError(
|
|
312
|
+
f"BlockSeq.scan expects the block to return a pair of (carry, extra), got {block_result}"
|
|
313
|
+
)
|
|
122
314
|
|
|
123
|
-
|
|
124
|
-
|
|
315
|
+
carry, extra = block_result
|
|
316
|
+
|
|
317
|
+
carry = tree_checkpoint_name(carry, self._carry_ckpt_name)
|
|
318
|
+
extra = tree_checkpoint_name(extra, self._output_ckpt_name)
|
|
319
|
+
|
|
320
|
+
out.append(extra)
|
|
321
|
+
|
|
322
|
+
return carry, haliax.tree_util.tree_map(lambda *x: haliax.stack(self.Block, x), *out)
|
|
323
|
+
|
|
324
|
+
do_scan = self.gradient_checkpointing.checkpoint(self._carry_ckpt_name, self._output_ckpt_name, do_scan)
|
|
325
|
+
|
|
326
|
+
return do_scan(init, *extra_args, **extra_kwargs)
|
|
125
327
|
|
|
126
328
|
def fold(self, init: T, *args, **kwargs) -> T:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
329
|
+
def do_fold(init, *args, **kwargs):
|
|
330
|
+
carry = init
|
|
331
|
+
for i, block in enumerate(self.blocks):
|
|
332
|
+
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
333
|
+
functools.partial(BlockSeq._slice_out, self.Block, i), (args, kwargs)
|
|
334
|
+
)
|
|
335
|
+
carry = block(carry, *block_args, **block_kwargs)
|
|
336
|
+
carry = tree_checkpoint_name(carry, self._carry_ckpt_name)
|
|
337
|
+
return carry
|
|
338
|
+
|
|
339
|
+
do_fold = self.gradient_checkpointing.checkpoint(self._carry_ckpt_name, self._output_ckpt_name, do_fold)
|
|
340
|
+
|
|
341
|
+
return do_fold(init, *args, **kwargs)
|
|
136
342
|
|
|
137
343
|
def unstacked(self) -> Sequence[M]:
|
|
138
344
|
return self.blocks
|
|
@@ -174,6 +380,14 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
174
380
|
|
|
175
381
|
return state_dict
|
|
176
382
|
|
|
383
|
+
@property
|
|
384
|
+
def _output_ckpt_name(self):
|
|
385
|
+
return f"BlockSeq[{self.Block}, {self.blocks[0].__class__.__name__}].outputs"
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def _carry_ckpt_name(self):
|
|
389
|
+
return f"BlockSeq[{self.Block}, {self.blocks[0].__class__.__name__}].carry"
|
|
390
|
+
|
|
177
391
|
|
|
178
392
|
class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
179
393
|
"""
|
|
@@ -192,12 +406,12 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
192
406
|
that the function has the same control flow for every element of the stack.
|
|
193
407
|
|
|
194
408
|
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
|
|
409
|
+
output, while "scan" is the same as a for loop that accumulates a list of outputs as well as the final output.
|
|
196
410
|
|
|
197
411
|
Stacked also supports gradient checkpointing, which is useful for very large models that don't fit in memory.
|
|
198
412
|
|
|
199
413
|
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
|
|
414
|
+
returns: if the module returns a single output, use "fold"; if the module returns a sequence of outputs and
|
|
201
415
|
an output to be passed to the next layer, use "scan". More concretely, for a transformer, you would use "scan" if
|
|
202
416
|
you wanted to return a kv cache (or the attention matrix) as well as the output of the transformer. If you just
|
|
203
417
|
wanted the output of the transformer, you would use "fold".
|
|
@@ -227,48 +441,65 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
227
441
|
|
|
228
442
|
stacked: M
|
|
229
443
|
Block: Axis = eqx.static_field()
|
|
230
|
-
|
|
231
|
-
gradient_checkpointing: bool = eqx.static_field()
|
|
232
|
-
prevent_cse: bool = eqx.static_field()
|
|
444
|
+
gradient_checkpointing: StackedCheckpointPolicy = eqx.static_field()
|
|
233
445
|
|
|
234
446
|
@classmethod
|
|
235
447
|
def init(
|
|
236
|
-
cls,
|
|
448
|
+
cls,
|
|
449
|
+
Block: Axis,
|
|
450
|
+
module: Type[M],
|
|
451
|
+
*,
|
|
452
|
+
gradient_checkpointing: bool | StackedCheckpointPolicy | str = False,
|
|
453
|
+
prevent_cse: bool | None = None,
|
|
237
454
|
) -> ModuleInit["Stacked[M]"]:
|
|
238
455
|
"""
|
|
239
456
|
Initialize a Stacked module. This method is curried: you can pass in the Block and module, and it will return
|
|
240
457
|
a function that takes (batched) arguments to the vmapped module's init method.
|
|
241
|
-
|
|
242
|
-
:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
Block: The axis that will be stacked over. This is typically a "layer" axis, but could be any axis.
|
|
461
|
+
module: The module that will be stacked. This module must take a batched input and return a batched output.
|
|
462
|
+
gradient_checkpointing: Whether to use gradient checkpointing. If True, uses the default policy. If a string,
|
|
463
|
+
uses the policy specified by the string. If a StackedCheckpointPolicy, uses that policy.
|
|
464
|
+
prevent_cse: Whether to prevent common subexpression elimination in the checkpointed function. This is useful
|
|
465
|
+
for debugging, but may slow down the function.
|
|
246
466
|
"""
|
|
247
467
|
|
|
468
|
+
gradient_checkpointing = StackedCheckpointPolicy._mk(gradient_checkpointing)
|
|
469
|
+
|
|
470
|
+
if prevent_cse is not None:
|
|
471
|
+
warnings.warn(
|
|
472
|
+
"The prevent_cse argument is deprecated and will be removed in a future version of Haliax. Use the"
|
|
473
|
+
" StackedCheckpointPolicy instead.",
|
|
474
|
+
DeprecationWarning,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
gradient_checkpointing = dataclasses.replace(gradient_checkpointing, prevent_cse=prevent_cse)
|
|
478
|
+
|
|
248
479
|
@functools.wraps(module)
|
|
249
480
|
def fn(*args, **kwargs):
|
|
250
481
|
stacked = haliax.vmap(module.init, Block)(*args, **kwargs)
|
|
251
|
-
return Stacked(stacked, Block, gradient_checkpointing
|
|
482
|
+
return Stacked(stacked, Block, gradient_checkpointing)
|
|
252
483
|
|
|
253
484
|
return fn
|
|
254
485
|
|
|
255
486
|
def scan(self, init, *extra_args, **extra_kwargs):
|
|
256
487
|
"""
|
|
257
488
|
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
|
|
489
|
+
to the input, passing the output of one instance to the next instance. It returns a stack of outputs as
|
|
259
490
|
well as the final output.
|
|
260
491
|
|
|
261
492
|
That is, it behaves similarly to the following Python code:
|
|
262
493
|
|
|
263
494
|
```python
|
|
264
495
|
carry = init
|
|
265
|
-
|
|
496
|
+
outputs = []
|
|
266
497
|
|
|
267
498
|
for block in self.stacked:
|
|
268
499
|
carry, extra = block(carry)
|
|
269
|
-
|
|
500
|
+
outputs.append(extra)
|
|
270
501
|
|
|
271
|
-
return carry, hax.stack(Block,
|
|
502
|
+
return carry, hax.stack(Block, outputs)
|
|
272
503
|
```
|
|
273
504
|
|
|
274
505
|
Args:
|
|
@@ -279,11 +510,24 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
279
510
|
Returns:
|
|
280
511
|
|
|
281
512
|
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
513
|
+
carry_name = self._carry_ckpt_name
|
|
514
|
+
output_name = self._output_ckpt_name
|
|
515
|
+
|
|
516
|
+
def do_block(carry, block, *args, **kwargs):
|
|
517
|
+
carry, out = block(carry, *args, **kwargs)
|
|
518
|
+
carry = tree_checkpoint_name(carry, carry_name)
|
|
519
|
+
out = tree_checkpoint_name(out, output_name)
|
|
520
|
+
return carry, out
|
|
521
|
+
|
|
522
|
+
def do_scan(init, *extra_args, **extra_kwargs):
|
|
523
|
+
carry, out = haliax.scan(do_block, self.Block)(init, self.stacked, *extra_args, **extra_kwargs)
|
|
524
|
+
# carry = _tree_checkpoint_name(carry, carry_name)
|
|
525
|
+
# out = _tree_checkpoint_name(out, output_name)
|
|
526
|
+
return carry, out
|
|
527
|
+
|
|
528
|
+
do_scan = self.gradient_checkpointing.checkpoint(carry_name, output_name, do_scan)
|
|
529
|
+
|
|
530
|
+
return do_scan(init, *extra_args, **extra_kwargs)
|
|
287
531
|
|
|
288
532
|
def fold(self, init, *args, **kwargs):
|
|
289
533
|
"""
|
|
@@ -300,19 +544,26 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
300
544
|
```
|
|
301
545
|
|
|
302
546
|
Args:
|
|
303
|
-
init:
|
|
304
|
-
*args:
|
|
305
|
-
**kwargs:
|
|
547
|
+
init: The initial value of carry to pass to the first block
|
|
548
|
+
*args: Extra arguments to pass to the blocks. These are passed directly to the blocks
|
|
549
|
+
**kwargs: Extra keyword arguments to pass to the blocks. These are passed directly to the blocks
|
|
306
550
|
|
|
307
551
|
Returns:
|
|
308
552
|
|
|
309
553
|
"""
|
|
310
|
-
|
|
311
|
-
do_block = filter_checkpoint(self._do_block, prevent_cse=self.prevent_cse)
|
|
312
|
-
else:
|
|
313
|
-
do_block = self._do_block
|
|
554
|
+
carry_name = self._carry_ckpt_name
|
|
314
555
|
|
|
315
|
-
|
|
556
|
+
def do_block(carry, block, *args, **kwargs):
|
|
557
|
+
carry = block(carry, *args, **kwargs)
|
|
558
|
+
carry = tree_checkpoint_name(carry, carry_name)
|
|
559
|
+
return carry
|
|
560
|
+
|
|
561
|
+
def do_fold(init, *extra_args, **extra_kwargs):
|
|
562
|
+
return haliax.fold(do_block, self.Block)(init, self.stacked, *extra_args, **extra_kwargs)
|
|
563
|
+
|
|
564
|
+
do_scan = self.gradient_checkpointing.checkpoint(carry_name, self._output_ckpt_name, do_fold)
|
|
565
|
+
|
|
566
|
+
return do_scan(init, *args, **kwargs)
|
|
316
567
|
|
|
317
568
|
@staticmethod
|
|
318
569
|
def _do_block(carry, block, *extra_args, **extra_kwargs):
|
|
@@ -363,6 +614,14 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
363
614
|
out = super().from_state_dict(stacked, prefix=prefix) # type: ignore
|
|
364
615
|
return out
|
|
365
616
|
|
|
617
|
+
@property
|
|
618
|
+
def _carry_ckpt_name(self):
|
|
619
|
+
return f"Stacked[{self.Block}, {self.stacked.__class__.__name__}].carry"
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def _output_ckpt_name(self):
|
|
623
|
+
return f"Stacked[{self.Block}, {self.stacked.__class__.__name__}].outputs"
|
|
624
|
+
|
|
366
625
|
|
|
367
626
|
def _stack_state_dict(state_dict: StateDict, prefix: Optional[str] = None) -> StateDict:
|
|
368
627
|
"""
|
haliax/random.py
CHANGED
|
@@ -24,7 +24,6 @@ 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)
|
|
28
27
|
jax_array = jrandom.uniform(key=key, shape=jax_shape, dtype=dtype, minval=minval, maxval=maxval)
|
|
29
28
|
return haliax.auto_sharded(NamedArray(jax_array, shape))
|
|
30
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev332
|
|
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/
|
|
@@ -61,8 +61,8 @@ Despite the focus on legibility, Haliax
|
|
|
61
61
|
is also **fast**, typically about as fast as "pure" JAX code.
|
|
62
62
|
Haliax is also built to be **scalable**: it
|
|
63
63
|
can support [Fully-Sharded Data Parallelism (FSDP)](https://engineering.fb.com/2021/07/15/open-source/fsdp/) and Tensor Parallelism with [just a few lines of code](https://colab.research.google.com/drive/1QX4yH3zRFF3Xiibf1aahETcSQ5nbcUMz). Haliax powers [Levanter](https://github.com/stanford-crfm/levanter),
|
|
64
|
-
our companion library for training large language models and other foundation models, with scale proven up to
|
|
65
|
-
and up to
|
|
64
|
+
our companion library for training large language models and other foundation models, with scale proven up to 70B parameters
|
|
65
|
+
and up to TPU v4-2048.
|
|
66
66
|
|
|
67
67
|
## Example: Attention
|
|
68
68
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
haliax/__about__.py,sha256=
|
|
2
|
-
haliax/__init__.py,sha256=
|
|
1
|
+
haliax/__about__.py,sha256=1Bc7sSYtp49Iv6Rc1ZOV2dLFJ-vdQxZRlu4f9wKlkYg,27
|
|
2
|
+
haliax/__init__.py,sha256=CQHrLfNXSO8hd27zqqzDeJP6Vzls_BKAE65VA48wlAc,29075
|
|
3
3
|
haliax/axis.py,sha256=U8Ugd2d-u55RoQQqRiITKm1mzsZkDh3rThfAgkTWOwo,20757
|
|
4
4
|
haliax/core.py,sha256=d-9nppDP_-IcxF1yHXDzzVNbP7WIadA1ojb3xHP_-SQ,71000
|
|
5
5
|
haliax/debug.py,sha256=0qEGgIsEw3Jkp40oxgBAjD0Ps-cxi3QNP42uMRGlM84,3612
|
|
6
6
|
haliax/hof.py,sha256=jDcii2IwAfhoNYTCYsdeS8vpSKmtzioPKZ6LBAVuWn8,18205
|
|
7
|
-
haliax/jax_utils.py,sha256=
|
|
7
|
+
haliax/jax_utils.py,sha256=Kz7GeLBp7740AA0z7UBe1aFNfIPK25J8WUtJtbHWzsU,7131
|
|
8
8
|
haliax/ops.py,sha256=JZOinmXbG3VmKl4HhgeH6pD3mQ83PElNPRqNIEQp3BU,5741
|
|
9
9
|
haliax/partitioning.py,sha256=YzR7AJd1cy3HO0oGAJc5o0I928UD1m_OjvqMaNGccEA,23111
|
|
10
10
|
haliax/quantization.py,sha256=3CkqXsMp9wXesEKCwvBrlODEmaPPHR11hIwOFb2HKdM,10591
|
|
11
|
-
haliax/random.py,sha256=
|
|
11
|
+
haliax/random.py,sha256=Qasyt7CSqbcUC9-Q715dGsctpZS0qzHKJ-9smUzoP78,13077
|
|
12
12
|
haliax/specialized_fns.py,sha256=reaG54m6BFTDEgInpylEaZ1YRnKlwuwLikEw7Jv3vDQ,1470
|
|
13
13
|
haliax/state_dict.py,sha256=xsuxktpgzYRlc43IizwmEX0sIjnAypfGprrAugLWjWQ,1664
|
|
14
14
|
haliax/tree_util.py,sha256=2S6455r1pYImF9ROHdjXVoybok_fcf18yLUtySaswuw,5582
|
|
@@ -24,7 +24,7 @@ haliax/_src/parsing.py,sha256=9JDE3unCjS8AN_B5kiAy1yV0AemYW0jL8A_T_bdcLHs,11189
|
|
|
24
24
|
haliax/_src/rearrange.py,sha256=SWZAIMSrqQzqwZDxrMk25q6IOaK4PyDUEm-VOSe0N18,19498
|
|
25
25
|
haliax/_src/state_dict.py,sha256=YcOtA_7B99_0VYBI1-WLHb4ZcxIfxQ1f1UyollTXlPI,17276
|
|
26
26
|
haliax/_src/util.py,sha256=pXizGeJfRcx7QA1YVAxvru2EnMGIXAhZdcPGkwatP_M,1566
|
|
27
|
-
haliax/nn/__init__.py,sha256=
|
|
27
|
+
haliax/nn/__init__.py,sha256=WeiO9JGBK-Am0NZ-s2UW7OmR4cFZbQAakuTDcMMnLg4,2875
|
|
28
28
|
haliax/nn/activations.py,sha256=9h1uJ2pUWmb_OsRhqUGUJ6s95WZgxABDx7YWFD2tn5A,1723
|
|
29
29
|
haliax/nn/attention.py,sha256=5P6IvQ5G-hpStKFnZ4mEV379Af6pkCyIA0qWXMM3Ed4,11942
|
|
30
30
|
haliax/nn/conv.py,sha256=ozdvYBnb20CDz_xMKiXdpOW0ZMBZKlwadx21XafFw3E,15131
|
|
@@ -35,8 +35,8 @@ haliax/nn/loss.py,sha256=OsXpiidiKOD6gslXC54hywn9qm3shH9qyhx8baaps0Y,4565
|
|
|
35
35
|
haliax/nn/mlp.py,sha256=KW_7phxbt0C8CLEVahP5Zoq4SQruiGyg7ltRvLrTdPg,3695
|
|
36
36
|
haliax/nn/normalization.py,sha256=4e1rpVvZKW9w5eUsn8j0tBF6lAIs5YljsvOE_HaoOD8,2652
|
|
37
37
|
haliax/nn/pool.py,sha256=DHeswGramsqNvYufl36NbwgWgI-Iht9bnJy170Pwfu0,8530
|
|
38
|
-
haliax/nn/scan.py,sha256=
|
|
39
|
-
haliax-1.4.
|
|
40
|
-
haliax-1.4.
|
|
41
|
-
haliax-1.4.
|
|
42
|
-
haliax-1.4.
|
|
38
|
+
haliax/nn/scan.py,sha256=cm5GHVjdv8nWKKlo9hHzlyoj0Ubq9770EgqsdbUc7Z0,29163
|
|
39
|
+
haliax-1.4.dev332.dist-info/METADATA,sha256=Qcs512OIq18GGpSwTgAhbstqbxdVfhfm5RjuueAYPoA,7663
|
|
40
|
+
haliax-1.4.dev332.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
41
|
+
haliax-1.4.dev332.dist-info/licenses/LICENSE,sha256=bJiay7Nn5SHQ2n_4ZIT3AE0W1RGq4O7pxOApgBsaT64,11349
|
|
42
|
+
haliax-1.4.dev332.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|