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.
Files changed (106) hide show
  1. {haliax-1.4.dev336 → haliax-1.4.dev339}/PKG-INFO +1 -1
  2. {haliax-1.4.dev336 → haliax-1.4.dev339}/mkdocs.yml +0 -1
  3. haliax-1.4.dev339/src/haliax/__about__.py +1 -0
  4. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/__init__.py +2 -2
  5. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/jax_utils.py +19 -27
  6. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/__init__.py +4 -1
  7. haliax-1.4.dev339/src/haliax/nn/scan.py +425 -0
  8. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/quantization.py +6 -1
  9. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/random.py +1 -0
  10. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_scan.py +3 -56
  11. haliax-1.4.dev336/src/haliax/__about__.py +0 -1
  12. haliax-1.4.dev336/src/haliax/nn/scan.py +0 -684
  13. {haliax-1.4.dev336 → haliax-1.4.dev339}/.coveragerc +0 -0
  14. {haliax-1.4.dev336 → haliax-1.4.dev339}/.flake8 +0 -0
  15. {haliax-1.4.dev336 → haliax-1.4.dev339}/.github/workflows/publish_dev.yaml +0 -0
  16. {haliax-1.4.dev336 → haliax-1.4.dev339}/.github/workflows/run_pre_commit.yaml +0 -0
  17. {haliax-1.4.dev336 → haliax-1.4.dev339}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
  18. {haliax-1.4.dev336 → haliax-1.4.dev339}/.github/workflows/run_tests.yaml +0 -0
  19. {haliax-1.4.dev336 → haliax-1.4.dev339}/.gitignore +0 -0
  20. {haliax-1.4.dev336 → haliax-1.4.dev339}/.pre-commit-config.yaml +0 -0
  21. {haliax-1.4.dev336 → haliax-1.4.dev339}/.readthedocs.yaml +0 -0
  22. {haliax-1.4.dev336 → haliax-1.4.dev339}/CONTRIBUTING.md +0 -0
  23. {haliax-1.4.dev336 → haliax-1.4.dev339}/LICENSE +0 -0
  24. {haliax-1.4.dev336 → haliax-1.4.dev339}/README.md +0 -0
  25. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/api.md +0 -0
  26. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/broadcasting.md +0 -0
  27. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/cheatsheet.md +0 -0
  28. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/css/material.css +0 -0
  29. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/css/mkdocstrings.css +0 -0
  30. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/faq.md +0 -0
  31. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/data_parallel_mesh.png +0 -0
  32. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/data_parallel_mesh_replicated.png +0 -0
  33. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_1d.png +0 -0
  34. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_1d_zero.png +0 -0
  35. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d.png +0 -0
  36. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
  37. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
  38. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
  39. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
  40. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/figures/device_mesh_2d_zero.png +0 -0
  41. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/fp8.md +0 -0
  42. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/hof.md +0 -0
  43. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/index.md +0 -0
  44. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/indexing.md +0 -0
  45. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/matmul.md +0 -0
  46. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/nn.md +0 -0
  47. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/partitioning.md +0 -0
  48. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/rearrange.ipynb +0 -0
  49. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/rearrange.md +0 -0
  50. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/requirements.txt +0 -0
  51. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/stacked.md +0 -0
  52. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/state-dict.md +0 -0
  53. {haliax-1.4.dev336 → haliax-1.4.dev339}/docs/tutorial.md +0 -0
  54. {haliax-1.4.dev336 → haliax-1.4.dev339}/pyproject.toml +0 -0
  55. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/__init__.py +0 -0
  56. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/compile_utils.py +0 -0
  57. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/dot.py +0 -0
  58. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/einsum.py +0 -0
  59. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/fp8.py +0 -0
  60. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/parsing.py +0 -0
  61. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/rearrange.py +0 -0
  62. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/state_dict.py +0 -0
  63. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/_src/util.py +0 -0
  64. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/axis.py +0 -0
  65. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/core.py +0 -0
  66. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/debug.py +0 -0
  67. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/hof.py +0 -0
  68. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/activations.py +0 -0
  69. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/attention.py +0 -0
  70. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/conv.py +0 -0
  71. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/dropout.py +0 -0
  72. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/embedding.py +0 -0
  73. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/linear.py +0 -0
  74. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/loss.py +0 -0
  75. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/mlp.py +0 -0
  76. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/normalization.py +0 -0
  77. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/nn/pool.py +0 -0
  78. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/ops.py +0 -0
  79. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/partitioning.py +0 -0
  80. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/specialized_fns.py +0 -0
  81. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/state_dict.py +0 -0
  82. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/tree_util.py +0 -0
  83. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/types.py +0 -0
  84. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/util.py +0 -0
  85. {haliax-1.4.dev336 → haliax-1.4.dev339}/src/haliax/wrap.py +0 -0
  86. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/core_test.py +0 -0
  87. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_attention.py +0 -0
  88. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_axis.py +0 -0
  89. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_conv.py +0 -0
  90. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_debug.py +0 -0
  91. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_dot.py +0 -0
  92. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_einsum.py +0 -0
  93. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_fp8.py +0 -0
  94. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_hof.py +0 -0
  95. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_int8.py +0 -0
  96. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_nn.py +0 -0
  97. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_ops.py +0 -0
  98. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_parsing.py +0 -0
  99. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_partitioning.py +0 -0
  100. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_pool.py +0 -0
  101. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_random.py +0 -0
  102. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_rearrange.py +0 -0
  103. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_specialized_fns.py +0 -0
  104. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_state_dict.py +0 -0
  105. {haliax-1.4.dev336 → haliax-1.4.dev339}/tests/test_tree_util.py +0 -0
  106. {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.dev336
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/
@@ -91,7 +91,6 @@ nav:
91
91
  - Rearrange: 'rearrange.md'
92
92
  - Matrix Multiplication: 'matmul.md'
93
93
  - Neural Networks: 'nn.md'
94
- - Module Stacks: 'stacked.md'
95
94
  - Partitioning: 'partitioning.md'
96
95
  - Higher Order Functions: 'hof.md'
97
96
  - FP8: 'fp8.md'
@@ -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 tree_checkpoint_name
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.ad_checkpoint import checkpoint_name
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
- warnings.warn("filter_checkpoint is deprecated, use eqx.filter_checkpoint instead", DeprecationWarning)
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
- return eqx.filter_checkpoint(fun, prevent_cse=prevent_cse, policy=policy)
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 = jax_einsum._poly_einsum_handlers.get(ty, jax_einsum._default_poly_einsum_handler)
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, StackedCheckpointPolicy
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 jax.typing import DTypeLike
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.jax_utils import tree_checkpoint_name
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"