haliax 1.4.dev444__tar.gz → 1.4.dev446__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 (134) hide show
  1. {haliax-1.4.dev444 → haliax-1.4.dev446}/PKG-INFO +1 -1
  2. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/__about__.py +1 -1
  3. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/state_dict.py +20 -7
  4. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/jax_utils.py +82 -3
  5. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/scan.py +33 -0
  6. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_scan.py +59 -0
  7. {haliax-1.4.dev444 → haliax-1.4.dev446}/.agents/projects/api_parity.md +0 -0
  8. {haliax-1.4.dev444 → haliax-1.4.dev446}/.coveragerc +0 -0
  9. {haliax-1.4.dev444 → haliax-1.4.dev446}/.flake8 +0 -0
  10. {haliax-1.4.dev444 → haliax-1.4.dev446}/.github/workflows/publish_dev.yaml +0 -0
  11. {haliax-1.4.dev444 → haliax-1.4.dev446}/.github/workflows/run_pre_commit.yaml +0 -0
  12. {haliax-1.4.dev444 → haliax-1.4.dev446}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
  13. {haliax-1.4.dev444 → haliax-1.4.dev446}/.github/workflows/run_tests.yaml +0 -0
  14. {haliax-1.4.dev444 → haliax-1.4.dev446}/.gitignore +0 -0
  15. {haliax-1.4.dev444 → haliax-1.4.dev446}/.playbooks/add-types.md +0 -0
  16. {haliax-1.4.dev444 → haliax-1.4.dev446}/.playbooks/wrap-non-named.md +0 -0
  17. {haliax-1.4.dev444 → haliax-1.4.dev446}/.pre-commit-config.yaml +0 -0
  18. {haliax-1.4.dev444 → haliax-1.4.dev446}/.readthedocs.yaml +0 -0
  19. {haliax-1.4.dev444 → haliax-1.4.dev446}/AGENTS.md +0 -0
  20. {haliax-1.4.dev444 → haliax-1.4.dev446}/AUTHORS.md +0 -0
  21. {haliax-1.4.dev444 → haliax-1.4.dev446}/CONTRIBUTING.md +0 -0
  22. {haliax-1.4.dev444 → haliax-1.4.dev446}/CONTRIBUTORS.md +0 -0
  23. {haliax-1.4.dev444 → haliax-1.4.dev446}/LICENSE +0 -0
  24. {haliax-1.4.dev444 → haliax-1.4.dev446}/README.md +0 -0
  25. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/api.md +0 -0
  26. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/broadcasting.md +0 -0
  27. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/cheatsheet.md +0 -0
  28. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/css/material.css +0 -0
  29. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/css/mkdocstrings.css +0 -0
  30. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/faq.md +0 -0
  31. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/data_parallel_mesh.png +0 -0
  32. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/data_parallel_mesh_replicated.png +0 -0
  33. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/device_mesh_1d.png +0 -0
  34. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/device_mesh_1d_zero.png +0 -0
  35. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/device_mesh_2d.png +0 -0
  36. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
  37. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
  38. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
  39. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
  40. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/figures/device_mesh_2d_zero.png +0 -0
  41. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/fp8.md +0 -0
  42. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/index.md +0 -0
  43. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/indexing.md +0 -0
  44. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/matmul.md +0 -0
  45. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/nn.md +0 -0
  46. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/partitioning.md +0 -0
  47. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/primer.md +0 -0
  48. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/rearrange.ipynb +0 -0
  49. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/rearrange.md +0 -0
  50. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/requirements.txt +0 -0
  51. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/scan.md +0 -0
  52. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/state-dict.md +0 -0
  53. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/tutorial.md +0 -0
  54. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/typing.md +0 -0
  55. {haliax-1.4.dev444 → haliax-1.4.dev446}/docs/vmap.md +0 -0
  56. {haliax-1.4.dev444 → haliax-1.4.dev446}/etc/license_header.txt +0 -0
  57. {haliax-1.4.dev444 → haliax-1.4.dev446}/mkdocs.yml +0 -0
  58. {haliax-1.4.dev444 → haliax-1.4.dev446}/pyproject.toml +0 -0
  59. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/__init__.py +0 -0
  60. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/__init__.py +0 -0
  61. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/compile_utils.py +0 -0
  62. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/dot.py +0 -0
  63. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/einsum.py +0 -0
  64. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/fp8.py +0 -0
  65. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/parsing.py +0 -0
  66. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/rearrange.py +0 -0
  67. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/scan.py +0 -0
  68. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/_src/util.py +0 -0
  69. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/axis.py +0 -0
  70. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/core.py +0 -0
  71. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/debug.py +0 -0
  72. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/fft.py +0 -0
  73. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/field.py +0 -0
  74. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/haxtyping.py +0 -0
  75. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/hof.py +0 -0
  76. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/__init__.py +0 -0
  77. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/activations.py +0 -0
  78. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/attention.py +0 -0
  79. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/conv.py +0 -0
  80. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/dropout.py +0 -0
  81. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/embedding.py +0 -0
  82. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/linear.py +0 -0
  83. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/loss.py +0 -0
  84. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/mlp.py +0 -0
  85. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/mup.py +0 -0
  86. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/normalization.py +0 -0
  87. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/nn/pool.py +0 -0
  88. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/ops.py +0 -0
  89. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/partitioning.py +0 -0
  90. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/poly.py +0 -0
  91. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/quantization.py +0 -0
  92. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/random.py +0 -0
  93. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/specialized_fns.py +0 -0
  94. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/state_dict.py +0 -0
  95. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/tree.py +0 -0
  96. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/tree_util.py +0 -0
  97. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/types.py +0 -0
  98. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/util.py +0 -0
  99. {haliax-1.4.dev444 → haliax-1.4.dev446}/src/haliax/wrap.py +0 -0
  100. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/core_test.py +0 -0
  101. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_attention.py +0 -0
  102. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_axis.py +0 -0
  103. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_bitwise_ops.py +0 -0
  104. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_conv.py +0 -0
  105. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_debug.py +0 -0
  106. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_dot.py +0 -0
  107. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_dtype_typing.py +0 -0
  108. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_einsum.py +0 -0
  109. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_fft.py +0 -0
  110. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_field.py +0 -0
  111. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_fp8.py +0 -0
  112. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_hof.py +0 -0
  113. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_int8.py +0 -0
  114. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_moe_linear.py +0 -0
  115. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_mup_coordinate_check.py +0 -0
  116. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_mup_embedding.py +0 -0
  117. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_mup_linear.py +0 -0
  118. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_namedarray_typing.py +0 -0
  119. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_nan_reductions.py +0 -0
  120. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_nn.py +0 -0
  121. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_ops.py +0 -0
  122. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_parsing.py +0 -0
  123. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_partitioning.py +0 -0
  124. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_poly_ops.py +0 -0
  125. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_pool.py +0 -0
  126. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_random.py +0 -0
  127. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_rearrange.py +0 -0
  128. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_scatter_gather.py +0 -0
  129. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_specialized_fns.py +0 -0
  130. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_state_dict.py +0 -0
  131. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_tree_util.py +0 -0
  132. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_utils.py +0 -0
  133. {haliax-1.4.dev444 → haliax-1.4.dev446}/tests/test_visualize_sharding.py +0 -0
  134. {haliax-1.4.dev444 → haliax-1.4.dev446}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haliax
