Raw File
GPUSparseMatrixTests.cpp
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
// GPUSparseMatrix Unit tests should go here
//
#include "stdafx.h"
#include <math.h>
#ifdef _WIN32
#include <crtdefs.h>
#endif
#include "../../../Source/Math/GPUSparseMatrix.h"
#include "common.h"

using namespace Microsoft::MSR::CNTK;

// TODO: Move warning suppression to stdafx if necessary WI#: 83
//#pragma warning (disable: 4244 4245 4305)       // conversions and truncations; we don't care in this test project

namespace Microsoft { namespace MSR { namespace CNTK { namespace Test {

const float c_v[9] = {1, 4, 2, 3, 5, 7, 8, 9, 6};
const int c_i[5] = {0, 2, 4, 7, 9};
const int c_j[9] = {0, 1, 1, 2, 0, 3, 4, 2, 4};
const int c_rowCount = 4;
const int c_colCount = 5;
const int c_size = c_rowCount + c_colCount;

BOOST_AUTO_TEST_SUITE(GPUMatrixSuite)

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrixConstructorsAndInitializers, RandomSeedFixture)
{
    GPUSparseMatrix<float> m(c_deviceIdZero);
    BOOST_CHECK(m.IsEmpty());

    m.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);
    BOOST_CHECK_EQUAL(c_rowCount, m.GetNumRows());
    BOOST_CHECK_EQUAL(c_colCount, m.GetNumCols());
    BOOST_CHECK(!m.IsEmpty());

    const GPUSparseMatrix<float> m1 = m;
    BOOST_CHECK_EQUAL(c_rowCount, m1.GetNumRows());
    BOOST_CHECK_EQUAL(c_colCount, m1.GetNumCols());
    BOOST_CHECK(!m1.IsEmpty());

    const GPUSparseMatrix<float> m2(m);
    BOOST_CHECK_EQUAL(c_rowCount, m2.GetNumRows());
    BOOST_CHECK_EQUAL(c_colCount, m2.GetNumCols());
    BOOST_CHECK(!m2.IsEmpty());
}

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrixScaleAndAdd, RandomSeedFixture)
{
    const int m = 4;
    const int n = 5;    

    float a[m * n];
    float b[m * n];
    std::mt19937 rng(0);
    for (int i = 0; i < m * n; i++)
    {
        a[i] = static_cast<float>(rng());
        b[i] = static_cast<float>(rng());
    }

    const GPUMatrix<float> denseMatrixA(m, n, c_deviceIdZero, a, MatrixFlags::matrixFlagNormal);
    const GPUMatrix<float> denseMatrixB(m, n, c_deviceIdZero, b, MatrixFlags::matrixFlagNormal);

    GPUSparseMatrix<float> sparseMatrixA(c_deviceIdZero);
    sparseMatrixA.SetValue(denseMatrixA);
    GPUSparseMatrix<float> sparseMatrixB(c_deviceIdZero);
    sparseMatrixB.SetValue(denseMatrixB);

    GPUSparseMatrix<float> sparseMatrixC(c_deviceIdZero);
    const float alpha = 2;
    const float beta = 3;
    GPUSparseMatrix<float>::ScaleAndAdd(alpha, sparseMatrixA, beta, sparseMatrixB, sparseMatrixC);

    GPUSparseMatrix<float>::Scale(alpha, sparseMatrixC);

    const GPUMatrix<float> denseMatrixC = sparseMatrixC.CopyToDenseMatrix();
    unique_ptr<float[]> c(denseMatrixC.CopyToArray());
    for (int i = 0; i < m * n; i++)
    {        
        float res1 = alpha * (alpha * a[i] + beta * b[i]);
        float res2 = c[i];
        BOOST_REQUIRE_MESSAGE(AreEqual(res1, res2, Err<float>::Rel, Err<float>::Abs), 
                              "first mismatch at " << i << ", " << res1 << "!=" << res2 << ", relErr=" << (std::abs(res1 - res2) / std::max(std::abs(res1), \
                              std::abs(res2))) << ", absErr = " << std::abs(res1 - res2));
    }
}

BOOST_FIXTURE_TEST_CASE(GPUSparseDensePlusSparse, RandomSeedFixture)
{
    GPUSparseMatrix<float> sparseMatrix(c_deviceIdZero);

    sparseMatrix.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);

    GPUMatrix<float> denseMatrixB = GPUMatrix<float>::RandomUniform(c_rowCount, c_colCount, c_deviceIdZero, -2, 45, IncrementCounter());
    GPUMatrix<float> resultMatrix(denseMatrixB.GetNumRows(), denseMatrixB.GetNumCols(), c_deviceIdZero);

    const float alpha = 0.53f;
    const float beta = 1.0f;

    GPUSparseMatrix<float>::ScaleAndAdd(alpha, sparseMatrix, beta, denseMatrixB, resultMatrix);
    GPUMatrix<float>::ScaleAndAdd(alpha, sparseMatrix.CopyToDenseMatrix(), denseMatrixB);

