@@ -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+
4873def _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