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.
- haliax-1.4.dev447/.agents/refs.md +50 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/PKG-INFO +1 -1
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/api.md +36 -0
- haliax-1.4.dev447/docs/mutable-refs.md +133 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/mkdocs.yml +1 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/__about__.py +1 -1
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/__init__.py +7 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/jax_utils.py +2 -7
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/scan.py +33 -0
- haliax-1.4.dev447/src/haliax/ref.py +429 -0
- haliax-1.4.dev447/tests/test_named_ref.py +253 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_scan.py +59 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.agents/projects/api_parity.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.coveragerc +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.flake8 +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.gitignore +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.playbooks/add-types.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.playbooks/wrap-non-named.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/AGENTS.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/AUTHORS.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/CONTRIBUTORS.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/LICENSE +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/README.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/css/material.css +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/faq.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/fp8.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/index.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/indexing.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/matmul.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/nn.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/partitioning.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/primer.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/rearrange.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/requirements.txt +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/scan.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/state-dict.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/tutorial.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/typing.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/docs/vmap.md +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/etc/license_header.txt +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/pyproject.toml +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/scan.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/state_dict.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/core.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/fft.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/field.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/haxtyping.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/embedding.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/linear.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/mup.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/normalization.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/poly.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/random.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/tree.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/types.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/util.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/core_test.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_attention.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_axis.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_bitwise_ops.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_conv.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_debug.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_dot.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_dtype_typing.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_fft.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_field.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_hof.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_int8.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_moe_linear.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_mup_coordinate_check.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_mup_embedding.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_mup_linear.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_namedarray_typing.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_nan_reductions.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_nn.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_ops.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_poly_ops.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_pool.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_random.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_scatter_gather.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_utils.py +0 -0
- {haliax-1.4.dev445 → haliax-1.4.dev447}/tests/test_visualize_sharding.py +0 -0
- {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.
|
|
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.
|
|
@@ -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
|
-
|
|
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
|