haliax 1.4.dev441__tar.gz → 1.4.dev444__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.dev441 → haliax-1.4.dev444}/PKG-INFO +1 -1
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/__about__.py +1 -1
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/embedding.py +7 -2
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/linear.py +7 -2
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/scan.py +102 -40
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_mup_embedding.py +1 -1
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_mup_linear.py +10 -5
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_scan.py +239 -2
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.agents/projects/api_parity.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.coveragerc +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.flake8 +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.gitignore +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.playbooks/add-types.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.playbooks/wrap-non-named.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/AGENTS.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/AUTHORS.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/CONTRIBUTORS.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/LICENSE +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/README.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/api.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/css/material.css +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/faq.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/fp8.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/index.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/indexing.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/matmul.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/nn.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/partitioning.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/primer.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/rearrange.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/requirements.txt +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/scan.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/state-dict.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/tutorial.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/typing.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/docs/vmap.md +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/etc/license_header.txt +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/mkdocs.yml +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/pyproject.toml +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/__init__.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/scan.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/state_dict.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/core.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/fft.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/field.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/haxtyping.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/jax_utils.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/mup.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/normalization.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/poly.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/random.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/tree.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/types.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/util.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/core_test.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_attention.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_axis.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_bitwise_ops.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_conv.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_debug.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_dot.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_dtype_typing.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_fft.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_field.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_hof.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_int8.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_moe_linear.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_mup_coordinate_check.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_namedarray_typing.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_nan_reductions.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_nn.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_ops.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_poly_ops.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_pool.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_random.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_scatter_gather.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_utils.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/tests/test_visualize_sharding.py +0 -0
- {haliax-1.4.dev441 → haliax-1.4.dev444}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev444
|
|
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/
|
|
@@ -24,7 +24,12 @@ class Embedding(eqx.Module, ReparamEnabled):
|
|
|
24
24
|
# axes
|
|
25
25
|
Vocab: Axis = eqx.field(static=True)
|
|
26
26
|
Embed: AxisSpec = eqx.field(static=True)
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
_reparam_cls: type[AbstractEmbeddingReparam] = eqx.field(static=True, default=EmbeddingStandardParam)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def reparam(self) -> AbstractEmbeddingReparam:
|
|
32
|
+
return self._reparam_cls(self.Embed, self.Vocab)
|
|
28
33
|
|
|
29
34
|
@staticmethod
|
|
30
35
|
def init(
|
|
@@ -61,7 +66,7 @@ class Embedding(eqx.Module, ReparamEnabled):
|
|
|
61
66
|
weight = hax.random.truncated_normal(key, all_axes, -3, 3) * (
|
|
62
67
|
init_scale * reparam_cls.init_scale(Vocab, Embed)
|
|
63
68
|
)
|
|
64
|
-
return Embedding(weight=weight, Vocab=Vocab, Embed=Embed,
|
|
69
|
+
return Embedding(weight=weight, Vocab=Vocab, Embed=Embed, _reparam_cls=reparam_cls)
|
|
65
70
|
|
|
66
71
|
def __call__(self, input_ids: NamedArray, *, key: PRNGKeyArray | None = None):
|
|
67
72
|
"""Alias for `embed`. key is ignored."""
|
|
@@ -45,9 +45,14 @@ class Linear(ModuleWithStateDictSerialization, ReparamEnabled):
|
|
|
45
45
|
|
|
46
46
|
In: AxisSpec = eqx.field(static=True)
|
|
47
47
|
Out: AxisSpec = eqx.field(static=True)
|
|
48
|
-
reparam: AbstractLinearReparam = eqx.field(static=True)
|
|
49
48
|
dot_general: DotGeneralOp = eqx.field(default_factory=DotGeneralOp.default)
|
|
50
49
|
|
|
50
|
+
_reparam_cls: type[AbstractLinearReparam] = eqx.field(static=True, default=LinearStandardParam)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def reparam(self) -> AbstractLinearReparam:
|
|
54
|
+
return self._reparam_cls(self.In, self.Out)
|
|
55
|
+
|
|
51
56
|
@staticmethod
|
|
52
57
|
def init(
|
|
53
58
|
In: AxisSpec,
|
|
@@ -77,7 +82,7 @@ class Linear(ModuleWithStateDictSerialization, ReparamEnabled):
|
|
|
77
82
|
if dot_general is None:
|
|
78
83
|
dot_general = DotGeneralOp.default()
|
|
79
84
|
|
|
80
|
-
return Linear(weight, bias, In, Out, dot_general=dot_general,
|
|
85
|
+
return Linear(weight, bias, In, Out, dot_general=dot_general, _reparam_cls=reparam_cls)
|
|
81
86
|
|
|
82
87
|
@named_call
|
|
83
88
|
def __call__(self, inputs, *, key: PRNGKeyArray | None = None):
|
|
@@ -62,6 +62,22 @@ class ModuleInit(Protocol[M_co]):
|
|
|
62
62
|
def __call__(self, *args, **kwargs) -> M_co: ...
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
def _normalize_unroll(unroll: int | bool | None, block_size: int) -> int | bool:
|
|
66
|
+
"""Convert user-provided ``unroll`` values into something understood by ``jax.lax.scan``."""
|
|
67
|
+
|
|
68
|
+
if unroll is None:
|
|
69
|
+
return 1
|
|
70
|
+
|
|
71
|
+
if isinstance(unroll, bool):
|
|
72
|
+
return unroll
|
|
73
|
+
|
|
74
|
+
resolved = int(unroll)
|
|
75
|
+
if resolved < 1:
|
|
76
|
+
raise ValueError(f"unroll must be >= 1; got {resolved}.")
|
|
77
|
+
|
|
78
|
+
return resolved
|
|
79
|
+
|
|
80
|
+
|
|
65
81
|
class BlockFoldable(Protocol[M]):
|
|
66
82
|
"""Common interface for :class:`~haliax.nn.Stacked` and :class:`~haliax.nn.BlockSeq`.
|
|
67
83
|
|
|
@@ -84,29 +100,40 @@ class BlockFoldable(Protocol[M]):
|
|
|
84
100
|
prevent_cse: bool = False,
|
|
85
101
|
) -> ModuleInit[S]: ...
|
|
86
102
|
|
|
87
|
-
def scan(self, init: T, *extra_args, **extra_kwargs): ...
|
|
103
|
+
def scan(self, init: T, *extra_args, unroll: int | bool | None = None, **extra_kwargs): ...
|
|
88
104
|
|
|
89
|
-
def fold(self, init: T, *args, **kwargs) -> T: ...
|
|
105
|
+
def fold(self, init: T, *args, unroll: int | bool | None = None, **kwargs) -> T: ...
|
|
90
106
|
|
|
91
107
|
@overload
|
|
92
|
-
def fold_via(
|
|
108
|
+
def fold_via(
|
|
109
|
+
self, fn: FoldFunction[M, P, CarryT], *, unroll: int | bool | None = None
|
|
110
|
+
) -> Callable[Concatenate[CarryT, P], CarryT]: ...
|
|
93
111
|
|
|
94
112
|
@overload
|
|
95
|
-
def fold_via(
|
|
113
|
+
def fold_via(
|
|
114
|
+
self, fn: Callable[[M, CarryT], CarryT], *, unroll: int | bool | None = None
|
|
115
|
+
) -> Callable[[CarryT], CarryT]: ...
|
|
96
116
|
|
|
97
|
-
def fold_via(
|
|
117
|
+
def fold_via(
|
|
118
|
+
self, fn: Callable[..., CarryT], *, unroll: int | bool | None = None
|
|
119
|
+
) -> Callable[Concatenate[CarryT, P], CarryT]: ...
|
|
98
120
|
|
|
99
121
|
@overload
|
|
100
122
|
def scan_via(
|
|
101
|
-
self, fn: ScanFunction[M, CarryT, P, OutputT_co]
|
|
123
|
+
self, fn: ScanFunction[M, CarryT, P, OutputT_co], *, unroll: int | bool | None = None
|
|
102
124
|
) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]: ...
|
|
103
125
|
|
|
104
126
|
@overload
|
|
105
127
|
def scan_via(
|
|
106
|
-
self,
|
|
128
|
+
self,
|
|
129
|
+
fn: Callable[[M, CarryT], tuple[CarryT, OutputT_co]],
|
|
130
|
+
*,
|
|
131
|
+
unroll: int | bool | None = None,
|
|
107
132
|
) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]: ...
|
|
108
133
|
|
|
109
|
-
def scan_via(
|
|
134
|
+
def scan_via(
|
|
135
|
+
self, fn: Callable[..., tuple[CarryT, OutputT_co]], *, unroll: int | bool | None = None
|
|
136
|
+
) -> Callable[P, tuple[CarryT, OutputT_co]]: ...
|
|
110
137
|
|
|
111
138
|
@overload
|
|
112
139
|
def vmap_via(self, fn: VmapFunction[M, P, OutputT_co]) -> Callable[P, OutputT_co]: ...
|
|
@@ -181,15 +208,15 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
181
208
|
|
|
182
209
|
return fn
|
|
183
210
|
|
|
184
|
-
def scan(self, init: T, *extra_args, **extra_kwargs):
|
|
211
|
+
def scan(self, init: T, *extra_args, unroll: int | bool | None = None, **extra_kwargs):
|
|
185
212
|
def do_scan(init, *extra_args, **extra_kwargs):
|
|
186
213
|
out = []
|
|
187
214
|
carry = init
|
|
188
215
|
|
|
189
216
|
for i, block in enumerate(self.blocks):
|
|
190
|
-
|
|
191
217
|
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
192
|
-
functools.partial(BlockSeq._slice_out, self.Block, i),
|
|
218
|
+
functools.partial(BlockSeq._slice_out, self.Block, i),
|
|
219
|
+
(extra_args, extra_kwargs),
|
|
193
220
|
)
|
|
194
221
|
|
|
195
222
|
block_result = block(carry, *block_args, **block_kwargs)
|
|
@@ -210,12 +237,13 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
210
237
|
|
|
211
238
|
return do_scan(init, *extra_args, **extra_kwargs)
|
|
212
239
|
|
|
213
|
-
def fold(self, init: T, *args, **kwargs) -> T:
|
|
240
|
+
def fold(self, init: T, *args, unroll: int | bool | None = None, **kwargs) -> T:
|
|
214
241
|
def do_fold(init, *args, **kwargs):
|
|
215
242
|
carry = init
|
|
216
243
|
for i, block in enumerate(self.blocks):
|
|
217
244
|
(block_args, block_kwargs) = haliax.tree_util.tree_map(
|
|
218
|
-
functools.partial(BlockSeq._slice_out, self.Block, i),
|
|
245
|
+
functools.partial(BlockSeq._slice_out, self.Block, i),
|
|
246
|
+
(args, kwargs),
|
|
219
247
|
)
|
|
220
248
|
carry = block(carry, *block_args, **block_kwargs)
|
|
221
249
|
carry = tree_checkpoint_name(carry, self._carry_ckpt_name)
|
|
@@ -224,12 +252,16 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
224
252
|
return do_fold(init, *args, **kwargs)
|
|
225
253
|
|
|
226
254
|
@overload
|
|
227
|
-
def fold_via(
|
|
255
|
+
def fold_via(
|
|
256
|
+
self, fn: FoldFunction[M, P, CarryT], *, unroll: int | bool | None = None
|
|
257
|
+
) -> Callable[Concatenate[CarryT, P], CarryT]: ...
|
|
228
258
|
|
|
229
259
|
@overload
|
|
230
|
-
def fold_via(
|
|
260
|
+
def fold_via(
|
|
261
|
+
self, fn: Callable[[M, CarryT], CarryT], *, unroll: int | bool | None = None
|
|
262
|
+
) -> Callable[[CarryT], CarryT]: ...
|
|
231
263
|
|
|
232
|
-
def fold_via(self, fn: Callable[..., CarryT]):
|
|
264
|
+
def fold_via(self, fn: Callable[..., CarryT], *, unroll: int | bool | None = None):
|
|
233
265
|
"""Return a function that folds over the sequence using ``fn``.
|
|
234
266
|
|
|
235
267
|
``fn`` should take a block and a carry and return a new carry. The
|
|
@@ -247,15 +279,18 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
247
279
|
|
|
248
280
|
@overload
|
|
249
281
|
def scan_via(
|
|
250
|
-
self, fn: ScanFunction[M, CarryT, P, OutputT_co]
|
|
282
|
+
self, fn: ScanFunction[M, CarryT, P, OutputT_co], *, unroll: int | bool | None = None
|
|
251
283
|
) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]: ...
|
|
252
284
|
|
|
253
285
|
@overload
|
|
254
286
|
def scan_via(
|
|
255
|
-
self,
|
|
287
|
+
self,
|
|
288
|
+
fn: Callable[[M, CarryT], tuple[CarryT, OutputT_co]],
|
|
289
|
+
*,
|
|
290
|
+
unroll: int | bool | None = None,
|
|
256
291
|
) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]: ...
|
|
257
292
|
|
|
258
|
-
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]]):
|
|
293
|
+
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]], *, unroll: int | bool | None = None):
|
|
259
294
|
"""Return a function that scans over the sequence using ``fn``.
|
|
260
295
|
|
|
261
296
|
``fn`` should take a block and a carry and return ``(carry, output)``.
|
|
@@ -447,7 +482,7 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
447
482
|
|
|
448
483
|
return fn
|
|
449
484
|
|
|
450
|
-
def scan(self, init, *extra_args, **extra_kwargs):
|
|
485
|
+
def scan(self, init, *extra_args, unroll: int | bool | None = None, **extra_kwargs):
|
|
451
486
|
"""
|
|
452
487
|
Scan over the stacked module. This is the same as a for loop that applies each instance of the module in sequence
|
|
453
488
|
to the input, passing the output of one instance to the next instance. It returns a stack of outputs as
|
|
@@ -475,19 +510,24 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
475
510
|
|
|
476
511
|
"""
|
|
477
512
|
|
|
513
|
+
resolved_unroll = _normalize_unroll(unroll, self.Block.size)
|
|
514
|
+
|
|
478
515
|
def do_block(carry, block, *args, **kwargs):
|
|
479
516
|
carry, out = block(carry, *args, **kwargs)
|
|
480
517
|
return carry, out
|
|
481
518
|
|
|
482
519
|
def do_scan(init, *extra_args, **extra_kwargs):
|
|
483
|
-
carry, out = haliax.scan(
|
|
484
|
-
|
|
485
|
-
|
|
520
|
+
carry, out = haliax.scan(
|
|
521
|
+
do_block,
|
|
522
|
+
self.Block,
|
|
523
|
+
remat=self.gradient_checkpointing,
|
|
524
|
+
unroll=resolved_unroll,
|
|
525
|
+
)(init, self.stacked, *extra_args, **extra_kwargs)
|
|
486
526
|
return carry, out
|
|
487
527
|
|
|
488
528
|
return do_scan(init, *extra_args, **extra_kwargs)
|
|
489
529
|
|
|
490
|
-
def fold(self, init, *args, **kwargs):
|
|
530
|
+
def fold(self, init, *args, unroll: int | bool | None = None, **kwargs):
|
|
491
531
|
"""
|
|
492
532
|
Fold over the stacked module. This is the same as a for loop that applies each instance of the module in sequence
|
|
493
533
|
to the input, passing the output of one instance to the next instance.
|
|
@@ -510,66 +550,88 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
|
|
|
510
550
|
|
|
511
551
|
"""
|
|
512
552
|
|
|
553
|
+
resolved_unroll = _normalize_unroll(unroll, self.Block.size)
|
|
554
|
+
|
|
513
555
|
def do_block(carry, block, *args, **kwargs):
|
|
514
556
|
carry = block(carry, *args, **kwargs)
|
|
515
557
|
return carry
|
|
516
558
|
|
|
517
559
|
def do_fold(init, *extra_args, **extra_kwargs):
|
|
518
|
-
carry = haliax.fold(
|
|
519
|
-
|
|
520
|
-
|
|
560
|
+
carry = haliax.fold(
|
|
561
|
+
do_block,
|
|
562
|
+
self.Block,
|
|
563
|
+
remat=self.gradient_checkpointing,
|
|
564
|
+
unroll=resolved_unroll,
|
|
565
|
+
)(init, self.stacked, *extra_args, **extra_kwargs)
|
|
521
566
|
return carry
|
|
522
567
|
|
|
523
568
|
return do_fold(init, *args, **kwargs)
|
|
524
569
|
|
|
525
570
|
@overload
|
|
526
|
-
def fold_via(
|
|
571
|
+
def fold_via(
|
|
572
|
+
self, fn: FoldFunction[M, P, CarryT], *, unroll: int | bool | None = None
|
|
573
|
+
) -> Callable[Concatenate[CarryT, P], CarryT]: ...
|
|
527
574
|
|
|
528
575
|
@overload
|
|
529
|
-
def fold_via(
|
|
576
|
+
def fold_via(
|
|
577
|
+
self, fn: Callable[[M, CarryT], CarryT], *, unroll: int | bool | None = None
|
|
578
|
+
) -> Callable[[CarryT], CarryT]: ...
|
|
530
579
|
|
|
531
|
-
def fold_via(self, fn: Callable[..., CarryT]):
|
|
580
|
+
def fold_via(self, fn: Callable[..., CarryT], *, unroll: int | bool | None = None):
|
|
532
581
|
"""Return a function that folds over the stack using ``fn``.
|
|
533
582
|
|
|
534
583
|
``fn`` should take a block and a carry and return a new carry. The
|
|
535
584
|
returned function mirrors :func:`haliax.fold` over the block axis.
|
|
536
585
|
"""
|
|
537
586
|
|
|
587
|
+
resolved_unroll = _normalize_unroll(unroll, self.Block.size)
|
|
588
|
+
|
|
538
589
|
def do_block(carry: CarryT, block: M, *args, **kwargs) -> CarryT:
|
|
539
590
|
return fn(block, carry, *args, **kwargs)
|
|
540
591
|
|
|
541
592
|
def do_fold(init: CarryT, *args, **kwargs) -> CarryT:
|
|
542
|
-
return haliax.fold(
|
|
543
|
-
|
|
544
|
-
|
|
593
|
+
return haliax.fold(
|
|
594
|
+
do_block,
|
|
595
|
+
self.Block,
|
|
596
|
+
remat=self.gradient_checkpointing,
|
|
597
|
+
unroll=resolved_unroll,
|
|
598
|
+
)(init, self.stacked, *args, **kwargs)
|
|
545
599
|
|
|
546
600
|
return do_fold
|
|
547
601
|
|
|
548
602
|
@overload
|
|
549
603
|
def scan_via(
|
|
550
|
-
self, fn: ScanFunction[M, CarryT, P, OutputT_co]
|
|
604
|
+
self, fn: ScanFunction[M, CarryT, P, OutputT_co], *, unroll: int | bool | None = None
|
|
551
605
|
) -> Callable[Concatenate[CarryT, P], tuple[CarryT, OutputT_co]]: ...
|
|
552
606
|
|
|
553
607
|
@overload
|
|
554
608
|
def scan_via(
|
|
555
|
-
self,
|
|
609
|
+
self,
|
|
610
|
+
fn: Callable[[M, CarryT], tuple[CarryT, OutputT_co]],
|
|
611
|
+
*,
|
|
612
|
+
unroll: int | bool | None = None,
|
|
556
613
|
) -> Callable[[CarryT], tuple[CarryT, OutputT_co]]: ...
|
|
557
614
|
|
|
558
|
-
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]]):
|
|
615
|
+
def scan_via(self, fn: Callable[..., tuple[CarryT, OutputT_co]], *, unroll: int | bool | None = None):
|
|
559
616
|
"""Return a function that scans over the stack using ``fn``.
|
|
560
617
|
|
|
561
618
|
``fn`` should take a block and a carry and return ``(carry, output)``.
|
|
562
619
|
Semantics match :func:`haliax.scan` over the block axis.
|
|
563
620
|
"""
|
|
564
621
|
|
|
622
|
+
resolved_unroll = _normalize_unroll(unroll, self.Block.size)
|
|
623
|
+
|
|
565
624
|
def do_block(carry: CarryT, block: M, *args, **kwargs) -> tuple[CarryT, OutputT_co]:
|
|
566
625
|
carry, output = fn(block, carry, *args, **kwargs)
|
|
567
626
|
return carry, output
|
|
568
627
|
|
|
569
628
|
def do_scan(init: CarryT, *args, **kwargs) -> tuple[CarryT, OutputT_co]:
|
|
570
|
-
return haliax.scan(
|
|
571
|
-
|
|
572
|
-
|
|
629
|
+
return haliax.scan(
|
|
630
|
+
do_block,
|
|
631
|
+
self.Block,
|
|
632
|
+
remat=self.gradient_checkpointing,
|
|
633
|
+
unroll=resolved_unroll,
|
|
634
|
+
)(init, self.stacked, *args, **kwargs)
|
|
573
635
|
|
|
574
636
|
return do_scan
|
|
575
637
|
|
|
@@ -707,7 +769,7 @@ def _unstack_state_dict(state_dict: StateDict, prefix: str | None = None) -> Sta
|
|
|
707
769
|
for k, v in state_dict.items():
|
|
708
770
|
if k.startswith(prefix) and is_jax_or_hax_array_like(v):
|
|
709
771
|
for i, v_i in enumerate(v):
|
|
710
|
-
new_dict[f"{prefix}{i}.{k[len(prefix):]}"] = v_i
|
|
772
|
+
new_dict[f"{prefix}{i}.{k[len(prefix) :]}"] = v_i
|
|
711
773
|
else:
|
|
712
774
|
new_dict[k] = v
|
|
713
775
|
|
|
@@ -31,7 +31,7 @@ def test_mup_embedding_unembedding_scale():
|
|
|
31
31
|
Embed = (hax.Axis("E", 3),)
|
|
32
32
|
|
|
33
33
|
weight = hax.ones(hax.concat_axis_specs(Vocab, Embed))
|
|
34
|
-
layer = Embedding(weight=weight, Vocab=Vocab, Embed=Embed,
|
|
34
|
+
layer = Embedding(weight=weight, Vocab=Vocab, Embed=Embed, _reparam_cls=EmbeddingMup)
|
|
35
35
|
|
|
36
36
|
scale = layer.reparam.unembed_active_scale
|
|
37
37
|
assert scale == pytest.approx(1.0 / hax.axis_size(Embed))
|
|
@@ -11,7 +11,12 @@ import pytest
|
|
|
11
11
|
|
|
12
12
|
import haliax as hax
|
|
13
13
|
from haliax.nn import Linear
|
|
14
|
-
from haliax.nn.mup import
|
|
14
|
+
from haliax.nn.mup import (
|
|
15
|
+
InputLinearMup,
|
|
16
|
+
LinearStandardParam,
|
|
17
|
+
HiddenLinearMup,
|
|
18
|
+
OutputLinearMup,
|
|
19
|
+
)
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
@pytest.mark.parametrize("out_first", [True, False])
|
|
@@ -37,8 +42,8 @@ def test_mup_linear_call_matches_linear():
|
|
|
37
42
|
weight = hax.ones(hax.concat_axis_specs(Out, In)) * 0.5
|
|
38
43
|
bias = hax.full(Out, 0.25)
|
|
39
44
|
|
|
40
|
-
linear = Linear(weight, bias, In, Out,
|
|
41
|
-
mup = Linear(weight, bias, In, Out,
|
|
45
|
+
linear = Linear(weight, bias, In, Out, _reparam_cls=LinearStandardParam)
|
|
46
|
+
mup = Linear(weight, bias, In, Out, _reparam_cls=InputLinearMup)
|
|
42
47
|
|
|
43
48
|
inputs = hax.full(hax.concat_axis_specs(Batch, In), 2.0)
|
|
44
49
|
|
|
@@ -109,8 +114,8 @@ def test_input_linear_behaves_like_base_linear():
|
|
|
109
114
|
weight = hax.ones((Out, In)) * 0.1
|
|
110
115
|
bias = hax.zeros(Out)
|
|
111
116
|
|
|
112
|
-
linear = Linear(weight, bias, In, Out,
|
|
113
|
-
input_linear = Linear(weight, bias, In, Out,
|
|
117
|
+
linear = Linear(weight, bias, In, Out, _reparam_cls=LinearStandardParam)
|
|
118
|
+
input_linear = Linear(weight, bias, In, Out, _reparam_cls=InputLinearMup)
|
|
114
119
|
|
|
115
120
|
inputs = hax.random.normal(jrandom.PRNGKey(5), (Batch, In))
|
|
116
121
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import equinox as eqx
|
|
7
7
|
import jax
|
|
8
8
|
import pytest
|
|
9
|
+
import warnings
|
|
9
10
|
from equinox import filter_grad
|
|
10
11
|
|
|
11
12
|
import haliax as hax
|
|
@@ -164,6 +165,59 @@ def test_scan_with_aux_named_args():
|
|
|
164
165
|
assert hax.all(hax.isclose(z_scan, z_seq_scan, atol=1e-5))
|
|
165
166
|
|
|
166
167
|
|
|
168
|
+
def test_blockseq_unroll_never_warns():
|
|
169
|
+
class FoldModule(eqx.Module):
|
|
170
|
+
weight: hax.NamedArray
|
|
171
|
+
|
|
172
|
+
def __call__(self, carry: hax.NamedArray) -> hax.NamedArray:
|
|
173
|
+
return carry + self.weight
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def init(weight):
|
|
177
|
+
return FoldModule(weight=weight)
|
|
178
|
+
|
|
179
|
+
class ScanModule(eqx.Module):
|
|
180
|
+
weight: hax.NamedArray
|
|
181
|
+
|
|
182
|
+
def __call__(self, carry: hax.NamedArray) -> tuple[hax.NamedArray, hax.NamedArray]:
|
|
183
|
+
updated = carry + self.weight
|
|
184
|
+
return updated, updated
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def init(weight):
|
|
188
|
+
return ScanModule(weight=weight)
|
|
189
|
+
|
|
190
|
+
Block = hax.Axis("block", 3)
|
|
191
|
+
Value = hax.Axis("value", 2)
|
|
192
|
+
weights = hax.random.uniform(jax.random.PRNGKey(0), (Block, Value))
|
|
193
|
+
|
|
194
|
+
fold_seq = BlockSeq.init(Block, FoldModule)(weight=weights)
|
|
195
|
+
scan_seq = BlockSeq.init(Block, ScanModule)(weight=weights)
|
|
196
|
+
|
|
197
|
+
init_carry = hax.zeros(Value)
|
|
198
|
+
|
|
199
|
+
def fold_step(block: FoldModule, carry: hax.NamedArray) -> hax.NamedArray:
|
|
200
|
+
return block(carry)
|
|
201
|
+
|
|
202
|
+
def scan_step(block: ScanModule, carry: hax.NamedArray) -> tuple[hax.NamedArray, hax.NamedArray]:
|
|
203
|
+
return block(carry)
|
|
204
|
+
|
|
205
|
+
with warnings.catch_warnings(record=True) as caught:
|
|
206
|
+
warnings.simplefilter("always")
|
|
207
|
+
_ = fold_seq.fold(init_carry, unroll=2)
|
|
208
|
+
_ = fold_seq.fold(init_carry, unroll=True)
|
|
209
|
+
_ = fold_seq.fold(init_carry, unroll=False)
|
|
210
|
+
_ = fold_seq.fold_via(fold_step, unroll=True)(init_carry)
|
|
211
|
+
_ = fold_seq.fold_via(fold_step, unroll=False)(init_carry)
|
|
212
|
+
_ = scan_seq.scan(init_carry, unroll=2)
|
|
213
|
+
_ = scan_seq.scan(init_carry, unroll=True)
|
|
214
|
+
_ = scan_seq.scan(init_carry, unroll=False)
|
|
215
|
+
_ = scan_seq.scan_via(scan_step, unroll=True)(init_carry)
|
|
216
|
+
_ = scan_seq.scan_via(scan_step, unroll=False)(init_carry)
|
|
217
|
+
|
|
218
|
+
assert not caught
|
|
219
|
+
|
|
220
|
+
|
|
167
221
|
def test_stacked_to_state_dict():
|
|
168
222
|
class Module(eqx.Module):
|
|
169
223
|
named: hax.NamedArray
|
|
@@ -239,8 +293,18 @@ E = hax.Axis("E", 10)
|
|
|
239
293
|
[(E.size,), (Block.size, E.size), (Block.size, E.size)],
|
|
240
294
|
None,
|
|
241
295
|
),
|
|
242
|
-
(
|
|
243
|
-
|
|
296
|
+
(
|
|
297
|
+
"simple",
|
|
298
|
+
ScanCheckpointPolicy(simple=True),
|
|
299
|
+
[(E.size,), (Block.size, E.size)],
|
|
300
|
+
None,
|
|
301
|
+
),
|
|
302
|
+
(
|
|
303
|
+
"nested",
|
|
304
|
+
ScanCheckpointPolicy(simple=True, nested=2),
|
|
305
|
+
[(E.size,), (2, E.size)],
|
|
306
|
+
None,
|
|
307
|
+
),
|
|
244
308
|
(
|
|
245
309
|
"sin_offload",
|
|
246
310
|
ScanCheckpointPolicy(save_carries=True, offload_block_internals=["sin"]),
|
|
@@ -371,6 +435,97 @@ def test_scan_via():
|
|
|
371
435
|
assert hax.all(hax.isclose(outs, expected_outs))
|
|
372
436
|
|
|
373
437
|
|
|
438
|
+
def test_scan_via_with_unroll():
|
|
439
|
+
class Module(eqx.Module):
|
|
440
|
+
w: hax.NamedArray
|
|
441
|
+
|
|
442
|
+
def with_output(self, x):
|
|
443
|
+
out = x + self.w
|
|
444
|
+
return out, 2 * self.w
|
|
445
|
+
|
|
446
|
+
def __call__(self, carry):
|
|
447
|
+
return carry + self.w, 2 * self.w
|
|
448
|
+
|
|
449
|
+
@staticmethod
|
|
450
|
+
def init(named):
|
|
451
|
+
return Module(w=named)
|
|
452
|
+
|
|
453
|
+
Block = hax.Axis("block", 4)
|
|
454
|
+
E = hax.Axis("E", 6)
|
|
455
|
+
|
|
456
|
+
named = hax.random.uniform(jax.random.PRNGKey(0), (Block, E))
|
|
457
|
+
m = Stacked.init(Block, Module)(named=named)
|
|
458
|
+
|
|
459
|
+
x = hax.random.uniform(jax.random.PRNGKey(1), (E,))
|
|
460
|
+
|
|
461
|
+
default_carry, default_outs = m.scan_via(Module.with_output)(x)
|
|
462
|
+
carry, outs = m.scan_via(Module.with_output, unroll=2)(x)
|
|
463
|
+
|
|
464
|
+
assert hax.all(hax.isclose(carry, default_carry))
|
|
465
|
+
assert hax.all(hax.isclose(outs, default_outs))
|
|
466
|
+
|
|
467
|
+
default_carry_direct, default_outs_direct = m.scan(x)
|
|
468
|
+
carry_direct, outs_direct = m.scan(x, unroll=2)
|
|
469
|
+
|
|
470
|
+
assert hax.all(hax.isclose(carry_direct, default_carry_direct))
|
|
471
|
+
assert hax.all(hax.isclose(outs_direct, default_outs_direct))
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def test_scan_via_with_bool_unroll(monkeypatch):
|
|
475
|
+
class Module(eqx.Module):
|
|
476
|
+
w: hax.NamedArray
|
|
477
|
+
|
|
478
|
+
def with_output(self, x):
|
|
479
|
+
out = x + self.w
|
|
480
|
+
return out, 2 * self.w
|
|
481
|
+
|
|
482
|
+
def __call__(self, carry):
|
|
483
|
+
return carry + self.w, 2 * self.w
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def init(named):
|
|
487
|
+
return Module(w=named)
|
|
488
|
+
|
|
489
|
+
Block = hax.Axis("block", 4)
|
|
490
|
+
E = hax.Axis("E", 6)
|
|
491
|
+
|
|
492
|
+
named = hax.random.uniform(jax.random.PRNGKey(0), (Block, E))
|
|
493
|
+
m = Stacked.init(Block, Module)(named=named)
|
|
494
|
+
|
|
495
|
+
x = hax.random.uniform(jax.random.PRNGKey(1), (E,))
|
|
496
|
+
|
|
497
|
+
default_carry_via, default_outs_via = m.scan_via(Module.with_output)(x)
|
|
498
|
+
default_carry_direct, default_outs_direct = m.scan(x)
|
|
499
|
+
|
|
500
|
+
import haliax.nn.scan as hnn_scan
|
|
501
|
+
|
|
502
|
+
scan_calls: list[int | None] = []
|
|
503
|
+
original_scan = hnn_scan.haliax.scan
|
|
504
|
+
|
|
505
|
+
def wrapped_scan(*args, **kwargs):
|
|
506
|
+
scan_calls.append(kwargs.get("unroll"))
|
|
507
|
+
return original_scan(*args, **kwargs)
|
|
508
|
+
|
|
509
|
+
monkeypatch.setattr(hnn_scan.haliax, "scan", wrapped_scan)
|
|
510
|
+
|
|
511
|
+
carry_true, outs_true = m.scan_via(Module.with_output, unroll=True)(x)
|
|
512
|
+
carry_false, outs_false = m.scan_via(Module.with_output, unroll=False)(x)
|
|
513
|
+
carry_true_direct, outs_true_direct = m.scan(x, unroll=True)
|
|
514
|
+
carry_false_direct, outs_false_direct = m.scan(x, unroll=False)
|
|
515
|
+
|
|
516
|
+
assert scan_calls == [True, False, True, False]
|
|
517
|
+
|
|
518
|
+
assert hax.all(hax.isclose(carry_true, default_carry_via))
|
|
519
|
+
assert hax.all(hax.isclose(outs_true, default_outs_via))
|
|
520
|
+
assert hax.all(hax.isclose(carry_false, default_carry_via))
|
|
521
|
+
assert hax.all(hax.isclose(outs_false, default_outs_via))
|
|
522
|
+
|
|
523
|
+
assert hax.all(hax.isclose(carry_true_direct, default_carry_direct))
|
|
524
|
+
assert hax.all(hax.isclose(outs_true_direct, default_outs_direct))
|
|
525
|
+
assert hax.all(hax.isclose(carry_false_direct, default_carry_direct))
|
|
526
|
+
assert hax.all(hax.isclose(outs_false_direct, default_outs_direct))
|
|
527
|
+
|
|
528
|
+
|
|
374
529
|
def test_scan_via_multi_args():
|
|
375
530
|
class Module(eqx.Module):
|
|
376
531
|
w: hax.NamedArray
|
|
@@ -513,6 +668,88 @@ def test_fold_via_multi_args():
|
|
|
513
668
|
assert hax.all(hax.isclose(result, expected))
|
|
514
669
|
|
|
515
670
|
|
|
671
|
+
def test_fold_via_with_unroll():
|
|
672
|
+
class Module(eqx.Module):
|
|
673
|
+
w: hax.NamedArray
|
|
674
|
+
|
|
675
|
+
def intermediate(self, x):
|
|
676
|
+
return x + 2 * self.w
|
|
677
|
+
|
|
678
|
+
def __call__(self, carry):
|
|
679
|
+
return carry + self.w
|
|
680
|
+
|
|
681
|
+
@staticmethod
|
|
682
|
+
def init(named):
|
|
683
|
+
return Module(w=named)
|
|
684
|
+
|
|
685
|
+
Block = hax.Axis("block", 3)
|
|
686
|
+
E = hax.Axis("E", 5)
|
|
687
|
+
|
|
688
|
+
named = hax.random.uniform(jax.random.PRNGKey(0), (Block, E))
|
|
689
|
+
m = Stacked.init(Block, Module)(named=named)
|
|
690
|
+
|
|
691
|
+
x = hax.random.uniform(jax.random.PRNGKey(1), (E,))
|
|
692
|
+
|
|
693
|
+
default_result = m.fold_via(Module.intermediate)(x)
|
|
694
|
+
result_unroll = m.fold_via(Module.intermediate, unroll=2)(x)
|
|
695
|
+
|
|
696
|
+
assert hax.all(hax.isclose(result_unroll, default_result))
|
|
697
|
+
|
|
698
|
+
default_fold = m.fold(x)
|
|
699
|
+
fold_unroll = m.fold(x, unroll=2)
|
|
700
|
+
|
|
701
|
+
assert hax.all(hax.isclose(fold_unroll, default_fold))
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def test_fold_with_bool_unroll(monkeypatch):
|
|
705
|
+
class Module(eqx.Module):
|
|
706
|
+
w: hax.NamedArray
|
|
707
|
+
|
|
708
|
+
def __call__(self, x):
|
|
709
|
+
return x + self.w
|
|
710
|
+
|
|
711
|
+
def intermediate(self, x):
|
|
712
|
+
return x + 2 * self.w
|
|
713
|
+
|
|
714
|
+
@staticmethod
|
|
715
|
+
def init(named):
|
|
716
|
+
return Module(w=named)
|
|
717
|
+
|
|
718
|
+
Block = hax.Axis("block", 3)
|
|
719
|
+
E = hax.Axis("E", 5)
|
|
720
|
+
|
|
721
|
+
named = hax.random.uniform(jax.random.PRNGKey(0), (Block, E))
|
|
722
|
+
m = Stacked.init(Block, Module)(named=named)
|
|
723
|
+
|
|
724
|
+
x = hax.random.uniform(jax.random.PRNGKey(1), (E,))
|
|
725
|
+
|
|
726
|
+
default_fold = m.fold(x)
|
|
727
|
+
default_fold_via = m.fold_via(Module.intermediate)(x)
|
|
728
|
+
|
|
729
|
+
import haliax.nn.scan as hnn_scan
|
|
730
|
+
|
|
731
|
+
fold_calls: list[int | None] = []
|
|
732
|
+
original_fold = hnn_scan.haliax.fold
|
|
733
|
+
|
|
734
|
+
def wrapped_fold(*args, **kwargs):
|
|
735
|
+
fold_calls.append(kwargs.get("unroll"))
|
|
736
|
+
return original_fold(*args, **kwargs)
|
|
737
|
+
|
|
738
|
+
monkeypatch.setattr(hnn_scan.haliax, "fold", wrapped_fold)
|
|
739
|
+
|
|
740
|
+
fold_true = m.fold(x, unroll=True)
|
|
741
|
+
fold_false = m.fold(x, unroll=False)
|
|
742
|
+
via_true = m.fold_via(Module.intermediate, unroll=True)(x)
|
|
743
|
+
via_false = m.fold_via(Module.intermediate, unroll=False)(x)
|
|
744
|
+
|
|
745
|
+
assert fold_calls == [True, False, True, False]
|
|
746
|
+
|
|
747
|
+
assert hax.all(hax.isclose(fold_true, default_fold))
|
|
748
|
+
assert hax.all(hax.isclose(fold_false, default_fold))
|
|
749
|
+
assert hax.all(hax.isclose(via_true, default_fold_via))
|
|
750
|
+
assert hax.all(hax.isclose(via_false, default_fold_via))
|
|
751
|
+
|
|
752
|
+
|
|
516
753
|
def test_fold_via_static_args():
|
|
517
754
|
class Module(eqx.Module):
|
|
518
755
|
w: hax.NamedArray
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|
|
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
|