haliax 1.4.dev402__tar.gz → 1.4.dev404__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 (120) hide show
  1. {haliax-1.4.dev402 → haliax-1.4.dev404}/PKG-INFO +1 -1
  2. haliax-1.4.dev404/src/haliax/__about__.py +1 -0
  3. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/scan.py +72 -1
  4. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/partitioning.py +33 -1
  5. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_hof.py +33 -0
  6. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_partitioning.py +28 -0
  7. haliax-1.4.dev402/src/haliax/__about__.py +0 -1
  8. {haliax-1.4.dev402 → haliax-1.4.dev404}/.coveragerc +0 -0
  9. {haliax-1.4.dev402 → haliax-1.4.dev404}/.flake8 +0 -0
  10. {haliax-1.4.dev402 → haliax-1.4.dev404}/.github/workflows/publish_dev.yaml +0 -0
  11. {haliax-1.4.dev402 → haliax-1.4.dev404}/.github/workflows/run_pre_commit.yaml +0 -0
  12. {haliax-1.4.dev402 → haliax-1.4.dev404}/.github/workflows/run_quick_levanter_tests.yaml +0 -0
  13. {haliax-1.4.dev402 → haliax-1.4.dev404}/.github/workflows/run_tests.yaml +0 -0
  14. {haliax-1.4.dev402 → haliax-1.4.dev404}/.gitignore +0 -0
  15. {haliax-1.4.dev402 → haliax-1.4.dev404}/.playbooks/add-types.md +0 -0
  16. {haliax-1.4.dev402 → haliax-1.4.dev404}/.playbooks/wrap-non-named.md +0 -0
  17. {haliax-1.4.dev402 → haliax-1.4.dev404}/.pre-commit-config.yaml +0 -0
  18. {haliax-1.4.dev402 → haliax-1.4.dev404}/.readthedocs.yaml +0 -0
  19. {haliax-1.4.dev402 → haliax-1.4.dev404}/AGENTS.md +0 -0
  20. {haliax-1.4.dev402 → haliax-1.4.dev404}/CONTRIBUTING.md +0 -0
  21. {haliax-1.4.dev402 → haliax-1.4.dev404}/LICENSE +0 -0
  22. {haliax-1.4.dev402 → haliax-1.4.dev404}/README.md +0 -0
  23. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/api.md +0 -0
  24. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/broadcasting.md +0 -0
  25. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/cheatsheet.md +0 -0
  26. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/css/material.css +0 -0
  27. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/css/mkdocstrings.css +0 -0
  28. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/faq.md +0 -0
  29. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/data_parallel_mesh.png +0 -0
  30. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/data_parallel_mesh_replicated.png +0 -0
  31. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/device_mesh_1d.png +0 -0
  32. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/device_mesh_1d_zero.png +0 -0
  33. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/device_mesh_2d.png +0 -0
  34. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/device_mesh_2d_batch_partitioned.png +0 -0
  35. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/device_mesh_2d_data_replicated.png +0 -0
  36. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/device_mesh_2d_data_replicated_mlp_partitioned.png +0 -0
  37. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/device_mesh_2d_intermediate_fully_partitioned.png +0 -0
  38. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/figures/device_mesh_2d_zero.png +0 -0
  39. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/fp8.md +0 -0
  40. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/index.md +0 -0
  41. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/indexing.md +0 -0
  42. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/matmul.md +0 -0
  43. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/nn.md +0 -0
  44. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/partitioning.md +0 -0
  45. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/primer.md +0 -0
  46. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/rearrange.ipynb +0 -0
  47. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/rearrange.md +0 -0
  48. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/requirements.txt +0 -0
  49. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/scan.md +0 -0
  50. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/state-dict.md +0 -0
  51. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/tutorial.md +0 -0
  52. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/typing.md +0 -0
  53. {haliax-1.4.dev402 → haliax-1.4.dev404}/docs/vmap.md +0 -0
  54. {haliax-1.4.dev402 → haliax-1.4.dev404}/mkdocs.yml +0 -0
  55. {haliax-1.4.dev402 → haliax-1.4.dev404}/pyproject.toml +0 -0
  56. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/__init__.py +0 -0
  57. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/__init__.py +0 -0
  58. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/compile_utils.py +0 -0
  59. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/dot.py +0 -0
  60. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/einsum.py +0 -0
  61. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/fp8.py +0 -0
  62. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/parsing.py +0 -0
  63. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/rearrange.py +0 -0
  64. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/state_dict.py +0 -0
  65. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/_src/util.py +0 -0
  66. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/axis.py +0 -0
  67. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/core.py +0 -0
  68. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/debug.py +0 -0
  69. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/field.py +0 -0
  70. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/haxtyping.py +0 -0
  71. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/hof.py +0 -0
  72. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/jax_utils.py +0 -0
  73. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/__init__.py +0 -0
  74. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/activations.py +0 -0
  75. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/attention.py +0 -0
  76. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/conv.py +0 -0
  77. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/dropout.py +0 -0
  78. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/embedding.py +0 -0
  79. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/linear.py +0 -0
  80. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/loss.py +0 -0
  81. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/mlp.py +0 -0
  82. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/normalization.py +0 -0
  83. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/pool.py +0 -0
  84. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/nn/scan.py +0 -0
  85. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/ops.py +0 -0
  86. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/quantization.py +0 -0
  87. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/random.py +0 -0
  88. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/specialized_fns.py +0 -0
  89. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/state_dict.py +0 -0
  90. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/tree_util.py +0 -0
  91. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/types.py +0 -0
  92. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/util.py +0 -0
  93. {haliax-1.4.dev402 → haliax-1.4.dev404}/src/haliax/wrap.py +0 -0
  94. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/core_test.py +0 -0
  95. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_attention.py +0 -0
  96. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_axis.py +0 -0
  97. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_conv.py +0 -0
  98. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_debug.py +0 -0
  99. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_dot.py +0 -0
  100. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_dtype_typing.py +0 -0
  101. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_einsum.py +0 -0
  102. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_field.py +0 -0
  103. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_fp8.py +0 -0
  104. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_int8.py +0 -0
  105. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_moe_linear.py +0 -0
  106. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_namedarray_typing.py +0 -0
  107. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_nn.py +0 -0
  108. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_ops.py +0 -0
  109. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_parsing.py +0 -0
  110. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_pool.py +0 -0
  111. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_random.py +0 -0
  112. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_rearrange.py +0 -0
  113. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_scan.py +0 -0
  114. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_scatter_gather.py +0 -0
  115. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_specialized_fns.py +0 -0
  116. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_state_dict.py +0 -0
  117. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_tree_util.py +0 -0
  118. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_utils.py +0 -0
  119. {haliax-1.4.dev402 → haliax-1.4.dev404}/tests/test_visualize_sharding.py +0 -0
  120. {haliax-1.4.dev402 → haliax-1.4.dev404}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haliax
