Skip to content

Commit e9edb21

Browse files
committed
drop custom arange op
1 parent 428a7a1 commit e9edb21

4 files changed

Lines changed: 67 additions & 140 deletions

File tree

effectful/handlers/jax/monoid.py

Lines changed: 27 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,17 @@ def reduce(self, monoid, body, streams):
212212
used = [
213213
k
214214
for k, v in streams.items()
215-
if issubclass(typeof(v), jax.Array | jax.core.Tracer) and k in body_fvs
215+
if k in body_fvs
216+
and (
217+
issubclass(typeof(v), jax.Array | jax.core.Tracer)
218+
or isinstance(v, range)
219+
)
216220
]
217221
if not used:
218222
return fwd()
219223

220224
# an empty reduction axis collapses the whole reduce to the identity
221-
if any(_arange_length(streams[k]) == 0 for k in used):
225+
if any(not isinstance(streams[k], Term) and len(streams[k]) == 0 for k in used):
222226
return monoid.identity
223227

224228
tail_streams = {k: v for (k, v) in streams.items() if k not in used}
@@ -238,62 +242,15 @@ def delta(_index: tuple[int, ...], _weight: jax.Array) -> jax.Array:
238242
raise NotHandled
239243

240244

241-
arange = Operation.define(jnp.arange)
242-
243-
244-
def _range_start(term: Term):
245-
assert term.op == arange
246-
if "start" in term.kwargs:
247-
return term.kwargs["start"]
248-
if len(term.args) < 2:
249-
return 0
250-
return term.args[0]
251-
252-
253245
def _range_stop(term: Term):
254-
assert term.op == arange
246+
assert term.op == jnp.arange
255247
if "stop" in term.kwargs:
256248
return term.kwargs["stop"]
257249
if len(term.args) < 2:
258250
return term.args[0]
259251
return term.args[1]
260252

261253

