haliax 1.4.dev340__tar.gz → 1.4.dev342__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.dev340 → haliax-1.4.dev342}/.github/workflows/run_quick_levanter_tests.yaml +5 -3
- {haliax-1.4.dev340 → haliax-1.4.dev342}/PKG-INFO +1 -1
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/nn.md +3 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/state-dict.md +64 -2
- haliax-1.4.dev342/src/haliax/__about__.py +1 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/state_dict.py +66 -80
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/__init__.py +2 -4
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/linear.py +44 -1
- haliax-1.4.dev342/src/haliax/nn/normalization.py +169 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/state_dict.py +9 -9
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/tree_util.py +2 -9
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_state_dict.py +52 -4
- haliax-1.4.dev340/src/haliax/__about__.py +0 -1
- haliax-1.4.dev340/src/haliax/nn/normalization.py +0 -87
- {haliax-1.4.dev340 → haliax-1.4.dev342}/.coveragerc +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/.flake8 +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/.gitignore +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/LICENSE +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/README.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/api.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/css/material.css +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/faq.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/fp8.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/hof.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/index.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/indexing.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/matmul.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/partitioning.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/rearrange.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/requirements.txt +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/stacked.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/docs/tutorial.md +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/mkdocs.yml +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/pyproject.toml +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/__init__.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/core.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/jax_utils.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/embedding.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/nn/scan.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/random.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/types.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/util.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/core_test.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_attention.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_axis.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_conv.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_debug.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_dot.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_hof.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_int8.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_nn.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_ops.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_pool.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_random.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_scan.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev340 → haliax-1.4.dev342}/tests/test_utils.py +0 -0
|
@@ -17,19 +17,21 @@ jobs:
|
|
|
17
17
|
run: |
|
|
18
18
|
python -m pip install --upgrade pip
|
|
19
19
|
pip install flake8 pytest
|
|
20
|
-
pip install
|
|
20
|
+
pip install "jax[cpu]==0.5.3" "jaxlib[cpu]==0.5.3" .[dev]
|
|
21
21
|
|
|
22
22
|
- name: Install Levanter from source
|
|
23
23
|
run: |
|
|
24
24
|
cd ..
|
|
25
25
|
git clone https://github.com/stanford-crfm/levanter.git
|
|
26
26
|
cd levanter
|
|
27
|
-
pip install -e .
|
|
27
|
+
pip install -e .[tests]
|
|
28
|
+
pip install -r tests/requirements.txt
|
|
29
|
+
# i don't know why this is necessary
|
|
30
|
+
pip install tensorboardX
|
|
28
31
|
- name: Install Haliax on top
|
|
29
32
|
run: |
|
|
30
33
|
# install second since levanter will install a built version of haliax
|
|
31
34
|
cd ../haliax
|
|
32
|
-
pip install "jax[cpu]==0.4.35" "jaxlib[cpu]==0.4.35" .[dev]
|
|
33
35
|
- name: Test levanter with pytest
|
|
34
36
|
run: |
|
|
35
37
|
cd ../levanter
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev342
|
|
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/
|
|
@@ -122,6 +122,7 @@ For example, below is the implementation of `to_state_dict()` in [levanter.model
|
|
|
122
122
|
In this class, we want to preserve HF compatibility by saving untied output embeddings. (We chose not to implement
|
|
123
123
|
non-weight-tied embeddings.)
|
|
124
124
|
|
|
125
|
+
|
|
125
126
|
```python
|
|
126
127
|
from typing import Optional
|
|
127
128
|
|
|
@@ -155,8 +156,69 @@ to join the prefix to the keys of the state dict.
|
|
|
155
156
|
|
|
156
157
|
```
|
|
157
158
|
|
|
159
|
+
#### Flattening and Unflattening
|
|
160
|
+
|
|
161
|
+
Haliax differs from many NN frameworks, and PyTorch in particular, in supporting multiple axes as inputs and outputs
|
|
162
|
+
for linear transformations and layer norms. This means that a Linear layer's weight might have shape `(Heads, Dim, Out)`
|
|
163
|
+
rather than just `(In, Out)` (where `In = Heads * Dim`). To facilitate compatibility with PyTorch, we provide
|
|
164
|
+
two functions, `flatten_modules_for_export` and `unflatten_modules_from_export`, that can be used to convert
|
|
165
|
+
modules to and from a format that is compatible with PyTorch. These functions are used internally by
|
|
166
|
+
`to_torch_compatible_state_dict` and `from_torch_compatible_state_dict` and we expose them for advanced users.
|
|
167
|
+
|
|
168
|
+
If you are adding a new module, you can plug into this system by inheriting from [haliax.state_dict.ModuleWithStateDictSerialization][]
|
|
169
|
+
and overriding the `flatten_for_export` and `unflatten_from_export` methods. Here is an example from [haliax.nn.LayerNorm][]:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
Mod = TypeVar("Mod")
|
|
173
|
+
|
|
174
|
+
class LayerNormBase(ModuleWithStateDictSerialization):
|
|
175
|
+
def flatten_for_export(self: Mod) -> Mod:
|
|
176
|
+
if isinstance(self.axis, hax.Axis):
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
if self.weight is not None:
|
|
180
|
+
weight = self.weight.flatten("__OUT")
|
|
181
|
+
else:
|
|
182
|
+
weight = None
|
|
183
|
+
|
|
184
|
+
if self.bias is not None:
|
|
185
|
+
bias = self.bias.flatten("__OUT")
|
|
186
|
+
else:
|
|
187
|
+
bias = None
|
|
188
|
+
|
|
189
|
+
return dataclasses.replace(
|
|
190
|
+
self, weight=weight, bias=bias, axis=hax.flatten_axes(self.axis, "__OUT")
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def unflatten_from_export(self: Mod, template: Mod) -> Mod:
|
|
194
|
+
if template.axis == self.axis:
|
|
195
|
+
return self
|
|
196
|
+
|
|
197
|
+
if self.weight is not None:
|
|
198
|
+
assert isinstance(self.axis, hax.Axis), "Cannot unflatten weight with non-axis axis"
|
|
199
|
+
weight = hax.unflatten_axis(self.weight, self.axis, template.axis)
|
|
200
|
+
else:
|
|
201
|
+
weight = None
|
|
202
|
+
|
|
203
|
+
if self.bias is not None:
|
|
204
|
+
assert isinstance(self.axis, hax.Axis), "Cannot unflatten weight with non-axis axis"
|
|
205
|
+
bias = hax.unflatten_axis(self.bias, self.axis, template.axis)
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
bias = None
|
|
209
|
+
|
|
210
|
+
return dataclasses.replace(
|
|
211
|
+
self, weight=weight, bias=bias, axis=template.axis
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The code is a bit boilerplate-y but the idea is to find articulated axes in arrays and flatten them, while updating
|
|
216
|
+
any Axis members to match the new shape.
|
|
217
|
+
|
|
158
218
|
## API Reference
|
|
159
219
|
|
|
220
|
+
### Types
|
|
221
|
+
|
|
160
222
|
::: haliax.state_dict.StateDict
|
|
161
223
|
::: haliax.state_dict.ModuleWithStateDictSerialization
|
|
162
224
|
|
|
@@ -173,5 +235,5 @@ to join the prefix to the keys of the state dict.
|
|
|
173
235
|
|
|
174
236
|
::: haliax.state_dict.from_torch_compatible_state_dict
|
|
175
237
|
::: haliax.state_dict.to_torch_compatible_state_dict
|
|
176
|
-
::: haliax.state_dict.
|
|
177
|
-
::: haliax.state_dict.
|
|
238
|
+
::: haliax.state_dict.flatten_modules_for_export
|
|
239
|
+
::: haliax.state_dict.unflatten_modules_from_export
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.dev342"
|
|
@@ -18,6 +18,7 @@ from haliax._src.util import index_where
|
|
|
18
18
|
from haliax.axis import Axis
|
|
19
19
|
from haliax.core import NamedArray, flatten_axes, named
|
|
20
20
|
from haliax.jax_utils import is_jax_array_like, is_scalarish
|
|
21
|
+
from haliax.tree_util import scan_aware_tree_map
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
try:
|
|
@@ -32,14 +33,16 @@ T = TypeVar("T")
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def from_torch_compatible_state_dict(
|
|
35
|
-
t: T, state_dict: StateDict, *,
|
|
36
|
+
t: T, state_dict: StateDict, *, unflatten: bool = True, prefix: Optional[str] = None
|
|
36
37
|
) -> T:
|
|
37
38
|
"""
|
|
38
39
|
Convert a state dict to a tree that is compatible with the structure of `t`.
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
If unflatten is true, then the weights in the state dict are assumed to have been flattened (as by flatten_modules_for_export).
|
|
42
|
+
|
|
43
|
+
This applies [haliax.state_dict.from_state_dict][]
|
|
41
44
|
"""
|
|
42
|
-
if
|
|
45
|
+
if unflatten:
|
|
43
46
|
t = _flatten_to_unflatten(t, state_dict, prefix)
|
|
44
47
|
else:
|
|
45
48
|
t = from_state_dict(t, state_dict, prefix=prefix)
|
|
@@ -47,6 +50,45 @@ def from_torch_compatible_state_dict(
|
|
|
47
50
|
return t
|
|
48
51
|
|
|
49
52
|
|
|
53
|
+
def flatten_modules_for_export(t: T) -> T:
|
|
54
|
+
"""
|
|
55
|
+
Flatten all modules in a tree for export to torch.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def _flatten_module(module):
|
|
59
|
+
if isinstance(module, ModuleWithStateDictSerialization):
|
|
60
|
+
module = module.flatten_for_export()
|
|
61
|
+
module = scan_aware_tree_map(
|
|
62
|
+
_flatten_module,
|
|
63
|
+
module,
|
|
64
|
+
is_leaf=lambda x: x is not module and isinstance(x, ModuleWithStateDictSerialization),
|
|
65
|
+
)
|
|
66
|
+
return module
|
|
67
|
+
|
|
68
|
+
return scan_aware_tree_map(_flatten_module, t, is_leaf=lambda x: isinstance(x, ModuleWithStateDictSerialization))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def unflatten_modules_from_export(t: T, template: T) -> T:
|
|
72
|
+
"""
|
|
73
|
+
Unflatten all modules in a tree after import from torch.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def _unflatten_module(module, template):
|
|
77
|
+
if isinstance(module, ModuleWithStateDictSerialization):
|
|
78
|
+
module = module.unflatten_from_export(template)
|
|
79
|
+
module = scan_aware_tree_map(
|
|
80
|
+
_unflatten_module,
|
|
81
|
+
module,
|
|
82
|
+
template,
|
|
83
|
+
is_leaf=lambda x: x is not module and isinstance(x, ModuleWithStateDictSerialization),
|
|
84
|
+
)
|
|
85
|
+
return module
|
|
86
|
+
|
|
87
|
+
return scan_aware_tree_map(
|
|
88
|
+
_unflatten_module, t, template, is_leaf=lambda x: isinstance(x, ModuleWithStateDictSerialization)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
50
92
|
def _flatten_to_unflatten(t, state_dict, prefix):
|
|
51
93
|
"""
|
|
52
94
|
Flatten the torch compatible state_dict before loading into t, and then recover the unflattened layers.
|
|
@@ -59,9 +101,9 @@ def _flatten_to_unflatten(t, state_dict, prefix):
|
|
|
59
101
|
return jnp.zeros(struct.shape, struct.dtype)
|
|
60
102
|
|
|
61
103
|
t = jax.tree.map(_dt_struct_to_array, t)
|
|
62
|
-
flat_t =
|
|
104
|
+
flat_t = flatten_modules_for_export(t)
|
|
63
105
|
flat_t = from_state_dict(flat_t, state_dict, prefix=prefix)
|
|
64
|
-
t =
|
|
106
|
+
t = unflatten_modules_from_export(flat_t, t)
|
|
65
107
|
return t
|
|
66
108
|
|
|
67
109
|
|
|
@@ -103,6 +145,25 @@ class ModuleWithStateDictSerialization(eqx.Module):
|
|
|
103
145
|
"""Returns a dict mapping eqx.Module keys to torch keys that need to be renamed for serialization"""
|
|
104
146
|
return {}
|
|
105
147
|
|
|
148
|
+
def flatten_for_export(self: Mod) -> Mod:
|
|
149
|
+
"""
|
|
150
|
+
Flatten articulated named arrays for export to torch. In general this method should, for a linear layer, flatten
|
|
151
|
+
all input axes into a single axis, and all output axes into a single axis. You can do whatever else
|
|
152
|
+
you want to support pytorch-compatible serialization if you want.
|
|
153
|
+
|
|
154
|
+
This method is less general than to_state_dict and is only called when using to_torch_compatible_state_dict.
|
|
155
|
+
"""
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def unflatten_from_export(self: Mod, template: Mod) -> Mod:
|
|
159
|
+
"""
|
|
160
|
+
Unflatten the module after import from torch.
|
|
161
|
+
|
|
162
|
+
Template has the proper structure (e.g. articulated named axes) but the values are meaningless.
|
|
163
|
+
"""
|
|
164
|
+
del template
|
|
165
|
+
return self
|
|
166
|
+
|
|
106
167
|
|
|
107
168
|
def from_state_dict(tree: T, state_dict: StateDict, prefix: Optional[str] = None) -> T:
|
|
108
169
|
"""
|
|
@@ -372,78 +433,3 @@ def load_state_dict(path):
|
|
|
372
433
|
"""
|
|
373
434
|
state_dict = safetensors.numpy.load_file(path)
|
|
374
435
|
return state_dict
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
def flatten_linear_layers(tree: T) -> T:
|
|
378
|
-
"""
|
|
379
|
-
In PyTorch, linear layers are stored as a 2d weight matrix and a 1d bias vector. In Haliax,
|
|
380
|
-
linear layers can have arbitrary dimensions, grouped into input and output axes. This function
|
|
381
|
-
flattens the linear layers in a tree to be compatible with PyTorch-style state dicts.
|
|
382
|
-
|
|
383
|
-
:param tree:
|
|
384
|
-
"""
|
|
385
|
-
from haliax.nn import Linear
|
|
386
|
-
|
|
387
|
-
def _flatten_linear(layer):
|
|
388
|
-
if not isinstance(layer, Linear):
|
|
389
|
-
return layer
|
|
390
|
-
|
|
391
|
-
weight = layer.weight
|
|
392
|
-
bias = layer.bias
|
|
393
|
-
|
|
394
|
-
new_Out: Axis = flatten_axes(layer.Out, "__OUT__")
|
|
395
|
-
new_In: Axis = flatten_axes(layer.In, "__IN__")
|
|
396
|
-
|
|
397
|
-
if weight.array is not None:
|
|
398
|
-
out_first = layer._out_first
|
|
399
|
-
weight = weight.flatten_axes(layer.Out, new_Out).flatten_axes(layer.In, new_In)
|
|
400
|
-
|
|
401
|
-
if out_first:
|
|
402
|
-
weight = weight.rearrange((..., "__OUT__", "__IN__"))
|
|
403
|
-
else:
|
|
404
|
-
weight = weight.rearrange((..., "__IN__", "__OUT__"))
|
|
405
|
-
|
|
406
|
-
if isinstance(bias, NamedArray):
|
|
407
|
-
bias = bias.flatten_axes(layer.Out, new_Out)
|
|
408
|
-
|
|
409
|
-
return dataclasses.replace(layer, weight=weight, bias=bias, In=new_In, Out=new_Out) # type: ignore
|
|
410
|
-
|
|
411
|
-
return jax.tree.map(_flatten_linear, tree, is_leaf=lambda x: isinstance(x, Linear))
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
def unflatten_linear_layers(template: T, tree_with_flattened_linears: T) -> T:
|
|
415
|
-
"""
|
|
416
|
-
Unflattens linear layers in a tree that was flattened with [haliax.state_dict.flatten_linear_layers][].
|
|
417
|
-
Template has the same structure as the tree that was flattened, but with the original (unflattened)
|
|
418
|
-
linear layers.
|
|
419
|
-
|
|
420
|
-
Returns:
|
|
421
|
-
The same tree as `tree_with_flattened_linears`, but with the linear layers unflattened to match
|
|
422
|
-
the structure of `template`.
|
|
423
|
-
"""
|
|
424
|
-
|
|
425
|
-
from haliax.nn import Linear
|
|
426
|
-
|
|
427
|
-
def _unflatten_linear(template, flattened):
|
|
428
|
-
assert isinstance(template, Linear) == isinstance(flattened, Linear)
|
|
429
|
-
|
|
430
|
-
if not isinstance(template, Linear):
|
|
431
|
-
return flattened
|
|
432
|
-
|
|
433
|
-
weight = flattened.weight
|
|
434
|
-
bias = flattened.bias
|
|
435
|
-
|
|
436
|
-
if weight.array is not None:
|
|
437
|
-
weight = weight.unflatten_axis("__OUT__", template.Out).unflatten_axis("__IN__", template.In)
|
|
438
|
-
weight = weight.rearrange(template.weight.axes)
|
|
439
|
-
|
|
440
|
-
if isinstance(bias, NamedArray):
|
|
441
|
-
bias = bias.unflatten_axis("__OUT__", template.Out)
|
|
442
|
-
assert template.bias is not None, "Flattened bias but template has no bias"
|
|
443
|
-
bias = bias.rearrange(template.bias.axes)
|
|
444
|
-
|
|
445
|
-
return dataclasses.replace(template, weight=weight, bias=bias) # type: ignore
|
|
446
|
-
|
|
447
|
-
return jax.tree.map(
|
|
448
|
-
_unflatten_linear, template, tree_with_flattened_linears, is_leaf=lambda x: isinstance(x, Linear)
|
|
449
|
-
)
|
|
@@ -36,14 +36,11 @@ from .embedding import Embedding
|
|
|
36
36
|
from .linear import Linear
|
|
37
37
|
from .loss import binary_cross_entropy_loss, cross_entropy_loss, cross_entropy_loss_and_log_normalizers, reduce_loss
|
|
38
38
|
from .mlp import MLP
|
|
39
|
-
from .normalization import LayerNorm, log_softmax, logsumexp, softmax, standardize
|
|
39
|
+
from .normalization import LayerNorm, RmsNorm, log_softmax, logsumexp, softmax, standardize
|
|
40
40
|
from .pool import max_pool, mean_pool, min_pool
|
|
41
41
|
from .scan import BlockSeq, Stacked
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
# TODO: support where in softmax, etc
|
|
45
|
-
|
|
46
|
-
|
|
47
44
|
def one_hot(x: NamedArray | int, class_axis: Axis, *, dtype=None) -> NamedArray:
|
|
48
45
|
"""
|
|
49
46
|
Convert an integer to a one-hot vector. This is basically a generalization of [jax.nn.one_hot][]
|
|
@@ -87,6 +84,7 @@ __all__ = [
|
|
|
87
84
|
"LayerNorm",
|
|
88
85
|
"Linear",
|
|
89
86
|
"Embedding",
|
|
87
|
+
"RmsNorm",
|
|
90
88
|
"Stacked",
|
|
91
89
|
"BlockSeq",
|
|
92
90
|
"MLP",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
import math
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
@@ -6,13 +7,14 @@ from jax.random import PRNGKey
|
|
|
6
7
|
|
|
7
8
|
import haliax as hax
|
|
8
9
|
|
|
10
|
+
from .._src.state_dict import Mod, ModuleWithStateDictSerialization
|
|
9
11
|
from ..axis import AxisSpec
|
|
10
12
|
from ..core import NamedArray
|
|
11
13
|
from ..jax_utils import named_call
|
|
12
14
|
from ..quantization import DotGeneralOp
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class Linear(
|
|
17
|
+
class Linear(ModuleWithStateDictSerialization):
|
|
16
18
|
"""A named Linear layer. This module allows you to specify multiple named axes for both input
|
|
17
19
|
and output, which is occasionally useful."""
|
|
18
20
|
|
|
@@ -71,6 +73,47 @@ class Linear(eqx.Module):
|
|
|
71
73
|
|
|
72
74
|
return q
|
|
73
75
|
|
|
76
|
+
def flatten_for_export(self: Mod) -> Mod:
|
|
77
|
+
if isinstance(self.Out, hax.Axis) and isinstance(self.In, hax.Axis):
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
weight = self.weight
|
|
81
|
+
bias = self.bias
|
|
82
|
+
|
|
83
|
+
new_Out = hax.flatten_axes(self.Out, "__OUT__")
|
|
84
|
+
new_In = hax.flatten_axes(self.In, "__IN__")
|
|
85
|
+
|
|
86
|
+
if weight.array is not None:
|
|
87
|
+
out_first = self._out_first
|
|
88
|
+
weight = weight.flatten_axes(self.Out, new_Out).flatten_axes(self.In, new_In)
|
|
89
|
+
|
|
90
|
+
if out_first:
|
|
91
|
+
weight = weight.rearrange((..., "__OUT__", "__IN__"))
|
|
92
|
+
else:
|
|
93
|
+
weight = weight.rearrange((..., "__IN__", "__OUT__"))
|
|
94
|
+
|
|
95
|
+
if isinstance(bias, NamedArray):
|
|
96
|
+
bias = bias.flatten_axes(self.Out, new_Out)
|
|
97
|
+
|
|
98
|
+
return dataclasses.replace(self, weight=weight, bias=bias, In=new_In, Out=new_Out)
|
|
99
|
+
|
|
100
|
+
def unflatten_from_export(self: Mod, template: Mod) -> Mod:
|
|
101
|
+
weight = self.weight
|
|
102
|
+
bias = self.bias
|
|
103
|
+
|
|
104
|
+
if (template.In, template.Out) == (self.In, self.Out):
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
if weight.array is not None:
|
|
108
|
+
weight = weight.unflatten_axis("__OUT__", template.Out).unflatten_axis("__IN__", template.In)
|
|
109
|
+
weight = weight.rearrange(template.weight.axes)
|
|
110
|
+
|
|
111
|
+
if isinstance(bias, NamedArray):
|
|
112
|
+
bias = bias.unflatten_axis("__OUT__", template.Out)
|
|
113
|
+
bias = bias.rearrange(template.bias.axes)
|
|
114
|
+
|
|
115
|
+
return dataclasses.replace(template, weight=weight, bias=bias)
|
|
116
|
+
|
|
74
117
|
@property
|
|
75
118
|
def _out_first(self):
|
|
76
119
|
"""
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from typing import Optional, TypeVar
|
|
4
|
+
|
|
5
|
+
import equinox as eqx
|
|
6
|
+
from jax import nn as jnn
|
|
7
|
+
from jax import numpy as jnp
|
|
8
|
+
|
|
9
|
+
import haliax
|
|
10
|
+
import haliax as hax
|
|
11
|
+
|
|
12
|
+
from .._src.state_dict import Mod, ModuleWithStateDictSerialization
|
|
13
|
+
from ..axis import AxisSelection, AxisSpec
|
|
14
|
+
from ..core import NamedArray
|
|
15
|
+
from ..types import Scalar
|
|
16
|
+
from ..wrap import unwrap_namedarrays, wrap_axiswise_call, wrap_reduction_call
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
A = TypeVar("A", Scalar, NamedArray, jnp.ndarray)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LayerNormBase(ModuleWithStateDictSerialization):
|
|
23
|
+
axis: AxisSpec = eqx.static_field()
|
|
24
|
+
weight: Optional[NamedArray]
|
|
25
|
+
bias: Optional[NamedArray]
|
|
26
|
+
eps: float = eqx.static_field(default=1e-5)
|
|
27
|
+
dtype: Optional[jnp.dtype] = eqx.field(default=None, static=True)
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def __call__(self, x: NamedArray) -> NamedArray:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def init(
|
|
35
|
+
cls,
|
|
36
|
+
axis: AxisSpec,
|
|
37
|
+
eps: float = 1e-5,
|
|
38
|
+
*,
|
|
39
|
+
use_weight: bool = True,
|
|
40
|
+
use_bias: bool = True,
|
|
41
|
+
dtype: Optional[jnp.dtype] = None,
|
|
42
|
+
):
|
|
43
|
+
if use_weight:
|
|
44
|
+
weight = hax.ones(axis)
|
|
45
|
+
else:
|
|
46
|
+
weight = None
|
|
47
|
+
|
|
48
|
+
if use_bias:
|
|
49
|
+
bias = hax.zeros(axis)
|
|
50
|
+
else:
|
|
51
|
+
bias = None
|
|
52
|
+
|
|
53
|
+
return cls(axis, weight, bias, eps, dtype)
|
|
54
|
+
|
|
55
|
+
def flatten_for_export(self: Mod) -> Mod:
|
|
56
|
+
if isinstance(self.axis, hax.Axis):
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
if self.weight is not None:
|
|
60
|
+
weight = self.weight.flatten("__OUT")
|
|
61
|
+
else:
|
|
62
|
+
weight = None
|
|
63
|
+
|
|
64
|
+
if self.bias is not None:
|
|
65
|
+
bias = self.bias.flatten("__OUT")
|
|
66
|
+
else:
|
|
67
|
+
bias = None
|
|
68
|
+
|
|
69
|
+
return dataclasses.replace(self, weight=weight, bias=bias, axis=hax.flatten_axes(self.axis, "__OUT"))
|
|
70
|
+
|
|
71
|
+
def unflatten_from_export(self: Mod, template: Mod) -> Mod:
|
|
72
|
+
if template.axis == self.axis:
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
if self.weight is not None:
|
|
76
|
+
assert isinstance(self.axis, hax.Axis), "Cannot unflatten weight with non-axis axis"
|
|
77
|
+
weight = hax.unflatten_axis(self.weight, self.axis, template.axis)
|
|
78
|
+
else:
|
|
79
|
+
weight = None
|
|
80
|
+
|
|
81
|
+
if self.bias is not None:
|
|
82
|
+
assert isinstance(self.axis, hax.Axis), "Cannot unflatten weight with non-axis axis"
|
|
83
|
+
bias = hax.unflatten_axis(self.bias, self.axis, template.axis)
|
|
84
|
+
|
|
85
|
+
else:
|
|
86
|
+
bias = None
|
|
87
|
+
|
|
88
|
+
return dataclasses.replace(self, weight=weight, bias=bias, axis=template.axis)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class LayerNorm(LayerNormBase):
|
|
92
|
+
r"""
|
|
93
|
+
Normalises the input along the specified axis (or axes), using the mean and variance of the
|
|
94
|
+
input along that axis.
|
|
95
|
+
"""
|
|
96
|
+
axis: AxisSpec = eqx.field(static=True)
|
|
97
|
+
weight: Optional[NamedArray]
|
|
98
|
+
bias: Optional[NamedArray]
|
|
99
|
+
|
|
100
|
+
eps: float = eqx.field(default=1e-5, static=True)
|
|
101
|
+
dtype: Optional[jnp.dtype] = eqx.field(default=None, static=True)
|
|
102
|
+
|
|
103
|
+
def __call__(self, x: NamedArray) -> NamedArray:
|
|
104
|
+
dtype = x.dtype
|
|
105
|
+
mean = x.mean(self.axis)
|
|
106
|
+
var = x.var(self.axis)
|
|
107
|
+
inv = hax.rsqrt(var + self.eps)
|
|
108
|
+
out = (x - mean) * inv
|
|
109
|
+
out = out.astype(dtype)
|
|
110
|
+
|
|
111
|
+
if self.weight is not None:
|
|
112
|
+
out = self.weight * out
|
|
113
|
+
if self.bias is not None:
|
|
114
|
+
out = out + self.bias
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class RmsNorm(LayerNormBase):
|
|
119
|
+
r"""
|
|
120
|
+
Implements RMS normalization, which normalizes the input by dividing by the root mean square of the input.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __call__(self, x: NamedArray) -> NamedArray:
|
|
124
|
+
in_dtype = x.dtype
|
|
125
|
+
x = x.astype(self.dtype)
|
|
126
|
+
var = hax.mean(hax.square(x), axis=self.axis)
|
|
127
|
+
inv = hax.rsqrt(var + self.eps)
|
|
128
|
+
out = x * inv
|
|
129
|
+
out = out.astype(in_dtype)
|
|
130
|
+
|
|
131
|
+
if self.weight is not None:
|
|
132
|
+
out = self.weight * out
|
|
133
|
+
if self.bias is not None:
|
|
134
|
+
out = out + self.bias
|
|
135
|
+
return out
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def logsumexp(a: A, axis: Optional[AxisSelection] = None) -> A:
|
|
139
|
+
# TODO: logsumexp indirectly supports where via `b`. we should support it directly
|
|
140
|
+
return wrap_reduction_call(jnn.logsumexp, a, axis=axis, single_axis_only=False, supports_where=False)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# TODO: support where in softmax, etc
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def softmax(a: A, axis: Optional[AxisSelection] = None) -> A:
|
|
147
|
+
return wrap_axiswise_call(jnn.softmax, a, axis=axis, single_axis_only=False)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def log_softmax(a: A, axis: Optional[AxisSelection] = None) -> A:
|
|
151
|
+
return wrap_axiswise_call(jnn.log_softmax, a, axis=axis, single_axis_only=False)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def standardize(
|
|
155
|
+
x: NamedArray,
|
|
156
|
+
axis: AxisSpec,
|
|
157
|
+
*,
|
|
158
|
+
mean: Optional[NamedArray] = None,
|
|
159
|
+
variance: Optional[NamedArray] = None,
|
|
160
|
+
epsilon: float = 1e-5,
|
|
161
|
+
where: Optional[NamedArray] = None,
|
|
162
|
+
) -> NamedArray:
|
|
163
|
+
"""Analogous to [jax.nn.standardize][], but with support for NamedArrays."""
|
|
164
|
+
x, mean, variance, where = haliax.broadcast_arrays(x, mean, variance, where) # type: ignore
|
|
165
|
+
raw_x, mean, variance, where = unwrap_namedarrays(x, mean, variance, where)
|
|
166
|
+
axis_indices = x._lookup_indices(axis)
|
|
167
|
+
|
|
168
|
+
plain = jnn.standardize(raw_x, axis_indices, mean=mean, variance=variance, epsilon=epsilon, where=where)
|
|
169
|
+
return NamedArray(plain, x.axes)
|
|
@@ -8,14 +8,14 @@ from haliax.types import FilterSpec
|
|
|
8
8
|
from ._src.state_dict import (
|
|
9
9
|
ModuleWithStateDictSerialization,
|
|
10
10
|
StateDict,
|
|
11
|
-
|
|
11
|
+
flatten_modules_for_export,
|
|
12
12
|
from_state_dict,
|
|
13
13
|
from_torch_compatible_state_dict,
|
|
14
14
|
load_state_dict,
|
|
15
15
|
save_state_dict,
|
|
16
16
|
to_numpy_state_dict,
|
|
17
17
|
to_state_dict,
|
|
18
|
-
|
|
18
|
+
unflatten_modules_from_export,
|
|
19
19
|
with_prefix,
|
|
20
20
|
)
|
|
21
21
|
|
|
@@ -24,23 +24,23 @@ T = TypeVar("T")
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def to_torch_compatible_state_dict(
|
|
27
|
-
t: T, *,
|
|
27
|
+
t: T, *, flatten: bool = True, prefix: Optional[str] = None, filter: FilterSpec = is_jax_array_like
|
|
28
28
|
) -> StateDict:
|
|
29
29
|
"""
|
|
30
30
|
Convert a tree to a state dict that is compatible with torch-style state dicts.
|
|
31
31
|
|
|
32
|
-
This applies [
|
|
32
|
+
This applies the same logic as [to_state_dict][] but also uses [haliax.state_dict.ModuleWithStateDictSerialization.flatten_for_export][] to flatten
|
|
33
33
|
|
|
34
34
|
Args:
|
|
35
35
|
t: The tree to convert
|
|
36
|
-
|
|
36
|
+
flatten: Whether to flatten axes using flatten_for_export
|
|
37
37
|
prefix: The prefix to use for the state dict keys
|
|
38
38
|
filter: The filter to use for selecting which nodes to include in the state dict. By default, this includes only
|
|
39
39
|
array-like objects (e.g. JAX and NumPy arrays).
|
|
40
40
|
"""
|
|
41
41
|
t = equinox.filter(t, filter)
|
|
42
|
-
if
|
|
43
|
-
t =
|
|
42
|
+
if flatten:
|
|
43
|
+
t = flatten_modules_for_export(t)
|
|
44
44
|
return to_numpy_state_dict(t, prefix=prefix)
|
|
45
45
|
|
|
46
46
|
|
|
@@ -50,11 +50,11 @@ __all__ = [
|
|
|
50
50
|
"load_state_dict",
|
|
51
51
|
"save_state_dict",
|
|
52
52
|
"from_state_dict",
|
|
53
|
-
"flatten_linear_layers",
|
|
54
|
-
"unflatten_linear_layers",
|
|
55
53
|
"with_prefix",
|
|
56
54
|
"to_state_dict",
|
|
57
55
|
"to_numpy_state_dict",
|
|
58
56
|
"StateDict",
|
|
59
57
|
"to_torch_compatible_state_dict",
|
|
58
|
+
"flatten_modules_for_export",
|
|
59
|
+
"unflatten_modules_from_export",
|
|
60
60
|
]
|
|
@@ -30,17 +30,10 @@ def tree_map(fn, tree, *rest, is_leaf=None):
|
|
|
30
30
|
|
|
31
31
|
def scan_aware_tree_map(fn, tree, *rest, is_leaf=None):
|
|
32
32
|
"""
|
|
33
|
-
Version of [haliax.tree_util.tree_map][] that is aware of the scan-layer pattern, specifically as
|
|
33
|
+
Version of [haliax.tree_util.tree_map][] that is aware of the scan-layer pattern, specifically as implemented
|
|
34
34
|
in hax.nn.Stacked. This function will (implicitly) apply the transform to each layer in each Stacked module
|
|
35
35
|
(using vmap). If there are no Stacked modules in the tree, this function is equivalent to [haliax.tree_util.tree_map][].
|
|
36
36
|
|
|
37
|
-
Args:
|
|
38
|
-
fn:
|
|
39
|
-
tree:
|
|
40
|
-
*rest:
|
|
41
|
-
is_leaf:
|
|
42
|
-
|
|
43
|
-
Returns:
|
|
44
37
|
"""
|
|
45
38
|
old_is_leaf = is_leaf
|
|
46
39
|
if is_leaf is None:
|
|
@@ -55,7 +48,7 @@ def scan_aware_tree_map(fn, tree, *rest, is_leaf=None):
|
|
|
55
48
|
new_inner = haliax.vmap(mapped_fn, x.Block)(x.stacked, *[r.stacked for r in rest])
|
|
56
49
|
return dataclasses.replace(x, stacked=new_inner) # type: ignore
|
|
57
50
|
else:
|
|
58
|
-
return fn(x)
|
|
51
|
+
return fn(x, *rest)
|
|
59
52
|
|
|
60
53
|
return tree_map(rec_fn, tree, *rest, is_leaf=is_leaf)
|
|
61
54
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
from typing import Any
|
|
2
3
|
|
|
3
4
|
import equinox as eqx
|
|
@@ -6,9 +7,10 @@ import jax.numpy as jnp
|
|
|
6
7
|
import pytest
|
|
7
8
|
|
|
8
9
|
import haliax as hax
|
|
10
|
+
from haliax._src.state_dict import flatten_modules_for_export, unflatten_modules_from_export
|
|
9
11
|
from haliax.nn import Linear
|
|
10
|
-
from haliax.nn.scan import _stack_state_dict, _unstack_state_dict
|
|
11
|
-
from haliax.state_dict import
|
|
12
|
+
from haliax.nn.scan import Stacked, _stack_state_dict, _unstack_state_dict
|
|
13
|
+
from haliax.state_dict import from_state_dict, to_state_dict
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
@pytest.mark.parametrize("out_dims_first", [True, False])
|
|
@@ -24,7 +26,7 @@ def test_flatten_linear_layers(out_dims_first: bool):
|
|
|
24
26
|
else:
|
|
25
27
|
assert linear.weight.axes == (H, W, D, B)
|
|
26
28
|
|
|
27
|
-
flat_linear =
|
|
29
|
+
flat_linear = linear.flatten_for_export()
|
|
28
30
|
|
|
29
31
|
flat_state_dict = to_state_dict(flat_linear)
|
|
30
32
|
if out_dims_first:
|
|
@@ -36,7 +38,7 @@ def test_flatten_linear_layers(out_dims_first: bool):
|
|
|
36
38
|
|
|
37
39
|
# now unflatten it
|
|
38
40
|
linear2 = Linear.init((H, W), (D, B), key=jax.random.PRNGKey(1), use_bias=True, out_first=out_dims_first)
|
|
39
|
-
new_linear =
|
|
41
|
+
new_linear = flat_linear.unflatten_from_export(linear2)
|
|
40
42
|
|
|
41
43
|
if out_dims_first:
|
|
42
44
|
assert new_linear.weight.axes == (D, B, H, W)
|
|
@@ -127,3 +129,49 @@ def test_to_from_state_dict():
|
|
|
127
129
|
m2 = from_state_dict(m2, state_dict)
|
|
128
130
|
assert jnp.all(m2.a == a)
|
|
129
131
|
assert jnp.all(m2.b == b)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_export_layer_norm():
|
|
135
|
+
D = hax.Axis("D", 10)
|
|
136
|
+
E = hax.Axis("E", 20)
|
|
137
|
+
layer_norm = hax.nn.LayerNorm.init((D, E), eps=1e-5, use_weight=True, use_bias=True)
|
|
138
|
+
|
|
139
|
+
flat_layer_norm = layer_norm.flatten_for_export()
|
|
140
|
+
|
|
141
|
+
flat_state_dict = to_state_dict(flat_layer_norm)
|
|
142
|
+
|
|
143
|
+
assert flat_state_dict["weight"].shape == (D.size * E.size,)
|
|
144
|
+
assert flat_state_dict["bias"].shape == (D.size * E.size,)
|
|
145
|
+
assert flat_state_dict["weight"].dtype == flat_state_dict["bias"].dtype == layer_norm.weight.dtype
|
|
146
|
+
|
|
147
|
+
# now unflatten it
|
|
148
|
+
layer_norm2 = hax.nn.LayerNorm.init((D, E), eps=1e-5, use_weight=True, use_bias=True)
|
|
149
|
+
# ensure we have different weights
|
|
150
|
+
layer_norm2 = dataclasses.replace(layer_norm2, weight=layer_norm2.weight + 1, bias=layer_norm2.bias + 1)
|
|
151
|
+
|
|
152
|
+
new_layer_norm = flat_layer_norm.unflatten_from_export(layer_norm2)
|
|
153
|
+
|
|
154
|
+
assert layer_norm == new_layer_norm
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_stacked_layer_norm():
|
|
158
|
+
L = hax.Axis("L", 4)
|
|
159
|
+
D = hax.Axis("D", 10)
|
|
160
|
+
E = hax.Axis("E", 20)
|
|
161
|
+
|
|
162
|
+
norms = Stacked.init(L, hax.nn.LayerNorm)((D, E), eps=1e-5, use_weight=True, use_bias=True)
|
|
163
|
+
|
|
164
|
+
norms_flat = flatten_modules_for_export(norms)
|
|
165
|
+
|
|
166
|
+
flat_state_dict = to_state_dict(norms_flat)
|
|
167
|
+
|
|
168
|
+
assert flat_state_dict["0.weight"].shape == (D.size * E.size,)
|
|
169
|
+
assert flat_state_dict["0.bias"].shape == (D.size * E.size,)
|
|
170
|
+
assert flat_state_dict["1.weight"].shape == (D.size * E.size,)
|
|
171
|
+
|
|
172
|
+
# now unflatten it
|
|
173
|
+
norms2 = Stacked.init(L, hax.nn.LayerNorm)((D, E), eps=1e-5, use_weight=True, use_bias=True)
|
|
174
|
+
|
|
175
|
+
new_norms = unflatten_modules_from_export(norms_flat, norms2)
|
|
176
|
+
|
|
177
|
+
assert norms == new_norms
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.4.dev340"
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
from typing import Optional, TypeVar
|
|
2
|
-
|
|
3
|
-
import equinox as eqx
|
|
4
|
-
from jax import nn as jnn
|
|
5
|
-
from jax import numpy as jnp
|
|
6
|
-
|
|
7
|
-
import haliax
|
|
8
|
-
import haliax as hax
|
|
9
|
-
|
|
10
|
-
from ..axis import AxisSelection, AxisSpec
|
|
11
|
-
from ..core import NamedArray
|
|
12
|
-
from ..types import Scalar
|
|
13
|
-
from ..wrap import unwrap_namedarrays, wrap_axiswise_call, wrap_reduction_call
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
A = TypeVar("A", Scalar, NamedArray, jnp.ndarray)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class LayerNorm(eqx.Module):
|
|
20
|
-
r"""
|
|
21
|
-
Normalises the input along the specified axis (or axes), using the mean and variance of the
|
|
22
|
-
input along that axis.
|
|
23
|
-
"""
|
|
24
|
-
axis: AxisSpec = eqx.static_field()
|
|
25
|
-
weight: Optional[NamedArray]
|
|
26
|
-
bias: Optional[NamedArray]
|
|
27
|
-
|
|
28
|
-
eps: float = eqx.static_field(default=1e-5)
|
|
29
|
-
|
|
30
|
-
@staticmethod
|
|
31
|
-
def init(axis: AxisSpec, eps: float = 1e-5, use_weight: bool = True, use_bias: bool = True):
|
|
32
|
-
if use_weight:
|
|
33
|
-
weight = hax.ones(axis)
|
|
34
|
-
else:
|
|
35
|
-
weight = None
|
|
36
|
-
if use_bias:
|
|
37
|
-
bias = hax.zeros(axis)
|
|
38
|
-
else:
|
|
39
|
-
bias = None
|
|
40
|
-
|
|
41
|
-
return LayerNorm(axis, weight, bias, eps)
|
|
42
|
-
|
|
43
|
-
def __call__(self, x: NamedArray) -> NamedArray:
|
|
44
|
-
mean = x.mean(self.axis)
|
|
45
|
-
var = x.var(self.axis)
|
|
46
|
-
inv = hax.rsqrt(var + self.eps)
|
|
47
|
-
out = (x - mean) * inv
|
|
48
|
-
|
|
49
|
-
if self.weight is not None:
|
|
50
|
-
out = self.weight * out
|
|
51
|
-
if self.bias is not None:
|
|
52
|
-
out = out + self.bias
|
|
53
|
-
return out
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def logsumexp(a: A, axis: Optional[AxisSelection] = None) -> A:
|
|
57
|
-
# TODO: logsumexp indirectly supports where via `b`. we should support it directly
|
|
58
|
-
return wrap_reduction_call(jnn.logsumexp, a, axis=axis, single_axis_only=False, supports_where=False)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# TODO: support where in softmax, etc
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def softmax(a: A, axis: Optional[AxisSelection] = None) -> A:
|
|
65
|
-
return wrap_axiswise_call(jnn.softmax, a, axis=axis, single_axis_only=False)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def log_softmax(a: A, axis: Optional[AxisSelection] = None) -> A:
|
|
69
|
-
return wrap_axiswise_call(jnn.log_softmax, a, axis=axis, single_axis_only=False)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def standardize(
|
|
73
|
-
x: NamedArray,
|
|
74
|
-
axis: AxisSpec,
|
|
75
|
-
*,
|
|
76
|
-
mean: Optional[NamedArray] = None,
|
|
77
|
-
variance: Optional[NamedArray] = None,
|
|
78
|
-
epsilon: float = 1e-5,
|
|
79
|
-
where: Optional[NamedArray] = None,
|
|
80
|
-
) -> NamedArray:
|
|
81
|
-
"""Analogous to [jax.nn.standardize][], but with support for NamedArrays."""
|
|
82
|
-
x, mean, variance, where = haliax.broadcast_arrays(x, mean, variance, where) # type: ignore
|
|
83
|
-
raw_x, mean, variance, where = unwrap_namedarrays(x, mean, variance, where)
|
|
84
|
-
axis_indices = x._lookup_indices(axis)
|
|
85
|
-
|
|
86
|
-
plain = jnn.standardize(raw_x, axis_indices, mean=mean, variance=variance, epsilon=epsilon, where=where)
|
|
87
|
-
return NamedArray(plain, x.axes)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|