3
- Version: 1.4.dev444
3
+ Version: 1.4.dev446
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/
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
 
6
- __version__ = "1.4.dev444"
6
+ __version__ = "1.4.dev446"
@@ -13,7 +13,7 @@ import jax
13
13
  import jax.numpy as jnp
14
14
  import numpy as np
15
15
  from jax import ShapeDtypeStruct
16
- from jax.experimental.multihost_utils import sync_global_devices
16
+ from haliax.jax_utils import sync_global_devices
17
17
  from jax.sharding import Mesh, NamedSharding, PartitionSpec
18
18
  from jax.tree_util import DictKey, FlattenedIndexKey, GetAttrKey, SequenceKey
19
19
  from jaxtyping import PyTree
@@ -68,7 +68,11 @@ def flatten_modules_for_export(t: T) -> T:
68
68
  )
69
69
  return module
70
70
 
71
- return scan_aware_tree_map(_flatten_module, t, is_leaf=lambda x: isinstance(x, ModuleWithStateDictSerialization))
71
+ return scan_aware_tree_map(
72
+ _flatten_module,
73
+ t,
74
+ is_leaf=lambda x: isinstance(x, ModuleWithStateDictSerialization),
75
+ )
72
76
 
73
77
 
74
78
  def unflatten_modules_from_export(t: T, template: T) -> T:
@@ -88,7 +92,10 @@ def unflatten_modules_from_export(t: T, template: T) -> T:
88
92
  return module