262-
def _range_step(term: Term):
263-
assert term.op == arange
264-
if "step" in term.kwargs:
265-
return term.kwargs["step"]
266-
if len(term.args) < 3:
267-
return 1
268-
return term.args[2]
269-
270-
271-
def _is_simple_range(term: Term) -> bool:
272-
if term.op != arange:
273-
return False
274-
275-
start = _range_start(term)
276-
step = _range_step(term)
277-
return (
278-
not isinstance(start, Term)
279-
and start == 0
280-
and not isinstance(step, Term)
281-
and step == 1
282-
)
283-
284-
285-
def _arange_length(term) -> int | None:
286-
"""Concrete length of an ``arange`` term, or ``None`` if it is not an
287-
``arange`` term or any of its bounds is symbolic (a ``Term``).
288-
"""
289-
if not (isinstance(term, Term) and term.op is arange):
290-
return None
291-
a, b, s = _range_start(term), _range_stop(term), _range_step(term)
292-
if any(isinstance(x, Term) for x in (a, b, s)):
293-
return None
294-
return len(range(int(a), int(b), int(s)))
295-
296-
297254
def _substitute_streams(expr, streams, vars):
298255
"""Substitute each op in ``vars`` into its uses within ``expr``, replacing it
299256
with a fresh named dimension.
@@ -322,24 +279,20 @@ def _substitute_streams(expr, streams, vars):
322279

323280
# concrete-bounded arange streams slice their direct-index uses; the rest
324281
# gather.
325-
arange_bounds: dict[Operation, tuple] = {}
326-
for k in vars:
327-
v = streams[k]
328-
if isinstance(v, Term) and v.op is arange:
329-
a, b, s = _range_start(v), _range_stop(v), _range_step(v)
330-
if not any(isinstance(x, Term) for x in (a, b, s)):
331-
arange_bounds[k] = (a, b, s)
282+
range_streams: dict[Operation, range] = {
283+
k: streams[k] for k in vars if isinstance(streams[k], range)
284+
}
332285

333286
# PHASE 1 (direct): slice a bare range var used as an array index.
334-
if arange_bounds:
287+
if range_streams:
335288

336289
def _jax_getitem(arr, index):
337290
inner_index, outer_index = [], []
338291
progress = False
339292
for i in index:
340-
if isinstance(i, Term) and i.op in arange_bounds:
341-
a, b, s = arange_bounds[i.op]
342-
inner_index.append(slice(a, b, s))
293+
if isinstance(i, Term) and i.op in range_streams:
294+
r = range_streams[i.op]
295+
inner_index.append(slice(r.start, r.stop, r.step))
343296
outer_index.append(fresh[i.op]())
344297
progress = True
345298
else:
@@ -356,13 +309,13 @@ def _jax_getitem(arr, index):
356309
# PHASE 2 (indirect): substitute any remaining uses of each var.
357310
subst: dict = {}
358311
for k in vars:
359-
if k in arange_bounds:
360-
a, b, s = arange_bounds[k]
361-
subst[k] = deffn(unbind_dims(jnp.arange(a, b, s), fresh[k]))
312+
if k in range_streams:
313+
r = range_streams[k]
314+
subst[k] = deffn(unbind_dims(jnp.arange(r.start, r.stop, r.step), fresh[k]))
362315
else:
363316
subst[k] = deffn(unbind_dims(streams[k], fresh[k]))
364-
expr = handler(typing.cast(Interpretation, subst))(evaluate)(expr)
365317

318+
expr = handler(typing.cast(Interpretation, subst))(evaluate)(expr)
366319
return expr, fresh
367320

368321

@@ -420,7 +373,11 @@ def _(self, monoid: Monoid, body, streams: Streams):
420373

421374
head_op: Operation = head_index.op
422375
head_stream = streams[head_op]
423-
if not (isinstance(head_stream, Term) and _is_simple_range(head_stream)):
376+
if not (
377+
isinstance(head_stream, range)
378+
and head_stream.start == 0
379+
and head_stream.step == 1
380+
):
424381
return fwd()
425382

426383
# peel the head index: substitute it into the weight (slicing direct
@@ -485,15 +442,16 @@ def _(self, monoid: Monoid, body, streams: Streams):
485442
simple_ranges = {
486443
k: v
487444
for (k, v) in streams.items()
488-
if isinstance(v, Term) and _is_simple_range(v)
445+
if isinstance(v, range) and v.start == 0 and v.step == 1
489446
}
490447
for u, u_stream in simple_ranges.items():
491448
if fvsof(u_stream) & stream_vars:
492449
continue
493450

494-
for v, v_stream in simple_ranges.items():
451+
for v, v_stream in streams.items():
495452
if (
496453
isinstance(v_stream, Term)
454+
and v_stream.op == jnp.arange
497455
and isinstance(_range_stop(v_stream), Term)
498456
and _range_stop(v_stream).op == u
499457
):
@@ -516,27 +474,6 @@ def _(self, monoid: Monoid, body, streams: Streams):
516474
return fwd()
517475

518476

519-
class ReduceRange(ObjectInterpretation):
520-
"""Replace concrete-range stream values with materialized ``jnp.arange``.
521-
522-
reduce(M, streams ∪ {v: range(a, b, s)}, body)
523-
≡ reduce(M, streams ∪ {v: jnp.arange(a, b, s)}, body)
524-
525-
when ``a``, ``b``, ``s`` are concrete and ``body`` is not a delta term.
526-
Delegates the actual reduction to whichever handler picks up the
527-
materialized ``jax.Array`` streams.
528-
"""
529-
530-
@implements(Monoid.reduce)
531-
def _(self, monoid: Monoid, body, streams: Streams):
532-
if arange in fvsof((body, streams)):
533-
intp: Interpretation = {arange: jnp.arange}
534-
subst_body = handler(intp)(evaluate)(body)
535-
subst_streams = handler(intp)(evaluate)(streams)
536-
return monoid.reduce(subst_body, subst_streams)
537-
return fwd()
538-
539-
540477
# Cross-cutting delta rules not yet implemented:
541478
#
542479
# - **Delta-commuting** (DC-hoist): for any pure op ``f`` (no Scoped binders
@@ -649,7 +586,7 @@ def einsum(subscripts: str, /, *operands: jax.Array) -> jax.Array:
649586
body = Product.plus(*factors)
650587

651588
out_tuple = tuple(ops[c]() for c in out_spec)
652-
streams = {op: arange(sizes[c]) for c, op in ops.items()}
589+
streams = {op: range(sizes[c]) for c, op in ops.items()}
653590
with handler(NormalizeIntp):
654591
norm = deffn(Sum.reduce(delta(out_tuple, body), streams), *arrays)
655592
result = norm(*operands)
@@ -658,7 +595,6 @@ def einsum(subscripts: str, /, *operands: jax.Array) -> jax.Array:
658595

659596

660597
NormalizeIntp.extend(
661-
# ReduceRange(),
662598
ArrayReduce(),
663599
ReduceSumProductContraction(),
664600
ReduceDeltaIndependent(),

effectful/handlers/jax/numpy/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535

3636
einsum = Operation.define(_einsum_named)
3737

38-
3938
# Tell mypy about our wrapped functions.
4039
if TYPE_CHECKING:
41-
from jax.numpy import * # noqa: F403
40+
from jax.numpy import * # type: ignore[assignment] # noqa: F403

effectful/ops/monoid.py

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -139,24 +139,6 @@ def reduce[A, B, U: Body](
139139
broadcasting behavior; the default rule only handles the empty-stream
140140
case.
141141
"""
142-
for stream_key, stream_body, streams_tail in outer_stream(streams):
143-
if isinstance(stream_body, Term):
144-
continue
145-
stream_values_iter = iter(stream_body)
146-
147-
# if we iterate and get a term instead of a real iterator, skip
148-
if isinstance(stream_values_iter, Term):
149-
continue
150-
151-
new_reduces = []
152-
for stream_val in stream_values_iter:
153-
with handler({stream_key: deffn(stream_val)}):
154-
eval_args = evaluate((body, streams_tail))
155-
assert isinstance(eval_args, tuple)
156-
new_reduces.append(
157-
self.reduce(*eval_args) if streams_tail else eval_args[0]
158-
)
159-
return self.plus(*new_reduces)
160142
raise NotHandled
161143

