haliax 1.4.dev412__tar.gz → 1.4.dev419__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 (131) hide show
  1. {haliax-1.4.dev412 → haliax-1.4.dev419}/.agents/projects/api_parity.md +12 -12
  2. {haliax-1.4.dev412 → haliax-1.4.dev419}/AUTHORS.md +1 -0
  3. {haliax-1.4.dev412 → haliax-1.4.dev419}/PKG-INFO +2 -1
  4. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/api.md +96 -11
  5. haliax-1.4.dev419/docs/primer.md +183 -0
  6. {haliax-1.4.dev412 → haliax-1.4.dev419}/pyproject.toml +1 -2
  7. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/__about__.py +1 -1
  8. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/__init__.py +51 -12
  9. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/state_dict.py +1 -1
  10. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/core.py +24 -4
  11. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/fft.py +4 -1
  12. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/__init__.py +18 -7
  13. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/linear.py +1 -1
  14. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/ops.py +2 -4
  15. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/partitioning.py +101 -62
  16. haliax-1.4.dev419/src/haliax/poly.py +304 -0
  17. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/core_test.py +46 -0
  18. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_bitwise_ops.py +4 -0
  19. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_fft.py +5 -3
  20. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_moe_linear.py +3 -2
  21. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_nan_reductions.py +4 -0
  22. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_nn.py +18 -0
  23. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_partitioning.py +15 -27
  24. haliax-1.4.dev419/tests/test_poly_ops.py +134 -0
  25. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_utils.py +5 -2
  26. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_visualize_sharding.py +5 -2
  27. haliax-1.4.dev419/uv.lock +1951 -0
  28. haliax-1.4.dev412/docs/primer.md +0 -114
  29. haliax-1.4.dev412/uv.lock +0 -1711
  30. {haliax-1.4.dev412 → haliax-1.4.dev419}/.coveragerc +0 -0
  31. {haliax-1.4.dev412 → haliax-1.4.dev419}/.flake8 +0 -0
  32. {haliax-1.4.dev412 → haliax-1.4.dev419}/.github/workflows/publish_dev.yaml +0 -0
  33. {haliax-1.4.dev412 → haliax-1.4.dev419}/.github/workflows/run_pre_commit.yaml +0 -0
  34. {haliax-1.4.dev412 → haliax-1.4.dev419}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
  35. {haliax-1.4.dev412 → haliax-1.4.dev419}/.github/workflows/run_tests.yaml +0 -0
  36. {haliax-1.4.dev412 → haliax-1.4.dev419}/.gitignore +0 -0
  37. {haliax-1.4.dev412 → haliax-1.4.dev419}/.playbooks/add-types.md +0 -0
  38. {haliax-1.4.dev412 → haliax-1.4.dev419}/.playbooks/wrap-non-named.md +0 -0
  39. {haliax-1.4.dev412 → haliax-1.4.dev419}/.pre-commit-config.yaml +0 -0
  40. {haliax-1.4.dev412 → haliax-1.4.dev419}/.readthedocs.yaml +0 -0
  41. {haliax-1.4.dev412 → haliax-1.4.dev419}/AGENTS.md +0 -0
  42. {haliax-1.4.dev412 → haliax-1.4.dev419}/CONTRIBUTING.md +0 -0
  43. {haliax-1.4.dev412 → haliax-1.4.dev419}/CONTRIBUTORS.md +0 -0
  44. {haliax-1.4.dev412 → haliax-1.4.dev419}/LICENSE +0 -0
  45. {haliax-1.4.dev412 → haliax-1.4.dev419}/README.md +0 -0
  46. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/broadcasting.md +0 -0
  47. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/cheatsheet.md +0 -0
  48. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/css/material.css +0 -0
  49. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/css/mkdocstrings.css +0 -0
  50. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/faq.md +0 -0
  51. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/data_parallel_mesh.png +0 -0
  52. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/data_parallel_mesh_replicated.png +0 -0
  53. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/device_mesh_1d.png +0 -0
  54. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/device_mesh_1d_zero.png +0 -0
  55. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/device_mesh_2d.png +0 -0
  56. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
  57. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
  58. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
  59. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
  60. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/figures/device_mesh_2d_zero.png +0 -0
  61. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/fp8.md +0 -0
  62. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/index.md +0 -0
  63. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/indexing.md +0 -0
  64. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/matmul.md +0 -0
  65. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/nn.md +0 -0
  66. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/partitioning.md +0 -0
  67. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/rearrange.ipynb +0 -0
  68. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/rearrange.md +0 -0
  69. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/requirements.txt +0 -0
  70. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/scan.md +0 -0
  71. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/state-dict.md +0 -0
  72. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/tutorial.md +0 -0
  73. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/typing.md +0 -0
  74. {haliax-1.4.dev412 → haliax-1.4.dev419}/docs/vmap.md +0 -0
  75. {haliax-1.4.dev412 → haliax-1.4.dev419}/etc/license_header.txt +0 -0
  76. {haliax-1.4.dev412 → haliax-1.4.dev419}/mkdocs.yml +0 -0
  77. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/__init__.py +0 -0
  78. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/compile_utils.py +0 -0
  79. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/dot.py +0 -0
  80. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/einsum.py +0 -0
  81. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/fp8.py +0 -0
  82. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/parsing.py +0 -0
  83. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/rearrange.py +0 -0
  84. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/scan.py +0 -0
  85. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/_src/util.py +0 -0
  86. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/axis.py +0 -0
  87. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/debug.py +0 -0
  88. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/field.py +0 -0
  89. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/haxtyping.py +0 -0
  90. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/hof.py +0 -0
  91. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/jax_utils.py +0 -0
  92. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/activations.py +0 -0
  93. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/attention.py +0 -0
  94. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/conv.py +0 -0
  95. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/dropout.py +0 -0
  96. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/embedding.py +0 -0
  97. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/loss.py +0 -0
  98. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/mlp.py +0 -0
  99. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/normalization.py +0 -0
  100. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/pool.py +0 -0
  101. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/nn/scan.py +0 -0
  102. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/quantization.py +0 -0
  103. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/random.py +0 -0
  104. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/specialized_fns.py +0 -0
  105. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/state_dict.py +0 -0
  106. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/tree_util.py +0 -0
  107. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/types.py +0 -0
  108. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/util.py +0 -0
  109. {haliax-1.4.dev412 → haliax-1.4.dev419}/src/haliax/wrap.py +0 -0
  110. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_attention.py +0 -0
  111. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_axis.py +0 -0
  112. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_conv.py +0 -0
  113. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_debug.py +0 -0
  114. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_dot.py +0 -0
  115. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_dtype_typing.py +0 -0
  116. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_einsum.py +0 -0
  117. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_field.py +0 -0
  118. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_fp8.py +0 -0
  119. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_hof.py +0 -0
  120. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_int8.py +0 -0
  121. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_namedarray_typing.py +0 -0
  122. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_ops.py +0 -0
  123. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_parsing.py +0 -0
  124. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_pool.py +0 -0
  125. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_random.py +0 -0
  126. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_rearrange.py +0 -0
  127. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_scan.py +0 -0
  128. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_scatter_gather.py +0 -0
  129. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_specialized_fns.py +0 -0
  130. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_state_dict.py +0 -0
  131. {haliax-1.4.dev412 → haliax-1.4.dev419}/tests/test_tree_util.py +0 -0
