haliax 1.4.dev352__tar.gz → 1.4.dev354__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 (107) hide show
  1. {haliax-1.4.dev352 → haliax-1.4.dev354}/PKG-INFO +1 -1
  2. haliax-1.4.dev354/src/haliax/__about__.py +1 -0
  3. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/core.py +50 -17
  4. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/hof.py +15 -4
  5. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/core_test.py +88 -4
  6. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_hof.py +19 -0
  7. haliax-1.4.dev352/src/haliax/__about__.py +0 -1
  8. {haliax-1.4.dev352 → haliax-1.4.dev354}/.coveragerc +0 -0
  9. {haliax-1.4.dev352 → haliax-1.4.dev354}/.flake8 +0 -0
  10. {haliax-1.4.dev352 → haliax-1.4.dev354}/.github/workflows/publish_dev.yaml +0 -0
  11. {haliax-1.4.dev352 → haliax-1.4.dev354}/.github/workflows/run_pre_commit.yaml +0 -0
  12. {haliax-1.4.dev352 → haliax-1.4.dev354}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
  13. {haliax-1.4.dev352 → haliax-1.4.dev354}/.github/workflows/run_tests.yaml +0 -0
  14. {haliax-1.4.dev352 → haliax-1.4.dev354}/.gitignore +0 -0
  15. {haliax-1.4.dev352 → haliax-1.4.dev354}/.pre-commit-config.yaml +0 -0
  16. {haliax-1.4.dev352 → haliax-1.4.dev354}/.readthedocs.yaml +0 -0
  17. {haliax-1.4.dev352 → haliax-1.4.dev354}/CONTRIBUTING.md +0 -0
  18. {haliax-1.4.dev352 → haliax-1.4.dev354}/LICENSE +0 -0
  19. {haliax-1.4.dev352 → haliax-1.4.dev354}/README.md +0 -0
  20. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/api.md +0 -0
  21. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/broadcasting.md +0 -0
  22. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/cheatsheet.md +0 -0
  23. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/css/material.css +0 -0
  24. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/css/mkdocstrings.css +0 -0
  25. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/faq.md +0 -0
  26. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/data_parallel_mesh.png +0 -0
  27. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/data_parallel_mesh_replicated.png +0 -0
  28. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/device_mesh_1d.png +0 -0
  29. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/device_mesh_1d_zero.png +0 -0
  30. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/device_mesh_2d.png +0 -0
  31. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
  32. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
  33. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
  34. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
  35. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/figures/device_mesh_2d_zero.png +0 -0
  36. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/fp8.md +0 -0
  37. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/index.md +0 -0
  38. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/indexing.md +0 -0
  39. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/matmul.md +0 -0
  40. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/nn.md +0 -0
  41. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/partitioning.md +0 -0
  42. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/rearrange.ipynb +0 -0
  43. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/rearrange.md +0 -0
  44. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/requirements.txt +0 -0
  45. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/scan.md +0 -0
  46. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/state-dict.md +0 -0
  47. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/tutorial.md +0 -0
  48. {haliax-1.4.dev352 → haliax-1.4.dev354}/docs/vmap.md +0 -0
  49. {haliax-1.4.dev352 → haliax-1.4.dev354}/mkdocs.yml +0 -0
  50. {haliax-1.4.dev352 → haliax-1.4.dev354}/pyproject.toml +0 -0
  51. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/__init__.py +0 -0
  52. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/__init__.py +0 -0
  53. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/compile_utils.py +0 -0
  54. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/dot.py +0 -0
  55. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/einsum.py +0 -0
  56. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/fp8.py +0 -0
  57. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/parsing.py +0 -0
  58. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/rearrange.py +0 -0
  59. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/scan.py +0 -0
  60. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/state_dict.py +0 -0
  61. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/_src/util.py +0 -0
  62. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/axis.py +0 -0
  63. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/debug.py +0 -0
  64. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/jax_utils.py +0 -0
  65. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/__init__.py +0 -0
  66. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/activations.py +0 -0
  67. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/attention.py +0 -0
  68. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/conv.py +0 -0
  69. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/dropout.py +0 -0
  70. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/embedding.py +0 -0
  71. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/linear.py +0 -0
  72. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/loss.py +0 -0
  73. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/mlp.py +0 -0
  74. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/normalization.py +0 -0
  75. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/pool.py +0 -0
  76. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/nn/scan.py +0 -0
  77. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/ops.py +0 -0
  78. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/partitioning.py +0 -0
  79. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/quantization.py +0 -0
  80. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/random.py +0 -0
  81. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/specialized_fns.py +0 -0
  82. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/state_dict.py +0 -0
  83. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/tree_util.py +0 -0
  84. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/types.py +0 -0
  85. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/util.py +0 -0
  86. {haliax-1.4.dev352 → haliax-1.4.dev354}/src/haliax/wrap.py +0 -0
  87. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_attention.py +0 -0
  88. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_axis.py +0 -0
  89. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_conv.py +0 -0
  90. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_debug.py +0 -0
  91. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_dot.py +0 -0
  92. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_einsum.py +0 -0
  93. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_fp8.py +0 -0
  94. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_int8.py +0 -0
  95. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_nn.py +0 -0
  96. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_ops.py +0 -0
  97. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_parsing.py +0 -0
  98. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_partitioning.py +0 -0
  99. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_pool.py +0 -0
  100. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_random.py +0 -0
  101. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_rearrange.py +0 -0
  102. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_scan.py +0 -0
  103. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_scatter_gather.py +0 -0
  104. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_specialized_fns.py +0 -0
  105. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_state_dict.py +0 -0
  106. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_tree_util.py +0 -0
  107. {haliax-1.4.dev352 → haliax-1.4.dev354}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haliax