162144
@Operation.define
@@ -368,16 +350,28 @@ def plus(self, monoid, *args):
368350
return fwd()
369351

370352

371-
class ReduceNoStreams(ObjectInterpretation):
372-
"""Implements the identity
373-
reduce(R, ∅, body) = 0
374-
"""
375-
353+
class ReducePartial(ObjectInterpretation):
376354
@implements(Monoid.reduce)
377-
def reduce(self, monoid, _, streams):
378-
if len(streams) == 0:
379-
return monoid.identity
380-
return fwd()
355+
def _(self, monoid, body, streams):
356+
for stream_key, stream_body, streams_tail in outer_stream(streams):
357+
if isinstance(stream_body, Term):
358+
continue
359+
stream_values_iter = iter(stream_body)
360+
361+
# if we iterate and get a term instead of a real iterator, skip
362+
if isinstance(stream_values_iter, Term):
363+
continue
364+
365+
new_reduces = []
366+
for stream_val in stream_values_iter:
367+
with handler({stream_key: deffn(stream_val)}):
368+
eval_args = evaluate((body, streams_tail))
369+
assert isinstance(eval_args, tuple)
370+
new_reduces.append(
371+
monoid.reduce(*eval_args) if streams_tail else eval_args[0]
372+
)
373+
return monoid.plus(*new_reduces)
374+
return monoid.identity
381375

382376

383377
class ReduceFusion(ObjectInterpretation):
@@ -880,7 +874,7 @@ def extend(self, *intps: Interpretation) -> typing.Self:
880874
MonoidOverSequence(),
881875
MonoidOverMapping(),
882876
MonoidOverCallable(),
883-
ReduceNoStreams(),
877+
ReducePartial(),
884878
ReduceFusion(),
885879
ReduceSplit(),
886880
ReduceFactorization(),

0 commit comments

Comments
 (0)