89
93
 
90
94
  return scan_aware_tree_map(
91
- _unflatten_module, t, template, is_leaf=lambda x: isinstance(x, ModuleWithStateDictSerialization)
95
+ _unflatten_module,
96
+ t,
97
+ template,
98
+ is_leaf=lambda x: isinstance(x, ModuleWithStateDictSerialization),
92
99
  )
93
100
 
94
101
 
@@ -187,9 +194,13 @@ def from_state_dict(tree: T, state_dict: StateDict, prefix: str | None = None) -
187
194
  else:
188
195
  return default_eqx_module_from_state_dict(tree, state_dict, prefix)
189
196
  elif isinstance(tree, list):
190
- return [from_state_dict(item, state_dict, with_prefix(prefix, str(i))) for i, item in enumerate(tree)] # type: ignore
197
+ return [
198
+ from_state_dict(item, state_dict, with_prefix(prefix, str(i))) for i, item in enumerate(tree)
199
+ ] # type: ignore
191
200
  elif isinstance(tree, dict):
192
- return {k: from_state_dict(v, state_dict, prefix=with_prefix(prefix, k)) for k, v in tree.items()} # type: ignore
201
+ return {
202
+ k: from_state_dict(v, state_dict, prefix=with_prefix(prefix, k)) for k, v in tree.items()
203
+ } # type: ignore
193
204
  elif isinstance(tree, NamedArray):
194
205
  if prefix is None:
195
206
  raise ValueError("Cannot extract a leaf value from a torch dict without a prefix")
@@ -383,7 +394,8 @@ def to_numpy_state_dict(model, prefix: str | None = None) -> StateDict:
383
394
  # TODO: ensure that this mesh arranges devices correctly
384
395
  # (jax seems to do this internally itself, so we should be fine?)
385
396
  process_mesh = Mesh(
386
- np.array(jax.devices()).reshape((jax.process_count(), -1)), ("process", "device")
397
+ np.array(jax.devices()).reshape((jax.process_count(), -1)),
398
+ ("process", "device"),
387
399
  )
388
400
 
389
401
  # now we need to find an axis along which we can shard the array.
@@ -391,7 +403,8 @@ def to_numpy_state_dict(model, prefix: str | None = None) -> StateDict:
391
403
 
392
404
  try:
393
405
  axis_to_shard = index_where(
394
- lambda axis_size: axis_size % process_mesh.devices.size == 0, arr.shape
406
+ lambda axis_size: axis_size % process_mesh.devices.size == 0,
407
+ arr.shape,
395
408
  )
396
409
  except ValueError:
397
410
  return np.array(arr)
@@ -6,6 +6,7 @@
6
6
  import functools as ft
7
7
  import typing
8
8
  import warnings
9
+ import zlib
9
10
  from typing import Any, Callable, Sequence
10
11
 
11
12
  import equinox as eqx
@@ -14,6 +15,8 @@ import numpy as np
14
15
  from jax import Array
15
16
  from jax import numpy as jnp
16
17
  from jax import random as jrandom
18
+ from jax.experimental.multihost_utils import host_local_array_to_global_array
19
+ from jax.sharding import PartitionSpec
17
20
  from jax.ad_checkpoint import checkpoint_name
18
21
  from jax.typing import DTypeLike
19
22
  from jaxtyping import PRNGKeyArray
@@ -39,6 +42,71 @@ class Static(eqx.Module):
39
42
  value: Any = eqx.field(static=True)
40
43
 
41
44
 
