Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit 1549649

Browse files
Create a generalized JITLinker
1 parent 9859c79 commit 1549649

2 files changed

Lines changed: 186 additions & 172 deletions

File tree

aesara/link/basic.py

Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from abc import ABC, abstractmethod
12
from copy import copy, deepcopy
23
from 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

Comments
 (0)