packages feed

abstract-par-accelerate-0.3.2: Control/Monad/Par/Accelerate.hs

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, TypeFamilies #-}
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wall #-}

module Control.Monad.Par.Accelerate 
       (
         -- * The Class
         ParAccelerate(..),
       
#ifdef ACC_IO         
         -- * Example applications of `unsafeHybrid`
         unsafeHybridVector,
         unsafeHybridIArray
#endif
       ) where 

import Control.Monad.Par.Class
import Data.Array.IArray (IArray)
import Foreign (Ptr, Storable)
import qualified Data.Array.IArray as IArray    
import qualified Data.Vector.Storable as Vector

-- From 'accelerate':
import Data.Array.Accelerate (Acc, Arrays, Shape)
import Data.Array.Accelerate.Array.Sugar (EltRepr,Elt,Array,DIM1,toIArray)

#ifdef ACC_IO         
-- From 'accelerate-io', or 'accelerate" <= 0.10
import qualified Data.Array.Accelerate.IO as IO 
#endif

--------------------------------------------------------------------------------

-- | A class containing Accelerate-specific `Par` operations.
-- 
-- A minimal complete instance contains:
--  * one of `runAccWith` or `spawnAccWith`
--  * `getDefaultAccImpl`
--  * `compileAcc`.
class ParFuture iv p => ParAccelerate iv p where 
  
  -- | Run an Accelerate computation and wait for its result.  In the
  -- context of a `Par` computation this can result in better
  -- performance than using an Accelerate-provided `run` function
  -- directly, because this version enables the CPU work scheduler to do
  -- other work while waiting for the GPU computation to complete.
  -- 
  -- Moreover, when configured with a high-performance /CPU/ Accelerate backend
  -- in the future this routine can enable automatic CPU/GPU work partitioning.
  -- 
  -- The specific Accelerate implementation is NOT specified when
  -- calling `runAcc`.  That choice is deferred to the point where
  -- `runPar` is invoked for the scheduler in question.
  runAcc   :: (Arrays a) => Acc a -> p a
  runAcc comp = do runner <- getDefaultAccImpl
                   runAccWith runner comp

  -- | Like `runAcc` but runs the Accelerate computation asynchronously.
  spawnAcc :: (Arrays a) => Acc a -> p (iv a)
  spawnAcc comp = do runner <- getDefaultAccImpl
                     spawnAccWith runner comp

  
-- | Spawn an computation which may execute /either/ on the CPU or GPU
  -- based on runtime load.  The CPU and GPU implementations may employ
  -- completely different algorithms; this is an UNSAFE operation which
  -- will not guarantee determinism unless the user ensures that the
  -- result of both computations is always equivalent.
  -- 
  --     
  -- A common application of `unsafeHybrid` is the following:
  --
  -- > unsafeHybrid Data.Array.Accelerate.IO.toVector
  --
  unsafeHybrid :: Arrays b => (b -> a) -> (p a, Acc b) -> p (iv a)
  unsafeHybrid cvrt pr = do runner <- getDefaultAccImpl 
                            unsafeHybridWith runner cvrt pr
                                           
  ------------------------------------------------------------
  -- * Control over selecting the Accelerate implementation.

  -- Retrieve the Accelerate @run@ function that is the default for
  -- this execution, i.e. the one used for `runAcc` or `spawnAcc`.
  getDefaultAccImpl :: Arrays a => p (Acc a -> a)

  -- | Like `runAcc` but specify a specific Accelerate implementation, e.g. @CUDA.run@.
  runAccWith   :: (Arrays a) => (Acc a -> a) -> Acc a -> p a
  runAccWith runner comp = spawnAccWith runner comp >>= get  
  
  -- | Analogous to `runAccWith`.
  spawnAccWith :: (Arrays a) => (Acc a -> a) -> Acc a -> p (iv a)
  -- This default implementation is actually QUITE BAD.  It's an
  -- anti-pattern.  We don't want to wait until the spawned
  -- computation is executed to enqueue the GPU computation.  This is
  -- a problem with child-stealing Par implemenations, but not so much
  -- with parent-stealing ones.
  spawnAccWith runner acc = spawn_ $ runAccWith runner  acc

  -- | Analogous to other @*With@ functions.
  unsafeHybridWith :: Arrays b => (Acc b -> b) -> (b -> a) -> (p a, Acc b) -> p (iv a)
  -- This default implementation simply /always/ runs the GPU version:
  unsafeHybridWith runner cvrt (_, acc) = 
    spawn_ $ do x <- runAccWith runner acc
                return (cvrt x)

  -- TODO: to be fully consistent we should perhaps have
  -- compileAccWith, but that gets complicated.

  ------------------------------------------------------------
  -- TODO: We would really like to add this, but it requires more than
  -- getDefaultAccImpl can provide right now.
#if 0
  -- | Prepare a GPU computation for repeated execution.  
  -- 
  -- Typically, this is applied to its first argument once in an outer
  -- scope then applied to its second argument repeatedly inside a loop.
  -- 
  -- Whereas the normal `runAcc` will /attempt/ to cache compiled
  -- programs and avoid recompilation, this function guarantees no
  -- recompilation and further avoids some overhead from re-executing
  -- the Accelerate front-end.
  -- 
  -- See "Data.Array.Accelerate.CUDA.run1" for more explanation.
  compileAcc :: (Arrays a, Arrays b) => (Acc a -> Acc b) -> a -> p b
#endif



--------------------------------------------------------------------------------

#ifdef ACC_IO         
-- | An example application of `unsafeHybrid` for vectors.
unsafeHybridVector :: (Vector.Storable a, Elt a, 
                       IO.BlockPtrs (EltRepr a) ~ ((), Ptr a), 
                       ParAccelerate iv p)
                  => (p (Vector.Vector a), Acc (Array DIM1 a))
                  -> p (iv (Vector.Vector a))
-- /TODO/: make a variant with unrestricted 'Shape' that, e.g., yields
-- a vector in row-major order.
unsafeHybridVector = unsafeHybrid IO.toVector


-- | An example application of `unsafeHybrid` for any IArray type.
unsafeHybridIArray :: ( EltRepr ix ~ EltRepr sh
                     , IArray a e, IArray.Ix ix
                     , Shape sh, Elt ix, Elt e 
                     , ParAccelerate iv p)
                  => (p (a ix e), Acc (Array sh e))
                  ->  p (iv (a ix e))               
unsafeHybridIArray = unsafeHybrid toIArray
                     --IO.toArray 
#endif