    BOOST_CHECK(denseMatrixB.IsEqualTo(resultMatrix, c_epsilonFloatE5));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseElementwiseTimesDense, RandomSeedFixture)
{
    GPUSparseMatrix<float> lhs(c_deviceIdZero);
    lhs.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);

    const GPUMatrix<float> rhs = GPUMatrix<float>::RandomUniform(c_rowCount, c_colCount, c_deviceIdZero, -2, 45, IncrementCounter());

    const GPUMatrix<float> product = GPUSparseMatrix<float>::ElementProductOf(lhs, rhs);

    GPUMatrix<float> denseProduct(c_deviceIdZero);
    denseProduct.AssignElementProductOf(lhs.CopyToDenseMatrix(), rhs);

    BOOST_CHECK(product.IsEqualTo(denseProduct));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseTimesDense, RandomSeedFixture)
{
    GPUSparseMatrix<float> lhs(c_deviceIdZero);
    BOOST_CHECK(lhs.IsEmpty());
    lhs.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);
    BOOST_CHECK_EQUAL(c_rowCount, lhs.GetNumRows());
    BOOST_CHECK_EQUAL(c_colCount, lhs.GetNumCols());
    BOOST_CHECK(!lhs.IsEmpty());

    const GPUMatrix<float> rhs = GPUMatrix<float>::Eye(c_colCount, c_deviceIdZero);
    GPUMatrix<float> result = GPUMatrix<float>::Ones(c_rowCount, c_colCount, c_deviceIdZero);

    GPUSparseMatrix<float>::MultiplyAndWeightedAdd(1, lhs, false, rhs, false, 1, result);

    float *arr = result.CopyToArray();
    CPUMatrix<float> ccpu(c_rowCount, c_colCount, arr, MatrixFlags::matrixFlagNormal);
    delete[] arr;

    BOOST_CHECK_EQUAL(1 + 1, ccpu(0, 0));
    BOOST_CHECK_EQUAL(4 + 1, ccpu(0, 1));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(0, 2));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(0, 3));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(0, 4));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(1, 0));
    BOOST_CHECK_EQUAL(2 + 1, ccpu(1, 1));
    BOOST_CHECK_EQUAL(3 + 1, ccpu(1, 2));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(1, 3));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(1, 4));
    BOOST_CHECK_EQUAL(5 + 1, ccpu(2, 0));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(2, 1));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(2, 2));
    BOOST_CHECK_EQUAL(7 + 1, ccpu(2, 3));
    BOOST_CHECK_EQUAL(8 + 1, ccpu(2, 4));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(3, 0));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(3, 1));
    BOOST_CHECK_EQUAL(9 + 1, ccpu(3, 2));
    BOOST_CHECK_EQUAL(0 + 1, ccpu(3, 3));
    BOOST_CHECK_EQUAL(6 + 1, ccpu(3, 4));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseTimesDenseRandom, RandomSeedFixture)
{
    const bool matrixTransB = false;
    for (auto sparseFormat : {MatrixFormat::matrixFormatSparseCSR, MatrixFormat::matrixFormatSparseCSC})
    {
        for (bool matrixTransA : {false, true})
        {
            for (size_t m : {1, 10, 100})
            {
                for (size_t k : {1, 10, 100})
                {
                    for (size_t n : {1, 10, 100})
                    {
                        size_t dim1 = m, dim2 = k, dim3 = k, dim4 = n;
                        if (matrixTransA)
                        {
                            dim1 = k;
                            dim2 = m;
                        }
                        // SPARSE
                        GPUMatrix<float> lhsDense(c_deviceIdZero);
                        lhsDense.AssignTruncateBottomOf(GPUMatrix<float>::RandomUniform(dim1, dim2, c_deviceIdZero, -3.0f, 1.0f, IncrementCounter()), 0);
                        GPUSparseMatrix<float> lhs(lhsDense, sparseFormat);

                        // DENSE
                        GPUMatrix<float> rhs = GPUMatrix<float>::RandomUniform(dim3, dim4, c_deviceIdZero, -1.0f, 1.0f, IncrementCounter());

                        // RESULT of Dense x Sparse
                        GPUMatrix<float> resultSP = GPUMatrix<float>::Ones(m, n, c_deviceIdZero);
                        GPUSparseMatrix<float>::MultiplyAndWeightedAdd(1, lhs, matrixTransA, rhs, matrixTransB, 1, resultSP);

                        // RESULT of Dense x Dense
                        GPUMatrix<float> resultDS = GPUMatrix<float>::Ones(m, n, c_deviceIdZero);
                        GPUMatrix<float>::MultiplyAndWeightedAdd(1, lhsDense, matrixTransA, rhs, matrixTransB, 1, resultDS);

                        float *arrSP = resultSP.CopyToArray();
                        CPUMatrix<float> resultSP_arr(m, n, arrSP, MatrixFlags::matrixFlagNormal);
                        delete[] arrSP;

                        float *arrDS = resultDS.CopyToArray();
                        CPUMatrix<float> resultDS_arr(m, n, arrDS, MatrixFlags::matrixFlagNormal);
                        delete[] arrDS;

                        // Check correctness with CPU results
                        for (int i = 0; i < m; i++) {
                            for (int j = 0; j < n; j++) {
                                BOOST_CHECK(fabs(resultSP_arr(i, j) - resultDS_arr(i, j)) < c_epsilonFloatE4);
                            }
                        }
                    }
                }
            }
        }
    }
}

#if 0
// TODO commented temporarily, this test (or underlying code) needs fixes

