haliax 1.4.dev331__tar.gz → 1.4.dev333__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.dev331 → haliax-1.4.dev333}/PKG-INFO +1 -1
  2. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/api.md +11 -1
  3. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/broadcasting.md +2 -2
  4. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/css/mkdocstrings.css +11 -1
  5. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/fp8.md +47 -21
  6. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/nn.md +5 -3
  7. haliax-1.4.dev333/docs/stacked.md +203 -0
  8. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/state-dict.md +18 -1
  9. {haliax-1.4.dev331 → haliax-1.4.dev333}/mkdocs.yml +15 -5
  10. haliax-1.4.dev333/src/haliax/__about__.py +1 -0
  11. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/__init__.py +2 -2
  12. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/state_dict.py +1 -1
  13. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/axis.py +2 -0
  14. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/jax_utils.py +27 -19
  15. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/__init__.py +1 -4
  16. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/embedding.py +38 -4
  17. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/linear.py +4 -1
  18. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/normalization.py +3 -0
  19. haliax-1.4.dev333/src/haliax/nn/scan.py +684 -0
  20. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/random.py +0 -1
  21. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_scan.py +56 -3
  22. haliax-1.4.dev331/src/haliax/__about__.py +0 -1
  23. haliax-1.4.dev331/src/haliax/nn/scan.py +0 -425
  24. {haliax-1.4.dev331 → haliax-1.4.dev333}/.coveragerc +0 -0
  25. {haliax-1.4.dev331 → haliax-1.4.dev333}/.flake8 +0 -0
  26. {haliax-1.4.dev331 → haliax-1.4.dev333}/.github/workflows/publish_dev.yaml +0 -0
  27. {haliax-1.4.dev331 → haliax-1.4.dev333}/.github/workflows/run_pre_commit.yaml +0 -0
  28. {haliax-1.4.dev331 → haliax-1.4.dev333}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
  29. {haliax-1.4.dev331 → haliax-1.4.dev333}/.github/workflows/run_tests.yaml +0 -0
  30. {haliax-1.4.dev331 → haliax-1.4.dev333}/.gitignore +0 -0
  31. {haliax-1.4.dev331 → haliax-1.4.dev333}/.pre-commit-config.yaml +0 -0
  32. {haliax-1.4.dev331 → haliax-1.4.dev333}/.readthedocs.yaml +0 -0
  33. {haliax-1.4.dev331 → haliax-1.4.dev333}/CONTRIBUTING.md +0 -0
  34. {haliax-1.4.dev331 → haliax-1.4.dev333}/LICENSE +0 -0
  35. {haliax-1.4.dev331 → haliax-1.4.dev333}/README.md +0 -0
  36. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/cheatsheet.md +0 -0
  37. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/css/material.css +0 -0
  38. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/faq.md +0 -0
  39. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/data_parallel_mesh.png +0 -0
  40. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/data_parallel_mesh_replicated.png +0 -0
  41. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_1d.png +0 -0
  42. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_1d_zero.png +0 -0
  43. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d.png +0 -0
  44. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
  45. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
  46. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
  47. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
  48. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_zero.png +0 -0
  49. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/hof.md +0 -0
  50. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/index.md +0 -0
  51. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/indexing.md +0 -0
  52. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/matmul.md +0 -0
  53. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/partitioning.md +0 -0
  54. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/rearrange.ipynb +0 -0
  55. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/rearrange.md +0 -0
  56. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/requirements.txt +0 -0
  57. {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/tutorial.md +0 -0
  58. {haliax-1.4.dev331 → haliax-1.4.dev333}/pyproject.toml +0 -0
  59. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/__init__.py +0 -0
  60. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/compile_utils.py +0 -0
  61. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/dot.py +0 -0
  62. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/einsum.py +0 -0
  63. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/fp8.py +0 -0
  64. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/parsing.py +0 -0
  65. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/rearrange.py +0 -0
  66. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/util.py +0 -0
  67. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/core.py +0 -0
  68. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/debug.py +0 -0
  69. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/hof.py +0 -0
  70. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/activations.py +0 -0
  71. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/attention.py +0 -0
  72. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/conv.py +0 -0
  73. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/dropout.py +0 -0
  74. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/loss.py +0 -0
  75. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/mlp.py +0 -0
  76. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/pool.py +0 -0
  77. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/ops.py +0 -0
  78. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/partitioning.py +0 -0
  79. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/quantization.py +0 -0
  80. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/specialized_fns.py +0 -0
  81. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/state_dict.py +0 -0
  82. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/tree_util.py +0 -0
  83. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/types.py +0 -0
  84. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/util.py +0 -0
  85. {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/wrap.py +0 -0
  86. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/core_test.py +0 -0
  87. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_attention.py +0 -0
  88. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_axis.py +0 -0
  89. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_conv.py +0 -0
  90. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_debug.py +0 -0
  91. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_dot.py +0 -0
  92. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_einsum.py +0 -0
  93. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_fp8.py +0 -0
  94. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_hof.py +0 -0
  95. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_int8.py +0 -0
  96. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_nn.py +0 -0
  97. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_ops.py +0 -0
  98. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_parsing.py +0 -0
  99. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_partitioning.py +0 -0
  100. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_pool.py +0 -0
  101. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_random.py +0 -0
  102. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_rearrange.py +0 -0
  103. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_specialized_fns.py +0 -0
  104. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_state_dict.py +0 -0
  105. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_tree_util.py +0 -0
  106. {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haliax
3
- Version: 1.4.dev331
3
+ Version: 1.4.dev333
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/
@@ -55,11 +55,11 @@ Occasionally, an axis size can be inferred in some circumstances but not others.
55
55
 
56
56
  ### Combining Arrays
57
57
 
58
- We don't include `hstack` or `vstack` because we prefer semantic axes.
59
58
 
60
59
  ::: haliax.concatenate
61
60
  ::: haliax.stack
62
61
 
62
+ (We don't include `hstack` or `vstack` because they are subsumed by `stack`.)
63
63
 
64
64
  ## Array Manipulation
65
65
 
@@ -289,6 +289,16 @@ See also the section on [Partitioning](partitioning.md).
289
289
  ::: haliax.partitioning.physical_axis_size
290
290
  ::: haliax.partitioning.sharding_for_axis
291
291
 
292
+
293
+ ## Gradient Checkpointing
294
+
295
+ Haliax mainly just defers to JAX and [equinox.filter_checkpoint][] for gradient checkpointing. However,
296
+ we provide a few utilities to make it easier to use.
297
+
298
+ See also [haliax.nn.StackedCheckpointPolicy][].
299
+
300
+ ::: haliax.tree_checkpoint_name
301
+
292
302
  ### Old API
293
303
 
294
304
  These functions are being deprecated and will be removed in a future release.
@@ -62,8 +62,8 @@ This method takes one or more axes and adds them to the array.
62
62
 
63
63
  ## Explicit Broadcasting Functions
64
64
 
65
- ::: haliax.broadcast_axis
66
- ::: haliax.broadcast_to
65
+ * [haliax.broadcast_axis][]
66
+ * [haliax.broadcast_to][]
67
67
 
68
68
 
69
69
  <!--
@@ -1,9 +1,19 @@
1
1
  /* Indentation. */
2
- div.doc-contents:not(.first) {
2
+ div.doc-contents {
3
3
  padding-left: 25px;
4
4
  border-left: .05rem solid var(--md-typeset-table-color);
5
5
  }
6
6
 
7
+
8
+ div.doc-class:not(.doc-contents .doc-contents)::after {
9
+ content: "";
10
+ display: block;
11
+ width: 100%;
12
+ height: 1px; /* Adjust thickness */
13
+ background-color: black; /* Adjust color */
14
+ margin: 10px 0; /* Adjust spacing */
15
+ }
16
+
7
17
  /* Mark external links as such. */
8
18
  a.external::after,
9
19
  a.autorefs-external::after {
@@ -1,20 +1,11 @@
1
- # FP8 Training
1
+ # Quantized Training
2
2
 
3
3
  !!! warning
4
4
 
5
- FP8 training in Haliax is currently experimental and may change in the future.
5
+ FP8 and Int8 training in Haliax is currently experimental and may change in the future.
6
6
 
7
- FP8 refers to 8-bit floating point numbers. FP8 is a massively reduced precision compared to the 32-bit floating point numbers
8
- or 16-bit floating point numbers that are typically used in deep learning: there are only 256 possible values in FP8, compared to
9
- the (almost) 2^32 in 32-bit and 2^16 in 16-bit. However, FP8 is still useful for training deep learning models, especially on
10
- hardware that is optimized for FP8. In particular, it can massively accelerate training on hardware that is optimized for FP8:
11
- H100 has 2x FP8 FLOPS compared to FP16 FLOPS and almost 60x(!) compared to F32 FLOPS.
12
-
13
- The FP8 in Haliax is currently designed to optimize throughput on FP8-enabled devices (currently H100) rather
14
- than to save memory. In particular, Haliax's FP8 support is not designed to quantize a model to FP8 for deployment,
15
- though this shouldn't be that hard to add for models that were trained using this functionality.
16
- We would be happy to accept contributions to add this functionality,
17
- and are happy to work with you to do so. In particular, adding this for models trained using Haliax's FP8 should be easy.
7
+ Haliax supports training with FP8 and int8. This is useful for training on hardware that is optimized for FP8 or Int8,
8
+ such as the H100 (fp8) or A100s (int8) and TPU v5 and newer (int8).
18
9
 
19
10
  ## TL;DR
20
11
 
@@ -38,15 +29,42 @@ module = haxq.apply_updates(module, updates, overwrite)
38
29
 
39
30
  And train your model like normal.
40
31
 
41
- ## How to use FP8
32
+ Similarly, you can use `Int8` by setting `Int8=True` in the `QuantizationConfig` object.
42
33
 
43
- To use FP8, you need to do two things:
44
34
 
45
- * Enable FP8 for the layers you want to use FP8
46
- * Modify your training step to be compatible with FP8
47
35
 
48
- Each of these is just a couple of lines of code.
36
+ ## What is FP8?
37
+
38
+ FP8 refers to 8-bit floating point numbers. FP8 is a massively reduced precision compared to the 32-bit floating point numbers
39
+ or 16-bit floating point numbers that are typically used in deep learning: there are only 256 possible values in FP8, compared to
40
+ the (almost) 2^32 in 32-bit and 2^16 in 16-bit. However, FP8 is still useful for training deep learning models, especially on
41
+ hardware that is optimized for FP8. In particular, it can massively accelerate training on hardware that is optimized for FP8:
42
+ H100 has 2x FP8 FLOPS compared to FP16 FLOPS and almost 60x(!) compared to F32 FLOPS.
43
+
44
+ The FP8 in Haliax is currently designed to optimize throughput on FP8-enabled devices (currently H100) rather
45
+ than to save memory. In particular, Haliax's FP8 support is not designed to quantize a model to FP8 for deployment,
46
+ though this shouldn't be that hard to add for models that were trained using this functionality.
47
+ We would be happy to accept contributions to add this functionality,
48
+ and are happy to work with you to do so. In particular, adding this for models trained using Haliax's FP8 should be easy.
49
+
50
+ See this [FP8 Primer](https://docs.nvidia.com/deeplearning/transformer-engine/user-guide/examples/fp8_primer.html) for more information on FP8.
51
+
52
+ ## What is Int8?
49
53
 
54
+ Int8 refers to 8-bit integers. Int8 has the same number of bits as FP8, but the interpretation is different: instead of
55
+ exponentially spaced numbers, Int8 has linearly spaced numbers.
56
+
57
+ In Haliax, we support Int8 training through Google's [AQT](https://github.com/google/aqt) library. AQT (for
58
+ "Accurate Quantization Training") is a library that allows you to train models with quantization-aware training (QAT).
59
+
60
+ ## How to use FP8 or Int8 in Haliax
61
+
62
+ To use quantized training in Haliax, you need to do three things:
63
+
64
+ * Enable FP8 (or int8) for the layers you want
65
+ * Modify your training step to be compatible
66
+
67
+ Each of these is just a couple of lines of code.
50
68
 
51
69
  ```python
52
70
  import haliax as hax
@@ -96,8 +114,8 @@ updates, opt_state = opt.update(grads, opt_state, params=module) # or however y
96
114
  module = hax.quantization.apply_updates(module, updates, grads)
97
115
  ```
98
116
 
99
- That's it! Just a few lines of code to enable FP8. The `quantize_linear_layers` function will transform your module to use FP8
100
- for linear layers (or a subset if you want), and the combo of `partition_for_grad_overwrite` and `apply_updates` function will apply the updates to the module
117
+ That's it! Just a few lines of code to enable FP8. The `quantize_linear_layers` function will transform your module to use
118
+ quantization-aware training for linear layers (or a subset if you want), and the combo of `partition_for_grad_overwrite` and `apply_updates` function will apply the updates to the module
101
119
  in a way that is compatible with FP8.
102
120
 
103
121
  ## How FP8 works
@@ -134,6 +152,13 @@ original precision. It remembers the maximum absolute value for each of the inp
134
152
  and scales the gradients back to the original precision. It remembers the maximum absolute value for the incoming
135
153
  gradient and stores it in the gradient.
136
154
 
155
+ ## How Int8 works
156
+
157
+ Int8 is in principle the same, though the details differ. AQT is a much more flexible library than the FP8 implementation,
158
+ because it can be a bit more finicky. We use AQT directly, and we recommend you look at the
159
+ [AQT documentation](https://github.com/google/aqt?tab=readme-ov-file#how-aqt-works-internally) for more
160
+ information on how it works.
161
+
137
162
  # API Reference
138
163
 
139
164
  ## Functions
@@ -152,7 +177,8 @@ gradient and stores it in the gradient.
152
177
 
153
178
  ::: haliax.quantization.DefaultDotGeneralOp
154
179
  ::: haliax.quantization.Fp8DotGeneralOp
180
+ ::: haliax.quantization.Int8DotGeneralOp
155
181
 
156
182
  ## Configuration
157
183
 
158
- ::: haliax.quantization.Fp8Config
184
+ ::: haliax.quantization.QuantizationConfig
@@ -5,6 +5,7 @@
5
5
 
6
6
  Haliax provides a small number of neural network modules that are compatible with Equinox, though
7
7
  they naturally all use [haliax.NamedArray][]. (We welcome PRs for more modules! Nothing too exotic though.)
8
+
8
9
  The most interesting of these modules is [haliax.nn.Stacked][], which allows you to create homogenous "stacks"
9
10
  of the same module (e.g. transformer blocks), which is a common pattern in deep learning.
10
11
 
@@ -21,11 +22,12 @@ of the same module (e.g. transformer blocks), which is a common pattern in deep
21
22
 
22
23
  ### Meta
23
24
 
24
- ::: haliax.nn.scan.BlockFoldable
25
- ::: haliax.nn.BlockSeq
26
- ::: haliax.nn.Stacked
27
25
  ::: haliax.nn.MLP
28
26
 
27
+ ### Stacked
28
+
29
+ See the full documentation of [Stacked](stacked.md).
30
+
29
31
  ### Convolution
30
32
 
31
33
  Unlike other frameworks, Haliax doesn't distinguish between 1D, 2D, and 3D, and general convolutions. Instead, we have
@@ -0,0 +1,203 @@
1
+ # Module Stacks
2
+
3
+ A core pattern for larger models in JAX is the "scan-over-layers" pattern, where you have a sequence of layers
4
+ that get stacked together, and you use [jax.lax.scan][] or [haliax.fold][] or [haliax.scan][] to apply them to a
5
+ sequence of inputs. In Haliax, layers are represented as [equinox.nn.Module][]s, and the [haliax.nn.Stacked][] module
6
+ provides a way to create a sequence of layers that can be applied to a sequence of inputs that implements the
7
+ scan-over-layers pattern.
8
+
9
+ ## Stacked
10
+
11
+ [haliax.nn.Stacked][] lets you apply a layer sequentially to an input, scanning over a "Layers" axis. For instance,
12
+ a Transformer might use a Stacked for its Transformer blocks:
13
+
14
+
15
+ ```python
16
+ class TransformerBlock(eqx.Module):
17
+
18
+ def __init__(self, config: TransformerConfig, layer_index, *, key):
19
+ attn_key, mlp_key = jax.random.split(key)
20
+ self.attention = Attention.init(config, key=attn_key)
21
+ self.mlp = MLP.init(config, key=mlp_key)
22
+ self.ln1 = LayerNorm.init(config.Hidden)
23
+ self.ln2 = LayerNorm.init(config.Hidden)
24
+ self.layer_index = layer_index
25
+
26
+ def __call__(self, x):
27
+ y = self.attention(self.ln1(x))
28
+ x = x + y
29
+ y = self.mlp(self.ln2(x))
30
+ return x + y
31
+
32
+ class Transformer(eqx.Module):
33
+ def __init__(self, config: TransformerConfig):
34
+ self.blocks = Stacked.init(Layers, TransformerBlock)(
35
+ config, # static configuration
36
+ scale=hax.arange(Layers), # dynamic configuration. Each layer gets a scalar scale value [0, 1, 2, ...]
37
+ key=jax.random.split(key, Layers), # dynamic configuration. Each layer gets a different key
38
+ )
39
+
40
+ def __call__(self, x: NamedArray) -> NamedArray:
41
+ # morally the equivalent of:
42
+ # for block in self.blocks:
43
+ # x = block(x)
44
+ # Except that it works better with JAX compile times.
45
+
46
+ return self.blocks.fold(x)
47
+ ```
48
+
49
+ You can think of [haliax.nn.Stacked][] as an analog to PyTorch's
50
+ [torch.nn.Sequential](https://pytorch.org/docs/stable/generated/torch.nn.Sequential.html), except that
51
+ every layer in the sequence must have exactly the same shape and configuration.
52
+
53
+ Internally, a Stacked is a single copy of the module, except that every NamedArray inside that module
54
+ has a Block axis prepended (as though they were stacked with [haliax.stack][]). Similarly, every JAX array
55
+ inside the module has its first axis prepended with an axis of the same size as the Block axis, as though
56
+ they were stacked with [jax.numpy.stack][].
57
+
58
+ When you call the Stacked, it scans over the Block axis, applying the module to each element of the Block.
59
+
60
+ ### Creating a Stacked
61
+
62
+ To create a Stacked, we provide `Stacked.init`, which takes a "Layers" [haliax.Axis][] and another Module as
63
+ well as args and kwargs for that module. The Layer is the axis that the Stacked will scan over, and the `args`
64
+ and `kwargs` are implicitly vmapped over the Layers.
65
+
66
+ For instance, to create a stack of GPT2 blocks, you might do:
67
+
68
+ ```python
69
+ import jax.random
70
+
71
+ blocks = Stacked.init(Layers, Gpt2Block)(
72
+ config, # static configuration
73
+ scale=hax.arange(Layers), # dynamic configuration. Each layer gets a scalar scale value [0, 1, 2, ...]
74
+ key=jax.random.split(key, Layers.size), # dynamic configuration. Each layer gets a different key
75
+ )
76
+ ```
77
+
78
+ Any NamedArray passed to the Stacked init will have its Layers axis (if present) vmapped over. Any
79
+ JAX array will have its first axis vmapped over.
80
+
81
+
82
+ ### Fold Blocks vs Scan Blocks
83
+
84
+ The Stacked module provides two ways to apply the layers: `fold` and `scan`. A fold is the moral equivalent of this for loop:
85
+
86
+ ```python
87
+ for block in self.blocks:
88
+ x = block(x)
89
+ ```
90
+
91
+ while a scan is the moral equivalent of this for loop:
92
+
93
+ ```python
94
+ out = []
95
+ for block in self.blocks:
96
+ x, y = block(x)
97
+ out.append(y)
98
+
99
+ return x, stack(out)
100
+ ```
101
+
102
+ Blocks can be coded to either support fold or scan, but not both.
103
+ A "fold Block" should have the signature `def __call__(self, x: Carry) -> Carry`,
104
+ while a "scan Block" should have the signature `def __call__(self, x: Carry) -> Tuple[Carry, Output]`.
105
+
106
+ (See also [jax.lax.scan][], [haliax.fold][], and [haliax.scan][].)
107
+
108
+ ### Requirements for Stacked Blocks
109
+
110
+ As we said above, the Stacked module requires that all the layers have the same shape and configuration.
111
+
112
+ A further constraint is that the elements of the stack must have the same Python control flow. This is the usual
113
+ constraint imposed on jit-compiled functions in JAX. All control flow must use `jax.lax` primitives like
114
+ [jax.lax.cond][], [jax.lax.while_loop][], and [jax.lax.scan][]. You can't use Python control flow like `if` or `for`
115
+ except for static control flow that is the same for all elements of the stack.
116
+
117
+ ## BlockSeq and BlockFoldable
118
+
119
+ We also provide a way to create a sequence of layers that can be applied to a sequence of inputs that implements the
120
+ same interface as [haliax.nn.Stacked][], but with a different implementation. This is the [haliax.nn.BlockSeq][] module.
121
+ BlockSeq implements those for loops directly, rather than using [haliax.fold][] or [haliax.scan][].
122
+
123
+ [haliax.nn.scan.BlockFoldable][] is an interface that both [haliax.nn.Stacked][] and [haliax.nn.BlockSeq][] implement.
124
+
125
+ ## Gradient Checkpointing
126
+
127
+ The [haliax.nn.Stacked][] module also provides a way to do gradient checkpointing, which can be useful for deep models.
128
+
129
+ Gradient checkpointing, aka rematerialization, is a technique for trading off memory usage for compute time.
130
+ Instead of storing all the intermediate activations of a model, you store only a subset and recompute the rest
131
+ as needed. (XLA automatically recomputes the rest for you as needed.)
132
+
133
+ [JAX's checkpointing mechanism]((https://docs.jax.dev/en/latest/gradient-checkpointing.html) is highly flexible,
134
+ and we provide a relatively simple interface to it for use with `Stacked`.
135
+
136
+ ### Simple Checkpointing
137
+ In the simplest case, you can enable a usually-good-enough checkpointing policy by passing `gradient_checkpointing=True`
138
+ to the `Stacked.init` call:
139
+
140
+ ```python
141
+ blocks = Stacked.init(Layers, TransformerBlock, gradient_checkpointing=True)(
142
+ config,
143
+ scale=hax.arange(Layers),
144
+ key=jax.random.split(key, Layers.size),
145
+ )
146
+ ```
147
+
148
+ This will preserve the intermediate "carries" and the "outputs" of the scans, while rematerializing (i.e. recomputing)
149
+ the rest of the computation as needed during backpropagation.
150
+
151
+ ### Custom Checkpointing Policies
152
+
153
+ If you need more control over the checkpointing policy, you can pass a [haliax.nn.StackedCheckpointPolicy][] object to
154
+ the Stacked init:
155
+
156
+ ```python
157
+ policy = StackedCheckpointPolicy(
158
+ save_carries=True, # default
159
+ save_outputs=True, # default
160
+ save_intermediates=False, # default
161
+ )
162
+ ```
163
+
164
+ ### Saving Block-Internal Values
165
+
166
+ "`intermediates`" refers to the internal computation of the block. If you set `save_intermediates=True`, then
167
+ all internals of every block will be saved. This can be expensive.
168
+
169
+ You can also pass a list of strings to `save_intermediates` to specify which intermediates to save.
170
+
171
+ You could, for instance, save the output of the attention layer using [haliax.tree_checkpoint_name][]:
172
+
173
+ ```python
174
+ class TransformerBlock(eqx.Module):
175
+ def __call__(self, x):
176
+ y = self.attention(self.ln1(x))
177
+ y = haliax.tree_checkpoint_name(y, "attn_out")
178
+ x = x + y
179
+ y = self.mlp(self.ln2(x))
180
+ return x + y
181
+
182
+ policy = StackedCheckpointPolicy(save_carries=True, save_block_internals=["attn_out"])
183
+ ```
184
+
185
+ With this policy, the output of the attention layer will be saved during the forward pass.
186
+
187
+ This will save an extra attention computation in the backward pass, adding $`O(N * Pos * Hidden)`$ memory usage,
188
+ which is double that required by the default policy.
189
+
190
+
191
+ ### Offloading Checkpointed Values
192
+
193
+ Both `save_carries` and `save_outputs` can either be a boolean or the string "offload". If "offload", then the
194
+ checkpointed values will be offloaded to the host during the forward pass, and reloaded during the backward pass.
195
+
196
+
197
+ ## API
198
+
199
+ ::: haliax.nn.Stacked
200
+ ::: haliax.nn.BlockSeq
201
+ ::: haliax.nn.scan.BlockFoldable
202
+
203
+ ::: haliax.nn.StackedCheckpointPolicy
@@ -157,4 +157,21 @@ to join the prefix to the keys of the state dict.
157
157
 
158
158
  ## API Reference
159
159
 
160
- ::: haliax.state_dict
160
+ ::: haliax.state_dict.StateDict
161
+ ::: haliax.state_dict.ModuleWithStateDictSerialization
162
+
163
+ ### Saving and Loading State Dicts
164
+ ::: haliax.state_dict.save_state_dict
165
+ ::: haliax.state_dict.load_state_dict
166
+
167
+ ### Converting betweewn State Dicts and Modules
168
+
169
+ ::: haliax.state_dict.from_state_dict
170
+ ::: haliax.state_dict.to_state_dict
171
+
172
+ ### Torch Compatibility
173
+
174
+ ::: haliax.state_dict.from_torch_compatible_state_dict
175
+ ::: haliax.state_dict.to_torch_compatible_state_dict
176
+ ::: haliax.state_dict.flatten_linear_layers
177
+ ::: haliax.state_dict.unflatten_linear_layers
@@ -43,25 +43,34 @@ plugins:
43
43
  - https://jax.readthedocs.io/en/latest/objects.inv
44
44
  - https://docs.kidger.site/equinox/objects.inv
45
45
  - https://einops.rocks/objects.inv
46
+ selection:
47
+ inherited_members: true
46
48
  options:
47
49
  docstring_options:
48
50
  ignore_init_summary: true
49
51
  docstring_style: sphinx
50
52
  show_source: false
51
- docstring_section_style: list
52
- heading_level: 5
53
+ filters:
54
+ - "!^_"
55
+ heading_level: 4
53
56
  inherited_members: true
57
+ members_order: source
54
58
  merge_init_into_class: true
59
+ parameter_headings: true
60
+ separate_signature: false
55
61
  load_external_modules: true
56
62
  preload_modules: [haliax, haliax.core]
57
- # separate_signature: true
63
+ show_if_no_docstring: true
58
64
  show_root_heading: true
59
65
  show_root_full_path: false
60
- # show_signature_annotations: true
61
- show_symbol_type_heading: false
66
+ show_signature_annotations: true
67
+ docstring_section_style: list
68
+ show_symbol_type_heading: true
62
69
  show_symbol_type_toc: false
63
70
  signature_crossrefs: true
64
71
  line_length: 100
72
+ summary: true
73
+
65
74
  - include-markdown
66
75
  extra_css:
67
76
  - css/material.css
@@ -85,6 +94,7 @@ nav:
85
94
  - Rearrange: 'rearrange.md'
86
95
  - Matrix Multiplication: 'matmul.md'
87
96
  - Neural Networks: 'nn.md'
97
+ - Module Stacks: 'stacked.md'
88
98
  - Partitioning: 'partitioning.md'
89
99
  - Higher Order Functions: 'hof.md'
90
100
  - FP8: 'fp8.md'
@@ -0,0 +1 @@
1
+ __version__ = "1.4.dev333"
@@ -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
  ]
@@ -395,7 +395,7 @@ def flatten_linear_layers(tree: T) -> T:
395
395
  new_In: Axis = flatten_axes(layer.In, "__IN__")
396
396
 
397
397
  if weight.array is not None:
398
- out_first = layer.out_first
398
+ out_first = layer._out_first
399
399
  weight = weight.flatten_axes(layer.Out, new_Out).flatten_axes(layer.In, new_In)
400
400
 
401
401
  if out_first:
@@ -35,6 +35,8 @@ def make_axes(**kwargs: int) -> Tuple[Axis, ...]:
35
35
  Example:
36
36
  ```
37
37
  X, Y = axes(X=10, Y=20)
38
+ ```
39
+
38
40
  """
39
41
  return tuple(Axis(name, size) for name, size in kwargs.items())
40
42
 
@@ -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)
@@ -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: