haliax 1.4.dev360__tar.gz → 1.4.dev363__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.dev360 → haliax-1.4.dev363}/.github/workflows/run_tests.yaml +1 -1
- {haliax-1.4.dev360 → haliax-1.4.dev363}/PKG-INFO +1 -1
- haliax-1.4.dev363/docs/typing.md +61 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/mkdocs.yml +1 -0
- haliax-1.4.dev363/src/haliax/__about__.py +1 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/__init__.py +6 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/core.py +173 -1
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/activations.py +0 -1
- haliax-1.4.dev363/src/haliax/typing.py +88 -0
- haliax-1.4.dev363/tests/test_dtype_typing.py +41 -0
- haliax-1.4.dev363/tests/test_namedarray_typing.py +64 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_nn.py +1 -1
- haliax-1.4.dev360/src/haliax/__about__.py +0 -1
- {haliax-1.4.dev360 → haliax-1.4.dev363}/.coveragerc +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/.flake8 +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/.gitignore +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/LICENSE +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/README.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/api.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/css/material.css +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/faq.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/fp8.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/index.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/indexing.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/matmul.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/nn.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/partitioning.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/rearrange.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/requirements.txt +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/scan.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/state-dict.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/tutorial.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/vmap.md +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/pyproject.toml +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/scan.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/state_dict.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/jax_utils.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/embedding.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/linear.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/normalization.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/scan.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/random.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/types.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/util.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/core_test.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_attention.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_axis.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_conv.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_debug.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_dot.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_hof.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_int8.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_ops.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_pool.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_random.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_scan.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_scatter_gather.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_utils.py +0 -0
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
run: |
|
|
18
18
|
python -m pip install --upgrade pip
|
|
19
19
|
pip install flake8 pytest
|
|
20
|
-
pip install
|
|
20
|
+
pip install -e .[dev]
|
|
21
21
|
- name: Test with pytest
|
|
22
22
|
run: |
|
|
23
23
|
XLA_FLAGS=--xla_force_host_platform_device_count=8 PYTHONPATH=tests:src:. pytest tests
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev363
|
|
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/
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# NamedArray Type Annotations
|
|
2
|
+
|
|
3
|
+
Haliax supports a lightweight syntax for specifying the axes of a `NamedArray`
|
|
4
|
+
in type annotations. Internally, `Named[...]` expands to
|
|
5
|
+
`Annotated[NamedArray, axes]`, so it works well with static type checkers like
|
|
6
|
+
``mypy``. The syntax mirrors normal indexing with axis names. Some examples:
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from haliax import Named
|
|
10
|
+
|
|
11
|
+
arr: Named["batch", "embed"]
|
|
12
|
+
arr: Named["batch embed ..."] # starts with these axes
|
|
13
|
+
arr: Named["... embed"] # ends with this axis
|
|
14
|
+
arr: Named["batch ... embed"] # contains these axes in order
|
|
15
|
+
arr: Named[{"batch", "embed"}] # has exactly these axes, order ignored
|
|
16
|
+
arr: Named[{"batch", "embed", ...}] # has at least these axes
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
At runtime you can verify that a `NamedArray` conforms to a particular
|
|
20
|
+
annotation using `matches_axes`:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
if not arr.matches_axes(Named["batch embed ..."]):
|
|
24
|
+
raise ValueError("unexpected axes")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## DType-aware annotations
|
|
28
|
+
|
|
29
|
+
Sometimes it is useful to express both the axes **and** the dtype in the type
|
|
30
|
+
annotation. The :mod:`haliax.typing` module defines symbolic types for all of
|
|
31
|
+
JAX's common dtypes that can be indexed just like ``Named``. In documentation
|
|
32
|
+
examples we'll use ``import haliax.typing as ht``:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import haliax.typing as ht
|
|
36
|
+
|
|
37
|
+
def foo(x: ht.f32["batch"]):
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def bar(x: ht.i32["batch"]):
|
|
41
|
+
...
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For convenience the module also provides aggregate categories ``Float``,
|
|
45
|
+
``Complex``, ``Int`` and ``UInt`` that match any floating point, complex,
|
|
46
|
+
signed integer or unsigned integer dtype respectively:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
def baz(x: ht.Float["batch"]):
|
|
50
|
+
...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
At runtime ``matches_axes`` also checks the dtype when one is present:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from haliax import Axis, zeros
|
|
57
|
+
import haliax.typing as ht
|
|
58
|
+
|
|
59
|
+
arr = zeros({"batch": 4})
|
|
60
|
+
assert arr.matches_axes(ht.f32["batch"]) # dtype and axes both match
|
|
61
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.dev363"
|
|
@@ -40,7 +40,10 @@ from .axis import (
|
|
|
40
40
|
selects_axis,
|
|
41
41
|
)
|
|
42
42
|
from .core import (
|
|
43
|
+
Named,
|
|
43
44
|
NamedArray,
|
|
45
|
+
NamedArrayAxes,
|
|
46
|
+
NamedArrayAxesSpec,
|
|
44
47
|
NamedOrNumeric,
|
|
45
48
|
are_shape_checks_enabled,
|
|
46
49
|
broadcast_arrays,
|
|
@@ -926,6 +929,9 @@ __all__ = [
|
|
|
926
929
|
"make_axes",
|
|
927
930
|
"axis_name",
|
|
928
931
|
"axis_size",
|
|
932
|
+
"NamedArrayAxesSpec",
|
|
933
|
+
"NamedArrayAxes",
|
|
934
|
+
"Named",
|
|
929
935
|
"NamedArray",
|
|
930
936
|
"broadcast_to",
|
|
931
937
|
"broadcast_axis",
|
|
@@ -58,9 +58,139 @@ def are_shape_checks_enabled():
|
|
|
58
58
|
return _ENABLE_SHAPE_CHECKS
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class NamedArrayAxes:
|
|
63
|
+
"""Representation of a :class:`NamedArray`'s axes for type annotations."""
|
|
64
|
+
|
|
65
|
+
before: Tuple[str, ...]
|
|
66
|
+
"""Names that must appear before any optional ellipsis."""
|
|
67
|
+
|
|
68
|
+
after: Tuple[str, ...] = ()
|
|
69
|
+
"""Names that must appear after any optional ellipsis."""
|
|
70
|
+
|
|
71
|
+
ordered: bool = True
|
|
72
|
+
"""Whether the axes must appear in this order."""
|
|
73
|
+
|
|
74
|
+
subset: bool = False
|
|
75
|
+
"""If ``True``, other axes may appear where the ellipsis is located."""
|
|
76
|
+
|
|
77
|
+
dtype: typing.Any | None = None
|
|
78
|
+
"""Optional dtype that the array should have."""
|
|
79
|
+
|
|
80
|
+
def __repr__(self) -> str:
|
|
81
|
+
dtype_prefix = ""
|
|
82
|
+
if self.dtype is not None:
|
|
83
|
+
dtype_obj = self.dtype
|
|
84
|
+
if hasattr(dtype_obj, "category"):
|
|
85
|
+
dtype_name = dtype_obj.name
|
|
86
|
+
else:
|
|
87
|
+
dtype_name = getattr(dtype_obj, "name", str(dtype_obj))
|
|
88
|
+
dtype_prefix = f"{dtype_name} "
|
|
89
|
+
|
|
90
|
+
if self.ordered:
|
|
91
|
+
parts = list(self.before)
|
|
92
|
+
if self.subset:
|
|
93
|
+
parts.append("...")
|
|
94
|
+
parts.extend(self.after)
|
|
95
|
+
spec = " ".join(parts)
|
|
96
|
+
return f"NamedArray[{dtype_prefix}{spec}]"
|
|
97
|
+
else:
|
|
98
|
+
part = ", ".join(self.before)
|
|
99
|
+
if self.subset:
|
|
100
|
+
if part:
|
|
101
|
+
part += ", ..."
|
|
102
|
+
else:
|
|
103
|
+
part = "..."
|
|
104
|
+
return f"NamedArray[{dtype_prefix}{{{part}}}]"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# a specification for NamedArray axes used in type annotations
|
|
108
|
+
NamedArrayAxesSpec = Union[
|
|
109
|
+
NamedArrayAxes,
|
|
110
|
+
str,
|
|
111
|
+
Sequence[str | EllipsisType],
|
|
112
|
+
set[str | EllipsisType],
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _parse_namedarray_axes(
|
|
117
|
+
item: NamedArrayAxesSpec | typing.Annotated["NamedArray", NamedArrayAxes]
|
|
118
|
+
) -> NamedArrayAxes:
|
|
119
|
+
origin = typing.get_origin(item)
|
|
120
|
+
if origin is typing.Annotated:
|
|
121
|
+
args = typing.get_args(item)
|
|
122
|
+
if len(args) >= 2:
|
|
123
|
+
item = args[1]
|
|
124
|
+
if isinstance(item, NamedArrayAxes):
|
|
125
|
+
return item
|
|
126
|
+
if isinstance(item, str):
|
|
127
|
+
parts = item.split()
|
|
128
|
+
if parts.count("...") > 1:
|
|
129
|
+
raise TypeError("Only one ellipsis allowed in NamedArray typing spec")
|
|
130
|
+
if "..." in parts:
|
|
131
|
+
idx = parts.index("...")
|
|
132
|
+
before_parts = tuple(parts[:idx])
|
|
133
|
+
after_parts = tuple(parts[idx + 1 :])
|
|
134
|
+
return NamedArrayAxes(before_parts, after_parts, ordered=True, subset=True)
|
|
135
|
+
else:
|
|
136
|
+
return NamedArrayAxes(tuple(parts), (), ordered=True, subset=False)
|
|
137
|
+
if isinstance(item, set) or isinstance(item, frozenset):
|
|
138
|
+
subset = False
|
|
139
|
+
names_list: List[str] = []
|
|
140
|
+
for part in item:
|
|
141
|
+
if part is Ellipsis:
|
|
142
|
+
if subset:
|
|
143
|
+
raise TypeError("Only one ellipsis allowed in NamedArray typing spec")
|
|
144
|
+
subset = True
|
|
145
|
+
else:
|
|
146
|
+
if not isinstance(part, str):
|
|
147
|
+
raise TypeError(f"Invalid axis spec: {part}")
|
|
148
|
+
names_list.append(part)
|
|
149
|
+
return NamedArrayAxes(tuple(names_list), (), ordered=False, subset=subset)
|
|
150
|
+
if isinstance(item, (tuple, list)):
|
|
151
|
+
subset = False
|
|
152
|
+
before_list: List[str] = []
|
|
153
|
+
after_list: List[str] = []
|
|
154
|
+
cur_list = before_list
|
|
155
|
+
for part in item:
|
|
156
|
+
if part is Ellipsis:
|
|
157
|
+
if subset:
|
|
158
|
+
raise TypeError("Only one ellipsis allowed in NamedArray typing spec")
|
|
159
|
+
subset = True
|
|
160
|
+
cur_list = after_list
|
|
161
|
+
else:
|
|
162
|
+
if not isinstance(part, str):
|
|
163
|
+
raise TypeError(f"Invalid axis spec: {part}")
|
|
164
|
+
cur_list.append(part)
|
|
165
|
+
if subset:
|
|
166
|
+
return NamedArrayAxes(tuple(before_list), tuple(after_list), ordered=True, subset=True)
|
|
167
|
+
else:
|
|
168
|
+
return NamedArrayAxes(tuple(before_list), (), ordered=True, subset=False)
|
|
169
|
+
raise TypeError(f"Invalid NamedArray typing spec: {item}")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Named:
|
|
173
|
+
"""Type annotation helper for :class:`NamedArray`.
|
|
174
|
+
|
|
175
|
+
``Named["batch embed"]`` expands to ``Annotated[NamedArray, axes]`` so that
|
|
176
|
+
type checkers treat it as a ``NamedArray`` at static time while the axis
|
|
177
|
+
metadata is available at runtime via :func:`typing.get_args`.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __class_getitem__(cls, item: NamedArrayAxesSpec) -> typing.Any:
|
|
181
|
+
axes = _parse_namedarray_axes(item)
|
|
182
|
+
return typing.Annotated[NamedArray, axes]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class NamedArrayMeta(type):
|
|
186
|
+
def __getitem__(cls, item: NamedArrayAxesSpec) -> typing.Any:
|
|
187
|
+
axes = _parse_namedarray_axes(item)
|
|
188
|
+
return typing.Annotated[NamedArray, axes]
|
|
189
|
+
|
|
190
|
+
|
|
61
191
|
@jax.tree_util.register_pytree_node_class
|
|
62
192
|
@dataclass(frozen=True)
|
|
63
|
-
class NamedArray:
|
|
193
|
+
class NamedArray(metaclass=NamedArrayMeta):
|
|
64
194
|
array: jnp.ndarray
|
|
65
195
|
axes: Tuple[Axis, ...]
|
|
66
196
|
|
|
@@ -145,6 +275,45 @@ class NamedArray:
|
|
|
145
275
|
"""Returns true if the given axis is present in this NamedArray."""
|
|
146
276
|
return self.axis_indices(axis) is not None
|
|
147
277
|
|
|
278
|
+
def matches_axes(self, spec: NamedArrayAxesSpec) -> bool:
|
|
279
|
+
"""Check whether this NamedArray conforms to the given `NamedArray` type.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
spec : NamedArrayAxesSpec
|
|
284
|
+
The specification to check against. It can be produced via the
|
|
285
|
+
``NamedArray[...]`` syntax or passed directly as a string or
|
|
286
|
+
sequence of axis names.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
ann = _parse_namedarray_axes(spec)
|
|
290
|
+
if ann.dtype is not None:
|
|
291
|
+
dtype_spec = ann.dtype
|
|
292
|
+
if hasattr(dtype_spec, "category"):
|
|
293
|
+
if not jnp.issubdtype(self.dtype, dtype_spec.category):
|
|
294
|
+
return False
|
|
295
|
+
elif self.dtype != dtype_spec:
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
names = tuple(ax.name for ax in self.axes)
|
|
299
|
+
if ann.ordered:
|
|
300
|
+
if not ann.subset:
|
|
301
|
+
return names == ann.before
|
|
302
|
+
if len(names) < len(ann.before) + len(ann.after):
|
|
303
|
+
return False
|
|
304
|
+
if names[: len(ann.before)] != ann.before:
|
|
305
|
+
return False
|
|
306
|
+
if ann.after and names[-len(ann.after) :] != ann.after:
|
|
307
|
+
return False
|
|
308
|
+
return True
|
|
309
|
+
else:
|
|
310
|
+
name_set = set(names)
|
|
311
|
+
spec_set = set(ann.before)
|
|
312
|
+
if ann.subset:
|
|
313
|
+
return spec_set.issubset(name_set)
|
|
314
|
+
else:
|
|
315
|
+
return name_set == spec_set
|
|
316
|
+
|
|
148
317
|
@overload
|
|
149
318
|
def axis_size(self, axis: AxisSelector) -> int: # type: ignore
|
|
150
319
|
...
|
|
@@ -1885,6 +2054,9 @@ def _convert_index_expr_to_dict(idx) -> dict[AxisSelector, NamedIndex]:
|
|
|
1885
2054
|
|
|
1886
2055
|
|
|
1887
2056
|
__all__ = [
|
|
2057
|
+
"NamedArrayAxesSpec",
|
|
2058
|
+
"NamedArrayAxes",
|
|
2059
|
+
"Named",
|
|
1888
2060
|
"NamedArray",
|
|
1889
2061
|
"named",
|
|
1890
2062
|
"slice",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing as tp
|
|
4
|
+
from dataclasses import dataclass, replace
|
|
5
|
+
|
|
6
|
+
import jax.numpy as jnp
|
|
7
|
+
|
|
8
|
+
from .core import NamedArray, NamedArrayAxes, _parse_namedarray_axes
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class DTypeCategory:
|
|
13
|
+
"""Represents a dtype category such as ``float`` or ``int``."""
|
|
14
|
+
|
|
15
|
+
name: str
|
|
16
|
+
category: tp.Any
|
|
17
|
+
|
|
18
|
+
def __repr__(self) -> str: # pragma: no cover - trivial
|
|
19
|
+
return self.name
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _wrap_namedarray_with_dtype(dtype):
|
|
23
|
+
class DTypeType:
|
|
24
|
+
def __class_getitem__(cls, axes_spec):
|
|
25
|
+
axes = _parse_namedarray_axes(axes_spec)
|
|
26
|
+
axes_with_dtype = replace(axes, dtype=dtype)
|
|
27
|
+
return tp.Annotated[NamedArray, axes_with_dtype]
|
|
28
|
+
|
|
29
|
+
return DTypeType
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _wrap_namedarray_with_category(category: DTypeCategory):
|
|
33
|
+
class DTypeType:
|
|
34
|
+
def __class_getitem__(cls, axes_spec):
|
|
35
|
+
axes = _parse_namedarray_axes(axes_spec)
|
|
36
|
+
axes_with_dtype = replace(axes, dtype=category)
|
|
37
|
+
return tp.Annotated[NamedArray, axes_with_dtype]
|
|
38
|
+
|
|
39
|
+
return DTypeType
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
f32 = _wrap_namedarray_with_dtype(jnp.float32)
|
|
43
|
+
f64 = _wrap_namedarray_with_dtype(jnp.float64)
|
|
44
|
+
f16 = _wrap_namedarray_with_dtype(jnp.float16)
|
|
45
|
+
bf16 = _wrap_namedarray_with_dtype(jnp.bfloat16)
|
|
46
|
+
|
|
47
|
+
i8 = _wrap_namedarray_with_dtype(jnp.int8)
|
|
48
|
+
i16 = _wrap_namedarray_with_dtype(jnp.int16)
|
|
49
|
+
i32 = _wrap_namedarray_with_dtype(jnp.int32)
|
|
50
|
+
i64 = _wrap_namedarray_with_dtype(jnp.int64)
|
|
51
|
+
|
|
52
|
+
u8 = _wrap_namedarray_with_dtype(jnp.uint8)
|
|
53
|
+
u16 = _wrap_namedarray_with_dtype(jnp.uint16)
|
|
54
|
+
u32 = _wrap_namedarray_with_dtype(jnp.uint32)
|
|
55
|
+
u64 = _wrap_namedarray_with_dtype(jnp.uint64)
|
|
56
|
+
|
|
57
|
+
bool_ = _wrap_namedarray_with_dtype(jnp.bool_)
|
|
58
|
+
complex64 = _wrap_namedarray_with_dtype(jnp.complex64)
|
|
59
|
+
complex128 = _wrap_namedarray_with_dtype(jnp.complex128)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
Float = _wrap_namedarray_with_category(DTypeCategory("float", jnp.floating))
|
|
63
|
+
Complex = _wrap_namedarray_with_category(DTypeCategory("complex", jnp.complexfloating))
|
|
64
|
+
Int = _wrap_namedarray_with_category(DTypeCategory("int", jnp.signedinteger))
|
|
65
|
+
UInt = _wrap_namedarray_with_category(DTypeCategory("uint", jnp.unsignedinteger))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
"f32",
|
|
70
|
+
"f64",
|
|
71
|
+
"f16",
|
|
72
|
+
"bf16",
|
|
73
|
+
"i8",
|
|
74
|
+
"i16",
|
|
75
|
+
"i32",
|
|
76
|
+
"i64",
|
|
77
|
+
"u8",
|
|
78
|
+
"u16",
|
|
79
|
+
"u32",
|
|
80
|
+
"u64",
|
|
81
|
+
"bool_",
|
|
82
|
+
"complex64",
|
|
83
|
+
"complex128",
|
|
84
|
+
"Float",
|
|
85
|
+
"Complex",
|
|
86
|
+
"Int",
|
|
87
|
+
"UInt",
|
|
88
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
import jax.numpy as jnp
|
|
6
|
+
|
|
7
|
+
from haliax import Axis, NamedArray
|
|
8
|
+
from haliax.typing import Float, Int, f32, i32
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_dtype_and_axes_annotation():
|
|
12
|
+
def foo(x: f32["batch embed"]): # type: ignore # noqa: F722
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
ann = typing.get_args(typing.get_type_hints(foo, include_extras=True)["x"])
|
|
16
|
+
assert ann[0] is NamedArray
|
|
17
|
+
spec = ann[1]
|
|
18
|
+
assert spec.dtype == jnp.float32
|
|
19
|
+
assert spec.before == ("batch", "embed")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_other_dtype_annotation():
|
|
23
|
+
def bar(x: i32["batch"]): # type: ignore # noqa: F722
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
spec = typing.get_args(typing.get_type_hints(bar, include_extras=True)["x"])[1]
|
|
27
|
+
assert spec.dtype == jnp.int32
|
|
28
|
+
assert spec.before == ("batch",)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_dtype_category_annotation_and_check():
|
|
32
|
+
def baz(x: Float["b"]): # type: ignore # noqa: F722
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
spec = typing.get_args(typing.get_type_hints(baz, include_extras=True)["x"])[1]
|
|
36
|
+
assert str(spec.dtype) == "float"
|
|
37
|
+
|
|
38
|
+
B = Axis("b", 1)
|
|
39
|
+
arr = NamedArray(jnp.ones((B.size,), dtype=jnp.float32), (B,))
|
|
40
|
+
assert arr.matches_axes(Float["b"]) # type: ignore
|
|
41
|
+
assert not arr.matches_axes(Int["b"]) # type: ignore
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
import jax.numpy as jnp
|
|
6
|
+
|
|
7
|
+
from haliax import Axis, Named, NamedArray
|
|
8
|
+
from haliax.typing import Float, Int, f32, i32
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_namedarray_type_syntax():
|
|
12
|
+
axes1 = typing.get_args(NamedArray["batch", "embed"])[1]
|
|
13
|
+
axes2 = typing.get_args(Named["batch embed"])[1] # type: ignore
|
|
14
|
+
assert axes1 == axes2
|
|
15
|
+
|
|
16
|
+
axes3 = typing.get_args(NamedArray["batch embed ..."])[1]
|
|
17
|
+
assert axes3.before == ("batch", "embed") and axes3.subset and axes3.after == ()
|
|
18
|
+
|
|
19
|
+
axes4 = typing.get_args(NamedArray[{"batch", "embed"}])[1]
|
|
20
|
+
assert set(axes4.before) == {"batch", "embed"} and not axes4.ordered
|
|
21
|
+
|
|
22
|
+
axes5 = typing.get_args(NamedArray[{"batch", "embed", ...}])[1]
|
|
23
|
+
assert set(axes5.before) == {"batch", "embed"} and not axes5.ordered and axes5.subset
|
|
24
|
+
|
|
25
|
+
axes6 = typing.get_args(NamedArray["... embed"])[1]
|
|
26
|
+
assert axes6.before == () and axes6.after == ("embed",) and axes6.subset
|
|
27
|
+
|
|
28
|
+
axes7 = typing.get_args(NamedArray["batch ... embed"])[1]
|
|
29
|
+
assert axes7.before == ("batch",) and axes7.after == ("embed",) and axes7.subset
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_named_param_annotation():
|
|
33
|
+
def foo(x: Named["batch", "embed"]): # type: ignore
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
axes = typing.get_args(typing.get_type_hints(foo, include_extras=True)["x"])[1]
|
|
37
|
+
assert axes.before == ("batch", "embed")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_namedarray_runtime_check():
|
|
41
|
+
Batch = Axis("batch", 2)
|
|
42
|
+
Embed = Axis("embed", 3)
|
|
43
|
+
arr = NamedArray(jnp.zeros((Batch.size, Embed.size)), (Batch, Embed))
|
|
44
|
+
assert arr.matches_axes(NamedArray["batch", "embed"])
|
|
45
|
+
assert arr.matches_axes(Named["batch embed"]) # type: ignore
|
|
46
|
+
assert arr.matches_axes(NamedArray["batch embed ..."])
|
|
47
|
+
assert arr.matches_axes(NamedArray[{"batch", "embed"}])
|
|
48
|
+
assert arr.matches_axes(NamedArray[{"batch", "embed", ...}])
|
|
49
|
+
assert not arr.matches_axes(NamedArray["embed batch"])
|
|
50
|
+
assert not arr.matches_axes(NamedArray[{"batch", "foo", ...}])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_namedarray_runtime_check_with_dtype():
|
|
54
|
+
Batch = Axis("batch", 2)
|
|
55
|
+
arr = NamedArray(jnp.zeros((Batch.size,), dtype=jnp.float32), (Batch,))
|
|
56
|
+
assert arr.matches_axes(f32["batch"]) # type: ignore
|
|
57
|
+
assert not arr.matches_axes(i32["batch"]) # type: ignore
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_namedarray_runtime_check_with_category():
|
|
61
|
+
B = Axis("batch", 1)
|
|
62
|
+
arr = NamedArray(jnp.zeros((B.size,), dtype=jnp.float32), (B,))
|
|
63
|
+
assert arr.matches_axes(Float["batch"]) # type: ignore
|
|
64
|
+
assert not arr.matches_axes(Int["batch"]) # type: ignore
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.4.dev360"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|