@@ -115,15 +115,15 @@ APIs that don't translate well to named tensors are intentionally omitted here.
115
115
  - [ ] `permute_dims`
116
116
  - [ ] `piecewise`
117
117
  - [ ] `place`
118
- - [ ] `poly`
119
- - [ ] `polyadd`
120
- - [ ] `polyder`
121
- - [ ] `polydiv`
122
- - [ ] `polyfit`
123
- - [ ] `polyint`
124
- - [ ] `polymul`
125
- - [ ] `polysub`
126
- - [ ] `polyval`
118
+ - [x] `poly`
119
+ - [x] `polyadd`
120
+ - [x] `polyder`
121
+ - [x] `polydiv`
122
+ - [x] `polyfit`
123
+ - [x] `polyint`
124
+ - [x] `polymul`
125
+ - [x] `polysub`
126
+ - [x] `polyval`
127
127
  - [ ] `pow`
128
128
  - [ ] `promote_types`
129
129
  - [ ] `put`
@@ -134,7 +134,7 @@ APIs that don't translate well to named tensors are intentionally omitted here.
134
134
  - [ ] `resize`
135
135
  - [ ] `result_type`
136
136
  - [ ] `rollaxis`
137
- - [ ] `roots`
137
+ - [x] `roots`
138
138
  - [ ] `rot90`
139
139
  - [ ] `select`
140
140
  - [ ] `setdiff1d`
@@ -148,7 +148,7 @@ APIs that don't translate well to named tensors are intentionally omitted here.
148
148
  - [ ] `tri`
149
149
  - [ ] `tril_indices`
150
150
  - [ ] `tril_indices_from`
151
- - [ ] `trim_zeros`
151
+ - [x] `trim_zeros`
152
152
  - [ ] `triu_indices`
153
153
  - [ ] `triu_indices_from`
154
154
  - [ ] `union1d`
@@ -156,7 +156,7 @@ APIs that don't translate well to named tensors are intentionally omitted here.
156
156
  - [ ] `unravel_index`
157
157
  - [ ] `unstack`
158
158
  - [ ] `unwrap`
159
- - [ ] `vander`
159
+ - [x] `vander`
160
160
  - [ ] `vsplit`
161
161
  - [ ] `vstack`
162
162
 
@@ -3,3 +3,4 @@
3
3
  The Levanter Authors currently include:
4
4
 
5
5
  - The Board of Trustees of the Leland Stanford Junior University
