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.
Files changed (111) hide show
  1. {haliax-1.4.dev360 → haliax-1.4.dev363}/.github/workflows/run_tests.yaml +1 -1
  2. {haliax-1.4.dev360 → haliax-1.4.dev363}/PKG-INFO +1 -1
  3. haliax-1.4.dev363/docs/typing.md +61 -0
  4. {haliax-1.4.dev360 → haliax-1.4.dev363}/mkdocs.yml +1 -0
  5. haliax-1.4.dev363/src/haliax/__about__.py +1 -0
  6. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/__init__.py +6 -0
  7. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/core.py +173 -1
  8. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/activations.py +0 -1
  9. haliax-1.4.dev363/src/haliax/typing.py +88 -0
  10. haliax-1.4.dev363/tests/test_dtype_typing.py +41 -0
  11. haliax-1.4.dev363/tests/test_namedarray_typing.py +64 -0
  12. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_nn.py +1 -1
  13. haliax-1.4.dev360/src/haliax/__about__.py +0 -1
  14. {haliax-1.4.dev360 → haliax-1.4.dev363}/.coveragerc +0 -0
  15. {haliax-1.4.dev360 → haliax-1.4.dev363}/.flake8 +0 -0
  16. {haliax-1.4.dev360 → haliax-1.4.dev363}/.github/workflows/publish_dev.yaml +0 -0
  17. {haliax-1.4.dev360 → haliax-1.4.dev363}/.github/workflows/run_pre_commit.yaml +0 -0
  18. {haliax-1.4.dev360 → haliax-1.4.dev363}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
  19. {haliax-1.4.dev360 → haliax-1.4.dev363}/.gitignore +0 -0
  20. {haliax-1.4.dev360 → haliax-1.4.dev363}/.pre-commit-config.yaml +0 -0
  21. {haliax-1.4.dev360 → haliax-1.4.dev363}/.readthedocs.yaml +0 -0
  22. {haliax-1.4.dev360 → haliax-1.4.dev363}/CONTRIBUTING.md +0 -0
  23. {haliax-1.4.dev360 → haliax-1.4.dev363}/LICENSE +0 -0
  24. {haliax-1.4.dev360 → haliax-1.4.dev363}/README.md +0 -0
  25. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/api.md +0 -0
  26. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/broadcasting.md +0 -0
  27. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/cheatsheet.md +0 -0
  28. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/css/material.css +0 -0
  29. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/css/mkdocstrings.css +0 -0
  30. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/faq.md +0 -0
  31. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/data_parallel_mesh.png +0 -0
  32. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/data_parallel_mesh_replicated.png +0 -0
  33. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_1d.png +0 -0
  34. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_1d_zero.png +0 -0
  35. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d.png +0 -0
  36. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
  37. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
  38. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
  39. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
  40. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/figures/device_mesh_2d_zero.png +0 -0
  41. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/fp8.md +0 -0
  42. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/index.md +0 -0
  43. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/indexing.md +0 -0
  44. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/matmul.md +0 -0
  45. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/nn.md +0 -0
  46. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/partitioning.md +0 -0
  47. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/rearrange.ipynb +0 -0
  48. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/rearrange.md +0 -0
  49. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/requirements.txt +0 -0
  50. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/scan.md +0 -0
  51. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/state-dict.md +0 -0
  52. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/tutorial.md +0 -0
  53. {haliax-1.4.dev360 → haliax-1.4.dev363}/docs/vmap.md +0 -0
  54. {haliax-1.4.dev360 → haliax-1.4.dev363}/pyproject.toml +0 -0
  55. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/__init__.py +0 -0
  56. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/compile_utils.py +0 -0
  57. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/dot.py +0 -0
  58. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/einsum.py +0 -0
  59. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/fp8.py +0 -0
  60. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/parsing.py +0 -0
  61. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/rearrange.py +0 -0
  62. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/scan.py +0 -0
  63. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/state_dict.py +0 -0
  64. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/_src/util.py +0 -0
  65. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/axis.py +0 -0
  66. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/debug.py +0 -0
  67. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/hof.py +0 -0
  68. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/jax_utils.py +0 -0
  69. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/__init__.py +0 -0
  70. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/attention.py +0 -0
  71. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/conv.py +0 -0
  72. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/dropout.py +0 -0
  73. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/embedding.py +0 -0
  74. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/linear.py +0 -0
  75. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/loss.py +0 -0
  76. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/mlp.py +0 -0
  77. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/normalization.py +0 -0
  78. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/pool.py +0 -0
  79. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/nn/scan.py +0 -0
  80. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/ops.py +0 -0
  81. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/partitioning.py +0 -0
  82. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/quantization.py +0 -0
  83. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/random.py +0 -0
  84. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/specialized_fns.py +0 -0
  85. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/state_dict.py +0 -0
  86. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/tree_util.py +0 -0
  87. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/types.py +0 -0
  88. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/util.py +0 -0
  89. {haliax-1.4.dev360 → haliax-1.4.dev363}/src/haliax/wrap.py +0 -0
  90. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/core_test.py +0 -0
  91. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_attention.py +0 -0
  92. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_axis.py +0 -0
  93. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_conv.py +0 -0
  94. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_debug.py +0 -0
  95. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_dot.py +0 -0
  96. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_einsum.py +0 -0
  97. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_fp8.py +0 -0
  98. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_hof.py +0 -0
  99. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_int8.py +0 -0
  100. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_ops.py +0 -0
  101. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_parsing.py +0 -0
  102. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_partitioning.py +0 -0
  103. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_pool.py +0 -0
  104. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_random.py +0 -0
  105. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_rearrange.py +0 -0
  106. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_scan.py +0 -0
  107. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_scatter_gather.py +0 -0
  108. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_specialized_fns.py +0 -0
  109. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_state_dict.py +0 -0
  110. {haliax-1.4.dev360 → haliax-1.4.dev363}/tests/test_tree_util.py +0 -0
  111. {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 jax==0.4.35 jaxlib==0.4.35 .[dev]
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.dev360
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
+ ```
@@ -90,6 +90,7 @@ nav:
90
90
  - Indexing and Slicing: 'indexing.md'
91
91
  - Rearrange: 'rearrange.md'
92
92
  - Matrix Multiplication: 'matmul.md'
93
+ - Type Annotations: 'typing.md'
93
94
  - Higher Order Functions:
94
95
  - Scan and Fold: 'scan.md'
95
96
  - Vectorization: 'vmap.md'
@@ -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",
@@ -89,7 +89,6 @@ def quick_gelu(x):
89
89
  return x * sigmoid(1.702 * x)
90
90
 
91
91
 
92
-
93
92
  def relu_squared(x: A) -> A:
94
93
  """ReLU squared activation function. jnp.square(jnp.maximum(0, x))"""
95
94
 
@@ -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
@@ -198,4 +198,4 @@ def test_relu_squared_scalar(use_jit):
198
198
  x_neg = -5.0
199
199
  expected_neg = 0.0
200
200
  actual_neg = f(x_neg)
201
- assert jnp.allclose(actual_neg, expected_neg)
201
+ assert jnp.allclose(actual_neg, expected_neg)
@@ -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