zorch.scan_body¶
Memoization for lax.scan bodies built outside a @jit zone.
lax.scan keys its trace cache on the identity of the function it is handed. A
body written as a def inside the calling function is therefore a new object on
every call, and the scan re-traces and recompiles an identical graph each time —
invisible in results, and measured here at two to three orders of magnitude over
the work being scanned.
jit has the same rule, which is why a scan inside a @jit zone needs nothing:
the jit cache absorbs it. This is for the eager paths, where the scan is the
outermost compiled thing. Note that functools.partial does not substitute:
jit sees through a partial to the wrapped function and its bound arguments,
lax.scan does not.
Decorate a factory that returns the body, keyed on whatever the body closes over. Those arguments must hash by value, or equal configurations will each get their own trace — frozen dataclasses of static config do this for free:
@scan_body
def _step(config):
def step(carry, x): ...
return step
lax.scan(_step(config), init, xs)
scan_body ¶
scan_body(factory: _Body) -> _Body
Memoize a scan-body factory so repeated calls reuse one traced body.
Source code in zorch/scan_body.py
37 38 39 40 41 42 | |