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 CHANGED
@@ -1 +1 @@
1
- __version__ = "1.4.dev330"
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 filter_checkpoint
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._src.numpy import lax_numpy
12
- from jax._src.typing import DTypeLike
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
- @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)
75
+ warnings.warn("filter_checkpoint is deprecated, use eqx.filter_checkpoint instead", DeprecationWarning)
79
76
 
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
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 = lax_numpy._poly_einsum_handlers.get(ty, lax_numpy._default_poly_einsum_handler)
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
- from typing import Any, Dict, Generic, Optional, Protocol, Sequence, Type, TypeVar, cast
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 filter_checkpoint, is_jax_array_like
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], Block: Axis, module: Type[M], *, gradient_checkpointing: bool = False, prevent_cse: bool = False
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: bool = eqx.static_field()
253
+ gradient_checkpointing: StackedCheckpointPolicy = eqx.static_field()
74
254
 
75
255
  @classmethod
76
256
  def init(
77
- cls: Type[S], Block: Axis, module: Type[M], *, gradient_checkpointing: bool = False, prevent_cse: bool = False
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
- del prevent_cse # not needed, but kept for compat with Stacked
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 them to slice them out.
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
- out = []
105
- carry = init
298
+ def do_scan(init, *extra_args, **extra_kwargs):
299
+ out = []
300
+ carry = init
106
301
 
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}"
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
- carry, extra = block_result
308
+ block_result = block(carry, *block_args, **block_kwargs)
120
309
 
121
- out.append(extra)
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
- # TODO: do we want to stack the outputs?
124
- return carry, out
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
- 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
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 intermediates as well as the final output.
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 intermediates and
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
- # TODO: support fancier gradient checkpointing
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, Block: Axis, module: Type[M], *, gradient_checkpointing: bool = False, prevent_cse: bool = False
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
- :param Block:
242
- :param module:
243
- :param gradient_checkpointing:
244
- :param prevent_cse:
245
- :return:
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, prevent_cse)
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 intermediates as
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
- intermediates = []
496
+ outputs = []
266
497
 
267
498
  for block in self.stacked:
268
499
  carry, extra = block(carry)
269
- intermediates.append(extra)
500
+ outputs.append(extra)
270
501
 
271
- return carry, hax.stack(Block, intermediates)
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
- 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)
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
- 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
554
+ carry_name = self._carry_ckpt_name
314
555
 
315
- return haliax.fold(do_block, self.Block)(init, self.stacked, *args, **kwargs)
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.dev330
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 20B parameters
65
- and up to a TPU v3-256 pod slice.
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=Ti7Y_fjcyYA2er6-VH68C3VRUnF4goWC22riUkEqT58,27
2
- haliax/__init__.py,sha256=kQRQMPmSqkqYwxxi00b6CV3FbZWh2ShN3HMYdJOEPaE,29045
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=fho2FYJUGbIvfg2ghU4Oll1bXEVYOI6KcZLXc9TRqUA,6959
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=DsRwdV_pvXHtYxmVjuzfPTRjaCpqqNssLp7DiZwJp9k,13114
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=45z1rwMUyfsJtacjvYhxHMxZpd5jrkdxAwulHNF6uK0,2890
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=0AENdvQXGV5c4h2jvbuHKMKF2eZVj2U4nZeTtldCZl4,16983
39
- haliax-1.4.dev330.dist-info/METADATA,sha256=A1aFm9W-6mQvL7NKrh17eRfCB0Y8RHXOPIc2mvlhHqc,7674
40
- haliax-1.4.dev330.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
- haliax-1.4.dev330.dist-info/licenses/LICENSE,sha256=bJiay7Nn5SHQ2n_4ZIT3AE0W1RGq4O7pxOApgBsaT64,11349
42
- haliax-1.4.dev330.dist-info/RECORD,,
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,,