Source code for firedrake.preconditioners.offload

from firedrake.preconditioners.assembled import PCBase
from firedrake.petsc import PETSc
from firedrake.utils import device_matrix_type, get_device_type

import firedrake.dmhooks as dmhooks

__all__ = ("OffloadPC",)


_device_vector_impls = {
    "CUDA": {
        "createWithArrays": "createCUDAWithArrays",
    },
    "HIP": {
        "createWithArrays": "createHIPWithArrays",
    },
}

# These matrix types require an expensive implicit -> dense -> sparse conversion when
# offloaded to a GPU. The A matrix does not need to be offloaded, therefore if the A
# matrix is any of these types, do not offload it.
_no_offload_mat_types = ("python", "schurcomplement")


[docs] class OffloadPC(PCBase): """Offload PC from CPU to GPU and back. Internally this makes a PETSc PC object that can be controlled by options using the extra options prefix ``offload_``. """ _prefix = "offload_"
[docs] def call_device_vec_impl(self, x, func_name: str, *args, **kwargs): return getattr(x, _device_vector_impls[self.device_type][func_name])( *args, **kwargs )
def _create_on_device_nullspace(self, ns: PETSc.NullSpace) -> PETSc.NullSpace: if ns.handle == 0: return ns ns_dev_vecs = [] for v in ns.getVecs(): v_dev = PETSc.Vec() self.call_device_vec_impl(v_dev, "createWithArrays", v.array_r, None) ns_dev_vecs.append(v_dev) ns_dev = PETSc.NullSpace() ns_dev.create(ns.hasConstant(), ns_dev_vecs, comm=ns.comm) return ns_dev
[docs] def initialize(self, pc): A, P = pc.getOperators() if pc.type != "python": raise ValueError("Expecting PC type python") opc = pc if P.type == "python": context = P.getPythonContext() # It only makes sense to precondition/invert a diagonal # block in general. That's all we're going to allow. if not context.on_diag: raise ValueError("Only makes sense to invert diagonal block") prefix = pc.getOptionsPrefix() or "" options_prefix = prefix + self._prefix self.device_mat = device_matrix_type(warn=(pc.comm.rank == 0)) self.device_type = get_device_type() dm = opc.getDM() pc = PETSc.PC().create(comm=opc.comm) pc.setDM(dm) pc.setOptionsPrefix(options_prefix) if self.device_mat is not None: with PETSc.Log.Event("Event: initialize offload"): P_dev = PETSc.Mat() P_dev = P.convert(mat_type=self.device_mat, out=P_dev) if A.handle == P.handle: A_dev = P_dev elif A.type in _no_offload_mat_types: A_dev = A else: A_dev = PETSc.Mat() A_dev = A.convert(mat_type=self.device_mat, out=A_dev) P_dev.setNullSpace(self._create_on_device_nullspace(P.getNullSpace())) P_dev.setTransposeNullSpace(self._create_on_device_nullspace(P.getTransposeNullSpace())) P_dev.setNearNullSpace(self._create_on_device_nullspace(P.getNearNullSpace())) pc.setOperators(A_dev, P_dev) else: pc.setOperators(A, P) # Simplest reconstruction we can manage octx = dmhooks.get_appctx(dm) self._ctx_ref = octx.reconstruct(mat_type=self.device_mat, pmat_type=self.device_mat) self.pc = pc with dmhooks.add_hooks(dm, self, appctx=self._ctx_ref, save=False): self.pc.setFromOptions()
[docs] def update(self, pc): A, P = pc.getOperators() A_dev, P_dev = self.pc.getOperators() # Perform a value-only copy P.copy(P_dev, structure=PETSc.Mat.Structure.SAME_NONZERO_PATTERN) if A_dev.handle != P_dev.handle and A.type not in _no_offload_mat_types: # Perform a value-only copy A.copy(A_dev, structure=PETSc.Mat.Structure.SAME_NONZERO_PATTERN)
# Convert vectors to CUDA, solve and get solution on CPU back
[docs] def apply(self, pc, x, y, transpose=False): pc_apply = self.pc.applyTranspose if transpose else self.pc.apply dm = pc.getDM() if self.device_mat is None: with dmhooks.add_hooks(dm, self, appctx=self._ctx_ref): pc_apply(x, y) else: with PETSc.Log.Event("Event: apply offload"): with dmhooks.add_hooks(dm, self, appctx=self._ctx_ref): with PETSc.Log.Event("Event: vectors offload"): # Create the to-be-offloaded vector y_dev = PETSc.Vec() # Use device implementation of 'createWithArrays' function self.call_device_vec_impl(y_dev, "createWithArrays", y.array_r, None) # Create the to-be-offloaded vector x_dev = PETSc.Vec() # Use device implementation of 'createWithArrays' function self.call_device_vec_impl(x_dev, "createWithArrays", x.array_r, None) with PETSc.Log.Event("Event: solve"): pc_apply(x_dev, y_dev) with PETSc.Log.Event("Event: vectors copy back"): # y is already designated as host storage for y_dev, so calling # getArray is sufficient to synchronise the vector on the device # with y on the host y_dev.getArray(True)
[docs] def applyTranspose(self, pc, x, y): self.apply(pc, x, y, transpose=True)
[docs] def view(self, pc, viewer=None): super().view(pc, viewer) if hasattr(self, "pc"): viewer.printfASCII("PC to solve on GPU\n") self.pc.view(viewer)