6
+ - Open Athena AI Foundation, Inc.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haliax
3
- Version: 1.4.dev412
3
+ Version: 1.4.dev419
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/
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3
17
17
  Requires-Python: >=3.10
18
18
  Requires-Dist: aqtp>=0.8.2
19
19
  Requires-Dist: equinox>=0.10.6
20
+ Requires-Dist: jax>=0.6.2
20
21
  Requires-Dist: jaxtyping>=0.2.20
21
22
  Requires-Dist: jmp>=0.0.4
22
23
  Requires-Dist: safetensors>=0.4.3
@@ -6,19 +6,89 @@ arrays (see [haliax.zeros][] and [haliax.ones][]) as well as things like reducti
6
6
 
7
7
  ## Axis Types
8
8
 
9
- There are four types related to [haliax.Axis][] you will see in the API reference:
9
+ If you already speak NumPy or `jax.numpy`, think of Haliax as swapping positional axes (`axis=0`) for named axes
10
+ (`axis="batch"`). The type hints in this section describe the different ways those named axes can be provided to the API.
11
+ They appear throughout the documentation and in signatures so that you can quickly tell which forms are accepted.
10
12
 
11
- * [haliax.Axis][]: This is the main type for representing axes. It is a dataclass with a name and a size.
12
- * [haliax.AxisSelector][]: This is a type alias for either [haliax.Axis][] or a `str`. This type is used when we want
13
- one axis and the size can be inferred from the inputs.
14
- * [haliax.AxisSpec][]: This is a type alias for either [haliax.Axis][] or a tuple/list of [haliax.Axis][]. This type is
15
- used when we want one or more axes and the sizes cannot be inferred from the inputs, for instance when creating arrays.
16
- * [haliax.AxisSelection][]: This is a type alias for either [haliax.AxisSelector][] or a tuple/list of [haliax.AxisSelector][].
17
- This type is used when we want one or more axes and the sizes can be inferred from the inputs, for instance when
18
- reducing an array.
13
+ | Name | Accepts | When to use it | Example |
14
+ | --- | --- | --- | --- |
15
+ | [`Axis`][haliax.Axis] | `Axis(name: str, size: int)` | Define a named dimension with an explicit size | `Batch = Axis("batch", 32)` |
16
+ | [`AxisSelector`][haliax.AxisSelector] | `Axis` or `str` | Refer to an existing axis whose size can be inferred from the arrays you pass in | `x.sum(axis="batch")` |
17
+ | [`AxisSpec`][haliax.AxisSpec] | `dict[str, int]`, `Axis`, or a sequence of `Axis` objects | Create or reshape arrays when the axis sizes must be provided | `hax.zeros((Batch, Feature))` |
18
+ | [`AxisSelection`][haliax.AxisSelection] | `dict[str, int | None]`, `AxisSpec`, or a sequence of `AxisSelector` values | Work with one or more existing axes (reductions, indexing helpers, flattening, …) | `x.sum(axis=("batch", Feature))` |
19
19
 
