haliax 1.4.dev332__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.dev332 → haliax-1.4.dev333}/PKG-INFO +1 -1
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/api.md +11 -1
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/broadcasting.md +2 -2
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/css/mkdocstrings.css +11 -1
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/fp8.md +47 -21
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/nn.md +5 -3
- haliax-1.4.dev333/docs/stacked.md +203 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/state-dict.md +18 -1
- {haliax-1.4.dev332 → haliax-1.4.dev333}/mkdocs.yml +14 -5
- haliax-1.4.dev333/src/haliax/__about__.py +1 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/state_dict.py +1 -1
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/axis.py +2 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/embedding.py +38 -4
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/linear.py +4 -1
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/normalization.py +3 -0
- haliax-1.4.dev332/src/haliax/__about__.py +0 -1
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.coveragerc +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.flake8 +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.gitignore +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/LICENSE +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/README.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/css/material.css +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/faq.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/hof.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/index.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/indexing.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/matmul.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/partitioning.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/rearrange.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/requirements.txt +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/docs/tutorial.md +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/pyproject.toml +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/__init__.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/core.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/jax_utils.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/nn/scan.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/random.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/types.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/util.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/core_test.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_attention.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_axis.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_conv.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_debug.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_dot.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_hof.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_int8.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_nn.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_ops.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_pool.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_random.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_scan.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev332 → haliax-1.4.dev333}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev332 → 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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.dev333"
|
|
@@ -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:
|
|
@@ -24,26 +24,60 @@ class Embedding(eqx.Module):
|
|
|
24
24
|
|
|
25
25
|
@staticmethod
|
|
26
26
|
def init(Vocab: Axis, Embed: AxisSpec, *, init_scale: float = 1, key, initializer_range: Optional[float] = None):
|
|
27
|
+
"""
|
|
28
|
+
Initialize an Embedding module.
|
|
29
|
+
|
|
30
|
+
An embedding module is a simple lookup table that maps integer indices to vectors or tensors.
|
|
31
|
+
Weights are initialized with a truncated normal distribution with a standard deviation of
|
|
32
|
+
`init_scale / output_size`.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
Vocab: Size of the vocabulary
|
|
36
|
+
Embed: Shape of the embedding vectors. May be a single axis or a full AxisSpec
|
|
37
|
+
init_scale: Scale of the initialization
|
|
38
|
+
key: PRNG key
|
|
39
|
+
initializer_range: Deprecated. Use init_scale instead.
|
|
40
|
+
"""
|
|
27
41
|
if initializer_range is not None:
|
|
28
42
|
warnings.warn("initializer_range is deprecated. Use init_std instead.", DeprecationWarning)
|
|
29
43
|
init_scale = initializer_range
|
|
30
44
|
|
|
31
45
|
all_axes = (Vocab,) + ensure_tuple(Embed)
|
|
32
46
|
output_size = hax.axis_size(Embed)
|
|
33
|
-
weight = hax.random.truncated_normal(key, all_axes, -3, 3) * (init_scale /
|
|
47
|
+
weight = hax.random.truncated_normal(key, all_axes, -3, 3) * (init_scale / output_size)
|
|
34
48
|
return Embedding(weight=weight, Vocab=Vocab, Embed=Embed)
|
|
35
49
|
|
|
36
|
-
def __call__(self, input_ids, *, key: Optional[PRNGKeyArray] = None):
|
|
50
|
+
def __call__(self, input_ids: NamedArray, *, key: Optional[PRNGKeyArray] = None):
|
|
51
|
+
"""Alias for `embed`. key is ignored."""
|
|
37
52
|
return self.embed(input_ids)
|
|
38
53
|
|
|
39
54
|
@named_call
|
|
40
|
-
def embed(self, input_ids):
|
|
55
|
+
def embed(self, input_ids: NamedArray):
|
|
56
|
+
"""
|
|
57
|
+
Args:
|
|
58
|
+
input_ids: token IDs with shape > {Vocab}
|
|
59
|
+
"""
|
|
41
60
|
input_embeds = self.weight.take(self.Vocab, input_ids)
|
|
42
61
|
return input_embeds
|
|
43
62
|
|
|
44
|
-
def unembed(self, input_embeds):
|
|
63
|
+
def unembed(self, input_embeds: NamedArray):
|
|
64
|
+
"""
|
|
65
|
+
Unembed the input embeddings back to the vocabulary space.
|
|
66
|
+
|
|
67
|
+
Equivalent to `input_embeds.dot(self.weight, axis=self.Embed)`.
|
|
68
|
+
"""
|
|
45
69
|
return input_embeds.dot(self.weight, axis=self.Embed)
|
|
46
70
|
|
|
47
71
|
def resize_embeddings(self, new_size: int, key: Optional[PRNGKeyArray] = None):
|
|
72
|
+
"""
|
|
73
|
+
Resize the embedding layer to a new size.
|
|
74
|
+
Args:
|
|
75
|
+
new_size: New size of the vocabulary
|
|
76
|
+
key: PRNG key for initialization of any new weights
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Embedding: Resized embedding layer
|
|
80
|
+
|
|
81
|
+
"""
|
|
48
82
|
new_weights = resize_axis(self.weight, self.Vocab, new_size, key=key)
|
|
49
83
|
return dataclasses.replace(self, Vocab=self.Vocab.resize(new_size), weight=new_weights) # type: ignore
|
|
@@ -72,7 +72,10 @@ class Linear(eqx.Module):
|
|
|
72
72
|
return q
|
|
73
73
|
|
|
74
74
|
@property
|
|
75
|
-
def
|
|
75
|
+
def _out_first(self):
|
|
76
|
+
"""
|
|
77
|
+
Returns: bool: Whether the output axes are first in the weight matrix
|
|
78
|
+
"""
|
|
76
79
|
# We do it this way because of scan layers
|
|
77
80
|
if isinstance(self.Out, hax.Axis):
|
|
78
81
|
return self.weight.axes[-1] != self.Out
|
|
@@ -58,6 +58,9 @@ def logsumexp(a: A, axis: Optional[AxisSelection] = None) -> A:
|
|
|
58
58
|
return wrap_reduction_call(jnn.logsumexp, a, axis=axis, single_axis_only=False, supports_where=False)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
# TODO: support where in softmax, etc
|
|
62
|
+
|
|
63
|
+
|
|
61
64
|
def softmax(a: A, axis: Optional[AxisSelection] = None) -> A:
|
|
62
65
|
return wrap_axiswise_call(jnn.softmax, a, axis=axis, single_axis_only=False)
|
|
63
66
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.4.dev332"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|