haliax 1.4.dev445__tar.gz → 1.4.dev447__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 (138) hide show
  1. haliax-1.4.dev447/.agents/refs.md +50 -0
  2. {haliax-1.4.dev445 → haliax-1.4.dev447}/PKG-INFO +1 -1
  3. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/api.md +36 -0
  4. haliax-1.4.dev447/docs/mutable-refs.md +133 -0
  5. {haliax-1.4.dev445 → haliax-1.4.dev447}/mkdocs.yml +1 -0
  6. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/__about__.py +1 -1
  7. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/__init__.py +7 -0
  8. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/jax_utils.py +2 -7
  9. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/scan.py +33 -0
  10. haliax-1.4.dev447/src/haliax/ref.py +429 -0
  11. haliax-1.4.dev447/tests/test_named_ref.py +253 -0
  12. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_scan.py +59 -0
  13. {haliax-1.4.dev445 → haliax-1.4.dev447}/.agents/projects/api_parity.md +0 -0
  14. {haliax-1.4.dev445 → haliax-1.4.dev447}/.coveragerc +0 -0
  15. {haliax-1.4.dev445 → haliax-1.4.dev447}/.flake8 +0 -0
  16. {haliax-1.4.dev445 → haliax-1.4.dev447}/.github/workflows/publish_dev.yaml +0 -0
  17. {haliax-1.4.dev445 → haliax-1.4.dev447}/.github/workflows/run_pre_commit.yaml +0 -0
  18. {haliax-1.4.dev445 → haliax-1.4.dev447}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
  19. {haliax-1.4.dev445 → haliax-1.4.dev447}/.github/workflows/run_tests.yaml +0 -0
  20. {haliax-1.4.dev445 → haliax-1.4.dev447}/.gitignore +0 -0
  21. {haliax-1.4.dev445 → haliax-1.4.dev447}/.playbooks/add-types.md +0 -0
  22. {haliax-1.4.dev445 → haliax-1.4.dev447}/.playbooks/wrap-non-named.md +0 -0
  23. {haliax-1.4.dev445 → haliax-1.4.dev447}/.pre-commit-config.yaml +0 -0
  24. {haliax-1.4.dev445 → haliax-1.4.dev447}/.readthedocs.yaml +0 -0
  25. {haliax-1.4.dev445 → haliax-1.4.dev447}/AGENTS.md +0 -0
  26. {haliax-1.4.dev445 → haliax-1.4.dev447}/AUTHORS.md +0 -0
  27. {haliax-1.4.dev445 → haliax-1.4.dev447}/CONTRIBUTING.md +0 -0
  28. {haliax-1.4.dev445 → haliax-1.4.dev447}/CONTRIBUTORS.md +0 -0
  29. {haliax-1.4.dev445 → haliax-1.4.dev447}/LICENSE +0 -0
  30. {haliax-1.4.dev445 → haliax-1.4.dev447}/README.md +0 -0
  31. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/broadcasting.md +0 -0
  32. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/cheatsheet.md +0 -0
  33. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/css/material.css +0 -0
  34. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/css/mkdocstrings.css +0 -0
  35. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/faq.md +0 -0
  36. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/data_parallel_mesh.png +0 -0
  37. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/data_parallel_mesh_replicated.png +0 -0
  38. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_1d.png +0 -0
  39. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_1d_zero.png +0 -0
  40. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d.png +0 -0
  41. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
  42. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
  43. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
  44. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
  45. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_zero.png +0 -0
  46. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/fp8.md +0 -0
  47. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/index.md +0 -0
  48. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/indexing.md +0 -0
  49. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/matmul.md +0 -0
  50. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/nn.md +0 -0
  51. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/partitioning.md +0 -0
  52. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/primer.md +0 -0
  53. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/rearrange.ipynb +0 -0
  54. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/rearrange.md +0 -0
  55. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/requirements.txt +0 -0
  56. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/scan.md +0 -0
  57. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/state-dict.md +0 -0
  58. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/tutorial.md +0 -0
  59. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/typing.md +0 -0
  60. {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/vmap.md +0 -0
  61. {haliax-1.4.dev445 → haliax-1.4.dev447}/etc/license_header.txt +0 -0
  62. {haliax-1.4.dev445 → haliax-1.4.dev447}/pyproject.toml +0 -0
  63. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/__init__.py +0 -0
  64. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/compile_utils.py +0 -0
  65. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/dot.py +0 -0
  66. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/einsum.py +0 -0
  67. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/fp8.py +0 -0
  68. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/parsing.py +0 -0
  69. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/rearrange.py +0 -0
  70. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/scan.py +0 -0
  71. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/state_dict.py +0 -0
  72. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/util.py +0 -0
  73. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/axis.py +0 -0
  74. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/core.py +0 -0
  75. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/debug.py +0 -0
  76. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/fft.py +0 -0
  77. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/field.py +0 -0
  78. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/haxtyping.py +0 -0
  79. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/hof.py +0 -0
  80. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/__init__.py +0 -0
  81. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/activations.py +0 -0
  82. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/attention.py +0 -0
  83. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/conv.py +0 -0
  84. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/dropout.py +0 -0
  85. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/embedding.py +0 -0
  86. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/linear.py +0 -0
  87. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/loss.py +0 -0
  88. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/mlp.py +0 -0
  89. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/mup.py +0 -0
  90. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/normalization.py +0 -0
  91. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/pool.py +0 -0
  92. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/ops.py +0 -0
  93. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/partitioning.py +0 -0
  94. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/poly.py +0 -0
  95. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/quantization.py +0 -0
  96. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/random.py +0 -0
  97. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/specialized_fns.py +0 -0
  98. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/state_dict.py +0 -0
  99. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/tree.py +0 -0
  100. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/tree_util.py +0 -0
  101. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/types.py +0 -0
  102. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/util.py +0 -0
  103. {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/wrap.py +0 -0
  104. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/core_test.py +0 -0
  105. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_attention.py +0 -0
  106. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_axis.py +0 -0
  107. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_bitwise_ops.py +0 -0
  108. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_conv.py +0 -0
  109. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_debug.py +0 -0
  110. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_dot.py +0 -0
  111. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_dtype_typing.py +0 -0
  112. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_einsum.py +0 -0
  113. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_fft.py +0 -0
  114. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_field.py +0 -0
  115. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_fp8.py +0 -0
  116. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_hof.py +0 -0
  117. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_int8.py +0 -0
  118. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_moe_linear.py +0 -0
  119. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_mup_coordinate_check.py +0 -0
  120. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_mup_embedding.py +0 -0
  121. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_mup_linear.py +0 -0
  122. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_namedarray_typing.py +0 -0
  123. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_nan_reductions.py +0 -0
  124. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_nn.py +0 -0
  125. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_ops.py +0 -0
  126. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_parsing.py +0 -0
  127. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_partitioning.py +0 -0
  128. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_poly_ops.py +0 -0
  129. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_pool.py +0 -0
  130. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_random.py +0 -0
  131. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_rearrange.py +0 -0
  132. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_scatter_gather.py +0 -0
  133. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_specialized_fns.py +0 -0
  134. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_state_dict.py +0 -0
  135. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_tree_util.py +0 -0
  136. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_utils.py +0 -0
  137. {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_visualize_sharding.py +0 -0
  138. {haliax-1.4.dev445 → haliax-1.4.dev447}/uv.lock +0 -0
@@ -0,0 +1,50 @@
1
+ # NamedRef & SliceRef Plan
2
+
3
+ ## Goals
4
+ - Provide a `NamedRef` abstraction mirroring `jax.Ref` while preserving Haliax axis metadata.
5
+ - Offer helper APIs (`hax.new_ref`, `hax.freeze`, `hax.ref.get/swap`) that operate on named refs and enforce shape/axis compatibility.
6
+ - Enable "slice refs" that partially apply named indexers so subsequent operations only supply the remaining indices, including composing multiple slice refs.
7
+
8
+ ## Architectural Outline
9
+ 1. **Core data model**
10
+ - Introduce `NamedRef` in `src/haliax/core.py` (or a focused `src/haliax/ref.py` module) as a thin wrapper around `jax.Ref` plus axis metadata (likely reuse `NamedArray` named shape helpers).
11
+ - Register `NamedRef` as a PyTree node: treat the underlying `jax.Ref` handle as a leaf to avoid unwanted tracing semantics.
12
+ - Expose properties: `.axes`, `.named_shape`, `.shape`, `.dtype`, and `.value` (reads through `ref[...]`).
13
+
14
+ 2. **Creation and conversion helpers**
15
+ - Implement `haliax.new_ref(value, axes|named_shape)` returning `NamedRef`; allow existing `NamedArray` inputs to propagate their axis info.
16
+ - Add `haliax.freeze(ref)` returning a `NamedArray` while invalidating the underlying `jax.Ref`, preserving metadata.
17
+ - Provide `haliax.ref.get(ref, index)` / `haliax.ref.swap(ref, index, value)` that accept named indexers and return/require `NamedArray` values.
18
+
19
+ 3. **Indexing semantics**
20
+ - Reuse the existing NamedArray indexing utilities (`axis_spec_to_tuple`, `_normalize_indexers`, `_NamedIndexUpdateRef`) so `NamedRef.__getitem__` returns `NamedArray` views and `__setitem__` / `.set` accept either `NamedArray` or raw arrays (with shape checks).
21
+ - Enforce error messaging consistent with NamedArray when axes mismatch or out-of-bounds indices occur.
22
+
23
+ 4. **Slice refs**
24
+ - Add a `NamedSliceRef` (or similar) that wraps a base `NamedRef` plus a frozen partial index mapping.
25
+ - Support construction via `NamedRef.slice(selector)` where `selector` is any named index expression (dict, `Axis`, tuple, etc.).
26
+ - On read/write, merge the stored selector with the new selector: the stored selector is applied first, and new selectors can further index the result. Implement logic to resolve nested slices (e.g. range then integer index) by normalizing everything to positional indices via helper utilities.
27
+ - Ensure composition is associative: calling `.slice()` on a `NamedSliceRef` should produce another `NamedSliceRef` that collapses selectors appropriately.
28
+ - Preserve dynamic slice selectors (Haliax `dslice` or `pallas.dslice`) so their static size information is retained during trace-time shape inference.
29
+ - Provide an `unsliced()` helper to recover the original reference when staged selectors are no longer needed.
30
+ - Handle ellipsis and implicit axes; ensure merging respects axis ordering and catches conflicting assignments.
31
+
32
+ 5. **Integration & ergonomics**
33
+ - Audit parts of the codebase that manipulate mutable state (e.g. caching layers, training loops) to expose the new API where appropriate. Initially, surface `NamedRef` under the `haliax.ref` namespace without retrofitting all call sites.
34
+ - Provide a `NamedRef.unsafe_buffer_pointer()` passthrough for parity with `jax.Ref` diagnostics.
35
+
36
+ 6. **Testing strategy**
37
+ - Add `tests/test_named_ref.py` covering: creation from arrays, indexing/assignment (scalar, slices, advanced), `jit` usage, vmap plumbing, and ensuring restrictions (e.g. returning refs from jit) raise clear errors.
38
+ - Include dedicated tests for slice refs verifying merged indexing (simple axis, slices-of-slices, dict updates, ellipsis) and interactions under `jit`.
39
+ - Validate that gradients through pure functions using NamedRef internally behave like the array equivalents.
40
+
41
+ 7. **Documentation & guidelines**
42
+ - Document the new API in `docs/api.md` (table entry and short section) and, if needed, add a primer example in `docs/primer.md` showing plumbing state with NamedRef.
43
+ - Update contributor notes that future ref-based utilities must keep numerical tolerances intact (no relaxed tolerances), aligning with the existing testing guidelines.
44
+ - Call out any additional guidelines discovered during implementation (e.g., keeping dslice selectors intact for static shapes).
45
+ - Consider a `.playbooks/` entry if ref plumbing becomes a common flow after initial implementation.
46
+
47
+ ## Open Questions / Follow-ups
48
+ - Confirm whether `NamedRef` needs to support asynchronous dispatch or pytree flattening beyond leaf behavior.
49
+ - Decide if we want implicit conversion between `NamedRef` and `NamedArray` in certain helpers or require explicit `.value` reads for clarity.
50
+ - Explore caching strategies or wrappers for `foreach`-style utilities once the base `NamedRef` and slice refs land.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haliax
3
- Version: 1.4.dev445
3
+ Version: 1.4.dev447
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/
@@ -179,6 +179,42 @@ See also the section on [Indexing and Slicing](indexing.md).
179
179
  ::: haliax.take
180
180
  ::: haliax.updated_slice
181
181
 
182
+ ## Mutable References
183
+
184
+ See also the section on [Mutable References](mutable-refs.md).
185
+
186
+ JAX provides [`jax.Ref`][], a mutable array reference that can be read or written in place while remaining compatible
187
+ with transformations such as `jax.jit` or `jax.grad`. Haliax mirrors that API with [`haliax.NamedRef`][haliax.NamedRef],
188
+ which carries axis metadata so you can keep using named indexing when plumbing state through your programs.
189
+
190
+ You introduce a new reference with [`haliax.new_ref`][haliax.new_ref]. The returned object behaves much like a
191
+ [`NamedArray`][haliax.NamedArray] for indexing purposes: `ref[{"batch": 0}]` reads a slice, and assignments like
192
+ `ref[{"token": slice(1, 3)}] = update` perform in-place updates on the underlying buffer. If you need to stage part of a
193
+ reference for repeated use, call [`NamedRef.slice`][haliax.NamedRef.slice] to create a *slice ref*. Slice refs remember a
194
+ partial indexing expression so you only supply the remaining axes during reads or writes:
195
+
196
+ ```python
197
+ Cache = hax.Axis("layers", 24)
198
+ Head = hax.Axis("head", 8)
199
+ cache = hax.zeros((Cache, Head))
200
+ cache_ref = hax.new_ref(cache)
201
+
202
+ # Pin the layer axis once so subsequent lookups only specify the head coordinate.
203
+ layer_ref = cache_ref.slice({"layers": slice(4, 8)})
204
+ layer_ref[{"layers": 0, "head": 3}] = 1.0 # updates layer 4, head 3 in the original cache
205
+ ```
206
+
207
+ When you are done mutating a reference, call [`haliax.freeze`][haliax.freeze] to invalidate it and recover a final
208
+ `NamedArray` snapshot. You can also perform atomic-style updates with [`haliax.swap`][haliax.swap] (or the functional
209
+ helpers under `haliax.ref`).
210
+
211
+ ::: haliax.NamedRef
212
+ ::: haliax.new_ref
213
+ ::: haliax.freeze
214
+ ::: haliax.swap
215
+
216
+ [`jax.Ref`]: https://docs.jax.dev/en/latest/array_refs.html
217
+
182
218
  #### Dynamic Slicing
183
219
 
184
220
  ::: haliax.dslice
@@ -0,0 +1,133 @@
1
+ # Mutable References
2
+
3
+ JAX now exposes [`jax.Ref`](https://jax.readthedocs.io/en/latest/notebooks/refs.html), a mutable buffer that
4
+ remains compatible with transformations such as `jax.jit`, `jax.grad`, and friends. Haliax mirrors that idea with
5
+ [`haliax.NamedRef`][haliax.NamedRef], preserving axis metadata so you can keep using named indexing while wiring state
6
+ through pure computations.
7
+
8
+ This page walks through the common idioms you will encounter when working with named references, including ways to
9
+ slice references, compose partial selectors, and freeze or swap out the underlying data when you are done.
10
+
11
+ ## Creating a `NamedRef`
12
+
13
+ Use [`haliax.new_ref`][haliax.new_ref] to wrap an existing tensor. The helper accepts either a
14
+ [`NamedArray`][haliax.NamedArray] or a raw JAX array plus an axis specification.
15
+
16
+ ```python
17
+ import haliax as hax
18
+ import jax
19
+
20
+ Batch, Feature = hax.make_axes(Batch=4, Feature=8)
21
+ weights = hax.random.normal(jax.random.PRNGKey(0), (Batch, Feature))
22
+ weights_ref = hax.new_ref(weights)
23
+
24
+ # You can still inspect the named shape.
25
+ assert weights_ref.axes == weights.axes
26
+ assert weights_ref.shape == {"Batch": 4, "Feature": 8}
27
+ ```
28
+
29
+ Inside a pure function you can allocate fresh references to stage intermediate results:
30
+
31
+ ```python
32
+ @jax.jit
33
+ def normalize(x: hax.NamedArray) -> hax.NamedArray:
34
+ ref = hax.new_ref(x)
35
+ ref[{"Batch": slice(None)}] = ref[{"Batch": slice(None)}] - hax.mean(x, axis=Batch)
36
+ return ref[...]
37
+ ```
38
+
39
+ References follow JAX's rules: you may not return the ref itself from inside a transformed function, pass the same ref
40
+ argument multiple times, or close over a ref that is also passed as a parameter. Those restrictions keep aliasing under
41
+ control so transformations can reason about side effects.
42
+
43
+ ## Reading and writing
44
+
45
+ `NamedRef` behaves like a lightweight view of the underlying buffer. Reading always returns a `NamedArray`, while writing
46
+ accepts either bare scalars/JAX arrays or another `NamedArray` with matching axes.
47
+
48
+ ```python
49
+ logits_ref = hax.new_ref(hax.zeros((Batch, Feature)))
50
+
51
+ # Read a slice.
52
+ logits = logits_ref[{"Batch": 0}]
53
+
54
+ # Write back in-place.
55
+ logits_ref[{"Batch": 0}] = hax.random.uniform(jax.random.PRNGKey(1), logits.axes)
56
+
57
+ # Mutations are visible through other aliasing refs.
58
+ assert logits_ref[{"Batch": 0}].array is not None
59
+ ```
60
+
61
+ If you prefer a functional style, use [`haliax.ref.get`][haliax.ref.get] or [`haliax.swap`][haliax.swap]. The latter
62
+ swaps in a new value and returns the previous contents, similar to `dict.setdefault`.
63
+
64
+ ## Slice references
65
+
66
+ Plumbing state often requires staging a subset of the axes. `NamedRef.slice` lets you pre-apply a named indexer to a
67
+ reference and reuse it later without repeating the prefix. We call the result a *slice ref*.
68
+
69
+ ```python
70
+ Cache = hax.Axis("layer", 24)
71
+ Head = hax.Axis("head", 8)
72
+ cache = hax.zeros((Cache, Head))
73
+ cache_ref = hax.new_ref(cache)
74
+
75
+ # Focus on a subset of layers.
76
+ window = cache_ref.slice({"layer": slice(4, 8)})
77
+
78
+ # Indexing the slice ref automatically splices the prefix into the base ref.
79
+ window[{"layer": 0, "head": 3}] = 1.0 # updates layer 4, head 3 in the original buffer
80
+ # `.value()` reads the staged slice.
81
+ window_value = window.value()
82
+ ```
83
+
84
+ Slice refs compose. Applying `.slice(...)` to an existing slice ref merges the new selection with the previous one, so
85
+ `cache_ref.slice({"layer": slice(1, 4)}).slice({"layer": 0})` updates the second layer of the original cache. Integer and
86
+ slice prefixes are supported; advanced indexing still needs to happen at call time (`slice` with `NamedArray` selectors or
87
+ lists is not yet folded into the prefix).
88
+
89
+ If you need to recover the original reference (without any staged prefixes), call [`NamedRef.unsliced`][haliax.NamedRef.unsliced].
90
+
91
+ The merging logic mirrors `NamedArray.__getitem__`: ellipses expand to the remaining axes, implicit dimensions are
92
+ filled in order, and conflicting assignments raise errors. See [Indexing and Slicing](indexing.md) for a refresher.
93
+
94
+ ## Working with transformations
95
+
96
+ Because `NamedRef` is registered as a PyTree node, you can nest it inside larger pytree structures or pass it through
97
+ `jax.tree_map` for bookkeeping. Only the underlying `jax.Ref` is considered a leaf, so axis metadata is treated as static
98
+ structure and does not trigger recompilation.
99
+
100
+ Keep in mind the JAX restrictions on mutable functions:
101
+
102
+ - Do not return a ref from inside `jax.jit`, `jax.grad`, `jax.vmap`, `jax.lax.scan`, etc.
103
+ - Avoid closing over a ref you also pass into the function (JAX disallows those aliasing patterns).
104
+ - Use `jax.lax.stop_gradient` if you are plumbing values that should not affect differentiation.
105
+
106
+ Pure functions that create refs internally continue to compose normally with autodiff and vectorization. If your ref is
107
+ only used for bookkeeping (e.g., collecting auxiliary statistics), stop gradients before writing into it:
108
+
109
+ ```python
110
+ def collect_metrics(x, stats_ref):
111
+ y = hax.sin(x)
112
+ stats_ref[{"Batch": slice(None)}] += jax.lax.stop_gradient(y)
113
+ return y
114
+ ```
115
+
116
+ ## Freezing and swapping
117
+
118
+ When you are done mutating a reference, call [`haliax.freeze`][haliax.freeze] to convert it back into an immutable
119
+ `NamedArray`.
120
+
121
+ ```python
122
+ final = hax.freeze(cache_ref)
123
+ assert isinstance(final, hax.NamedArray)
124
+ ```
125
+
126
+ [`haliax.swap`][haliax.swap] provides an atomic-style update: it returns the previous value while storing the new one.
127
+ The helper integrates with slice refs, so you can swap just a subset of the buffer.
128
+
129
+ ```python
130
+ prev = hax.swap(cache_ref, {"layer": slice(0, 2)}, hax.ones((Cache.resize(2), Head)))
131
+ ```
132
+
133
+ See [`tests/test_named_ref.py`](tests/test_named_ref.py) for runnable examples that exercise the API.
@@ -88,6 +88,7 @@ nav:
88
88
  - Named Arrays:
89
89
  - Broadcasting: 'broadcasting.md'
90
90
  - Indexing and Slicing: 'indexing.md'
91
+ - Mutable References: 'mutable-refs.md'
91
92
  - Rearrange: 'rearrange.md'
92
93
  - Matrix Multiplication: 'matmul.md'
93
94
  - Type Annotations: 'typing.md'
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
 
6
- __version__ = "1.4.dev445"
6
+ __version__ = "1.4.dev447"
@@ -16,6 +16,7 @@ import haliax.nn as nn
16
16
  import haliax.quantization as quantization
17
17
  import haliax.random as random
18
18
  import haliax.state_dict as state_dict
19
+ import haliax.ref as ref # noqa: F401
19
20
  import haliax.tree as tree # noqa: F401
20
21
  import haliax.tree_util as tree_util
21
22
  import haliax.util as util
@@ -68,6 +69,7 @@ from .core import (
68
69
  unflatten_axis,
69
70
  updated_slice,
70
71
  )
72
+ from .ref import NamedRef, freeze, get, new_ref, swap
71
73
  from .haxtyping import Named
72
74
  from .hof import fold, map, scan, vmap
73
75
  from .jax_utils import tree_checkpoint_name
@@ -1350,5 +1352,10 @@ __all__ = [
1350
1352
  "NamedArrayAxes",
1351
1353
  "NamedArrayAxesSpec",
1352
1354
  "Named",
1355
+ "NamedRef",
1356
+ "new_ref",
1357
+ "freeze",
1358
+ "swap",
1359
+ "get",
1353
1360
  "set_mesh",
1354
1361
  ]
@@ -17,6 +17,7 @@ from jax import numpy as jnp
17
17
  from jax import random as jrandom
18
18
  from jax.experimental.multihost_utils import host_local_array_to_global_array
19
19
  from jax.sharding import PartitionSpec
20
+ from jax._src.state.indexing import Slice
20
21
  from jax.ad_checkpoint import checkpoint_name
21
22
  from jax.typing import DTypeLike
22
23
  from jaxtyping import PRNGKeyArray
@@ -218,13 +219,7 @@ def is_in_jit():
218
219
 
219
220
 
220
221
  def is_pallas_dslice(x: object) -> bool:
221
- try:
222
- from jax.experimental.pallas import dslice as pdslice
223
- except ImportError:
224
- return False
225
-
226
- _PALLAS_DSLICE_TYPE = type(pdslice(0, 1))
227
- return isinstance(x, _PALLAS_DSLICE_TYPE)
222
+ return isinstance(x, Slice)
228
223
 
229
224
 
230
225
  def is_scalarish(x):
@@ -150,6 +150,11 @@ class BlockFoldable(Protocol[M]):
150
150
  """
151
151
  ...
152
152
 
153
+ def get_layer(self, index: int) -> M:
154
+ """Return the ``index``th layer of the folded module."""
155
+
156
+ ...
157
+
153
158
 
154
159
  class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
155
160
  """
@@ -377,6 +382,11 @@ class BlockSeq(ModuleWithStateDictSerialization, Generic[M]):
377
382
 
378
383
  return state_dict
379
384
 
385
+ def get_layer(self, index: int) -> M:
386
+ """Return the ``index``th block in this sequential container."""
387
+
388
+ return self.blocks[index]
389
+
380
390
  @property
381
391
  def _output_ckpt_name(self):
382
392
  return f"BlockSeq[{self.Block}, {self.blocks[0].__class__.__name__}].outputs"
@@ -442,6 +452,12 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
442
452
  Block: Axis = eqx.field(static=True)
443
453
  gradient_checkpointing: ScanCheckpointPolicy = eqx.field(static=True)
444
454
 
455
+ @property
456
+ def Layers(self) -> Axis:
457
+ """Alias for :attr:`Block` used by some downstream code."""
458
+
459
+ return self.Block
460
+
445
461
  @classmethod
446
462
  def init(
447
463
  cls,
@@ -700,6 +716,23 @@ class Stacked(ModuleWithStateDictSerialization, Generic[M]):
700
716
  unstacked_leaves = tuple(zip(*unstacked_leaves))
701
717
  return tuple(map(lambda x: jax.tree_util.tree_unflatten(structure, x), unstacked_leaves))
702
718
 
719
+ def get_layer(self, index: int) -> M:
720
+ """Return the ``index``th layer of this stacked module."""
721
+
722
+ def select_leaf(leaf):
723
+ if isinstance(leaf, haliax.NamedArray):
724
+ if haliax.selects_axis(leaf.axes, self.Block):
725
+ return leaf[self.Block, index]
726
+ else:
727
+ return leaf
728
+ if is_jax_or_hax_array_like(leaf):
729
+ if getattr(leaf, "shape", ()) and leaf.shape[0] == self.Block.size:
730
+ return leaf[index]
731
+ return leaf
732
+ return leaf
733
+
734
+ return haliax.tree_util.tree_map(select_leaf, self.stacked)
735
+
703
736
  def to_state_dict(self, prefix: str | None = None) -> StateDict:
704
737
  # this method needs to "devectorize" the blocks, so that we have a list of blocks h.0.FOO, h.1.FOO, etc.
705
738
  # first just do the normal thing with our own dict, which we'll post-process