BOOST_FIXTURE_TEST_CASE(GPUDenseTimesSparse, RandomSeedFixture)
{
    GPUSparseMatrix<float> sparseMatrixA(matrixFormatSparseCSR, 0);
    BOOST_CHECK(sparseMatrixA.IsEmpty());

    sparseMatrixA.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);
    BOOST_CHECK_EQUAL(c_rowCount, sparseMatrixA.GetNumRows());
    BOOST_CHECK_EQUAL(c_colCount, sparseMatrixA.GetNumCols());
    BOOST_CHECK(!sparseMatrixA.IsEmpty());

    const GPUSparseMatrix<float> sparseTransposeA = sparseMatrixA.Transpose();
    const GPUMatrix<float> denseTransposeA = sparseTransposeA.CopyToDenseMatrix();

    float arrA_times_transposeA[19] = { 17, 8, 5, 0, 8, 13, 0, 27, 5, 0, 138, 48, 0, 27, 48, 117 };

    const GPUMatrix<float> cet(c_rowCount, c_rowCount, c_deviceIdZero, arrA_times_transposeA, MatrixFlags::matrixFlagNormal);
    GPUMatrix<float> cres(c_rowCount, c_rowCount, c_deviceIdZero);
    GPUSparseMatrix<float>::Multiply(sparseMatrixA, denseTransposeA, cres);
    BOOST_CHECK(cres.IsEqualTo(cet));

    float arrAT_times_matrixA[25] = { 26, 4, 0, 35, 40, 4, 20, 6, 0, 0, 0, 6, 90, 0, 54, 35, 0, 0, 49, 56, 40, 0, 54, 56, 100 };
    const GPUMatrix<float> cet1(c_colCount, c_colCount, c_deviceIdZero, arrAT_times_matrixA, MatrixFlags::matrixFlagNormal);
    GPUMatrix<float> cres1(c_colCount, c_colCount, c_deviceIdZero);
    GPUSparseMatrix<float>::Multiply(denseTransposeA, sparseMatrixA, cres1);
    BOOST_CHECK(cres1.IsEqualTo(cet1));

    const GPUMatrix<float> matrixB = GPUMatrix<float>::RandomUniform(c_rowCount + c_colCount, c_rowCount, c_deviceIdZero, -100, 100, IncrementCounter());
    GPUMatrix<float> matrixC(c_size, c_colCount, c_deviceIdZero);
    GPUSparseMatrix<float>::Multiply(matrixB, sparseMatrixA, matrixC); // C=BA

    const GPUMatrix<float> transposeB = matrixB.Transpose();
    const GPUSparseMatrix<float> transposeA = sparseMatrixA.Transpose();
    GPUMatrix<float> transposeC(c_colCount, c_size, c_deviceIdZero);
    GPUSparseMatrix<float>::Multiply(transposeA, transposeB, transposeC); // CT = AT*BT = (BA)T
    const GPUMatrix<float> twiceTransposeC = transposeC.Transpose(); // CTT = C;

    BOOST_CHECK(twiceTransposeC.IsEqualTo(matrixC, c_epsilonFloatE4));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseTimesSparse, RandomSeedFixture)
{
    GPUSparseMatrix<float> matrixA;
    BOOST_CHECK(matrixA.IsEmpty());
    matrixA.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);

    GPUSparseMatrix<float> transposeA = matrixA.Transpose();

    GPUSparseMatrix<float> product;
    GPUSparseMatrix<float>::Multiply(transposeA, false, matrixA, false, product);

    const float expectedProduct[25] = { 26, 4, 0, 35, 40, 4, 20, 6, 0, 0, 0, 6, 90, 0, 54, 35, 0, 0, 49, 56, 40, 0, 54, 56, 100 };  // A * AT
    const float * const arr = product.CopyToDenseMatrix().CopyToArray();
    BOOST_CHECK_EQUAL_COLLECTIONS(&expectedProduct[0], &expectedProduct[25], &arr[0], &arr[25]);

    delete[] arr;
}

#endif

BOOST_FIXTURE_TEST_CASE(GPUSparseElementWise, RandomSeedFixture)
{
    GPUSparseMatrix<float> matrixA(c_deviceIdZero);
    BOOST_CHECK(matrixA.IsEmpty());
    matrixA.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);

    GPUSparseMatrix<float> poweredMatrix(c_deviceIdZero);
    const float somePower = 3.14f;
    poweredMatrix.ResizeAsAndCopyIndexFrom(matrixA);
    matrixA.ElementWisePower(somePower, matrixA, poweredMatrix);

    float *arr = nullptr;
    int *ii = nullptr;
    int *jj = nullptr;
    size_t ea, nz, nr, nc;
    poweredMatrix.GetMatrixFromCSRFormat(ii, jj, arr, ea, nz, nr, nc);

    for (int index = 0; index < c_size; ++index)
    {
        float y = powf(c_v[index], somePower);
        BOOST_CHECK(fabsf(y - arr[index]) < c_epsilonFloatE3);
    }

    delete[] arr;
    delete[] ii;
    delete[] jj;
}

BOOST_FIXTURE_TEST_CASE(GPUSparseIsEqual, RandomSeedFixture)
{
    GPUSparseMatrix<float> firstMatrix(c_deviceIdZero);
    firstMatrix.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);

    GPUSparseMatrix<float> secondMatrix(c_deviceIdZero);
    secondMatrix.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);
    BOOST_CHECK(secondMatrix.IsEqualTo(firstMatrix));

    GPUSparseMatrix<float> emptyMatrix(c_deviceIdZero);
    BOOST_CHECK(!emptyMatrix.IsEqualTo(firstMatrix));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseDenseConversions, RandomSeedFixture)
{
    GPUSparseMatrix<float> sparseMatrixA(c_deviceIdZero);
    BOOST_CHECK(sparseMatrixA.IsEmpty());
    sparseMatrixA.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);
    BOOST_CHECK_EQUAL(c_rowCount, sparseMatrixA.GetNumRows());
    BOOST_CHECK_EQUAL(c_colCount, sparseMatrixA.GetNumCols());
    BOOST_CHECK(!sparseMatrixA.IsEmpty());

    const GPUMatrix<float> denseMatrixA = sparseMatrixA.CopyToDenseMatrix();
    BOOST_CHECK_EQUAL(c_rowCount, denseMatrixA.GetNumRows());
    BOOST_CHECK_EQUAL(c_colCount, denseMatrixA.GetNumCols());

    float *arr = denseMatrixA.CopyToArray();
    CPUMatrix<float> cpuMatrix(denseMatrixA.GetNumRows(), denseMatrixA.GetNumCols(), arr, MatrixFlags::matrixFlagNormal);
    delete[] arr;

    BOOST_CHECK_EQUAL(1, cpuMatrix(0, 0));
    BOOST_CHECK_EQUAL(4, cpuMatrix(0, 1));
    BOOST_CHECK_EQUAL(0, cpuMatrix(0, 2));
    BOOST_CHECK_EQUAL(0, cpuMatrix(0, 3));
    BOOST_CHECK_EQUAL(0, cpuMatrix(0, 4));
    BOOST_CHECK_EQUAL(5, cpuMatrix(2, 0));
    BOOST_CHECK_EQUAL(0, cpuMatrix(2, 1));
    BOOST_CHECK_EQUAL(0, cpuMatrix(2, 2));
    BOOST_CHECK_EQUAL(7, cpuMatrix(2, 3));
    BOOST_CHECK_EQUAL(8, cpuMatrix(2, 4));

    GPUSparseMatrix<float> sparseMatrixB(c_deviceIdZero);
    sparseMatrixB.SetValue(denseMatrixA);
    const GPUMatrix<float> denseMatrixB = sparseMatrixB.CopyToDenseMatrix();
    arr = denseMatrixB.CopyToArray();
    const CPUMatrix<float> cpuMatrixB(denseMatrixB.GetNumRows(), denseMatrixB.GetNumCols(), arr, MatrixFlags::matrixFlagNormal);
    delete[] arr;

    BOOST_CHECK_EQUAL(1, cpuMatrixB(0, 0));
    BOOST_CHECK_EQUAL(4, cpuMatrixB(0, 1));
    BOOST_CHECK_EQUAL(0, cpuMatrixB(0, 2));
    BOOST_CHECK_EQUAL(0, cpuMatrixB(0, 3));
    BOOST_CHECK_EQUAL(0, cpuMatrixB(0, 4));
    BOOST_CHECK_EQUAL(5, cpuMatrixB(2, 0));
    BOOST_CHECK_EQUAL(0, cpuMatrixB(2, 1));
    BOOST_CHECK_EQUAL(0, cpuMatrixB(2, 2));
    BOOST_CHECK_EQUAL(7, cpuMatrixB(2, 3));
    BOOST_CHECK_EQUAL(8, cpuMatrixB(2, 4));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseTranspose, RandomSeedFixture)
{
    GPUSparseMatrix<float> sparseMatrix(c_deviceIdZero);
    BOOST_CHECK(sparseMatrix.IsEmpty());
    sparseMatrix.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);

    const GPUSparseMatrix<float> transposeMatrixA = sparseMatrix.Transpose();
    GPUSparseMatrix<float> inplaceTranposeMatrix(sparseMatrix);
    inplaceTranposeMatrix.InplaceTranspose();
    BOOST_CHECK(inplaceTranposeMatrix.IsEqualTo(transposeMatrixA));

    const GPUSparseMatrix<float> tranposeMatrixB = sparseMatrix.Transpose();
    GPUSparseMatrix<float> assignedTransposeMatrix(c_deviceIdZero);
    assignedTransposeMatrix.AssignTransposeOf(tranposeMatrixB);
    BOOST_CHECK(assignedTransposeMatrix.IsEqualTo(sparseMatrix));

    sparseMatrix.InplaceTranspose();
    BOOST_CHECK(!assignedTransposeMatrix.IsEqualTo(sparseMatrix));

    sparseMatrix.InplaceTranspose();
    BOOST_CHECK(assignedTransposeMatrix.IsEqualTo(sparseMatrix));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseNormTests, RandomSeedFixture)
{
    GPUSparseMatrix<float> matrix(c_deviceIdZero);
    BOOST_CHECK(matrix.IsEmpty());
    matrix.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);

    const float frobenius = matrix.FrobeniusNorm();
    BOOST_CHECK(fabsf(16.882f - frobenius) < c_epsilonFloatE4);

    const float ninf = matrix.MatrixNormInf();
    BOOST_CHECK_EQUAL(9, ninf);

    const float n1 = matrix.MatrixNorm1();
    BOOST_CHECK_EQUAL(45, n1);
}

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrixInnerProduct, RandomSeedFixture)
{
    GPUSparseMatrix<float> matrixOp1(c_deviceIdZero);
    BOOST_CHECK(matrixOp1.IsEmpty());
    matrixOp1.SetMatrixFromCSRFormat(c_i, c_j, c_v, c_size, c_rowCount, c_colCount);
    GPUMatrix<float> matrixOp1Dense = matrixOp1.CopyToDenseMatrix();

    const GPUMatrix<float> matrixOp2(GPUMatrix<float>::RandomUniform(c_rowCount, c_colCount, c_deviceIdZero, -3, 4, IncrementCounter()));
    const float x = GPUSparseMatrix<float>::InnerProductOfMatrices(matrixOp1, matrixOp2);
    const float y = GPUMatrix<float>::InnerProductOfMatrices(matrixOp1Dense, matrixOp2);
    BOOST_CHECK(fabsf(x - y) < c_epsilonFloatE5);

    GPUSparseMatrix<float> matrixOp1CSC(matrixOp1Dense, matrixFormatSparseCSC);
    const float x1 = GPUSparseMatrix<float>::InnerProductOfMatrices(matrixOp1CSC, matrixOp2);

    BOOST_CHECK(fabsf(x1 - y) < c_epsilonFloatE5);

    for(bool isColWise : {true, false})
    {
        GPUMatrix<float> matrixInnerProductDense(c_deviceIdZero);
        GPUMatrix<float> matrixInnerProductSparse(c_deviceIdZero);

        GPUMatrix<float>::InnerProduct(matrixOp1Dense, matrixOp2, matrixInnerProductDense, isColWise);
        GPUSparseMatrix<float>::InnerProduct(matrixOp1CSC, matrixOp2, matrixInnerProductSparse, isColWise);

        BOOST_CHECK(matrixInnerProductDense.IsEqualTo(matrixInnerProductSparse, c_epsilonFloatE5));
    }
}

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrixColumnSlice, RandomSeedFixture)
{
    float values[6] = {1, 4, 2, 5, 3, 6};
    GPUMatrix<float> denseA(2, 3, c_deviceIdZero, values, MatrixFlags::matrixFlagNormal);
    GPUSparseMatrix<float> sparseA(c_deviceIdZero, MatrixFormat::matrixFormatSparseCSC);
    sparseA.SetValue(denseA);

    const GPUMatrix<float> sliceColumn1 = denseA.ColumnSlice(0, 2);
    const GPUMatrix<float> denseColumn1 = sparseA.ColumnSlice(0, 2).CopyColumnSliceToDense(0, 2);
    BOOST_CHECK(sliceColumn1.IsEqualTo(denseColumn1, c_epsilonFloatE4));

    const GPUMatrix<float> sliceColumn2 = denseA.ColumnSlice(1, 2);
    const GPUMatrix<float> sparseColumn2 = sparseA.ColumnSlice(1, 2).CopyColumnSliceToDense(0, 2);
    BOOST_CHECK(sliceColumn2.IsEqualTo(sparseColumn2, c_epsilonFloatE4));

    BOOST_CHECK(!sliceColumn1.IsEqualTo(sparseColumn2, c_epsilonFloatE4));
    BOOST_CHECK(!sliceColumn2.IsEqualTo(denseColumn1, c_epsilonFloatE4));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrixCopyColumnSliceToDense, RandomSeedFixture)
{
    float values[6] = {1, 4, 2, 5, 3, 6};
    GPUMatrix<float> denseA(2, 3, c_deviceIdZero, values, MatrixFlags::matrixFlagNormal);
    GPUSparseMatrix<float> sparseA(c_deviceIdZero, MatrixFormat::matrixFormatSparseCSC);
    sparseA.SetValue(denseA);

    const GPUMatrix<float> sliceColumn1 = denseA.ColumnSlice(0, 2);
    const GPUMatrix<float> denseColumn1 = sparseA.CopyColumnSliceToDense(0, 2);
    BOOST_CHECK(sliceColumn1.IsEqualTo(denseColumn1, c_epsilonFloatE4));

    const GPUMatrix<float> sliceColumn2 = denseA.ColumnSlice(1, 2);
    const GPUMatrix<float> sparseColumn2 = sparseA.CopyColumnSliceToDense(1, 2);
    BOOST_CHECK(sliceColumn2.IsEqualTo(sparseColumn2, c_epsilonFloatE4));

    BOOST_CHECK(!sliceColumn1.IsEqualTo(sparseColumn2, c_epsilonFloatE4));
    BOOST_CHECK(!sliceColumn2.IsEqualTo(denseColumn1, c_epsilonFloatE4));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrix1DConvolutionFixedInit, RandomSeedFixture)
{
    const bool zeroPadding = false;
    const int horizontalSubsample = 1;
    const int verticalSubsample = 1;
    const int inputWidth = 6;
    const int inputHeight = 1;
    const int inputChannels = 1;
    const int kernelWidth = 2;
    const int kernelHeight = inputHeight;
    const int outputWidth = zeroPadding ? inputWidth : (inputWidth >= kernelWidth ? 1 + (inputWidth - kernelWidth) / horizontalSubsample : 0);
    const int outputHeight = inputHeight;
    const int outputChannels = 3;
    const int batchSize = 1;
    const int m = outputChannels;
    const int k = kernelWidth * inputChannels;
    const int l = inputWidth * inputChannels;
    const int n = batchSize;
    const float alpha = 1.0f;
    const float beta = 0.0f;

    float weights[9] = {1, 1, 1, 1, 1, 1};
    float values[9] = {1, 2, 3, 4, 5, 6};
    GPUMatrix<float> denseMatrixA(m, k, 0, weights, matrixFlagNormal);
    ;
    GPUMatrix<float> denseMatrixB(l, n, 0, values, matrixFlagNormal);
    ;
    GPUSparseMatrix<float> sparseMatrixB(c_deviceIdZero, matrixFormatSparseCSC);
    GPUMatrix<float> denseMatrixTemp(1, 1, c_deviceIdZero);  // this should get resized automatically
    GPUMatrix<float> resultMatrixBase(1, 1, c_deviceIdZero); // this should get resized automatically
    GPUMatrix<float> resultMatrixExp(1, 1, c_deviceIdZero);  // this should get resized automatically

    sparseMatrixB.SetValue(denseMatrixB);

    denseMatrixTemp.AssignPackedConvolutionInput(denseMatrixB,
                                                 inputWidth, inputHeight, inputChannels,
                                                 outputWidth, outputHeight, outputChannels,
                                                 kernelWidth, kernelHeight, horizontalSubsample, verticalSubsample, zeroPadding);

    GPUMatrix<float>::MultiplyAndWeightedAdd(alpha, denseMatrixA, false, denseMatrixTemp, false, beta, resultMatrixBase);
    resultMatrixBase.Reshape(outputWidth * outputChannels, batchSize);

    GPUSparseMatrix<float>::ConvolveAndWeightedAdd(alpha, denseMatrixA, false, sparseMatrixB, false, beta, resultMatrixExp, inputChannels, horizontalSubsample, zeroPadding, true);

    BOOST_CHECK(resultMatrixExp.IsEqualTo(resultMatrixBase, c_epsilonFloatE5));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrix1DConvolutionRandomInit, RandomSeedFixture)
{
    for (auto transposeA : {false, true})
    {
        for (auto transposeB : {false, true})
        {
            for (auto zeroPadding : {false}) // TODO: There's a discrepancy w.r.t. padding - needs to be fixed!!!
            {
                for (auto inputChannels : {1, 10})
                {
                    for (auto horizontalSubsample : {1, 2, 3})
                    {
                        const int verticalSubsample = 1;
                        const int inputWidth = 10;
                        const int inputHeight = 1;
                        const int kernelWidth = 3;
                        const int kernelHeight = inputHeight;
                        const int outputWidth = zeroPadding ? inputWidth : (inputWidth >= kernelWidth ? 1 + (inputWidth - kernelWidth) / horizontalSubsample : 0);
                        const int outputHeight = inputHeight;
                        const int outputChannels = 100;
                        const int batchSize = 10;
                        const int m = outputChannels;
                        const int k = kernelWidth * inputChannels;
                        const int l = inputWidth * inputChannels;
                        const int n = batchSize;
                        const float alpha = 0.53f;
                        const float beta = transposeB ? 1.0f : 0.0f;
                        GPUMatrix<float> denseMatrixA = GPUMatrix<float>::RandomUniform(m, k, c_deviceIdZero, -1, 1, IncrementCounter());
                        GPUMatrix<float> denseMatrixB = GPUMatrix<float>::RandomUniform(l, n, c_deviceIdZero, -5, 5, IncrementCounter());
                        GPUMatrix<float> denseMatrixAT(k, m, c_deviceIdZero);
                        GPUMatrix<float> denseMatrixBT(l, n, c_deviceIdZero);
                        GPUSparseMatrix<float> sparseMatrixB(c_deviceIdZero, matrixFormatSparseCSC);
                        GPUMatrix<float> denseMatrixTemp(1, 1, c_deviceIdZero); // this should get resized automatically
                        GPUMatrix<float> resultMatrixBase(outputChannels, batchSize * outputWidth * outputHeight, c_deviceIdZero);
                        GPUMatrix<float> resultMatrixExp(outputWidth * outputHeight * outputChannels, batchSize, c_deviceIdZero);

                        if (transposeA)
                        {
                            denseMatrixAT.AssignTransposeOf(denseMatrixA);
                        }

                        if (transposeB)
                        {
                            denseMatrixBT.AssignTransposeOf(denseMatrixB);
                            sparseMatrixB.SetValue(denseMatrixBT);
                        }
                        else
                        {
                            sparseMatrixB.SetValue(denseMatrixB);
                        }

                        denseMatrixTemp.AssignPackedConvolutionInput(denseMatrixB,
                                                                     inputWidth, inputHeight, inputChannels,
                                                                     outputWidth, outputHeight, outputChannels,
                                                                     kernelWidth, kernelHeight, horizontalSubsample, verticalSubsample, zeroPadding);

                        GPUMatrix<float>::MultiplyAndWeightedAdd(alpha, denseMatrixA, false, denseMatrixTemp, false, beta, resultMatrixBase);
                        resultMatrixBase.Reshape(outputWidth * outputChannels, batchSize);

                        GPUSparseMatrix<float>::ConvolveAndWeightedAdd(alpha, (transposeA ? denseMatrixAT : denseMatrixA), transposeA, sparseMatrixB, transposeB, beta, resultMatrixExp, inputChannels, horizontalSubsample, zeroPadding, true);

                        BOOST_CHECK(resultMatrixExp.IsEqualTo(resultMatrixBase, c_epsilonFloatE2));
                    }
                }
            }
        }
    }
}

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrix1DConvolutionBackprop, RandomSeedFixture)
{
    const int inChannels = 50;
    const int inWidth = 10;
    const int inHeight = 1;
    const int batchSize = 20;
    const int kernelWidth = 3;
    const int kernelHeight = inHeight;
    const int horizontalSubsample = 1;
    const int verticalSubsample = 1;
    const bool zeroPadding = false;
    const int outChannels = 3;
    const int outWidth = zeroPadding ? (inWidth / horizontalSubsample) : (inWidth >= kernelWidth ? 1 + (inWidth - kernelWidth) / horizontalSubsample : 0);
    const int outHeight = inHeight;
    const float randomInitLowerBound = -1.0f;
    const float randomInitUpperBound = 1.0f;
    Matrix<float> outputGradientSubBatch = Matrix<float>::RandomUniform(outChannels, batchSize * outWidth, c_deviceIdZero, randomInitLowerBound, randomInitUpperBound, IncrementCounter());
    Matrix<float> inputSubBatch = Matrix<float>::RandomUniform(inChannels * inWidth, batchSize, c_deviceIdZero, randomInitLowerBound, randomInitUpperBound, IncrementCounter());
    Matrix<float> tempMatrix(1, 1, c_deviceIdZero);
    Matrix<float> inputGradientValues1 = Matrix<float>::Zeros(outChannels, inChannels * kernelWidth, c_deviceIdZero);
    Matrix<float> inputGradientValues2 = Matrix<float>::Zeros(outChannels, inChannels * kernelWidth, c_deviceIdZero);

    // Baseline
    tempMatrix.Resize(kernelWidth * kernelHeight * inChannels, outWidth * outHeight * batchSize);
    tempMatrix.AssignPackedConvolutionInput(inputSubBatch,
                                            inWidth, inHeight, inChannels, outWidth, outHeight, outChannels,
                                            kernelWidth, kernelHeight, horizontalSubsample, verticalSubsample, zeroPadding);
    Matrix<float>::MultiplyAndAdd(outputGradientSubBatch, false, tempMatrix, true, inputGradientValues1);

    // Optimized code path for 1-D convolution on GPU + Sparse
    Matrix<float> inputSubBatchSparseReordered(batchSize * inWidth, inChannels, c_deviceIdZero, MatrixType::SPARSE, MatrixFormat::matrixFormatSparseCSC);
    inputSubBatch.SwitchToMatrixType(MatrixType::SPARSE, MatrixFormat::matrixFormatSparseCSC, true);
    inputSubBatch.Reshape(inChannels, batchSize * inWidth);
    inputSubBatch.InplaceTranspose();
    Matrix<float>::TensorShuffleScaleAndAdd(0.0f, inputSubBatch, 1, inWidth, 1, batchSize, inChannels, 1.0f, inputSubBatchSparseReordered, inputSubBatchSparseReordered);

    Matrix<float> outputGradientSubBatchReshaped = Matrix<float>::Zeros(outChannels, batchSize * outWidth, c_deviceIdZero);
    Matrix<float> outputGradientSubBatchReordered = Matrix<float>::Zeros(batchSize * outWidth, outChannels, c_deviceIdZero);
    outputGradientSubBatchReshaped = outputGradientSubBatch.Transpose();
    Matrix<float>::TensorShuffleScaleAndAdd(0.0f, outputGradientSubBatchReshaped, 1, outWidth, 1, batchSize, outChannels, 1.0f, outputGradientSubBatchReordered, outputGradientSubBatchReordered);

    inputGradientValues2.Reshape(outChannels * kernelWidth, inChannels);
    Matrix<float>::ConvolveAndWeightedAdd(1, outputGradientSubBatchReordered, true, inputSubBatchSparseReordered, false, 1, inputGradientValues2, batchSize, horizontalSubsample, zeroPadding, false);
    inputGradientValues2.Reshape(outChannels, inChannels * kernelWidth);

    BOOST_CHECK(inputGradientValues2.IsEqualTo(inputGradientValues1, c_epsilonFloatE2));
}