3
- Version: 1.4.dev352
3
+ Version: 1.4.dev354
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.dev354"
@@ -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(self, start: Mapping[AxisSelector, int], update: "NamedArray") -> "NamedArray":
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
- broadcasted_axes = []
892
- for axis in array.axes:
893
- update_axis = update._lookup_indices(axis.name)
894
- if update_axis is None:
895
- broadcasted_axes.append(axis)
896
- else:
897
- broadcasted_axes.append(update.axes[update_axis])
898
-
899
- update = haliax.broadcast_to(update, broadcasted_axes, enforce_no_extra_axes=True)
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, update.array, array_slice_indices)
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
 
@@ -19,16 +19,16 @@ from ._src.scan import (
19
19
  map,
20
20
  scan,
21
21
  )
22
- from .axis import Axis, AxisSelector, selects_axis
22
+ from .axis import Axis, AxisSelection, AxisSelector, selects_axis
23
23
  from .core import NamedArray
24
24
  from .jax_utils import Static, broadcast_prefix, is_jax_array_like
25
25
  from .partitioning import physical_axis_name
26
- from .util import is_named_array
26
+ from .util import ensure_tuple, is_named_array
27
27
 
28
28
 
29
29
  def vmap(
30
30
  fn,
31
- axis: AxisSelector,
31
+ axis: AxisSelection,
32
32
  *,
33
33
  default: PyTree[UnnamedAxisSpec] = _zero_if_array_else_none,
34
34
  args: PyTree[UnnamedAxisSpec] = (),
@@ -43,7 +43,9 @@ def vmap(
43
43
 
44
44
  Args:
45
45
  fn (Callable): function to vmap over
46
- axis (Axis): axis to vmap over
46
+ axis (Axis or Sequence[Axis]): axis or axes to vmap over. If a sequence is
47
+ provided, the function will be vmapped over each axis in turn,
48
+ from innermost to outermost.
47
49
  default: how to handle (unnamed) arrays by default. Should be either an integer or None, or a callable that takes a PyTree leaf
48
50
  and returns an integer or None, or a PyTree prefix of the same. If an integer, the array will be mapped over that axis. If None, the array will not be mapped over.
49
51
  args: optional per-argument overrides for how to handle arrays. Should be a PyTree prefix of the same type as default.
@@ -53,6 +55,15 @@ def vmap(
53
55
  if kwargs is None:
54
56
  kwargs = {}
55
57
 
58
+ axes = ensure_tuple(axis) # type: ignore
59
+ if len(axes) > 1:
60
+ mapped = fn
61
+ for ax in reversed(axes):
62
+ mapped = vmap(mapped, ax, default=default, args=args, kwargs=kwargs)
63
+ return mapped
64
+ else:
65
+ axis = axes[0]
66
+
56
67
  signature = inspect.signature(fn)
57
68
 
58
69
  # this mirrors equinox's filter_vmap, but it's not really documented there so:
@@ -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
- import haliax as hax
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)
@@ -321,3 +321,22 @@ def test_vmap_error_for_incorrectly_specified_args():
321
321
  Width = Axis("Width", 3)
322
322
 
323
323
  hax.vmap(lambda a: Module(a), Batch)(Width)
324
+
325
+
326
+ def test_vmap_multiple_axes():
327
+ Batch1 = Axis("Batch1", 4)
328
+ Batch2 = Axis("Batch2", 3)
329
+ Width = Axis("Width", 2)
330
+ Depth = Axis("Depth", 5)
331
+
332
+ named = hax.random.uniform(PRNGKey(0), (Batch1, Batch2, Width, Depth))
333
+
334
+ def vmap_fun(x):
335
+ return x.sum(Width)
336
+
337
+ selected = hax.vmap(vmap_fun, (Batch1, Batch2))(named)
338
+
339
+ expected = jnp.sum(named.array, axis=2)
340
+
341
+ assert jnp.allclose(selected.array, expected)
342
+ assert selected.axes == (Batch1, Batch2, Depth)
@@ -1 +0,0 @@
1
- __version__ = "1.4.dev352"
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