20
- Occasionally, an axis size can be inferred in some circumstances but not others. When this happens, we still use
21
- `AxisSelector` but document the behavior in the docstring. A RuntimeError will be raised if the size cannot be inferred.
20
+ ### Axis
21
+
22
+ An [`Axis`][haliax.Axis] is the fundamental building block: it is a tiny dataclass that stores a name and a size. You can
23
+ construct one directly or use [`haliax.make_axes`][] to generate several at a time.
24
+
25
+ ```python
26
+ import haliax as hax
27
+ from haliax import Axis
28
+
29
+ Batch = Axis("batch", 32)
30
+ Feature = Axis("feature", 128)
31
+ x = hax.ones((Batch, Feature))
32
+ print(Batch.name, Batch.size)
33
+ ```
34
+
35
+ Using `Axis` objects keeps array creation explicit and gives reusable handles you can share between different tensors.
36
+ Equality compares both the name and size so you get guardrails when wiring pieces together.
37
+
38
+ ### AxisSelector
39
+
40
+ An [`AxisSelector`][haliax.AxisSelector] accepts either an `Axis` object or just the axis name as a string. It is used
41
+ whenever a function can read the axis size from one of its arguments. This mirrors how NumPy lets you pass `axis=0` when
42
+ reducing an array:
43
+
44
+ ```python
45
+ total = x.sum(axis=Batch) # using the Axis handle
46
+ same_total = x.sum(axis="batch") # using only the name
47
+ ```
48
+
49
+ Strings are convenient when you only care about the name, but `Axis` objects still work so you can keep using the handles
50
+ you created earlier. If an axis with that name is missing, Haliax raises a `ValueError`.
51
+
52
+ ### AxisSpec
53
+
54
+ An [`AxisSpec`][haliax.AxisSpec] is used when Haliax needs full size information to create or reshape an array. You can
55
+ provide a shape dictionary (sometimes called a "shape dict") that maps names to sizes, or a sequence of `Axis` objects:
56
+
57
+ ```python
58
+ shape = {"batch": 32, "feature": 128}
59
+ y = hax.zeros(shape) # using a shape dict
60
+ z = hax.zeros((Batch, Feature)) # using the Axis objects directly
61
+ ```
62
+
63
+ Both forms describe the same layout. Python dictionaries preserve insertion order, so the ordering in a shape dict matches
64
+ the order that axes appear in the array. Sequences must contain `Axis` objects (not plain strings) because Haliax cannot
65
+ otherwise know the axis sizes.
66
+
67
+ ### AxisSelection
68
+
69
+ [`AxisSelection`][haliax.AxisSelection] generalizes the previous aliases so you can talk about several axes at once. It
70
+ shows up in reductions, indexing helpers, axis-mapping utilities, and anywhere you might have written `axis=(0, 1)` in
71
+ NumPy. You may supply:
72
+
73
+ * a sequence mixing `Axis` objects and strings, e.g. `("batch", Feature)` when reducing two axes,
74
+ * an `AxisSpec`, which is handy when you already have a tuple of `Axis` objects, or
75
+ * a "partial shape dict" where the values are either sizes or `None` to indicate "any size". Dictionaries are useful when
76
+ you only care about a subset of axes or want to assert a particular size.
77
+
78
+ ```python
79
+ # Reduce over two axes using a tuple of selectors.
80
+ scalar = x.sum(axis=("batch", Feature))
81
+
82
+ # Ask for the axes by name and optionally pin sizes.
83
+ x.resolve_axis({"batch": None, "feature": None}) # returns {"batch": 32, "feature": 128}
84
+
85
+ from haliax.axis import selects_axis
86
+ assert selects_axis((Batch, "feature"), {"batch": None, "feature": 128})
87
+ ```
88
+
89
+ Occasionally, an axis size can be inferred in some circumstances but not others. When this happens we still use
90
+ `AxisSelector` (or `AxisSelection` for multiple axes) but document the behavior in the docstring. A `RuntimeError` will be
91
+ raised if the size cannot be inferred.
22
92
 
23
93
  ::: haliax.Axis
24
94
  ::: haliax.AxisSelector
@@ -270,6 +340,21 @@ These are all more or less directly from JAX's NumPy API.
270
340
  ::: haliax.subtract
271
341
  ::: haliax.true_divide
272
342
 
343
+ ### Polynomial Operations
344
+
345
+ ::: haliax.poly
346
+ ::: haliax.polyadd
347
+ ::: haliax.polysub
348
+ ::: haliax.polymul
349
+ ::: haliax.polydiv
350
+ ::: haliax.polyint
351
+ ::: haliax.polyder
352
+ ::: haliax.polyval
353
+ ::: haliax.polyfit
354
+ ::: haliax.roots
355
+ ::: haliax.trim_zeros
356
+ ::: haliax.vander
357
+
273
358
  ### Other Operations
274
359
 
275
360
  ::: haliax.bincount
