MatrixMultiply

From CSE231 Wiki
Jump to: navigation, search

Motivation

We gain experience using the parallel for loop constructs.

Background

Matrix multiplication is a simple mathematical operation which we will replicate in this studio. For our purposes, we will only deal with square matrices (same number of rows and columns). However, we will approach this problem with several different parallel constructs and approaches.

For those unfamiliar on how to multiply two matrices, take a look at these overviews:

If \mathbf{A} is an n \times m matrix and \mathbf{B} is an m  \times p matrix

for each i=[0..n) and for each j=[0..p)

(\mathbf{A}\mathbf{B})_{ij} = \sum_{k=1}^m A_{ik}B_{kj}

source: Matrix Multiplication on Wikipedia

Code To Investigate

SequentialMatrixMultiplier

We provide the sequential iterative implementation so you can focus just on becoming familiar with using Habanero's forall loops.

The point of the required portion of this studio is not to struggle with matrix multiplication, but rather to get some experience with the parallel for loop constructs in Habanero.

Feel free to use the provided sequential implementation in SequentialMatrixMultiplier as a reference:

	@Override
	public double[][] multiply(double[][] a, double[][] b) {
		final int N = a.length;
		final int M = a[0].length;
		final int P = b[0].length;

		if (b.length != M) {
			throw new IllegalArgumentException();
		}

		double[][] result = MatrixUtils.createMultiplyResultBufferInitializedToZeros(a, b);

		// a is an NxM matrix
		// b is an MxP matrix
		// result is an NxP matrix

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < P; j++) {
				// Note on initialization of arrays:
				//
				// when arrays are created in Java the individual elements are set to the
				// initial value of the type.
				// https://docs.oracle.com/javase/specs/jls/se7/html/jls-10.html#jls-10.6
				//
				// for the double type, the initial value is 0.0
				// https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5
				//
				// therefore, we can omit initializing the elements prior to the loop:
				// result[i][j] = 0.0;
				//
				for (int k = 0; k < M; k++) {
					result[i][j] += a[i][k] * b[k][j];
				}
			}
		}
		return result;
	}

The Core Questions

  • What are the tasks?
  • What is the data?
  • Is the data mutable?
  • If so, how is it shared?

Code To Use

Wiki's reference page

V5

forall(start, endExclusive, body)
forall(chunked(), start, endExclusive, body)
forall2d(aMin, aMaxExclusive, bMin, bMaxExclusive, body)
forall2d(chunked(), aMin, aMaxExclusive, bMin, bMaxExclusive, body)
chunked()
chunked(size)


Common Mistakes To Avoid

Attention niels epting.svg Warning:CSE 231 is exclusive on max. While we implemented our own X10/Habanero we changed forall(0, n-1, body) to forall(0, n, body) for everything.

Code To Implement

There are three methods you will need to implement, all of which are different ways to use parallel for loops to solve the problem. To assist you, the sequential implementation has already been completed for you. We recommend starting from the top and working your way down. There is also an optional recursive implementation and a manual grouping implementation which has been done for you (this is just to demonstrate how chunking works behind the scenes).

ForallForallMatrixMultiplier

class: ForallForallMatrixMultiplier.java Java.png
methods: multiply
package: matrixmultiply.studio
source folder: src/main/java

method: public double[][] multiply(double[][] a, double[][] b) Parallel.svg (parallel implementation required)

In this implementation, you will simply convert the sequential solution into a parallel one using two forall loops.

Forall2dMatrixMultiplier

class: Forall2dMatrixMultiplier.java Java.png
methods: multiply
package: matrixmultiply.studio
source folder: src/main/java

method: public double[][] multiply(double[][] a, double[][] b) Parallel.svg (parallel implementation required)

In this implementation, we will cut down the syntax of the two forall implementation with the use of V5’s forall2d method. Functionally, this method serves the purpose of using two forall loops. Take a look at the reference page if you have questions on how to utilize this loop.

Forall2dChunkedMatrixMultiplier

class: Forall2dChunkedMatrixMultiplier.java Java.png
methods: multiply
package: matrixmultiply.studio
source folder: src/main/java

method: public double[][] multiply(double[][] a, double[][] b) Parallel.svg (parallel implementation required)

In this implementation, we will add a minor performance boost to the process by using the forall-chunked construct. Although similar to the traditional forall loop, it increases performance using iteration grouping/chunking. This topic is discussed in detail in this Rice video and explained in the V5 documentation. There is no need to specify anything, allow the runtime to determine the chunking.

NOTE: we contemplated also assigning building a 1D forall chunked version. We deemed this more work that it was worth given that you are already building the 2d version. Just know that forall(chunked(), ...) exists for 1d loops as well.

Use chunking. It is a nice feature.

Optional Divide and Conquer Challenges

In this implementation, you will solve the matrix multiply problem sequentially and in parallel using recursion. Although this class should be able to take in a matrix of any size, try to imagine this as a 2x2 matrix in order to make it easier to solve. Once you solve the sequential method, the parallel method should look very similar with exception of an async/finish block.

In order to obtain the desired result matrix, you will need to recursively call the correct submatrices for each of the four result submatrices. Imagining this as a 2x2 matrix, remember that the dot products of the rows of the first matrix and the columns of the second matrix create the result matrix.

Hint: Each result submatrix should have two recursive calls, for a total of eight recursive calls.

SequentialDivideAndConquerMatrixMultiplier