3
- Version: 1.4.dev402
3
+ Version: 1.4.dev404
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.dev404"
@@ -1,9 +1,11 @@
1
1
  import dataclasses
2
2
  import functools as ft
3
+ import inspect
3
4
  from typing import Any, Callable, Literal, ParamSpec, Protocol, Sequence, Tuple, TypeVar, Union, overload
4
5
 
5
6
  import equinox as eqx
6
7
  import jax
8
+ import jax.tree_util as jtu
7
9
  from jaxtyping import PyTree
8
10
 
9
11
  import haliax
@@ -357,7 +359,46 @@ def scan(
357
359
  return carry, y
358
360
 
359
361
  true_axis = _infer_axis_size_from_tree(axis_first_xs, axis)
360
- axis_size = _infer_axis_size_from_tree(axis_first_xs, axis).size
362
+ axis_size = true_axis.size
363
+
364
+ # build a mapping from positional argument indices to their names for friendlier error messages
365
+ sig = inspect.signature(f)
366
+ arg_pos_names: dict[int, str] = {}
367
+ params = list(sig.parameters.values())[1:] # skip carry
368
+ pos_count = 0
369
+ var_pos_name: str | None = None
370
+ for param in params:
371
+ if param.kind in (
372
+ inspect.Parameter.POSITIONAL_ONLY,
373
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
374
+ ):
375
+ arg_pos_names[pos_count] = param.name
376
+ pos_count += 1
377
+ elif param.kind == inspect.Parameter.VAR_POSITIONAL:
378
+ var_pos_name = param.name
379
+ break
380
+ if var_pos_name is not None:
381
+ for i in range(pos_count, len(args)):
382
+ arg_pos_names[i] = f"{var_pos_name}[{i - pos_count}]"
383
+
384
+ path_leaves, _ = jtu.tree_flatten_with_path(axis_first_xs, is_leaf=is_named_array)
385
+ mismatched = []
386
+ for path, leaf in path_leaves:
387
+ if isinstance(leaf, NamedArray):
388
+ lead_size = leaf.array.shape[0]
389
+ elif is_jax_array_like(leaf):
390
+ lead_size = leaf.shape[0]
391
+ else:
392
+ continue
393
+ if lead_size != axis_size:
394
+ mismatched.append((path, lead_size))
395
+ if mismatched:
396
+ details = ", ".join(
397
+ f"{_format_tree_path(p, arg_pos_names)} has leading dimension {s}" for p, s in mismatched
398
+ )
399
+ raise ValueError(
400
+ f"scan got `length` argument of {axis_size} but some inputs had different leading axis sizes: {details}"
401
+ )
361
402
 
362
403
  nested_scan = checkpoint.nested
363
404
  outer_block_size = nested_scan_outer_block(nested_scan, axis_size)
@@ -508,6 +549,36 @@ def _zero_if_array_else_none(x: Any) -> ResolvedUnnamedAxisSpec:
508
549
  return 0 if is_jax_array_like(x) else None
509
550
 
510
551
 
552
+ def _format_tree_path(
553
+ path: tuple[jtu.KeyEntry, ...], arg_pos_names: dict[int, str] | None = None
554
+ ) -> str:
555
+ parts: list[str] = []
556
+ i = 0
557
+ if len(path) >= 2 and isinstance(path[0], jtu.SequenceKey):
558
+ if path[0].idx == 0 and isinstance(path[1], jtu.SequenceKey):
559
+ name = (arg_pos_names or {}).get(path[1].idx)
560
+ if name is not None:
561
+ parts.append(name)
562
+ else:
563
+ parts.append(f"[{path[1].idx}]")
564
+ i = 2
565
+ elif path[0].idx == 1 and isinstance(path[1], jtu.DictKey):
566
+ parts.append(str(path[1].key))
567
+ i = 2
568
+ for p in path[i:]:
569
+ if isinstance(p, jtu.GetAttrKey):
570
+ parts.append("." + p.name)
571
+ elif isinstance(p, jtu.DictKey):
572
+ parts.append(f"[{p.key!r}]")
573
+ elif isinstance(p, jtu.SequenceKey):
574
+ parts.append(f"[{p.idx}]")
575
+ else: # pragma: no cover - future-proofing
576
+ parts.append(str(p))
577
+ if parts and parts[0].startswith("."):
578
+ parts[0] = parts[0][1:]
579
+ return "".join(parts) or "<root>"
580
+
581
+
511
582
  def _infer_axis_size_from_tree(result, axis):
512
583
  if isinstance(axis, str):
513
584
  result_leaves = jax.tree_util.tree_leaves(result, is_leaf=_is_passive_array)
@@ -1,4 +1,5 @@
1
1
  import contextlib
2
+ import dataclasses
2
3
  import functools
3
4
  import threading
4
5
  import typing
@@ -210,6 +211,37 @@ def pspec_for(
210
211
  return None
211
212
  else:
212
213
  return pspec_for_axis(node.axes, resource_mapping)
214
+ elif isinstance(node, eqx.Module):
215
+ # handle eqx.Module explicitly so that we can look at axis_names metadata
216
+ updates: dict[str, typing.Any] = {}
217
+ for field in dataclasses.fields(node):
218
+ if field.metadata.get("static", False):
219
+ continue
220
+
221
+ value = getattr(node, field.name)
222
+ axis_names = field.metadata.get("axis_names") if field.metadata is not None else None
223
+ if axis_names is not None and is_jax_array_like(value):
224
+ current_sharding = (
225
+ getattr(value, "sharding", None) if preserve_existing_shardings else None
226
+ )
227
+ if current_sharding is not None:
228
+ updates[field.name] = None
229
+ else:
230
+ updates[field.name] = pspec_for_axis(axis_names, resource_mapping)
231
+ else:
232
+ updates[field.name] = htu.tree_map(
233
+ partition_spec, value, is_leaf=lambda x: isinstance(x, eqx.Module)
234
+ )
235
+
236
+ new_node = object.__new__(type(node))
237
+ for field in dataclasses.fields(node):
238
+ object.__setattr__(
239
+ new_node,
240
+ field.name,
241
+ updates.get(field.name, getattr(node, field.name)),
242
+ )
243
+
244
+ return new_node
213
245
  elif is_jax_array_like(node):
214
246
  sharding = getattr(node, "sharding", None)
215
247
  # TODO: these are usually replicated. Is there a better way to tell?
@@ -232,7 +264,7 @@ def pspec_for(
232
264
  else:
233
265
  return None
234
266
 
235
- return htu.tree_map(partition_spec, tree)
267
+ return htu.tree_map(partition_spec, tree, is_leaf=lambda x: isinstance(x, eqx.Module))
236
268
 
237
269
 
238
270
  def infer_resource_partitions(
@@ -5,6 +5,7 @@ from jax.random import PRNGKey
5
5
  import haliax as hax
6
6
  from haliax import Axis, NamedArray
7
7
  from haliax.util import is_named_array
8
+ import pytest
8
9
 
9
10
 
10
11
  def test_scan():
@@ -136,6 +137,38 @@ def test_scan_hierarchical():
136
137
  assert jnp.all(jnp.equal(selected.array, selected_blocked.array))
137
138
 
138
139
 
140
+ def test_scan_reports_mismatched_unnamed_array():
141
+ Height = Axis("Height", 2)
142
+
143
+ def f(c, x, y):
144
+ return c, x + y
145
+
146
+ good = jnp.zeros((Height.size, 3))
147
+ bad = jnp.zeros((Height.size - 1, 3))
148
+
149
+ with pytest.raises(ValueError) as e:
150
+ hax.scan(f, Height)(0, good, y=bad)
151
+
152
+ assert "y has leading dimension" in str(e.value)
153
+
154
+
155
+ def test_scan_reports_eqx_module_field_path():
156
+ Height = Axis("Height", 2)
157
+
158
+ class Foo(eqx.Module):
159
+ my_array: jnp.ndarray
160
+
161
+ foo = Foo(jnp.zeros((Height.size - 1, 3)))
162
+
163
+ def f(c, foo):
164
+ return c, foo.my_array
165
+
166
+ with pytest.raises(ValueError) as e:
167
+ hax.scan(f, Height)(0, foo)
168
+
169
+ assert "foo.my_array" in str(e.value)
170
+
171
+
139
172
  def test_fold():
140
173
  Height = Axis("Height", 10)
141
174
  Width = Axis("Width", 3)
@@ -59,6 +59,34 @@ def test_pspec_for_named_axes():
59
59
  assert specs.unnamed1 == PartitionSpec(None)
60
60
 
61
61
 
62
+ class ArrayModule(eqx.Module):
63
+ arr: Array = hax.field(axis_names=("dim2", "dim3"))
64
+
65
+
66
+ def test_pspec_for_plain_array_axis_names():
67
+ mesh = Mesh(np.array(jax.devices()).reshape(-1, 1), (ResourceAxis.DATA, ResourceAxis.MODEL))
68
+ with axis_mapping(resource_map), mesh:
69
+ mod = ArrayModule(jnp.ones((Dim2.size, Dim3.size)))
70
+
71
+ specs: ArrayModule = pspec_for(mod, preserve_existing_shardings=False)
72
+
73
+ assert specs.arr == PartitionSpec(ResourceAxis.DATA, ResourceAxis.MODEL)
74
+
75
+
76
+ class NestedArrayModule(eqx.Module):
77
+ inner: ArrayModule
78
+
79
+
80
+ def test_pspec_for_plain_array_axis_names_nested_module():
81
+ mesh = Mesh(np.array(jax.devices()).reshape(-1, 1), (ResourceAxis.DATA, ResourceAxis.MODEL))
82
+ with axis_mapping(resource_map), mesh:
83
+ mod = NestedArrayModule(ArrayModule(jnp.ones((Dim2.size, Dim3.size))))
84
+
85
+ specs: NestedArrayModule = pspec_for(mod, preserve_existing_shardings=False)
86
+
87
+ assert specs.inner.arr == PartitionSpec(ResourceAxis.DATA, ResourceAxis.MODEL)
88
+
89
+
62
90
  class MyModuleInit(eqx.Module):
63
91
  named: NamedArray
64
92
  unnamed1: Array
@@ -1 +0,0 @@
1
- __version__ = "1.4.dev402"
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