BOOST_FIXTURE_TEST_CASE(GPUSparseMatrixReshape, RandomSeedFixture)
{
    const int oldRowCount = 10;
    const int oldColCount = 3;
    const int newRowCount = 5;
    const int newColCount = 6;
    GPUMatrix<float> denseMatrixA = GPUMatrix<float>::RandomUniform(oldRowCount, oldColCount, c_deviceIdZero, -1, 1, IncrementCounter());
    GPUMatrix<float> denseMatrixB(denseMatrixA);
    GPUMatrix<float> denseMatrixC(newRowCount, newColCount, c_deviceIdZero);
    GPUSparseMatrix<float> sparseMatrix(c_deviceIdZero, matrixFormatSparseCSC);

    denseMatrixB.Reshape(newRowCount, newColCount);

    sparseMatrix.SetValue(denseMatrixA);
    sparseMatrix.Reshape(newRowCount, newColCount);
    sparseMatrix.CopyToDenseMatrix(denseMatrixC);

    BOOST_CHECK(denseMatrixC.IsEqualTo(denseMatrixB, c_epsilonFloatE5));
    BOOST_CHECK(!denseMatrixC.IsEqualTo(denseMatrixA, c_epsilonFloatE5));
    BOOST_CHECK(sparseMatrix.IsValid());
}