class: SequentialDivideAndConquerMatrixMultiplier.java Java.png
methods: sequentialKernel
package: matrixmultiply.challenge
source folder: src/main/java

method: /* package-private */ static void sequentialKernel(OffsetSubMatrix result, OffsetSubMatrix a, OffsetSubMatrix b) Sequential.svg (sequential implementation only)

In class SequentialDivideAndConquerMatrixMultiplier, method sequentialKernel you will find your base case and the sub matrices prepared for you.

/* package-private */ static void sequentialKernel(OffsetSubMatrix result, OffsetSubMatrix a, OffsetSubMatrix b) {
	if (result.getSize() == 1) {
		// result.values[result.row][result.col] += a.values[a.row][a.col]*b.values[b.row][b.col];
		result.addToValueAtOffset(a.getValueAtOffset() * b.getValueAtOffset());
	} else {
		OffsetSubMatrix result11 = result.newSub11();
		OffsetSubMatrix result12 = result.newSub12();
		OffsetSubMatrix result21 = result.newSub21();
		OffsetSubMatrix result22 = result.newSub22();

		OffsetSubMatrix a11 = a.newSub11();
		OffsetSubMatrix a12 = a.newSub12();
		OffsetSubMatrix a21 = a.newSub21();
		OffsetSubMatrix a22 = a.newSub22();

		OffsetSubMatrix b11 = b.newSub11();
		OffsetSubMatrix b12 = b.newSub12();
		OffsetSubMatrix b21 = b.newSub21();
		OffsetSubMatrix b22 = b.newSub22();

		// https://en.wikipedia.org/wiki/Matrix_multiplication_algorithm#Divide_and_conquer_algorithm
		throw new NotYetImplementedException();
	}
}

You simply need to make the appropriate recursive calls to compute the result on the right:

\begin{pmatrix}
\mathbf{A}_{11} & \mathbf{A}_{12} \\
\mathbf{A}_{21} & \mathbf{A}_{22} \\
\end{pmatrix} \begin{pmatrix}
\mathbf{B}_{11} & \mathbf{B}_{12} \\
\mathbf{B}_{21} & \mathbf{B}_{22} \\
\end{pmatrix} = \begin{pmatrix}
\mathbf{A}_{11} \mathbf{B}_{11} + \mathbf{A}_{12} \mathbf{B}_{21} & \mathbf{A}_{11} \mathbf{B}_{12} + \mathbf{A}_{12} \mathbf{B}_{22}\\
\mathbf{A}_{21} \mathbf{B}_{11} + \mathbf{A}_{22} \mathbf{B}_{21} & \mathbf{A}_{21} \mathbf{B}_{12} + \mathbf{A}_{22} \mathbf{B}_{22}\\
\end{pmatrix}

source: Wikipedia Parallel Matrix Multiplication

ParallelDivideAndConquerMatrixMultiplier

class: ParallelDivideAndConquerMatrixMultiplier.java Java.png
methods: parallelKernel
package: matrixmultiply.challenge
source folder: src/main/java

Again, given the following:

\begin{pmatrix}
\mathbf{A}_{11} \mathbf{B}_{11} + \mathbf{A}_{12} \mathbf{B}_{21} & \mathbf{A}_{11} \mathbf{B}_{12} + \mathbf{A}_{12} \mathbf{B}_{22}\\
\mathbf{A}_{21} \mathbf{B}_{11} + \mathbf{A}_{22} \mathbf{B}_{21} & \mathbf{A}_{21} \mathbf{B}_{12} + \mathbf{A}_{22} \mathbf{B}_{22}\\
\end{pmatrix}

source: Wikipedia Parallel Matrix Multiplication

What computation can be done in parallel? What computation must be performed sequentially?

method: private static void parallelKernel(OffsetSubMatrix result, OffsetSubMatrix a, OffsetSubMatrix b, IntPredicate isParallelPredicate) Parallel.svg (parallel implementation required)

private static void parallelKernel(OffsetSubMatrix result, OffsetSubMatrix a, OffsetSubMatrix b,
		IntPredicate isParallelPredicate) throws InterruptedException, ExecutionException {
	if (isParallelPredicate.test(result.getSize())) {
		OffsetSubMatrix result11 = result.newSub11();
		OffsetSubMatrix result12 = result.newSub12();
		OffsetSubMatrix result21 = result.newSub21();
		OffsetSubMatrix result22 = result.newSub22();

		OffsetSubMatrix a11 = a.newSub11();
		OffsetSubMatrix a12 = a.newSub12();
		OffsetSubMatrix a21 = a.newSub21();
		OffsetSubMatrix a22 = a.newSub22();

		OffsetSubMatrix b11 = b.newSub11();
		OffsetSubMatrix b12 = b.newSub12();
		OffsetSubMatrix b21 = b.newSub21();
		OffsetSubMatrix b22 = b.newSub22();

		throw new NotYetImplementedException();
	} else {
		SequentialDivideAndConquerMatrixMultiplier.sequentialKernel(result, a, b);
	}
}


Testing Your Solution

Correctness

class: MatrixMultiplyTestSuite.java Junit.png
package: matrixmultiply.studio
source folder: src/test/java

Optional Fun Divide And Conquer Matrix Multiply Correctness

class: DivideAndConquerMatrixMultiplyTestSuite.java Junit.png
package: matrixmultiply.challenge
source folder: src/test/java

Performance

class: MatrixMultiplicationTiming.java Noun Project stopwatch icon 386232 cc.svg
package: matrixmultiply.studio
source folder: src/performance/java