| Copyright | [2008..2017] Manuel M T Chakravarty Gabriele Keller [2009..2017] Trevor L. McDonell [2014..2014] Frederik M. Madsen |
|---|---|
| License | BSD3 |
| Maintainer | Trevor L. McDonell <tmcdonell@cse.unsw.edu.au> |
| Stability | experimental |
| Portability | non-portable (GHC extensions) |
| Safe Haskell | None |
| Language | Haskell98 |
Data.Array.Accelerate.Interpreter
Contents
Description
This interpreter is meant to be a reference implementation of the semantics of the embedded array language. The emphasis is on defining the semantics clearly, not on performance.
Interpret an array expression
Accelerate is an embedded language that distinguishes between vanilla arrays (e.g. in Haskell memory on the CPU) and embedded arrays (e.g. in device memory on a GPU), as well as the computations on both of these. Since Accelerate is an embedded language, programs written in Accelerate are not compiled by the Haskell compiler (GHC). Rather, each Accelerate backend is a runtime compiler which generates and executes parallel SIMD code of the target language at application runtime.
The type constructor Acc represents embedded collective array operations.
A term of type Acc a is an Accelerate program which, once executed, will
produce a value of type a (an Array or a tuple of Arrays). Collective
operations of type Acc a comprise many scalar expressions, wrapped in
type constructor Exp, which will be executed in parallel. Although
collective operations comprise many scalar operations executed in parallel,
scalar operations cannot initiate new collective operations: this
stratification between scalar operations in Exp and array operations in
Acc helps statically exclude nested data parallelism, which is difficult
to execute efficiently on constrained hardware such as GPUs.
- A simple example
As a simple example, to compute a vector dot product we can write:
dotp :: Num a => Vector a -> Vector a -> Acc (Scalar a)
dotp xs ys =
let
xs' = use xs
ys' = use ys
in
fold (+) 0 ( zipWith (*) xs' ys' )The function dotp consumes two one-dimensional arrays (Vectors) of
values, and produces a single (Scalar) result as output. As the return type
is wrapped in the type Acc, we see that it is an embedded Accelerate
computation - it will be evaluated in the object language of dynamically
generated parallel code, rather than the meta language of vanilla Haskell.
As the arguments to dotp are plain Haskell arrays, to make these available
to Accelerate computations they must be embedded with the
use function.
An Accelerate backend is used to evaluate the embedded computation and return
the result back to vanilla Haskell. Calling the run function of a backend
will generate code for the target architecture, compile, and execute it. For
example, the following backends are available:
- accelerate-llvm-native: for execution on multicore CPUs
- accelerate-llvm-ptx: for execution on NVIDIA CUDA-capable GPUs
See also Exp, which encapsulates embedded scalar computations.
- Avoiding nested parallelism
As mentioned above, embedded scalar computations of type Exp can not
initiate further collective operations.
Suppose we wanted to extend our above dotp function to matrix-vector
multiplication. First, let's rewrite our dotp function to take Acc arrays
as input (which is typically what we want):
dotp :: Num a => Acc (Vector a) -> Acc (Vector a) -> Acc (Scalar a) dotp xs ys = fold (+) 0 ( zipWith (*) xs ys )
We might then be inclined to lift our dot-product program to the following
(incorrect) matrix-vector product, by applying dotp to each row of the
input matrix:
mvm_ndp :: Num a => Acc (Matrix a) -> Acc (Vector a) -> Acc (Vector a)
mvm_ndp mat vec =
let Z :. rows :. cols = unlift (shape mat) :: Z :. Exp Int :. Exp Int
in generate (index1 rows)
(\row -> the $ dotp vec (slice mat (lift (row :. All))))Here, we use generate to create a one-dimensional
vector by applying at each index a function to slice
out the corresponding row of the matrix to pass to the dotp function.
However, since both generate and
slice are data-parallel operations, and moreover that
slice depends on the argument row given to it by
the generate function, this definition requires
nested data-parallelism, and is thus not permitted. The clue that this
definition is invalid is that in order to create a program which will be
accepted by the type checker, we must use the function
the to retrieve the result of the dotp operation,
effectively concealing that dotp is a collective array computation in order
to match the type expected by generate, which is that
of scalar expressions. Additionally, since we have fooled the type-checker,
this problem will only be discovered at program runtime.
In order to avoid this problem, we can make use of the fact that operations
in Accelerate are rank polymorphic. The fold
operation reduces along the innermost dimension of an array of arbitrary
rank, reducing the rank (dimensionality) of the array by one. Thus, we can
replicate the input vector to as many rows there
are in the input matrix, and perform the dot-product of the vector with every
row simultaneously:
mvm :: A.Num a => Acc (Matrix a) -> Acc (Vector a) -> Acc (Vector a)
mvm mat vec =
let Z :. rows :. cols = unlift (shape mat) :: Z :. Exp Int :. Exp Int
vec' = A.replicate (lift (Z :. rows :. All)) vec
in
A.fold (+) 0 ( A.zipWith (*) mat vec' )Note that the intermediate, replicated array vec' is never actually created
in memory; it will be fused directly into the operation which consumes it. We
discuss fusion next.
- Fusion
Array computations of type Acc will be subject to array fusion;
Accelerate will combine individual Acc computations into a single
computation, which reduces the number of traversals over the input data and
thus improves performance. As such, it is often useful to have some intuition
on when fusion should occur.
The main idea is to first partition array operations into two categories:
- Element-wise operations, such as
map,generate, andbackpermute. Each element of these operations can be computed independently of all others. - Collective operations such as
fold,scanl, andstencil. To compute each output element of these operations requires reading multiple elements from the input array(s).
Element-wise operations fuse together whenever the consumer operation uses a single element of the input array. Element-wise operations can both fuse their inputs into themselves, as well be fused into later operations. Both these examples should fuse into a single loop:


If the consumer operation uses more than one element of the input array
(typically, via generate indexing an array multiple
times), then the input array will be completely evaluated first; no fusion
occurs in this case, because fusing the first operation into the second
implies duplicating work.
On the other hand, collective operations can fuse their input arrays into themselves, but on output always evaluate to an array; collective operations will not be fused into a later step. For example:

Here the element-wise sequence (use
+ generate + zipWith) will
fuse into a single operation, which then fuses into the collective
fold operation. At this point in the program the
fold must now be evaluated. In the final step the
map reads in the array produced by
fold. As there is no fusion between the
fold and map steps, this
program consists of two "loops"; one for the use
+ generate + zipWith
+ fold step, and one for the final
map step.
You can see how many operations will be executed in the fused program by
Show-ing the Acc program, or by using the debugging option -ddump-dot
to save the program as a graphviz DOT file.
As a special note, the operations unzip and
reshape, when applied to a real array, are executed
in constant time, so in this situation these operations will not be fused.
- Tips
- Since
Accrepresents embedded computations that will only be executed when evaluated by a backend, we can programatically generate these computations using the meta language Haskell; for example, unrolling loops or embedding input values into the generated code. - It is usually best to keep all intermediate computations in
Acc, and onlyrunthe computation at the very end to produce the final result. This enables optimisations between intermediate results (e.g. array fusion) and, if the target architecture has a separate memory space, as is the case of GPUs, to prevent excessive data transfers.
Instances
class (Typeable a, Typeable (ArrRepr a)) => Arrays a Source #
Arrays consists of nested tuples of individual Arrays, currently up to
15-elements wide. Accelerate computations can thereby return multiple
results.
Minimal complete definition
arrays, flavour, toArr, fromArr
Instances
| Arrays () Source # | |
| (Arrays a, Arrays b) => Arrays (a, b) Source # | |
| (Shape sh, Elt e) => Arrays (Array sh e) Source # | |
| (Arrays a, Arrays b, Arrays c) => Arrays (a, b, c) Source # | |
| (Arrays a, Arrays b, Arrays c, Arrays d) => Arrays (a, b, c, d) Source # | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e) => Arrays (a, b, c, d, e) Source # | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f) => Arrays (a, b, c, d, e, f) Source # | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g) => Arrays (a, b, c, d, e, f, g) Source # | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h) => Arrays (a, b, c, d, e, f, g, h) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h)) flavour :: (a, b, c, d, e, f, g, h) -> ArraysFlavour (a, b, c, d, e, f, g, h) toArr :: ArrRepr (a, b, c, d, e, f, g, h) -> (a, b, c, d, e, f, g, h) fromArr :: (a, b, c, d, e, f, g, h) -> ArrRepr (a, b, c, d, e, f, g, h) | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h, Arrays i) => Arrays (a, b, c, d, e, f, g, h, i) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h, i) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h, i)) flavour :: (a, b, c, d, e, f, g, h, i) -> ArraysFlavour (a, b, c, d, e, f, g, h, i) toArr :: ArrRepr (a, b, c, d, e, f, g, h, i) -> (a, b, c, d, e, f, g, h, i) fromArr :: (a, b, c, d, e, f, g, h, i) -> ArrRepr (a, b, c, d, e, f, g, h, i) | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h, Arrays i, Arrays j) => Arrays (a, b, c, d, e, f, g, h, i, j) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h, i, j) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h, i, j)) flavour :: (a, b, c, d, e, f, g, h, i, j) -> ArraysFlavour (a, b, c, d, e, f, g, h, i, j) toArr :: ArrRepr (a, b, c, d, e, f, g, h, i, j) -> (a, b, c, d, e, f, g, h, i, j) fromArr :: (a, b, c, d, e, f, g, h, i, j) -> ArrRepr (a, b, c, d, e, f, g, h, i, j) | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h, Arrays i, Arrays j, Arrays k) => Arrays (a, b, c, d, e, f, g, h, i, j, k) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h, i, j, k) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h, i, j, k)) flavour :: (a, b, c, d, e, f, g, h, i, j, k) -> ArraysFlavour (a, b, c, d, e, f, g, h, i, j, k) toArr :: ArrRepr (a, b, c, d, e, f, g, h, i, j, k) -> (a, b, c, d, e, f, g, h, i, j, k) fromArr :: (a, b, c, d, e, f, g, h, i, j, k) -> ArrRepr (a, b, c, d, e, f, g, h, i, j, k) | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h, Arrays i, Arrays j, Arrays k, Arrays l) => Arrays (a, b, c, d, e, f, g, h, i, j, k, l) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h, i, j, k, l) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l)) flavour :: (a, b, c, d, e, f, g, h, i, j, k, l) -> ArraysFlavour (a, b, c, d, e, f, g, h, i, j, k, l) toArr :: ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l) -> (a, b, c, d, e, f, g, h, i, j, k, l) fromArr :: (a, b, c, d, e, f, g, h, i, j, k, l) -> ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l) | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h, Arrays i, Arrays j, Arrays k, Arrays l, Arrays m) => Arrays (a, b, c, d, e, f, g, h, i, j, k, l, m) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h, i, j, k, l, m) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m)) flavour :: (a, b, c, d, e, f, g, h, i, j, k, l, m) -> ArraysFlavour (a, b, c, d, e, f, g, h, i, j, k, l, m) toArr :: ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m) -> (a, b, c, d, e, f, g, h, i, j, k, l, m) fromArr :: (a, b, c, d, e, f, g, h, i, j, k, l, m) -> ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m) | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h, Arrays i, Arrays j, Arrays k, Arrays l, Arrays m, Arrays n) => Arrays (a, b, c, d, e, f, g, h, i, j, k, l, m, n) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n)) flavour :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n) -> ArraysFlavour (a, b, c, d, e, f, g, h, i, j, k, l, m, n) toArr :: ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n) fromArr :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n) -> ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n) | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h, Arrays i, Arrays j, Arrays k, Arrays l, Arrays m, Arrays n, Arrays o) => Arrays (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)) flavour :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) -> ArraysFlavour (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) toArr :: ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) fromArr :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) -> ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) | |
| (Arrays a, Arrays b, Arrays c, Arrays d, Arrays e, Arrays f, Arrays g, Arrays h, Arrays i, Arrays j, Arrays k, Arrays l, Arrays m, Arrays n, Arrays o, Arrays p) => Arrays (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) Source # | |
Methods arrays :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) -> ArraysR (ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)) flavour :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) -> ArraysFlavour (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) toArr :: ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) fromArr :: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) -> ArrRepr (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) | |
run :: Arrays a => Acc a -> a Source #
Run a complete embedded array program using the reference interpreter.