1+ from abc import ABC , abstractmethod
12from copy import copy , deepcopy
23from typing import (
34 TYPE_CHECKING ,
@@ -146,7 +147,7 @@ def __deepcopy__(self, memo: Dict[int, Any]) -> "Container":
146147 return r
147148
148149
149- class Linker :
150+ class Linker ( ABC ) :
150151 """
151152 Base type for all linkers.
152153
@@ -189,6 +190,7 @@ def clone(self, allow_gc: Optional[bool] = None) -> "Linker":
189190 new ._allow_gc = allow_gc
190191 return new
191192
193+ @abstractmethod
192194 def make_thunk (self , ** kwargs ) -> ThunkType :
193195 """
194196 This function must return a triplet (function, input_variables,
@@ -211,9 +213,6 @@ def make_thunk(self, **kwargs) -> ThunkType:
211213 print e.data # 3.0 iff inplace == True (else unknown)
212214
213215 """
214- raise NotImplementedError (
215- f"make_thunk method of { type (self )} is not implemented."
216- )
217216
218217 @deprecated ("Marked for deletion. Only tests use it." )
219218 def make_function (self , unpack_single : bool = True , ** kwargs ) -> Callable :
@@ -630,3 +629,171 @@ def wrapper(*args):
630629 f (* args )
631630
632631 return WrapLinker (linkers , wrapper )
632+
633+
634+ class JITLinker (PerformLinker ):
635+ """A ``Linker`` that JIT compiles a ``FunctionGraph`` into a single runnable thunk.
636+
637+ The entirety of ``Linker.fgraph`` is converted into a single JIT compiled
638+ thunk that is run by an Aesara ``VM``.
639+
640+ """
641+
642+ @abstractmethod
643+ def fgraph_convert (
644+ self , fgraph , order , input_storage , output_storage , storage_map , ** kwargs
645+ ):
646+ """Convert a ``FunctionGraph`` into a JIT-able function."""
647+
648+ @abstractmethod
649+ def create_thunk_inputs (self , storage_map : Dict [Variable , List [Any ]]) -> List [Any ]:
650+ """Pre-process inputs for the generated thunk.
651+
652+ Parameters
653+ ==========
654+ storage_map
655+ A ``dict`` mapping ``Variable``s to their storage lists.
656+
657+ Returns
658+ =======
659+ A list of thunk inputs
660+ """
661+
662+ @abstractmethod
663+ def jit_compile (self , fn : Callable ) -> Callable :
664+ """JIT compile a converted ``FunctionGraph``."""
665+
666+ def create_jitable_thunk (
667+ self , compute_map , order , input_storage , output_storage , storage_map
668+ ):
669+ """Create a thunk for each output of the `Linker`s `FunctionGraph`.
670+
671+ This is differs from the other thunk-making function in that it only
672+ produces thunks for the `FunctionGraph` output nodes.
673+
674+ Parameters
675+ ----------
676+ compute_map: dict
677+ The compute map dictionary.
678+ order
679+ input_storage
680+ output_storage
681+ storage_map: dict
682+ The storage map dictionary.
683+
684+ Returns
685+ -------
686+ thunks: list
687+ A tuple containing the thunks.
688+ output_nodes: list and their
689+ A tuple containing the output nodes.
690+
691+ """
692+ output_nodes = [o .owner for o in self .fgraph .outputs ]
693+
694+ converted_fgraph = self .fgraph_convert (
695+ self .fgraph ,
696+ order = order ,
697+ input_storage = input_storage ,
698+ output_storage = output_storage ,
699+ storage_map = storage_map ,
700+ )
701+
702+ thunk_inputs = self .create_thunk_inputs (storage_map )
703+
704+ thunks = []
705+
706+ thunk_outputs = [storage_map [n ] for n in self .fgraph .outputs ]
707+
708+ fgraph_jit = self .jit_compile (converted_fgraph )
709+
710+ def thunk (
711+ fgraph = self .fgraph ,
712+ fgraph_jit = fgraph_jit ,
713+ thunk_inputs = thunk_inputs ,
714+ thunk_outputs = thunk_outputs ,
715+ ):
716+ outputs = fgraph_jit (* [x [0 ] for x in thunk_inputs ])
717+
718+ for o_node , o_storage , o_val in zip (fgraph .outputs , thunk_outputs , outputs ):
719+ compute_map [o_node ][0 ] = True
720+ if len (o_storage ) > 1 :
721+ assert len (o_storage ) == len (o_val )
722+ for i , o_sub_val in enumerate (o_val ):
723+ o_storage [i ] = o_sub_val
724+ else :
725+ o_storage [0 ] = o_val
726+ return outputs
727+
728+ thunk .inputs = thunk_inputs
729+ thunk .outputs = thunk_outputs
730+ thunk .lazy = False
731+
732+ thunks .append (thunk )
733+
734+ # This is a bit hackish, but we only return one of the output nodes
735+ return thunks , output_nodes [:1 ]
736+
737+ def make_all (self , input_storage = None , output_storage = None , storage_map = None ):
738+ fgraph = self .fgraph
739+ nodes = self .schedule (fgraph )
740+ no_recycling = self .no_recycling
741+
742+ input_storage , output_storage , storage_map = map_storage (
743+ fgraph , nodes , input_storage , output_storage , storage_map
744+ )
745+
746+ compute_map = {}
747+ for k in storage_map :
748+ compute_map [k ] = [k .owner is None ]
749+
750+ thunks , nodes = self .create_jitable_thunk (
751+ compute_map , nodes , input_storage , output_storage , storage_map
752+ )
753+
754+ computed , last_user = gc_helper (nodes )
755+
756+ if self .allow_gc :
757+ post_thunk_old_storage = []
758+
759+ for node in nodes :
760+ post_thunk_old_storage .append (
761+ [
762+ storage_map [input ]
763+ for input in node .inputs
764+ if (input in computed )
765+ and (input not in fgraph .outputs )
766+ and (node == last_user [input ])
767+ ]
768+ )
769+ else :
770+ post_thunk_old_storage = None
771+
772+ if no_recycling is True :
773+ no_recycling = list (storage_map .values ())
774+ no_recycling = difference (no_recycling , input_storage )
775+ else :
776+ no_recycling = [
777+ storage_map [r ] for r in no_recycling if r not in fgraph .inputs
778+ ]
779+
780+ fn = streamline (
781+ fgraph , thunks , nodes , post_thunk_old_storage , no_recycling = no_recycling
782+ )
783+
784+ fn .allow_gc = self .allow_gc
785+ fn .storage_map = storage_map
786+
787+ return (
788+ fn ,
789+ [
790+ Container (input , storage )
791+ for input , storage in zip (fgraph .inputs , input_storage )
792+ ],
793+ [
794+ Container (output , storage , readonly = True )
795+ for output , storage in zip (fgraph .outputs , output_storage )
796+ ],
797+ thunks ,
798+ nodes ,
799+ )
0 commit comments