Skip to content

Commit d47b254

Browse files
committed
Cache compiled jax
1 parent eeab91e commit d47b254

1 file changed

Lines changed: 53 additions & 12 deletions

File tree

‎src/splinebox/basis_functions.py‎

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,31 @@ def _torch_compile_recompile_limit(limit=64):
4545
setattr(config, key, old)
4646

4747

48+
def _cached_compiled(fn, key, compile_fn):
49+
"""
50+
Return a compiled version of ``fn``, caching the compiled wrapper.
51+
52+
Constructing compiled wrappers, e.g. with ``torch.compile`` or
53+
``jax.jit``, is expensive, so the wrapper is constructed once per
54+
function instead of on every call. The wrapper is cached on the
55+
instance for bound methods, so that the ``self`` captured by the
56+
cached wrapper matches the instance, and on the function itself
57+
otherwise. ``key`` identifies the compilation configuration and must
58+
be distinct for functions that are compiled differently, e.g. for
59+
different backends.
60+
"""
61+
if hasattr(fn, "__self__"):
62+
owner, cache_key = fn.__self__, (fn.__func__, key)
63+
elif hasattr(fn, "__func__"):
64+
owner, cache_key = fn, (fn.__func__, key)
65+
else:
66+
owner, cache_key = fn, (fn, key)
67+
cache = owner.__dict__.setdefault("_compiled_wrappers", {})
68+
if cache_key not in cache:
69+
cache[cache_key] = compile_fn(fn)
70+
return cache[cache_key]
71+
72+
4873
def _item_set(arr, idx, value, xp):
4974
"""
5075
Assign ``value`` to ``arr[idx]`` and return the updated array.
@@ -200,11 +225,13 @@ def __call__(self, t, derivative=0, jit=True):
200225
if use_numba:
201226
return func(t)
202227
elif jit and array_api_compat.is_torch_namespace(xp):
203-
return xp.compile(fullgraph=True, dynamic=True, mode="reduce-overhead")(func)(t, xp)
228+
return _cached_compiled(
229+
func, "torch", lambda fn: xp.compile(fullgraph=True, dynamic=True, mode="reduce-overhead")(fn)
230+
)(t, xp)
204231
elif jit and array_api_compat.is_jax_namespace(xp):
205232
import jax
206233

207-
return jax.jit(func, static_argnames=["xp"])(t, xp)
234+
return _cached_compiled(func, "jax", lambda fn: jax.jit(fn, static_argnames=["xp"]))(t, xp)
208235
else:
209236
return func(t, xp)
210237

@@ -281,18 +308,25 @@ def filter_symmetric(self, s, jit=True):
281308
result = self._filter_symmetric_numba(s)
282309
elif jit and array_api_compat.is_torch_namespace(xp):
283310
with _torch_compile_recompile_limit():
284-
result = xp.compile(fullgraph=True, dynamic=False, mode="reduce-overhead")(self._filter_symmetric_xp)(
285-
s, xp
286-
)
311+
result = _cached_compiled(
312+
self._filter_symmetric_xp,
313+
"torch",
314+
lambda fn: xp.compile(fullgraph=True, dynamic=False, mode="reduce-overhead")(fn),
315+
)(s, xp)
287316
elif jit and array_api_compat.is_jax_namespace(xp):
288317
import jax
289318

290-
result = jax.jit(self._filter_symmetric_xp, static_argnames=["xp"])(s, xp)
319+
result = _cached_compiled(self._filter_symmetric_xp, "jax", lambda fn: jax.jit(fn, static_argnames=["xp"]))(
320+
s, xp
321+
)
291322
else:
292323
result = self._filter_symmetric_xp(s, xp)
293324

294325
if s.ndim == 1:
295-
return xp.squeeze(result)
326+
# array_api_compat.torch requires the axis argument for squeeze,
327+
# so the unit axes are determined explicitly.
328+
axes = tuple(axis for axis, size in enumerate(result.shape) if size == 1)
329+
return xp.squeeze(result, axis=axes) if axes else result
296330
return result
297331

298332
def _filter_symmetric_numba(self, s):
@@ -362,18 +396,25 @@ def filter_periodic(self, s, jit=True):
362396
result = self._filter_periodic_numba(s)
363397
elif jit and array_api_compat.is_torch_namespace(xp):
364398
with _torch_compile_recompile_limit():
365-
result = xp.compile(fullgraph=True, dynamic=False, mode="reduce-overhead")(self._filter_periodic_xp)(
366-
s, xp
367-
)
399+
result = _cached_compiled(
400+
self._filter_periodic_xp,
401+
"torch",
402+
lambda fn: xp.compile(fullgraph=True, dynamic=False, mode="reduce-overhead")(fn),
403+
)(s, xp)
368404
elif jit and array_api_compat.is_jax_namespace(xp):
369405
import jax
370406

371-
result = jax.jit(self._filter_periodic_xp, static_argnames=["xp"])(s, xp)
407+
result = _cached_compiled(self._filter_periodic_xp, "jax", lambda fn: jax.jit(fn, static_argnames=["xp"]))(
408+
s, xp
409+
)
372410
else:
373411
result = self._filter_periodic_xp(s, xp)
374412

375413
if s.ndim == 1:
376-
return xp.squeeze(result)
414+
# array_api_compat.torch requires the axis argument for squeeze,
415+
# so the unit axes are determined explicitly.
416+
axes = tuple(axis for axis, size in enumerate(result.shape) if size == 1)
417+
return xp.squeeze(result, axis=axes) if axes else result
377418
return result
378419

379420
def _filter_periodic_numba(self, s):

0 commit comments

Comments
 (0)