haliax 1.4.dev351__tar.gz → 1.4.dev353__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.dev351 → haliax-1.4.dev353}/PKG-INFO +1 -1
- haliax-1.4.dev353/src/haliax/__about__.py +1 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/core.py +70 -19
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/core_test.py +88 -4
- haliax-1.4.dev353/tests/test_scatter_gather.py +165 -0
- haliax-1.4.dev351/src/haliax/__about__.py +0 -1
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.coveragerc +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.flake8 +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.github/workflows/publish_dev.yaml +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.github/workflows/run_pre_commit.yaml +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.github/workflows/run_tests.yaml +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.gitignore +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.pre-commit-config.yaml +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/.readthedocs.yaml +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/CONTRIBUTING.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/LICENSE +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/README.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/api.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/broadcasting.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/cheatsheet.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/css/material.css +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/css/mkdocstrings.css +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/faq.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/data_parallel_mesh.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/data_parallel_mesh_replicated.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/device_mesh_1d.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/device_mesh_1d_zero.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/device_mesh_2d.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/figures/device_mesh_2d_zero.png +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/fp8.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/index.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/indexing.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/matmul.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/nn.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/partitioning.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/rearrange.ipynb +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/rearrange.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/requirements.txt +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/scan.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/state-dict.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/tutorial.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/docs/vmap.md +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/mkdocs.yml +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/pyproject.toml +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/__init__.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/__init__.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/compile_utils.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/dot.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/einsum.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/fp8.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/parsing.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/rearrange.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/scan.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/state_dict.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/_src/util.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/axis.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/debug.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/hof.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/jax_utils.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/__init__.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/activations.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/attention.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/conv.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/dropout.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/embedding.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/linear.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/loss.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/mlp.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/normalization.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/pool.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/nn/scan.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/ops.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/partitioning.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/quantization.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/random.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/specialized_fns.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/state_dict.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/tree_util.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/types.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/util.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/src/haliax/wrap.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_attention.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_axis.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_conv.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_debug.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_dot.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_einsum.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_fp8.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_hof.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_int8.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_nn.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_ops.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_parsing.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_partitioning.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_pool.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_random.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_rearrange.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_scan.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_specialized_fns.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_state_dict.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_tree_util.py +0 -0
- {haliax-1.4.dev351 → haliax-1.4.dev353}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haliax
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev353
|
|
4
4
|
Summary: Named Tensors for Legible Deep Learning in JAX
|
|
5
5
|
Project-URL: Homepage, https://github.com/stanford-crfm/haliax
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/stanford-crfm/haliax/issues/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.dev353"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import functools as ft
|
|
3
3
|
import typing
|
|
4
|
-
import warnings
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from math import prod
|
|
7
6
|
from types import EllipsisType
|
|
@@ -324,7 +323,9 @@ class NamedArray:
|
|
|
324
323
|
def slice(self, *args, **kwargs) -> "NamedArray":
|
|
325
324
|
return haliax.slice(self, *args, **kwargs)
|
|
326
325
|
|
|
327
|
-
def updated_slice(
|
|
326
|
+
def updated_slice(
|
|
327
|
+
self, start: Mapping[AxisSelector, Union[int, "NamedArray"]], update: "NamedArray"
|
|
328
|
+
) -> "NamedArray":
|
|
328
329
|
return haliax.updated_slice(self, start=start, update=update)
|
|
329
330
|
|
|
330
331
|
def take(self, axis: AxisSelector, index: Union[int, "NamedArray"]) -> "NamedArray":
|
|
@@ -854,7 +855,7 @@ def _slice_new(
|
|
|
854
855
|
|
|
855
856
|
|
|
856
857
|
def updated_slice(
|
|
857
|
-
array: NamedArray, start: Mapping[AxisSelector, Union[int, jnp.ndarray]], update: NamedArray
|
|
858
|
+
array: NamedArray, start: Mapping[AxisSelector, Union[int, jnp.ndarray, NamedArray]], update: NamedArray
|
|
858
859
|
) -> NamedArray:
|
|
859
860
|
"""
|
|
860
861
|
Updates a slice of an array with another array.
|
|
@@ -868,18 +869,42 @@ def updated_slice(
|
|
|
868
869
|
NamedArray: The updated array.
|
|
869
870
|
"""
|
|
870
871
|
|
|
872
|
+
# figure out which axis‐names to map over
|
|
873
|
+
map_axes: list[str] = []
|
|
874
|
+
for axis_sel, s in start.items():
|
|
875
|
+
if isinstance(s, NamedArray):
|
|
876
|
+
for ax in s.axes:
|
|
877
|
+
if ax.name not in map_axes:
|
|
878
|
+
map_axes.append(ax.name)
|
|
879
|
+
|
|
880
|
+
# need to vmap
|
|
881
|
+
if len(map_axes) > 0:
|
|
882
|
+
# scalar version: all starts are ints / tracers
|
|
883
|
+
f = updated_slice
|
|
884
|
+
for axis_name in map_axes:
|
|
885
|
+
# make sure that axis_name is in `array`. otherwise it doesn't make sense to vmap over it
|
|
886
|
+
if array._lookup_indices(axis_name) is None:
|
|
887
|
+
raise ValueError(f"axis {axis_name} not found in original array's axes: {array.shape}")
|
|
888
|
+
f = haliax.vmap(f, axis=axis_name)
|
|
889
|
+
return f(array, start, update)
|
|
890
|
+
|
|
871
891
|
array_slice_indices = [0] * len(array.axes)
|
|
872
892
|
for axis, s in start.items():
|
|
873
|
-
axis_index = array._lookup_indices(axis_name(axis))
|
|
893
|
+
axis_index = array._lookup_indices(haliax.axis_name(axis))
|
|
874
894
|
if axis_index is None:
|
|
875
895
|
raise ValueError(f"axis {axis} not found in {array}")
|
|
896
|
+
if isinstance(s, NamedArray): # this can happen in the vmap case
|
|
897
|
+
if s.ndim != 0:
|
|
898
|
+
raise ValueError(f"NamedArray {s} must be a scalar for axis {axis} in updated_slice")
|
|
899
|
+
s = s.scalar()
|
|
900
|
+
|
|
876
901
|
array_slice_indices[axis_index] = s
|
|
877
902
|
total_length = array.axes[axis_index].size
|
|
878
|
-
update_axis = update._lookup_indices(axis_name(axis))
|
|
903
|
+
update_axis = update._lookup_indices(haliax.axis_name(axis))
|
|
879
904
|
|
|
880
|
-
if update_axis is None:
|
|
881
|
-
raise ValueError(f"axis {axis} not found in {update}")
|
|
882
905
|
# if s is a tracer we can't check the size
|
|
906
|
+
if update_axis is None:
|
|
907
|
+
continue
|
|
883
908
|
if isinstance(s, int) and update.axes[update_axis].size + s > total_length:
|
|
884
909
|
raise ValueError(
|
|
885
910
|
f"update axis {axis} is too large to start at {s}. Array size is {total_length}, update size is"
|
|
@@ -888,17 +913,25 @@ def updated_slice(
|
|
|
888
913
|
|
|
889
914
|
# broadcasting here is a bit delicate because the sizes aren't necessarily the same
|
|
890
915
|
# we need to broadcast the update array to the same axis names as the array we're updating, adding them as necessary
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
if
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
916
|
+
if update.ndim > 0:
|
|
917
|
+
# if there are any axes in update that are not in array, it is an error:
|
|
918
|
+
axes_in_update = haliax.axis.without_axes(update.axes, array.axes)
|
|
919
|
+
if axes_in_update:
|
|
920
|
+
raise ValueError(
|
|
921
|
+
f"Update array with shape {update.shape} has axes {axes_in_update} that are not in the original array"
|
|
922
|
+
f" with shape {array.shape}. This is not allowed in updated_slice."
|
|
923
|
+
)
|
|
924
|
+
broadcasted_axes = []
|
|
925
|
+
for ax in array.axes:
|
|
926
|
+
upd_ax = update._lookup_indices(ax.name)
|
|
927
|
+
broadcasted_axes.append(ax if upd_ax is None else update.axes[upd_ax])
|
|
928
|
+
update = haliax.broadcast_to(update, broadcasted_axes, enforce_no_extra_axes=True)
|
|
929
|
+
upd_arr = update.array
|
|
930
|
+
else:
|
|
931
|
+
# scalar case: just add one axis so it doesn't get too mad
|
|
932
|
+
upd_arr = update.array.reshape((1,))
|
|
900
933
|
|
|
901
|
-
updated = jax.lax.dynamic_update_slice(array.array,
|
|
934
|
+
updated = jax.lax.dynamic_update_slice(array.array, upd_arr, array_slice_indices)
|
|
902
935
|
return NamedArray(updated, array.axes)
|
|
903
936
|
|
|
904
937
|
|
|
@@ -931,6 +964,7 @@ def _compute_new_axes_and_slices_for_index(
|
|
|
931
964
|
ordered_slices: list = [py_slice(None, None, None)] * len(array.axes) # type: ignore
|
|
932
965
|
kept_axes = [True] * len(array.axes)
|
|
933
966
|
array_slice_indices = []
|
|
967
|
+
index_axis_names = set()
|
|
934
968
|
|
|
935
969
|
for axis, slice_ in slices.items():
|
|
936
970
|
axis_index = array._lookup_indices(axis)
|
|
@@ -946,6 +980,8 @@ def _compute_new_axes_and_slices_for_index(
|
|
|
946
980
|
ordered_slices[axis_index] = slice_
|
|
947
981
|
array_slice_indices.append(axis_index)
|
|
948
982
|
kept_axes[axis_index] = False
|
|
983
|
+
for ax in slice_.axes:
|
|
984
|
+
index_axis_names.add(ax.name)
|
|
949
985
|
elif isinstance(slice_, list):
|
|
950
986
|
# we'll let JAX complain if this is wrong
|
|
951
987
|
ordered_slices[axis_index] = slice_
|
|
@@ -955,10 +991,17 @@ def _compute_new_axes_and_slices_for_index(
|
|
|
955
991
|
ordered_slices[axis_index] = slice_
|
|
956
992
|
kept_axes[axis_index] = False
|
|
957
993
|
elif slice_.ndim == 1:
|
|
958
|
-
|
|
959
|
-
|
|
994
|
+
target_axis = None
|
|
995
|
+
for i2, ax2 in enumerate(array.axes):
|
|
996
|
+
if i2 != axis_index and kept_axes[i2] and ax2.size == slice_.shape[0]:
|
|
997
|
+
target_axis = ax2
|
|
998
|
+
break
|
|
999
|
+
if target_axis is None:
|
|
1000
|
+
target_axis = axis
|
|
1001
|
+
ordered_slices[axis_index] = haliax.named(slice_, axis_name(target_axis))
|
|
960
1002
|
kept_axes[axis_index] = False
|
|
961
1003
|
array_slice_indices.append(axis_index)
|
|
1004
|
+
index_axis_names.add(axis_name(target_axis))
|
|
962
1005
|
else:
|
|
963
1006
|
raise ValueError(
|
|
964
1007
|
f"Only 0-d or 1-d unnamed arrays can be used for indexing. Got {slice_} for axis {axis}"
|
|
@@ -966,6 +1009,14 @@ def _compute_new_axes_and_slices_for_index(
|
|
|
966
1009
|
else:
|
|
967
1010
|
raise ValueError(f"Only NamedArrays can be used for advanced indexing. Got {slice_} for axis {axis}")
|
|
968
1011
|
|
|
1012
|
+
# If any index array uses axes that are already present in the array and not removed,
|
|
1013
|
+
# we need to explicitly advance-index those axes so numpy broadcasting works.
|
|
1014
|
+
for i, ax in enumerate(array.axes):
|
|
1015
|
+
if kept_axes[i] and ax.name in index_axis_names:
|
|
1016
|
+
ordered_slices[i] = haliax.arange(ax)
|
|
1017
|
+
array_slice_indices.append(i)
|
|
1018
|
+
kept_axes[i] = False
|
|
1019
|
+
|
|
969
1020
|
# advanced indexing
|
|
970
1021
|
if len(array_slice_indices) > 0:
|
|
971
1022
|
# this requires broadcasting
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import equinox as eqx
|
|
2
1
|
import jax
|
|
3
2
|
import jax.numpy as jnp
|
|
4
|
-
import pytest
|
|
5
3
|
from jax.random import PRNGKey
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
from haliax import Axis, NamedArray
|
|
5
|
+
from haliax import Axis, NamedArray, updated_slice
|
|
9
6
|
|
|
10
7
|
|
|
11
8
|
def test_unary_np_functions():
|
|
@@ -603,3 +600,90 @@ def test_slice_nd_array_present_dims():
|
|
|
603
600
|
assert jnp.all(jnp.equal(named1[{"H": index1}].array, named1.array[index1.array, :, :]))
|
|
604
601
|
|
|
605
602
|
# this is not ok, since the H would not be eliminated
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
import numpy as np
|
|
606
|
+
import pytest
|
|
607
|
+
|
|
608
|
+
import haliax as hax
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def test_scalar_updated_slice():
|
|
612
|
+
# Base case: scalar start on a 1D array
|
|
613
|
+
Seq = hax.Axis("seq", 5)
|
|
614
|
+
arr = hax.arange((Seq,), dtype=int)
|
|
615
|
+
# replace positions 2 and 3 with [100, 101]
|
|
616
|
+
upd = hax.named([100, 101], "seq")
|
|
617
|
+
|
|
618
|
+
result = updated_slice(arr, {"seq": 2}, upd)
|
|
619
|
+
# expect [0,1,100,101,4]
|
|
620
|
+
assert np.array_equal(result.array, np.array([0, 1, 100, 101, 4]))
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def test_ragged_single_token():
|
|
624
|
+
# Ragged case: one token per batch at different positions
|
|
625
|
+
Batch = hax.Axis("batch", 3)
|
|
626
|
+
Seq = hax.Axis("seq", 5)
|
|
627
|
+
cache = hax.zeros((Batch, Seq), dtype=int)
|
|
628
|
+
|
|
629
|
+
# lengths[b] is next free slot for batch b
|
|
630
|
+
lengths = hax.named([0, 1, 2], axis=Batch)
|
|
631
|
+
new_k = hax.named([7, 8, 9], axis=Batch)
|
|
632
|
+
|
|
633
|
+
result = updated_slice(cache, {"seq": lengths}, new_k)
|
|
634
|
+
|
|
635
|
+
# build expected NumPy array
|
|
636
|
+
exp = np.zeros((3, 5), int)
|
|
637
|
+
exp[0, 0] = 7
|
|
638
|
+
exp[1, 1] = 8
|
|
639
|
+
exp[2, 2] = 9
|
|
640
|
+
|
|
641
|
+
assert np.array_equal(result.array, exp)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def test_ragged_multi_token():
|
|
645
|
+
# Ragged case: a block of 2 tokens per batch at different positions
|
|
646
|
+
Batch = hax.Axis("batch", 2)
|
|
647
|
+
Seq = hax.Axis("seq", 5)
|
|
648
|
+
New = hax.Axis("seq", 2)
|
|
649
|
+
|
|
650
|
+
cache = hax.zeros((Batch, Seq), dtype=int)
|
|
651
|
+
lengths = hax.named([1, 3], axis=Batch)
|
|
652
|
+
# for batch=0 insert [1,2] at pos=1, for batch=1 insert [3,4] at pos=3
|
|
653
|
+
kv = hax.named([[1, 2], [3, 4]], axis=(Batch, New))
|
|
654
|
+
|
|
655
|
+
result = updated_slice(cache, {"seq": lengths}, kv)
|
|
656
|
+
|
|
657
|
+
exp = np.zeros((2, 5), int)
|
|
658
|
+
exp[0, 1] = 1
|
|
659
|
+
exp[0, 2] = 2
|
|
660
|
+
exp[1, 3] = 3
|
|
661
|
+
exp[1, 4] = 4
|
|
662
|
+
|
|
663
|
+
assert np.array_equal(result.array, exp)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def test_ragged_multi_token_bad_axis_name():
|
|
667
|
+
# Ragged case: a block of 2 tokens per batch at different positions
|
|
668
|
+
Batch = hax.Axis("batch", 2)
|
|
669
|
+
Seq = hax.Axis("seq", 5)
|
|
670
|
+
New = hax.Axis("new", 2)
|
|
671
|
+
|
|
672
|
+
cache = hax.zeros((Batch, Seq), dtype=int)
|
|
673
|
+
lengths = hax.named([1, 3], axis=Batch)
|
|
674
|
+
# for batch=0 insert [1,2] at pos=1, for batch=1 insert [3,4] at pos=3
|
|
675
|
+
kv = hax.named([[1, 2], [3, 4]], axis=(Batch, New))
|
|
676
|
+
|
|
677
|
+
with pytest.raises(ValueError, match="that are not in the original array with shape "):
|
|
678
|
+
updated_slice(cache, {"seq": lengths}, kv)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def test_update_overflow_error():
|
|
682
|
+
# Overflow: scalar start + update too large for axis → ValueError
|
|
683
|
+
Seq = hax.Axis("seq", 4)
|
|
684
|
+
arr = hax.zeros((Seq,), dtype=int)
|
|
685
|
+
# update of length 3 starting at pos=2 would run off the end (2+3 > 4)
|
|
686
|
+
upd = hax.arange((hax.Axis("seq", 3),), dtype=int)
|
|
687
|
+
|
|
688
|
+
with pytest.raises(ValueError):
|
|
689
|
+
updated_slice(arr, {"seq": 2}, upd)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import jax
|
|
2
|
+
import jax.numpy as jnp
|
|
3
|
+
|
|
4
|
+
import haliax as hax
|
|
5
|
+
from haliax import Axis, dslice
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# -----------------------------------------------------------------------------
|
|
9
|
+
# Helper for reference via take_along_axis
|
|
10
|
+
# -----------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _ref_gather(src, axis, idx):
|
|
14
|
+
ax_num = src.axes.index(axis)
|
|
15
|
+
# broadcast idx to match src without the gathered axis
|
|
16
|
+
other_axes = tuple(ax for ax in src.axes if ax != axis)
|
|
17
|
+
broadcast_axes = other_axes
|
|
18
|
+
for ax in idx.axes:
|
|
19
|
+
if ax not in broadcast_axes:
|
|
20
|
+
broadcast_axes += (ax,)
|
|
21
|
+
idx_b = hax.broadcast_to(idx, broadcast_axes, enforce_no_extra_axes=False)
|
|
22
|
+
if idx_b.array.ndim == src.array.ndim - 1:
|
|
23
|
+
idx_arr = idx_b.array[..., None]
|
|
24
|
+
else:
|
|
25
|
+
idx_arr = idx_b.array
|
|
26
|
+
out = jnp.take_along_axis(src.array, idx_arr, axis=ax_num)
|
|
27
|
+
if idx_b.array.ndim == src.array.ndim - 1:
|
|
28
|
+
out = out.squeeze(ax_num)
|
|
29
|
+
return out
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------- 1. single batched selector ----------------------
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_single_batched_selector():
|
|
36
|
+
B, S, V = Axis("batch", 4), Axis("seq", 3), Axis("vocab", 7)
|
|
37
|
+
x = hax.arange((B, S, V))
|
|
38
|
+
idx = hax.arange((B, S), dtype=jnp.int32) % V.size
|
|
39
|
+
out = x["vocab", idx]
|
|
40
|
+
assert out.axes == (B, S)
|
|
41
|
+
assert jnp.array_equal(out.array, _ref_gather(x, V, idx))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------- 2. selector adds new axis -----------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_selector_adds_new_axis():
|
|
48
|
+
B, S, V, T = Axis("batch", 2), Axis("seq", 3), Axis("vocab", 5), Axis("step", 4)
|
|
49
|
+
logits = hax.arange((B, S, V))
|
|
50
|
+
idx = hax.arange((B, T), dtype=jnp.int32) % V.size
|
|
51
|
+
out = logits["vocab", idx]
|
|
52
|
+
assert set(out.axes) == {B, S, T}
|
|
53
|
+
ref = jnp.transpose(_ref_gather(logits, V, idx), (0, 2, 1))
|
|
54
|
+
assert jnp.array_equal(out.array, ref)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ------------------------ 3. two contiguous selector arrays -------------------
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_two_contiguous_selectors():
|
|
61
|
+
B, X, Y = Axis("batch", 3), Axis("x", 5), Axis("y", 7)
|
|
62
|
+
a = hax.arange((B, X, Y))
|
|
63
|
+
ix = hax.arange((B,), dtype=jnp.int32) % X.size
|
|
64
|
+
iy = hax.arange((B,), dtype=jnp.int32) % Y.size
|
|
65
|
+
out = a["x", ix, "y", iy]
|
|
66
|
+
assert out.axes == (B,)
|
|
67
|
+
ref = a.array[jnp.arange(3), ix.array, iy.array]
|
|
68
|
+
assert jnp.array_equal(out.array, ref)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ------------------ 4. non-contiguous selectors → axes to front --------------
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_noncontig_selectors():
|
|
75
|
+
B, X, Z, Y = Axis("batch", 2), Axis("x", 4), Axis("z", 6), Axis("y", 5)
|
|
76
|
+
a = hax.arange((B, X, Z, Y))
|
|
77
|
+
ix = hax.arange((B,), dtype=jnp.int32) % X.size
|
|
78
|
+
iy = hax.arange((B,), dtype=jnp.int32) % Y.size
|
|
79
|
+
out = a["x", ix, "y", iy]
|
|
80
|
+
assert out.axes == (B, Z)
|
|
81
|
+
ref = a.array[jnp.arange(2), ix.array, :, iy.array]
|
|
82
|
+
assert jnp.array_equal(out.array, ref)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ----------------- 5. integer elimination + selector --------------------------
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_mixed_int_and_selector():
|
|
89
|
+
B, C, V = Axis("batch", 3), Axis("channel", 2), Axis("vocab", 6)
|
|
90
|
+
x = hax.arange((B, C, V))
|
|
91
|
+
idx = hax.arange((B,), dtype=jnp.int32) % V.size
|
|
92
|
+
out = x["channel", 1, "vocab", idx]
|
|
93
|
+
assert out.axes == (B,)
|
|
94
|
+
ref = x.array[:, 1, :][jnp.arange(3), idx.array]
|
|
95
|
+
assert jnp.array_equal(out.array, ref)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_dslice_with_selector():
|
|
99
|
+
B, S, V = Axis("batch", 2), Axis("seq", 5), Axis("vocab", 10)
|
|
100
|
+
x = hax.arange((B, S, V))
|
|
101
|
+
idx = (hax.arange((B, S), dtype=jnp.int32) + 2) % 4
|
|
102
|
+
shard = V.resize(4)
|
|
103
|
+
x_shard = x["vocab", dslice(0, shard)]
|
|
104
|
+
out = x_shard["vocab", idx]
|
|
105
|
+
assert out.axes == (B, S)
|
|
106
|
+
ref = x.array[:, :, :4][jnp.arange(B.size)[:, None], jnp.arange(S.size)[None, :], idx.array]
|
|
107
|
+
assert jnp.array_equal(out.array, ref)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_scalar_eliminates_axis():
|
|
111
|
+
B, S, V = Axis("batch", 2), Axis("seq", 3), Axis("vocab", 4)
|
|
112
|
+
x = hax.arange((B, S, V))
|
|
113
|
+
out = x["seq", 1]
|
|
114
|
+
assert out.axes == (B, V)
|
|
115
|
+
assert jnp.array_equal(out.array, x.array[:, 1, :])
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ----------------- 9. plain ndarray selector sugar ----------------------------
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_plain_ndarray_selector():
|
|
122
|
+
B, V = Axis("batch", 3), Axis("vocab", 5)
|
|
123
|
+
x = hax.arange((B, V))
|
|
124
|
+
idx = jnp.array([0, 2, 4], dtype=jnp.int32)
|
|
125
|
+
out = x["vocab", idx]
|
|
126
|
+
assert out.axes == (B,)
|
|
127
|
+
assert jnp.array_equal(out.array, x.array[jnp.arange(3), idx])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ----------------- 10. two selectors needing broadcast ------------------------
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_multiselector_broadcast():
|
|
134
|
+
B, S, V = Axis("batch", 2), Axis("seq", 3), Axis("vocab", 6)
|
|
135
|
+
a = hax.arange((B, S, V))
|
|
136
|
+
idx1 = hax.arange((B, S), dtype=jnp.int32) % V.size
|
|
137
|
+
out = a["vocab", idx1]
|
|
138
|
+
assert out.axes == (B, S)
|
|
139
|
+
assert jnp.array_equal(out.array, _ref_gather(a, V, idx1))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# ----------------- 11. scatter-ADD via .at[…].add -----------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_scatter_add():
|
|
146
|
+
B, S, V = Axis("batch", 2), Axis("seq", 3), Axis("vocab", 5)
|
|
147
|
+
x = hax.zeros((B, S, V))
|
|
148
|
+
idx = hax.arange((B, S), dtype=jnp.int32) % V.size
|
|
149
|
+
ones = hax.ones((B, S))
|
|
150
|
+
y = x.at[{V: idx}].add(ones)
|
|
151
|
+
ref = jnp.zeros((2, 3, 5)).at[jnp.arange(2)[:, None], jnp.arange(3)[None, :], idx.array].add(1.0)
|
|
152
|
+
assert jnp.array_equal(y.array, ref)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ----------------- 12. scatter-SET via .at[…].set -----------------------------
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_scatter_set():
|
|
159
|
+
B, V = Axis("batch", 2), Axis("vocab", 6)
|
|
160
|
+
x = hax.zeros((B, V))
|
|
161
|
+
idx = hax.named(jnp.array([1, 4]), B)
|
|
162
|
+
val = hax.ones(B) * 9
|
|
163
|
+
y = x.at[{V: idx}].set(val)
|
|
164
|
+
ref = jnp.zeros((2, 6)).at[jnp.arange(2), idx.array].set(9)
|
|
165
|
+
assert jnp.array_equal(y.array, ref)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.4.dev351"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|