BOOST_FIXTURE_TEST_CASE(GPUSparseTensorShuffleScaleAndAdd, RandomSeedFixture)
{
    size_t D = 13, S = 11, M = 7, K = 15, T = 8;
    GPUMatrix<float> denseMatrixA = GPUMatrix<float>::RandomUniform(D * S * M * K, T, c_deviceIdZero, -1, 1, IncrementCounter());
    GPUMatrix<float> denseMatrixB(D * S * M * K, T, c_deviceIdZero);
    GPUMatrix<float> denseMatrixC(D * S * M * K, T, c_deviceIdZero);
    GPUSparseMatrix<float> sparseMatrixA(c_deviceIdZero, matrixFormatSparseCSC);
    GPUSparseMatrix<float> sparseMatrixB(c_deviceIdZero, matrixFormatSparseCSC);
    sparseMatrixA.SetValue(denseMatrixA);

    GPUMatrix<float>::TensorShuffleScaleAndAdd(0, denseMatrixA, D, S, M, K, T, 1, denseMatrixB, denseMatrixB);
    GPUSparseMatrix<float>::TensorShuffleScaleAndAdd(0, sparseMatrixA, D, S, M, K, T, 1, sparseMatrixB, sparseMatrixB);
    sparseMatrixB.CopyToDenseMatrix(denseMatrixC);

    BOOST_CHECK(denseMatrixC.IsEqualTo(denseMatrixB, c_epsilonFloatE5));
    BOOST_CHECK(sparseMatrixB.IsValid());
}


