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.
- {haliax-1.4.dev331 → haliax-1.4.dev333}/PKG-INFO +1 -1
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/api.md +11 -1
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/broadcasting.md +2 -2
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/css/mkdocstrings.css +11 -1
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/fp8.md +47 -21
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/nn.md +5 -3
- haliax-1.4.dev333/docs/stacked.md +203 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/state-dict.md +18 -1
- {haliax-1.4.dev331 → haliax-1.4.dev333}/mkdocs.yml +15 -5
- haliax-1.4.dev333/src/haliax/__about__.py +1 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/__init__.py +2 -2
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/state_dict.py +1 -1
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/axis.py +2 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/jax_utils.py +27 -19
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/__init__.py +1 -4
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/embedding.py +38 -4
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/linear.py +4 -1
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/normalization.py +3 -0
- haliax-1.4.dev333/src/haliax/nn/scan.py +684 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/random.py +0 -1
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_scan.py +56 -3
- haliax-1.4.dev331/src/haliax/__about__.py +0 -1
- haliax-1.4.dev331/src/haliax/nn/scan.py +0 -425
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.coveragerc +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.flake8 +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.gitignore +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/LICENSE +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/README.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/css/material.css +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/faq.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/hof.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/index.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/indexing.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/matmul.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/partitioning.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/rearrange.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/requirements.txt +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/docs/tutorial.md +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/pyproject.toml +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/core.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/types.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/util.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/core_test.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_attention.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_axis.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_conv.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_debug.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_dot.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_hof.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_int8.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_nn.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_ops.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_pool.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_random.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev331 → haliax-1.4.dev333}/tests/test_tree_util.py +0 -0
- {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.
|
|
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.
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
/* Indentation. */
|
|
2
|
-
div.doc-contents
|
|
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
|
-
#
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
63
|
+
show_if_no_docstring: true
|
|
58
64
|
show_root_heading: true
|
|
59
65
|
show_root_full_path: false
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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.
|
|
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:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import functools as ft
|
|
2
2
|
import typing
|
|
3
|
+
import warnings
|
|
3
4
|
from typing import Any, Callable, Optional, Sequence, Union
|
|
4
5
|
|
|
5
6
|
import equinox as eqx
|
|
@@ -8,8 +9,8 @@ import numpy as np
|
|
|
8
9
|
from jax import Array
|
|
9
10
|
from jax import numpy as jnp
|
|
10
11
|
from jax import random as jrandom
|
|
11
|
-
from jax.
|
|
12
|
-
from jax.
|
|
12
|
+
from jax.ad_checkpoint import checkpoint_name
|
|
13
|
+
from jax.typing import DTypeLike
|
|
13
14
|
from jaxtyping import PRNGKeyArray
|
|
14
15
|
|
|
15
16
|
import haliax
|
|
@@ -27,6 +28,7 @@ except ImportError:
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
F = typing.TypeVar("F", bound=Callable[..., Any])
|
|
31
|
+
T = typing.TypeVar("T")
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class Static(eqx.Module):
|
|
@@ -70,23 +72,9 @@ def filter_eval_shape(*args, **kwargs):
|
|
|
70
72
|
def filter_checkpoint(fun: Callable, *, prevent_cse: bool = True, policy: Optional[Callable[..., bool]] = None):
|
|
71
73
|
"""As `jax.checkpoint`, but allows any Python object as inputs and outputs"""
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
def _fn(_static, _dynamic):
|
|
75
|
-
_args, _kwargs = eqx.combine(_static, _dynamic)
|
|
76
|
-
_out = fun(*_args, **_kwargs)
|
|
77
|
-
_dynamic_out, _static_out = eqx.partition(_out, is_jax_array_like)
|
|
78
|
-
return _dynamic_out, Static(_static_out)
|
|
75
|
+
warnings.warn("filter_checkpoint is deprecated, use eqx.filter_checkpoint instead", DeprecationWarning)
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@ft.wraps(fun)
|
|
83
|
-
def wrapper(*args, **kwargs):
|
|
84
|
-
dynamic, static = eqx.partition((args, kwargs), is_jax_array_like)
|
|
85
|
-
dynamic_out, static_out = checkpointed_fun(static, dynamic)
|
|
86
|
-
|
|
87
|
-
return eqx.combine(dynamic_out, static_out.value)
|
|
88
|
-
|
|
89
|
-
return wrapper
|
|
77
|
+
return eqx.filter_checkpoint(fun, prevent_cse=prevent_cse, policy=policy)
|
|
90
78
|
|
|
91
79
|
|
|
92
80
|
def is_jax_array_like(x):
|
|
@@ -202,7 +190,7 @@ def _jittable_dg_einsum(
|
|
|
202
190
|
contract_path = opt_einsum.contract_path
|
|
203
191
|
else:
|
|
204
192
|
ty = next(iter(non_constant_dim_types))
|
|
205
|
-
contract_path =
|
|
193
|
+
contract_path = jax_einsum._poly_einsum_handlers.get(ty, jax_einsum._default_poly_einsum_handler)
|
|
206
194
|
# using einsum_call=True here is an internal api for opt_einsum... sorry
|
|
207
195
|
operands, contractions = contract_path(*operands, einsum_call=True, use_blas=True, optimize=optimize)
|
|
208
196
|
|
|
@@ -212,3 +200,23 @@ def _jittable_dg_einsum(
|
|
|
212
200
|
if spec is not None:
|
|
213
201
|
einsum = jax.named_call(einsum, name=spec)
|
|
214
202
|
return einsum(operands, contractions, precision, preferred_element_type, _dot_general) # type: ignore[operator]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def tree_checkpoint_name(x: T, name: str) -> T:
|
|
206
|
+
"""
|
|
207
|
+
Checkpoint a tree of arrays with a given name. This is useful for gradient checkpointing.
|
|
208
|
+
This is equivalent to calling [jax.ad_checkpoint.checkpoint_name][]
|
|
209
|
+
except that it works for any PyTree, not just arrays.
|
|
210
|
+
|
|
211
|
+
See Also:
|
|
212
|
+
* [jax.ad_checkpoint.checkpoint_name][]
|
|
213
|
+
* [haliax.nn.StackedCheckpointPolicy][]
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
def _checkpoint_leaf(x):
|
|
217
|
+
if is_jax_array_like(x):
|
|
218
|
+
return checkpoint_name(x, name)
|
|
219
|
+
else:
|
|
220
|
+
return x
|
|
221
|
+
|
|
222
|
+
return jax.tree.map(_checkpoint_leaf, x)
|
|
@@ -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:
|