@@ -0,0 +1,183 @@
1
+ # Haliax Primer
2
+
3
+ Haliax provides named tensors built on top of JAX. This primer is written for LLM agents and other downstream libraries and collects the core ideas for quick reference.
4
+
5
+ ## Axes and Named Arrays
6
+
7
+ Arrays are indexed by `Axis` objects. You can define them explicitly or generate several with `make_axes`.
8
+ You may also specify shapes with a **shape dict**, mapping axis names to sizes.
9
+
10
+ ```python
11
+ import haliax as hax
12
+ from haliax import Axis
13
+
14
+ Batch = Axis("batch", 4)
15
+ Feature = Axis("feature", 8)
16
+ # or: Batch, Feature = hax.make_axes(batch=4, feature=8)
17
+ # using Axis objects
18
+ x = hax.zeros((Batch, Feature))
19
+ # or using a shape dict
20
+ shape = {"batch": 4, "feature": 8}
21
+ x = hax.zeros(shape)
22
+ ```
23
+
24
+ Most functions accept either axes or shape dicts interchangeably.
25
+
26
+ A tensor with named axes is a [`NamedArray`][haliax.NamedArray]. Elementwise operations mirror `jax.numpy` but accept named axes.
27
+
28
+ ### Ways to Describe Axes
29
+
30
+ If you're used to `axis=0` style code in NumPy or JAX, think of Haliax as swapping those positional indices for names
31
+ like `axis="batch"`. The API hints refer to a few helper aliases; this table shows how they map back to familiar
32
+ concepts:
33
+
34
+ | Name | Accepts | Typical use | Example |
35
+ | --- | --- | --- | --- |
36
+ | [`Axis`][haliax.Axis] | `Axis(name: str, size: int)` | Define a named dimension with a fixed size | `Batch = Axis("batch", 32)` |
37
+ | [`AxisSelector`][haliax.AxisSelector] | `Axis` or `str` | Refer to an existing axis when the size can be inferred from the argument | `x.sum(axis="batch")` |
38
+ | [`AxisSpec`][haliax.AxisSpec] | `dict[str, int]`, `Axis`, or a sequence of `Axis` objects | Supply complete shape information (array creation, reshaping) | `hax.zeros((Batch, Feature))` |
39
+ | [`AxisSelection`][haliax.AxisSelection] | `dict[str, int | None]`, `AxisSpec`, or a sequence of `AxisSelector` values | Work with one or more existing axes (reductions, indexing helpers, flattening, …) | `x.sum(axis=("batch", Feature))` |
40
+
41
+ The following sections expand on each alias with quick references and NumPy-style parallels.
42
+
43
+ #### `Axis`: reusable named dimensions
44
+
45
+ An [`Axis`][haliax.Axis] stores a `name` and a `size`. Create them directly or let
46
+ [`haliax.make_axes`][] build a handful at once. Because axes compare by both name and size, they act as reusable handles and
47
+ catch many wiring mistakes early.
48
+
49
+ ```python
50
+ Batch = Axis("batch", 32)
51
+ Feature = Axis("feature", 128)
52
+ x = hax.ones((Batch, Feature))
53
+ print(Batch.name, Batch.size)
54
+ ```
55
+
56
+ #### `AxisSelector`: when the size is already known
57
+
58
+ Many functions already see the array whose axes you're referencing (e.g. reductions). In those cases you can pass either the
59
+ `Axis` object or simply the axis name as a string. Haliax resolves the name against the array, similar to `axis=0` in NumPy.
60
+
61
+ ```python
62
+ total = x.sum(axis=Batch) # use the Axis handle
63
+ same_total = x.sum(axis="batch") # or just the name
64
+ ```
65
+
66
+ If you reference an axis name that isn't present, Haliax raises a `ValueError`.
67
+
68
+ #### `AxisSpec`: describing complete shapes
69
+
70
+ When Haliax needs explicit sizes—creating arrays, reshaping, broadcasting to a new axis—you provide an [`AxisSpec`][haliax.AxisSpec].
71
+ Shape dictionaries keep things close to standard Python, while sequences require actual `Axis` objects so the sizes stay explicit.
72
+
73
+ ```python
74
+ shape = {"batch": 32, "feature": 128}
75
+ y = hax.zeros(shape) # using a shape dict
76
+ z = hax.zeros((Batch, Feature)) # or a sequence of Axis objects
77
+ ```
78
+
79
+ Python dictionaries preserve insertion order, so the layout in a shape dict matches the order of axes in the resulting array.
80
+
81
+ #### `AxisSelection`: several axes at once
82
+
83
+ [`AxisSelection`][haliax.AxisSelection] is the plural form used by reductions, indexing helpers, and flattening utilities. Supply a
84
+ tuple mixing `Axis` objects and strings, reuse an existing `AxisSpec`, or pass a partial shape dict where values are either
85
+ sizes or `None` for "any size".
86
+
87
+ ```python
88
+ scalar = x.sum(axis=("batch", Feature))
89
+
90
+ # Ask for the axes by name and optionally pin sizes.
91
+ x.resolve_axis({"batch": None, "feature": None}) # returns {"batch": 32, "feature": 128}
92
+ ```
93
+
94
+ Partial shape dicts shine when you only care about a subset of axes or want assertions about their sizes. If the axis size
95
+ cannot be inferred from the provided arguments, Haliax raises a `RuntimeError`.
96
+
97
+ ## Indexing and Broadcasting
98
+
99
+ Use axis names when slicing. Dictionaries are convenient for several axes:
100
+
101
+ ```python
102
+ first = x["batch", 0]
103
+ sub = x["batch", 1:3]
104
+ # or with a dict
105
+ first = x[{"batch": 0}]
106
+ sub = x[{"batch": slice(1, 3)}]
107
+ ```
108
+
109
+ Axes broadcast by matching names. `broadcast_axis` adds a new axis to an array:
110
+
111
+ ```python
112
+ row = hax.arange(Feature)
113
+ outer = row.broadcast_axis(Batch) * hax.arange(Batch)
114
+ ```
115
+
116
+ See [Indexing and Slicing](indexing.md) and [Broadcasting](broadcasting.md) for details.
117
+
118
+ ## Rearranging Axes
119
+
120
+ `rearrange` changes axis order and can merge or split axes using einops‑style syntax. It is useful when interfacing with positional APIs.
121
+
122
+ ```python
123
+ # transpose features and batch
124
+ x_t = hax.rearrange(x, "batch feature -> feature batch")
125
+ ```
126
+
127
+ More examples appear in [Rearrange](rearrange.md).
128
+
129
+ ## Matrix Multiplication
130
+
131
+ `dot` contracts over named axes while preserving order independence.
132
+
133
+ ```python
134
+ Weight = Axis("weight", 8)
135
+ w = hax.ones((Feature, Weight))
136
+ prod = hax.dot(x, w, axis=Feature)
137
+ ```
138
+
139
+ For more complex contractions use [`einsum`][haliax.einsum]. See [Matrix Multiplication](matmul.md).
140
+
141
+ ## Scans and Folds
142
+
143
+ Use [`scan`][haliax.scan] or [`fold`][haliax.fold] to apply a function along an axis with optional gradient checkpointing.
144
+
145
+ ```python
146
+ Time = Axis("time", 10)
147
+ sequence = hax.ones((Time, Feature))
148
+
149
+ def add(prev, cur):
150
+ return prev + cur
151
+
152
+ result = hax.fold(add, Time)(hax.zeros((Feature,)), sequence)
153
+ ```
154
+
155
+ See [Scan and Fold](scan.md) for checkpointing policies and stacked modules.
156
+
157
+ ## Partitioning
158
+
159
+ Arrays and modules can be distributed across devices by mapping named axes to mesh axes:
160
+
161
+ ```python
162
+ with hax.axis_mapping({"batch": "data"}):
163
+ sharded = hax.shard(x)
164
+ ```
165
+
166
+ The [Partitioning](partitioning.md) guide explains how to set up device meshes and shard arrays.
167
+
168
+ ## Typing Support
169
+
170
+ Type annotations use `haliax.haxtyping` which extends `jaxtyping`:
171
+
172
+ ```python
173
+ import haliax.haxtyping as ht
174
+
175
+ def f(t: ht.Float[hax.NamedArray, "batch feature"]):
176
+ ...
177
+ ```
178
+
179
+ See [Typing](typing.md) for matching runtime checks and dtype-aware annotations.
180
+
181
+ ---
182
+
183
+ This primer highlights common patterns. The [cheatsheet](cheatsheet.md) lists many additional conversions from JAX to Haliax.
@@ -21,8 +21,7 @@ classifiers = [
21
21
  "Intended Audience :: Science/Research",
22
22
  ]