45
+ # Non-busted version of broadcast_one_to_all from jax.multihost_utils. (The issue is that if you use a non-contiguous
46
+ # mesh, their utility blows up because it makes a contiguous mesh.)
47
+
48
+
49
+ def _psum(xs: Any) -> Any:
50
+ return jax.tree.map(lambda x: jnp.sum(x, dtype=x.dtype, axis=0), xs)
51
+
52
+
53
+ def broadcast_one_to_all(in_tree: Any, is_source: bool | None = None) -> Any:
54
+ """Broadcast data from a source host (host 0 by default) to all other hosts.
55
+
56
+ Args:
57
+ in_tree: pytree of arrays - each array *must* have the same shape across the
58
+ hosts.
59
+ is_source: optional bool denoting whether the caller is the source. Only
60
+ 'source host' will contribute the data for the broadcast. If None, then
61
+ host 0 is used.
62
+
63
+ Returns:
64
+ A pytree matching in_tree where the leaves now all contain the data from the
65
+ first host.
66
+ """
67
+ if jax.process_count() == 1:
68
+ return jax.tree.map(np.asarray, in_tree)
69
+
70
+ if is_source is None:
71
+ is_source = jax.process_index() == 0
72
+
73
+ devices: np.ndarray = np.array(jax.devices()).reshape(jax.process_count(), jax.local_device_count())
74
+ global_mesh = jax.sharding.Mesh(devices, ("processes", "local_devices"))
75
+ pspec = PartitionSpec("processes")
76
+
77
+ def pre_jit(x):
78
+ if is_source:
79
+ inp = x
80
+ else:
81
+ inp = np.zeros_like(x)
82
+ inp = np.expand_dims(inp, axis=0)
83
+ return host_local_array_to_global_array(inp, global_mesh, pspec)
84
+
85
+ def post_jit(x):
86
+ return jax.device_get(x.addressable_data(0))
87
+
88
+ with haliax.partitioning.set_mesh(global_mesh):
89
+ in_tree = jax.tree.map(pre_jit, in_tree)
90
+ out_tree = jax.jit(
91
+ _psum,
92
+ out_shardings=jax.sharding.NamedSharding(global_mesh, PartitionSpec()),
93
+ )(in_tree)
94
+ return jax.tree.map(post_jit, out_tree)
95
+
96
+
97
+ def assert_equal(in_tree, fail_message: str = ""):
98
+ """Verifies that all the hosts have the same tree of values."""
99
+ expected = broadcast_one_to_all(in_tree)
100
+ if not jax.tree_util.tree_all(jax.tree_util.tree_map(lambda *x: np.all(np.equal(*x)), in_tree, expected)):
101
+ raise AssertionError(f"{fail_message} Expected: {expected}; got: {in_tree}.")
102
+
103
+
104
+ def sync_global_devices(name: str):
105
+ """Creates a barrier across all hosts/devices."""
106
+ h = np.uint32(zlib.crc32(name.encode()))
107
+ assert_equal(h, f"sync_global_devices name mismatch ('{name}')")
108
+
109
+
42
110
  def shaped_rng_split(key, split_shape: int | Sequence[int] = 2) -> PRNGKeyArray:
43
111
  if isinstance(split_shape, int):
44
112
  num_splits = split_shape
@@ -69,14 +137,25 @@ def maybe_rng_split(key: PRNGKeyArray | None, num: int = 2):
69
137
  def filter_eval_shape(*args, **kwargs):
70
138
  import warnings
71
139
 
72
- warnings.warn("filter_eval_shape is deprecated, use eqx.filter_eval_shape instead", DeprecationWarning)
140
+ warnings.warn(
141
+ "filter_eval_shape is deprecated, use eqx.filter_eval_shape instead",
142
+ DeprecationWarning,
143
+ )
73
144
  return eqx.filter_eval_shape(*args, **kwargs)
74
145
 
75
146
 
76
- def filter_checkpoint(fun: Callable, *, prevent_cse: bool = True, policy: Callable[..., bool] | None = None):
147
+ def filter_checkpoint(
148
+ fun: Callable,
149
+ *,
150
+ prevent_cse: bool = True,
151
+ policy: Callable[..., bool] | None = None,
152
+ ):
77
153
  """As `jax.checkpoint`, but allows any Python object as inputs and outputs"""
78
154
 
79
- warnings.warn("filter_checkpoint is deprecated, use eqx.filter_checkpoint instead", DeprecationWarning)
155
+ warnings.warn(
156
+ "filter_checkpoint is deprecated, use eqx.filter_checkpoint instead",
157
+ DeprecationWarning,
158
+ )
80
159
 
81
160
  return eqx.filter_checkpoint(fun, prevent_cse=prevent_cse, policy=policy)
82
161
 
@@ -150,6 +150,11 @@ class BlockFoldable(Protocol[M]):
150
150
  """
151
151
  ...
152
152
 
153
+ def get_layer(self, index: int) -> M:
154
+ """Return the ``index``th layer of the folded module."""
155
+
156
+ ...
157
+
153
158
 
154
159
  class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
155
160
  """
@@ -377,6 +382,11 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
377
382
 
378
383
  return state_dict
379
384
 
385
+ def get_layer(self, index: int) -> M:
386
+ """Return the ``index``th block in this sequential container."""
387
+
388
+ return self.blocks[index]
389
+
380
390
  @property
381
391
  def _output_ckpt_name(self):
