Skip to content

Commit d2108c0

Browse files
authored
Replace remaining uses of dm-tree with pytrees (#480)
* replace dm-tree with pytree * fix tests
1 parent 6288a47 commit d2108c0

4 files changed

Lines changed: 45 additions & 61 deletions

File tree

effectful/handlers/jax/_handlers.py

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
except ImportError:
1111
raise ImportError("JAX is required to use effectful.handlers.jax")
1212

13-
import tree
14-
1513
from effectful.internals.runtime import interpreter
1614
from effectful.ops.semantics import apply, evaluate, fvsof, typeof
1715
from effectful.ops.syntax import (
@@ -93,7 +91,7 @@ def _is_eager(t):
9391

9492
if not (
9593
isinstance(t, Term)
96-
and all(_is_eager(a) for a in tree.flatten((t.args, t.kwargs)))
94+
and all(_is_eager(a) for a in jax.tree.flatten((t.args, t.kwargs))[0])
9795
):
9896
return t
9997

@@ -118,7 +116,7 @@ def reindex_flat_array(t):
118116
result = jnp.reshape(t, result_shape)
119117
return jax_getitem(result, tuple(k() for k in sized_fvs.keys()))
120118

121-
result = tree.map_structure(reindex_flat_array, flat_result)
119+
result = jax.tree.map(reindex_flat_array, flat_result)
122120
return result
123121

124122

@@ -146,9 +144,9 @@ def _jax_op(*args, **kwargs) -> jax.Array:
146144
# which partial_eval handles.
147145
return typing.cast(jax.Array, _partial_eval(tm))
148146
elif not any(
149-
tree.flatten(
150-
tree.map_structure(lambda x: isinstance(x, Term), (args, kwargs))
151-
)
147+
jax.tree.flatten(
148+
jax.tree.map(lambda x: isinstance(x, Term), (args, kwargs))
149+
)[0]
152150
):
153151
return typing.cast(jax.Array, jax_fn(*args, **kwargs))
154152
else:
@@ -165,9 +163,9 @@ def _register_jax_op_no_partial_eval[**P, T](jax_fn: Callable[P, T]):
165163
@defop
166164
def _jax_op(*args, **kwargs) -> jax.Array:
167165
if not any(
168-
tree.flatten(
169-
tree.map_structure(lambda x: isinstance(x, Term), (args, kwargs))
170-
)
166+
jax.tree.flatten(
167+
jax.tree.map(lambda x: isinstance(x, Term), (args, kwargs))
168+
)[0]
171169
):
172170
return typing.cast(jax.Array, jax_fn(*args, **kwargs))
173171
else:
@@ -210,11 +208,9 @@ def bind_dims[T, A, B](
210208
>>> bind_dims(t, b, a).shape
211209
(3, 2)
212210
"""
213-
if tree.is_nested(value):
214-
return tree.map_structure(lambda v: bind_dims(v, *names), value)
215-
216-
semantic_type = typeof(value)
217-
return __dispatch(semantic_type)(value, *names)
211+
if jax.tree_util.treedef_is_leaf(jax.tree.structure(value)):
212+
return __dispatch(typeof(value))(value, *names)
213+
return jax.tree.map(lambda v: bind_dims(v, *names), value)
218214

219215

220216
@defop
@@ -225,11 +221,9 @@ def unbind_dims[T, A, B](
225221
*names: Annotated[Operation[[], jax.Array], Scoped[B]],
226222
) -> Annotated[T, Scoped[A | B]]:
227223
"""Convert positional dimensions to named dimensions."""
228-
if tree.is_nested(value):
229-
return tree.map_structure(lambda v: unbind_dims(v, *names), value)
230-
231-
semantic_type = typeof(value)
232-
return __dispatch(semantic_type)(value, *names)
224+
if jax.tree_util.treedef_is_leaf(jax.tree.structure(value)):
225+
return __dispatch(typeof(value))(value, *names)
226+
return jax.tree.map(lambda v: unbind_dims(v, *names), value)
233227

234228

235229
def jit(f, *args, **kwargs):
@@ -244,7 +238,7 @@ def _indexed_func_wrapper[**P, S, T](
244238
# index expressions for the result of the function
245239
indexes = None
246240

247-
# hide index lists from tree.map_structure
241+
# hide index lists from jax.tree.mapping
248242
class Indexes:
249243
def __init__(self, sizes):
250244
self.sizes = sizes
@@ -260,21 +254,16 @@ def deindex_tensor(t, i):
260254
return t_
261255

262256
ret = func(*args, **kwargs)
263-
indexes = tree.map_structure(lambda t: Indexes(sizesof(t)), ret)
264-
tensors = tree.map_structure(lambda t, i: deindex_tensor(t, i), ret, indexes)
257+
indexes = jax.tree.map(lambda t: Indexes(sizesof(t)), ret)
258+
tensors = jax.tree.map(lambda t, i: deindex_tensor(t, i), ret, indexes)
265259
return tensors
266260

267261
# reapply the stored indexes to a result
268262
def reindex(ret, starting_dim=0):
269263
def index_expr(i):
270264
return (slice(None),) * (starting_dim) + tuple(x() for x in i.indexes)
271265

272-
if tree.is_nested(ret):
273-
indexed_ret = tree.map_structure(
274-
lambda t, i: getitem(t, index_expr(i)), ret, indexes
275-
)
276-
else:
277-
indexed_ret = getitem(ret, index_expr(indexes))
266+
indexed_ret = jax.tree.map(lambda t, i: getitem(t, index_expr(i)), ret, indexes)
278267

279268
return indexed_ret
280269

effectful/handlers/jax/_terms.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import Any, cast
55

66
import jax
7-
import tree
87

98
import effectful.handlers.jax.numpy as jnp
109
from effectful.handlers.jax._handlers import (
@@ -455,10 +454,10 @@ def _bind_dims_array(t: jax.Array, *args: Operation[[], jax.Array]) -> jax.Array
455454

456455
def _evaluate(expr):
457456
if isinstance(expr, Term):
458-
(args, kwargs) = tree.map_structure(_evaluate, (expr.args, expr.kwargs))
457+
(args, kwargs) = jax.tree.map(_evaluate, (expr.args, expr.kwargs))
459458
return _partial_eval(expr)
460-
if tree.is_nested(expr):
461-
return tree.map_structure(_evaluate, expr)
459+
if not jax.tree_util.treedef_is_leaf(jax.tree.structure(expr)):
460+
return jax.tree.map(_evaluate, expr)
462461
return expr
463462

464463
if not isinstance(t, Term):

effectful/handlers/torch.py

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
except ImportError:
1010
raise ImportError("PyTorch is required to use effectful.handlers.torch")
1111

12-
import tree
12+
import torch.utils._pytree as pytree
1313

1414
from effectful.internals.runtime import interpreter
1515
from effectful.internals.tensor_utils import _desugar_tensor_index
@@ -91,7 +91,7 @@ def _partial_eval(t: Expr[torch.Tensor]) -> Expr[torch.Tensor]:
9191
isinstance(t, Term)
9292
and all(
9393
isinstance(a, torch.Tensor) or not isinstance(a, Term) or a.op in sized_fvs
94-
for a in tree.flatten((t.args, t.kwargs))
94+
for a in pytree.tree_flatten((t.args, t.kwargs))[0]
9595
)
9696
):
9797
return t
@@ -126,7 +126,7 @@ def reindex_flat_tensor(t):
126126
result = t.reshape(inds[0].shape + t.shape[1:])
127127
return torch_getitem(result, tuple(k() for k in sized_fvs.keys()))
128128

129-
result = tree.map_structure(reindex_flat_tensor, flat_result)
129+
result = pytree.tree_map(reindex_flat_tensor, flat_result)
130130
return result
131131

132132

@@ -135,9 +135,7 @@ def reindex_flat_tensor(t):
135135
def bind_dims[
136136
A,
137137
B,
138-
HasDims: torch.Tensor
139-
| torch.distributions.Distribution
140-
| tree.Structure[torch.Tensor | torch.distributions.Distribution],
138+
HasDims: pytree.PyTree | torch.Tensor | torch.distributions.Distribution,
141139
](
142140
value: Annotated[HasDims, Scoped[A | B]],
143141
*names: Annotated[Operation[[], torch.Tensor], Scoped[B]],
@@ -157,8 +155,8 @@ def bind_dims[
157155
>>> bind_dims(t[a(), b()], b, a).shape
158156
torch.Size([3, 2])
159157
"""
160-
if tree.is_nested(value):
161-
return tree.map_structure(lambda v: bind_dims(v, *names), value)
158+
if not pytree.tree_is_leaf(value):
159+
return pytree.tree_map(lambda v: bind_dims(v, *names), value)
162160
raise NotHandled
163161

164162

@@ -209,15 +207,13 @@ def _bind_dims_tensor(
209207
def unbind_dims[
210208
A,
211209
B,
212-
HasDims: torch.Tensor
213-
| torch.distributions.Distribution
214-
| tree.Structure[torch.Tensor | torch.distributions.Distribution],
210+
HasDims: pytree.PyTree | torch.Tensor | torch.distributions.Distribution,
215211
](
216212
value: Annotated[HasDims, Scoped[A | B]],
217213
*names: Annotated[Operation[[], torch.Tensor], Scoped[B]],
218214
) -> Annotated[HasDims, Scoped[A | B]]:
219-
if tree.is_nested(value):
220-
return tree.map_structure(lambda v: unbind_dims(v, *names), value)
215+
if not pytree.tree_is_leaf(value):
216+
return pytree.tree_map(lambda v: unbind_dims(v, *names), value)
221217
raise NotHandled
222218

223219

@@ -256,9 +252,9 @@ def _torch_op(*args, **kwargs) -> torch.Tensor:
256252
# which partial_eval handles.
257253
return typing.cast(torch.Tensor, _partial_eval(tm))
258254
elif not any(
259-
tree.flatten(
260-
tree.map_structure(lambda x: isinstance(x, Term), (args, kwargs))
261-
)
255+
pytree.tree_flatten(
256+
pytree.tree_map(lambda x: isinstance(x, Term), (args, kwargs))
257+
)[0]
262258
):
263259
return typing.cast(torch.Tensor, torch_fn(*args, **kwargs))
264260
else:
@@ -600,7 +596,7 @@ def _indexed_func_wrapper[**P, S, T](
600596
# index expressions for the result of the function
601597
indexes = None
602598

603-
# hide index lists from tree.map_structure
599+
# hide index lists from pytree.tree_map
604600
class Indexes:
605601
def __init__(self, sizes):
606602
self.sizes = sizes
@@ -616,17 +612,17 @@ def deindex_tensor(t, i):
616612
return t_
617613

618614
ret = func(*args, **kwargs)
619-
indexes = tree.map_structure(lambda t: Indexes(sizesof(t)), ret)
620-
tensors = tree.map_structure(lambda t, i: deindex_tensor(t, i), ret, indexes)
615+
indexes = pytree.tree_map(lambda t: Indexes(sizesof(t)), ret)
616+
tensors = pytree.tree_map(lambda t, i: deindex_tensor(t, i), ret, indexes)
621617
return tensors
622618

623619
# reapply the stored indexes to a result
624620
def reindex(ret, starting_dim=0):
625621
def index_expr(i):
626622
return (slice(None),) * (starting_dim) + tuple(x() for x in i.indexes)
627623

628-
if tree.is_nested(ret):
629-
indexed_ret = tree.map_structure(
624+
if not pytree.tree_is_leaf(ret):
625+
indexed_ret = pytree.tree_map(
630626
lambda t, i: torch_getitem(t, index_expr(i)), ret, indexes
631627
)
632628
else:
@@ -677,7 +673,7 @@ def jvp(func, *args, **kwargs):
677673
# hide deindexed_func from _register_torch_op
678674
jvp_func = functools.partial(torch.func.jvp, deindexed_func)
679675
ret = _register_torch_op(jvp_func)(*args, **kwargs)
680-
return tree.map_structure(reindex, ret)
676+
return pytree.tree_map(reindex, ret)
681677

682678

683679
@functools.wraps(torch.func.vjp)
@@ -699,15 +695,15 @@ def repack_primals(primals):
699695
def wrapper(*primals):
700696
nonlocal indexed_result
701697
indexed_result = func(*repack_primals(primals))
702-
return tree.map_structure(
698+
return pytree.tree_map(
703699
lambda t: bind_dims(t, *list(sizesof(t).keys())), indexed_result
704700
)
705701

706702
unindexed_primals = [t[0] for t in unpacked_primals]
707703
_, vjpfunc = torch.func.vjp(wrapper, *unindexed_primals, **kwargs)
708704

709705
def vjpfunc_wrapper(*tangents):
710-
unindexed_tangents = tree.map_structure(
706+
unindexed_tangents = pytree.tree_map(
711707
lambda t: bind_dims(t, *list(sizesof(t).keys())), tangents
712708
)
713709
grads = vjpfunc(*unindexed_tangents)

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ Source = "https://github.com/BasisResearch/effectful"
3636
"Bug Tracker" = "https://github.com/BasisResearch/effectful/issues"
3737

3838
[project.optional-dependencies]
39-
torch = ["torch", "dm-tree"]
40-
pyro = ["pyro-ppl>=1.9.1", "dm-tree"]
41-
jax = ["jax", "dm-tree"]
42-
numpyro = ["numpyro>=0.19", "dm-tree"]
39+
torch = ["torch"]
40+
pyro = ["pyro-ppl>=1.9.1"]
41+
jax = ["jax"]
42+
numpyro = ["numpyro>=0.19"]
4343
llm = [
4444
"litellm",
4545
"pillow",

0 commit comments

Comments
 (0)