23
23
  dependencies = [
24
- # we require that you install jax yourself, since the extras vary by system.
25
- # jax = {version = ">=0.4.19,<0.5.0"}
24
+ "jax >= 0.6.2",
26
25
  "equinox>=0.10.6",
27
26
  "jaxtyping>=0.2.20",
28
27
  "jmp>=0.0.4",
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
 
6
- __version__ = "1.4.dev412"
6
+ __version__ = "1.4.dev419"
@@ -8,11 +8,8 @@ from typing import Optional, Sequence
8
8
 
9
9
  import jax
10
10
  import jax.numpy as jnp
11
+ from jax.typing import DTypeLike
11
12
 
12
- try:
13
- from jax.typing import DTypeLike
14
- except ImportError:
15
- from jax._src.typing import DTypeLike
16
13
 
17
14
  import haliax.debug as debug
18
15
  import haliax.nn as nn
@@ -96,6 +93,22 @@ from .ops import (
96
93
  bincount,
97
94
  where,
98
95
  )
96
+
97
+ from .poly import (
98
+ poly,
99
+ polyadd,
100
+ polysub,
101
+ polymul,
102
+ polydiv,
103
+ polyint,
104
+ polyder,
105
+ polyval,
106
+ polyfit,
107
+ roots,
108
+ trim_zeros,
109
+ vander,
110
+ )
111
+
99
112
  from .fft import (
100
113
  fft,
101
114
  fftfreq,
@@ -108,7 +121,7 @@ from .fft import (
108
121
  rfft,
109
122
  rfftfreq,
110
123
  )
111
- from .partitioning import auto_sharded, axis_mapping, fsdp, named_jit, shard, shard_with_axis_mapping
124
+ from .partitioning import auto_sharded, axis_mapping, fsdp, named_jit, set_mesh, shard, shard_with_axis_mapping
112
125
  from .specialized_fns import top_k
113
126
  from .types import Scalar
114
127
  from .util import is_named_array
@@ -715,7 +728,9 @@ def nanmean(
715
728
  where: Optional[NamedArray] = None,
716
729
  dtype: Optional[DTypeLike] = None,
717
730
  ) -> NamedArray:
718
- return wrap_reduction_call(jnp.nanmean, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype)
731
+ return wrap_reduction_call(
732
+ jnp.nanmean, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype
733
+ )
719
734
 
720
735
 
721
736
  def nanmin(
@@ -734,7 +749,9 @@ def nanprod(
734
749
  where: Optional[NamedArray] = None,
735
750
  dtype: Optional[DTypeLike] = None,
736
751
  ) -> NamedArray:
737
- return wrap_reduction_call(jnp.nanprod, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype)
752
+ return wrap_reduction_call(
753
+ jnp.nanprod, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype
754
+ )
738
755
 
739
756
 
740
757
  def nanstd(
@@ -745,7 +762,9 @@ def nanstd(
745
762
  ddof: int = 0,
746
763
  dtype: Optional[DTypeLike] = None,
747
764
  ) -> NamedArray:
748
- return wrap_reduction_call(jnp.nanstd, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype, ddof=ddof)
765
+ return wrap_reduction_call(
766
+ jnp.nanstd, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype, ddof=ddof
767
+ )
749
768
 
750
769
 
751
770
  def nansum(
@@ -755,7 +774,9 @@ def nansum(
755
774
  where: Optional[NamedArray] = None,
756
775
  dtype: Optional[DTypeLike] = None,
757
776
  ) -> NamedArray:
758
- return wrap_reduction_call(jnp.nansum, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype)
777
+ return wrap_reduction_call(
778
+ jnp.nansum, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype
779
+ )
759
780
 
760
781
 
761
782
  def nanvar(
@@ -766,7 +787,9 @@ def nanvar(
766
787
  ddof: int = 0,
767
788
  dtype: Optional[DTypeLike] = None,
768
789
  ) -> NamedArray:
769
- return wrap_reduction_call(jnp.nanvar, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype, ddof=ddof)
790
+ return wrap_reduction_call(
791
+ jnp.nanvar, array, axis, where, single_axis_only=False, supports_where=True, dtype=dtype, ddof=ddof
792
+ )
770
793
 
771
794
 
772
795
  # "Normalization" functions that use an axis but don't change the shape
@@ -807,14 +830,17 @@ def sort(a: NamedArray, axis: AxisSelector) -> NamedArray:
807
830
  return wrap_axiswise_call(jnp.sort, a, axis, single_axis_only=True)
808
831
 
809
832
 
810
- def argsort(a: NamedArray, axis: AxisSelector) -> NamedArray:
833
+ def argsort(a: NamedArray, axis: AxisSelector | None, *, stable: bool = False) -> NamedArray:
811
834
  """
812
835
  Named version of [jax.numpy.argsort](https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.argsort.html).
813
836
 
814
837
  If `axis` is None, the returned array will be a 1D array of indices that would sort the flattened array,
815
838
  identical to `jax.numpy.argsort(a.array)`.
839
+
840
+ Args:
841
+ stable: If ``True``, ensures that the indices of equal elements preserve their relative order.
816
842
  """
817
- return wrap_axiswise_call(jnp.argsort, a, axis, single_axis_only=True)
843
+ return wrap_axiswise_call(jnp.argsort, a, axis, single_axis_only=True, stable=stable)
818
844
 
819
845
 
820
846
  # elemwise binary ops
@@ -1226,6 +1252,18 @@ __all__ = [
1226
1252
  "clip",
1227
1253
  "tril",
1228
1254
  "triu",
1255
+ "poly",
1256
+ "polyadd",
1257
+ "polysub",
1258
+ "polymul",
1259
+ "polydiv",
1260
+ "polyint",
1261
+ "polyder",
1262
+ "polyval",
1263
+ "polyfit",
1264
+ "roots",
1265
+ "trim_zeros",
1266
+ "vander",
1229
1267
  "fft",
1230
1268
  "ifft",
1231
1269
  "hfft",
@@ -1311,4 +1349,5 @@ __all__ = [
1311
1349
  "NamedArrayAxes",
1312
1350
  "NamedArrayAxesSpec",
1313
1351
  "Named",
1352
+ "set_mesh",
1314
1353
  ]
@@ -197,7 +197,7 @@ def from_state_dict(tree: T, state_dict: StateDict, prefix: Optional[str] = None
197
197
  array = state_dict[prefix]
198
198
 
199
199
  if isinstance(array, np.ndarray):
200
- mesh = partitioning._get_mesh()
200
+ mesh = jax.sharding.get_abstract_mesh()
201
201
  # TODO: modernize this
202
202
  if jax.device_count() > 1: # this happens with the default mesh
203
203
  pspec = partitioning.pspec_for_axis(tree.axes)
@@ -623,8 +623,8 @@ class NamedArray(metaclass=NamedArrayMeta):
623
623
  def argmin(self, axis: Optional[AxisSelector]) -> "NamedArray": # pragma: no cover
624
624
  return haliax.argmin(self, axis=axis)
625
625
 
626
- def argsort(self, axis: AxisSelector) -> "NamedArray": # pragma: no cover
627
- return haliax.argsort(self, axis=axis)
626
+ def argsort(self, axis: AxisSelector | None, *, stable: bool = False) -> "NamedArray": # pragma: no cover
627
+ return haliax.argsort(self, axis=axis, stable=stable)
628
628
 
629
629
  def astype(self, dtype) -> "NamedArray": # pragma: no cover
630
630
  return NamedArray(self.array.astype(dtype), self.axes)
@@ -1183,6 +1183,26 @@ def index(array: NamedArray, slices: Mapping[AxisSelector, NamedIndex]) -> Named
1183
1183
  def _compute_new_axes_and_slices_for_index(
1184
1184
  array, slices
1185
1185
  ) -> tuple[AxisSpec, list[py_slice | dslice | jnp.ndarray | int | list[int]]]:
1186
+ def _is_integer_like_scalar_index(value: Any) -> bool:
1187
+ if isinstance(value, (int, np.integer)):
1188
+ return True
1189
+ if not is_jax_array_like(value):
1190
+ return False
1191
+ shape = getattr(value, "shape", None)
1192
+ if shape != ():
1193
+ return False
1194
+ dtype = getattr(value, "dtype", None)
1195
+ if dtype is None:
1196
+ return False
1197
+ return jnp.issubdtype(dtype, jnp.integer)
1198
+
1199
+ def _coerce_integer_like_index(value: Any):
1200
+ if isinstance(value, np.integer):
1201
+ return int(value)
1202
+ if type(value) is np.ndarray and value.shape == () and jnp.issubdtype(value.dtype, jnp.integer):
1203
+ return int(value.item())
1204
+ return value
1205
+
1186
1206
  ordered_slices: list = [py_slice(None, None, None)] * len(array.axes) # type: ignore
1187
1207
  kept_axes = [True] * len(array.axes)
1188
1208
  array_slice_indices = []
@@ -1205,8 +1225,8 @@ def _compute_new_axes_and_slices_for_index(
1205
1225
  kept_axes[axis_index] = False
1206
1226
  array_slice_indices.append(axis_index)
1207
1227
  index_axis_names.add(orig_axis.name)
1208
- elif isinstance(slice_, int):
1209
- ordered_slices[axis_index] = slice_
1228
+ elif _is_integer_like_scalar_index(slice_):
1229
+ ordered_slices[axis_index] = _coerce_integer_like_index(slice_)
1210
1230
  kept_axes[axis_index] = False
1211
1231
  elif isinstance(slice_, NamedArray):
1212
1232
  ordered_slices[axis_index] = slice_
@@ -1,3 +1,7 @@
1
+ # Copyright 2025 The Levanter Authors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  """Named wrappers around :mod:`jax.numpy.fft`.
2
6
 
3
7
  These functions mirror the behaviour of their :mod:`jax.numpy.fft` counterparts
@@ -43,7 +47,6 @@ import jax.numpy.fft as jfft
43
47
  from .axis import Axis, AxisSelector, AxisSelection
44
48
  from .core import NamedArray
45
49
 
46
-
47
50
  AxisSizeLike = int | Axis | None
48
51
  AxisMapping = Mapping[AxisSelector, AxisSizeLike]
49
52
 
@@ -3,6 +3,8 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
 
6
+ import numbers
7
+
6
8
  import jax.nn as jnn
7
9
  import jax.numpy as jnp
8
10
 
@@ -64,16 +66,25 @@ def one_hot(x: NamedArray | int, class_axis: Axis, *, dtype=None) -> NamedArray:
64
66
  # Disabling this to prevent a crash in XLA on GPU
65
67
  # return hax.auto_sharded(hax.named(array, x.axes + (class_axis,)))
66
68
  return hax.named(array, x.axes + (class_axis,))
67
- else:
68
- assert isinstance(x, int)
69
+
70
+ x_array = jnp.asarray(x)
71
+ if x_array.ndim != 0:
72
+ raise TypeError("one_hot expects a scalar integer or NamedArray, " f"but got array with shape {x_array.shape}")
73
+
74
+ if isinstance(x, numbers.Integral):
69
75
  assert class_axis.size > x >= -class_axis.size
76
+ else:
77
+ if not (jnp.issubdtype(x_array.dtype, jnp.integer) or jnp.issubdtype(x_array.dtype, jnp.bool_)):
78
+ raise TypeError(
79
+ "one_hot expects an integer scalar or NamedArray, " f"but got value with dtype {x_array.dtype}"
80
+ )
70
81
 
71
- one = 1
72
- if dtype is not None:
73
- one = dtype(one)
82
+ one = 1
83
+ if dtype is not None:
84
+ one = dtype(one)
74
85
 
75
- array = jnp.zeros(class_axis.size, dtype=dtype).at[x].set(one)
76
- return hax.auto_sharded(haliax.named(array, class_axis))
86
+ array = jnp.zeros(class_axis.size, dtype=dtype).at[x_array].set(one)
87
+ return hax.auto_sharded(haliax.named(array, class_axis))
77
88
 
78
89
 
79
90
  __all__ = [
@@ -251,7 +251,7 @@ def _gmm(lhs, rhs, group_sizes, out_axes, sharded=False, ar=False):
251
251
  else:
252
252
  gmm_fn = shard_map(
253
253
  partial(gmm_sharded, ar=ar),
254
- mesh=hax.partitioning._get_mesh(),
254
+ mesh=jax.sharding.get_abstract_mesh(),
255
255
  in_specs=(
256
256
  hax.partitioning.pspec_for_axis(lhs.axes),
257
257
  hax.partitioning.pspec_for_axis(rhs.axes),