BOOST_FIXTURE_TEST_CASE(GPUSparseMatrixOneHot, RandomSeedFixture)
{
    GPUSparseMatrix<double> result(c_deviceIdZero, matrixFormatSparseCSC);
    const size_t num_class = 6;

    double data[4] = { 1,2,3,4 };
    GPUMatrix<double> m0(2, 2, c_deviceIdZero);
    m0.SetValue(2, 2, c_deviceIdZero, data, matrixFormatRowMajor);

    double exp_data[24];
    memset(&exp_data[0], 0, sizeof(double) * 24);
    exp_data[1] = exp_data[9] = exp_data[14] = exp_data[22] = 1;
    GPUMatrix<double> exp(6, 4, c_deviceIdZero);
    exp.SetValue(6, 4, c_deviceIdZero, exp_data, matrixFormatColMajor);

    GPUSparseMatrix<double> exp_sparse(c_deviceIdZero, matrixFormatSparseCSC);
    exp_sparse.SetValue(exp);

    vector<size_t> shape(3);
    shape[0] = num_class; shape[1] = 2; shape[2] = 2;
    result.AssignOneHot(m0, shape, 0);

    BOOST_CHECK(result.IsValid());
    BOOST_CHECK(result.IsEqualTo(exp_sparse, 1e-6));

    vector<size_t> shape2(3);
    shape2[0] = 2; shape2[1] = num_class; shape2[2] = 2;

    GPUMatrix<double> exp2(12, 2, c_deviceIdZero);
    exp2.AssignOneHot(m0, shape2, 1);

    GPUSparseMatrix<double> result2(c_deviceIdZero, matrixFormatSparseCSC);
    result2.AssignOneHot(m0, shape2, 1);

    BOOST_CHECK(result2.IsValid());
    BOOST_CHECK(result2.IsEqualTo(exp2, 1e-6));

    double dirty_data[4] = { 1,-1,7,4 };
    GPUMatrix<double> dirty_m(2, 2, c_deviceIdZero);
    dirty_m.SetValue(2, 2, c_deviceIdZero, dirty_data, matrixFormatRowMajor);

    double dirty_exp_data[24];
    memset(&dirty_exp_data[0], 0, sizeof(double) * 24);
    dirty_exp_data[1] = dirty_exp_data[22] = 1;
    GPUMatrix<double> dirty_exp(6, 4, c_deviceIdZero);
    dirty_exp.SetValue(6, 4, c_deviceIdZero, dirty_exp_data, matrixFormatColMajor);

    GPUSparseMatrix<double> dirty_result(c_deviceIdZero, matrixFormatSparseCSC);
    dirty_result.AssignOneHot(dirty_m, shape, 0);

    GPUMatrix<double> dirty_dense = dirty_result.CopyToDenseMatrix();

    BOOST_CHECK(dirty_result.IsValid());
    BOOST_CHECK(dirty_dense.IsEqualTo(dirty_exp, 1e-6));

    double data2[2] = { 3,0 };
    GPUMatrix<double> m3(1, 2, c_deviceIdZero);
    m3.SetValue(1, 2, c_deviceIdZero, data2, matrixFormatRowMajor);

    double exp_data2[12];
    memset(&exp_data2[0], 0, sizeof(double) * 12);
    exp_data2[3] = exp_data2[6] = 1;
    GPUMatrix<double> exp_m(6, 2, c_deviceIdZero);
    exp_m.SetValue(6, 2, c_deviceIdZero, exp_data2, matrixFormatColMajor);

    GPUSparseMatrix<double> exp_m_sparse(c_deviceIdZero, matrixFormatSparseCSC);
    exp_m_sparse.SetValue(exp_m);

    vector<size_t> shape3(3);
    shape3[0] = num_class; shape3[1] = 2; shape3[2] = 1;

    GPUSparseMatrix<double> result_m(c_deviceIdZero, matrixFormatSparseCSC);
    result_m.AssignOneHot(m3, shape3, 0);

    BOOST_CHECK(result_m.IsValid());
    BOOST_CHECK(result_m.IsEqualTo(exp_m_sparse, 1e-6));
}

BOOST_AUTO_TEST_SUITE_END()
}
} } }
back to top