382
392
  return f"BlockSeq[{self.Block}, {self.blocks[0].__class__.__name__}].outputs"
@@ -442,6 +452,12 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
442
452
  Block: Axis = eqx.field(static=True)
443
453
  gradient_checkpointing: ScanCheckpointPolicy = eqx.field(static=True)
444
454
 
455
+ @property
456
+ def Layers(self) -> Axis:
457
+ """Alias for :attr:`Block` used by some downstream code."""
458
+
459
+ return self.Block
460
+
445
461
  @classmethod
446
462
  def init(
447
463
  cls,
@@ -700,6 +716,23 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
700
716
  unstacked_leaves = tuple(zip(*unstacked_leaves))
701
717
  return tuple(map(lambda x: jax.tree_util.tree_unflatten(structure, x), unstacked_leaves))
702
718
 
719
+ def get_layer(self, index: int) -> M:
720
+ """Return the ``index``th layer of this stacked module."""
721
+
722
+ def select_leaf(leaf):
723
+ if isinstance(leaf, haliax.NamedArray):
724
+ if haliax.selects_axis(leaf.axes, self.Block):
725
+ return leaf[self.Block, index]
726
+ else:
727
+ return leaf
728
+ if is_jax_or_hax_array_like(leaf):
729
+ if getattr(leaf, "shape", ()) and leaf.shape[0] == self.Block.size:
730
+ return leaf[index]
731
+ return leaf
732
+ return leaf
733
+
734
+ return haliax.tree_util.tree_map(select_leaf, self.stacked)
735
+
703
736
  def to_state_dict(self, prefix: str | None = None) -> StateDict:
704
737
  # this method needs to "devectorize" the blocks, so that we have a list of blocks h.0.FOO, h.1.FOO, etc.
705
738
  # first just do the normal thing with our own dict, which we'll post-process
@@ -51,6 +51,65 @@ def test_unstacked():
51
51
  assert hax.all(module.array == m.stacked.array[i])
52
52
 
53
53
 
54
+ def test_get_layer_stacked():
55
+ class Module(eqx.Module):
56
+ named: hax.NamedArray
57
+ array: jax.Array
58
+ static: int = eqx.static_field()
59
+
60
+ def __call__(self, x, *, key): # pragma: no cover - unused in this test
61
+ return x + self.array + self.static
62
+
63
+ @staticmethod
64
+ def init(named, array, static):
65
+ return Module(named=named, array=array, static=static)
66
+
67
+ Block = hax.Axis("block", 3)
68
+ E = hax.Axis("E", 4)
69
+
70
+ initial_named = hax.random.uniform(jax.random.PRNGKey(0), (Block, E))
71
+ arrays = jax.numpy.arange(Block.size)
72
+
73
+ stacked = Stacked.init(Block, Module)(named=initial_named, array=arrays, static=2)
74
+
75
+ layer = stacked.get_layer(1)
76
+
77
+ assert isinstance(layer, Module)
78
+ assert layer.static == 2
79
+ assert layer.named.axes == (E,)
80
+ assert hax.all(layer.named == initial_named["block", 1])
81
+ assert hax.all(layer.array == arrays[1])
82
+
83
+
84
+ def test_get_layer_blockseq():
85
+ class Module(eqx.Module):
86
+ named: hax.NamedArray
87
+ array: jax.Array
88
+ static: int = eqx.static_field()
89
+
90
+ def __call__(self, x, *, key): # pragma: no cover - unused in this test
91
+ return x + self.array + self.static
92
+
93
+ @staticmethod
94
+ def init(named, array, static):
95
+ return Module(named=named, array=array, static=static)
96
+
97
+ Block = hax.Axis("block", 3)
98
+ E = hax.Axis("E", 4)
99
+
100
+ initial_named = hax.random.uniform(jax.random.PRNGKey(0), (Block, E))
101
+ arrays = jax.numpy.arange(Block.size)
102
+
103
+ seq = BlockSeq.init(Block, Module)(named=initial_named, array=arrays, static=2)
104
+
105
+ layer = seq.get_layer(2)
106
+
107
+ assert isinstance(layer, Module)
108
+ assert layer.static == 2
109
+ assert hax.all(layer.named == initial_named["block", 2])
110
+ assert hax.all(layer.array == arrays[2])
111
+
112
+
54
113
  def test_vmap():
55
114
  class Module(eqx.Module):
56
115
  weight: hax.NamedArray
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes