swh:1:snp:f50ab94432af916b5fb8b4ad831e8dddded77084
Raw File
Tip revision: 8c0b71b69b0150a8045636a21cb27d821263edf0 authored by Yang Chen on 26 July 2018, 16:58:57 UTC
Check IsOutput() before invoking Owner()->IsBlock()
Tip revision: 8c0b71b
NDArrayViewTests.cpp
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//

#include "stdafx.h"
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include "CNTKLibrary.h"
#include "Common.h"
#include <functional>
#include <array>

using namespace CNTK;

namespace CNTK { namespace Test {

template <typename ElementType>
void CreateNDArrayViewOverStdArray()
{
    std::array<ElementType, 1> arrayData = { 3 };
    auto arrayDataView = MakeSharedObject<NDArrayView>(NDShape({}), arrayData);
    BOOST_TEST(arrayDataView->template DataBuffer<ElementType>() == arrayData.data(),
        "The DataBuffer of the NDArrayView does not match the original buffer it was created over");
}

template <typename ElementType>
NDArrayViewPtr GetClonedView(const NDShape viewShape, const NDArrayViewPtr dataView, const NDArrayViewPtr cpuDataView, const std::vector<ElementType> & data, const DeviceDescriptor& device)
{
    auto clonedView = dataView->DeepClone(false);
    ElementType* first = nullptr;
    const ElementType* second = cpuDataView->template DataBuffer<ElementType>();
    NDArrayViewPtr temp1CpuDataView, temp2CpuDataView;
    if ((device.Type() == DeviceKind::CPU))
    {

        BOOST_TEST(dataView->DataBuffer<ElementType>() == data.data(), "The DataBuffer of the NDArrayView does not match the original buffer it was created over");

        first = clonedView->WritableDataBuffer<ElementType>();
    }
    else
    {
        temp1CpuDataView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), viewShape, DeviceDescriptor::CPUDevice());
        temp1CpuDataView->CopyFrom(*clonedView);

        first = temp1CpuDataView->WritableDataBuffer<ElementType>();
    }

    for (size_t i = 0; i < viewShape.TotalSize(); ++i)
    {
        BOOST_TEST(first[i] == second[i], "The contents of the clone do not match expected");
    }

    first[0] += 1;
    if ((device.Type() != DeviceKind::CPU))
        clonedView->CopyFrom(*temp1CpuDataView);

    if ((device.Type() == DeviceKind::CPU))
    {
        first = clonedView->WritableDataBuffer<ElementType>();
        second = dataView->DataBuffer<ElementType>();
    }
    else
    {
        temp1CpuDataView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), viewShape, DeviceDescriptor::CPUDevice());
        temp1CpuDataView->CopyFrom(*clonedView);
        first = temp1CpuDataView->WritableDataBuffer<ElementType>();

        temp2CpuDataView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), viewShape, DeviceDescriptor::CPUDevice());
        temp2CpuDataView->CopyFrom(*dataView);
        second = temp2CpuDataView->DataBuffer<ElementType>();
    }

    BOOST_TEST(first[0] == (second[0] + 1), "The clonedView's contents do not match expected");

    return clonedView;
}

template <typename ElementType>
NDArrayViewPtr GetAliasView(const NDArrayViewPtr clonedView, const NDArrayViewPtr dataView)
{
    auto aliasView = clonedView->Alias(true);
    const ElementType* aliasViewBuffer = aliasView->DataBuffer<ElementType>();
    const ElementType* clonedDataBuffer = clonedView->DataBuffer<ElementType>();

    auto errorMsg = "The buffers underlying the alias view and the view it is an alias of are different!";

    BOOST_TEST(aliasViewBuffer == clonedDataBuffer, errorMsg);

    clonedView->CopyFrom(*dataView);

    BOOST_TEST(aliasViewBuffer == clonedDataBuffer, errorMsg);

    return aliasView;
}

template <typename ElementType>
void TestReadonliness(const NDArrayViewPtr aliasView, const NDArrayViewPtr dataView){
    auto errorMsg = "Was incorrectly able to get a writable buffer pointer from a readonly view";
    // Should not be able to get the WritableDataBuffer for a read-only view
    VerifyException([&aliasView]() {
        ElementType* aliasViewBuffer = aliasView->WritableDataBuffer<ElementType>();
        aliasViewBuffer;
    }, errorMsg);

    // Should not be able to copy into a read-only view
    VerifyException([&aliasView, &dataView]() {
        aliasView->CopyFrom(*dataView);
    }, errorMsg);
}

template <typename ElementType>
void TestNDArrayView(size_t numAxes, const DeviceDescriptor& device)
{
    size_t maxDimSize = 15;
    NDShape viewShape(numAxes);
    for (size_t i = 0; i < numAxes; ++i)
        viewShape[i] = (rand() % maxDimSize) + 1;

    CreateNDArrayViewOverStdArray<ElementType>();

    std::vector<ElementType> data(viewShape.TotalSize());
    ElementType scale = 19.0;
    ElementType offset = -4.0;
    for (size_t i = 0; i < viewShape.TotalSize(); ++i)
        data[i] = offset + ((((ElementType)rand()) / RAND_MAX) * scale);

    auto cpuDataView = MakeSharedObject<NDArrayView>(viewShape, data);
    BOOST_TEST((cpuDataView->template DataBuffer<ElementType>() == data.data()),
        "The DataBuffer of the NDArrayView does not match the original buffer it was created over");

    NDArrayViewPtr dataView;
    if ((device.Type() == DeviceKind::CPU))
        dataView = cpuDataView;
    else
    {
        dataView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), viewShape, device);
        dataView->CopyFrom(*cpuDataView);
    }

    BOOST_TEST((dataView->Device() == device), "Device of NDArrayView does not match 'device' it was created on");

    auto clonedView = GetClonedView<ElementType>(viewShape, dataView, cpuDataView, data, device);

    auto aliasView = GetAliasView<ElementType>(clonedView, dataView);

    TestReadonliness<ElementType>(aliasView, dataView);
}

template <typename ElementType>
void TestSparseCSCArrayView(size_t numAxes, const DeviceDescriptor& device)
{
    size_t maxDimSize = 15;
    NDShape viewShape(numAxes);
    for (size_t i = 0; i < numAxes; ++i)
        viewShape[i] = (rand() % maxDimSize) + 1;

    size_t numMatrixCols = (numAxes > 0) ? viewShape.SubShape(1).TotalSize() : 1;
    size_t numMatrixRows = (numAxes > 0) ? viewShape[0] : 1;

    std::vector<ElementType> referenceDenseData;
    std::vector<SparseIndexType> colsStarts;
    std::vector<SparseIndexType> rowIndices;
    std::vector<ElementType> nonZeroValues;
    size_t numNonZeroValues;
    std::tie(referenceDenseData, colsStarts, rowIndices, nonZeroValues, numNonZeroValues) = GenerateSequenceInCSC<ElementType>(numMatrixRows, numMatrixCols);

    auto cpuView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), viewShape, colsStarts.data(), rowIndices.data(), nonZeroValues.data(), numNonZeroValues, DeviceDescriptor::CPUDevice(), true);
    NDArrayViewPtr sparseView;
    if (device.Type() == DeviceKind::CPU)
    {
        sparseView = cpuView;
    }
    else
    {
        BOOST_TEST((device.Type() == DeviceKind::GPU), "Device type must be CPU or GPU.");
        sparseView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), StorageFormat::SparseCSC, viewShape, device);
        sparseView->CopyFrom(*cpuView);
    }

    // Copy it out to a dense matrix on the CPU and verify the data
    std::vector<ElementType> denseBuffer(viewShape.TotalSize());
    NDArrayView denseCPUView(viewShape, denseBuffer.data(), denseBuffer.size(), DeviceDescriptor::CPUDevice());
    denseCPUView.CopyFrom(*sparseView);
    BOOST_TEST(denseBuffer == referenceDenseData, "The dense buffer copied from the sparse NDArrayView does not match the expected one.");

    // Create an empty sparse CSC NDArrayView on the device and the copy data from a dense NDArrayView on CPU.
    NDArrayView emptySparseCSCArrayView(AsDataType<ElementType>(), StorageFormat::SparseCSC, viewShape, device);
    emptySparseCSCArrayView.CopyFrom(denseCPUView);
    // Then copy the data from the sparse CSC one into another CPU dense one for checking results.
    std::vector<ElementType> anotherDenseBuffer(viewShape.TotalSize());
    NDArrayView anotherDenseCPUView(viewShape, anotherDenseBuffer.data(), anotherDenseBuffer.size(), DeviceDescriptor::CPUDevice());
    anotherDenseCPUView.CopyFrom(emptySparseCSCArrayView);
    BOOST_TEST(anotherDenseBuffer == referenceDenseData, "The contents of the dense vector that the sparse NDArrayView is copied into do not match the expected values");
}

template <typename ElementType>
void TestSparseCSCDataBuffers(size_t numAxes, const DeviceDescriptor& device)
{
    size_t maxDimSize = 15;
    NDShape viewShape(numAxes);
    for (size_t i = 0; i < numAxes; ++i)
        viewShape[i] = (rand() % maxDimSize) + 1;

    size_t numMatrixCols = (numAxes > 0) ? viewShape.SubShape(1).TotalSize() : 1;
    size_t numMatrixRows = (numAxes > 0) ? viewShape[0] : 1;

    std::vector<ElementType> referenceDenseData;
    std::vector<SparseIndexType> expectedColsStarts;
    std::vector<SparseIndexType> expectedRowIndices;
    std::vector<ElementType> expectedNonZeroValues;
    size_t expectedNumNonZeroValues;
    std::tie(referenceDenseData, expectedColsStarts, expectedRowIndices, expectedNonZeroValues, expectedNumNonZeroValues) = GenerateSequenceInCSC<ElementType>(numMatrixRows, numMatrixCols);

    auto cpuView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), viewShape, expectedColsStarts.data(), expectedRowIndices.data(), expectedNonZeroValues.data(), expectedNumNonZeroValues, DeviceDescriptor::CPUDevice(), true);
    NDArrayViewPtr sparseView;
    if (device.Type() == DeviceKind::CPU)
    {
        sparseView = cpuView;
    }
    else
    {
        BOOST_TEST((device.Type() == DeviceKind::GPU), "Device type must be CPU or GPU.");
        sparseView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), StorageFormat::SparseCSC, viewShape, device);
        sparseView->CopyFrom(*cpuView);
    }

    const ElementType *outputNonZeroData;
    const SparseIndexType *outputColsStartsData;
    const SparseIndexType *outputRowIndicesData;
    size_t outputNumNonZeroData;
    // Get sparse matrix related data buffers for test.
    std::tie(outputNonZeroData, outputColsStartsData, outputRowIndicesData, outputNumNonZeroData) = sparseView->SparseCSCDataBuffers<ElementType>();

    BOOST_TEST(expectedNumNonZeroValues == outputNumNonZeroData, "The number of non-zero values does not match");
    BOOST_TEST(expectedNonZeroValues.size() == outputNumNonZeroData, "The number of non-zero values returned does not match that in the non-zero value buffers");
    if (device.Type() == DeviceKind::CPU)
    {
        BOOST_TEST(memcmp(expectedNonZeroValues.data(), outputNonZeroData, expectedNonZeroValues.size() * sizeof(ElementType)) == 0, "The non-zero value buffer does not match.");
        BOOST_TEST(memcmp(expectedColsStarts.data(), outputColsStartsData, expectedColsStarts.size() * sizeof(ElementType)) == 0, "The ColsStarts buffer does not match");
        BOOST_TEST(memcmp(expectedRowIndices.data(), outputRowIndicesData, expectedRowIndices.size() * sizeof(ElementType)) == 0, "The RowIndices buffer does not match");
    }
    else
    {
        BOOST_TEST((device.Type() == DeviceKind::GPU), "The device type of the NDArrayView is neither CPU nor GPU.");

        // The data buffers returned by SparseCSCDataBuffers() are on GPU, for testing we first create an NDArrayView using the returned data buffers, 
        // copy the created one to another one on CPU using dense format, and then compare the data with the expected one.
        // Another limitation here is NDArrayView::DeepClone() does not support the GPUSparseMatrix->CPUSparseMatrix, since Matrix::AssignValuesOf() has not implemented this feature
        // yet. This prevents from using AreEqual(NDArrayViewPtr, NDArrayViewPtr).
        NDArrayView viewFromOutput(AsDataType<ElementType>(), viewShape, outputColsStartsData, outputRowIndicesData, outputNonZeroData, outputNumNonZeroData, device, true);

        // Copy it out to a dense matrix on the CPU and verify the data
        std::vector<ElementType> denseBuffer(viewShape.TotalSize());
        NDArrayView denseCPUView(viewShape, denseBuffer.data(), denseBuffer.size(), DeviceDescriptor::CPUDevice());
        denseCPUView.CopyFrom(viewFromOutput);
        BOOST_TEST(denseBuffer == referenceDenseData, "The dense buffer copied from the sparse NDArrayView does not match the expected one.");
    }
}

template <typename ElementType>
void TestDataBuffer(size_t numAxes, const DeviceDescriptor& device)
{
    size_t maxDimSize = 7;
    NDShape viewShape(numAxes);
    for (size_t i = 0; i < numAxes; ++i)
        viewShape[i] = (rand() % maxDimSize) + 1;

    std::vector<ElementType> data(viewShape.TotalSize());
    ElementType scale = 12.0;
    ElementType offset = -3.0;
    for (size_t i = 0; i < viewShape.TotalSize(); ++i)
        data[i] = offset + ((((ElementType)rand()) / RAND_MAX) * scale);

    auto cpuDataView = MakeSharedObject<NDArrayView>(viewShape, data.data(), data.size(), DeviceDescriptor::CPUDevice());
    NDArrayViewPtr dataView;
    if (device.Type() == DeviceKind::CPU)
    {
        dataView = cpuDataView;
    }
    else
    {
        BOOST_TEST((device.Type() == DeviceKind::GPU), "Device type must be CPU or GPU.");
        dataView = MakeSharedObject<NDArrayView>(AsDataType<ElementType>(), viewShape, device);
        dataView->CopyFrom(*cpuDataView);
    }

    // Get DataBuffer for test.
    const ElementType* dataBuffer = dataView->template DataBuffer<ElementType>();

    // Verify DataBuffer.
    if ((device.Type() == DeviceKind::CPU))
    {
        BOOST_TEST(memcmp(dataBuffer, data.data(), data.size() * sizeof(ElementType)) == 0, "DataBuffer of NDArrayView on CPU does not match the source data.");
    }
    else
    {
        // We cannot directly compare dataBuffer on GPU with the buffer on CPU. Instead, we construct an NDArrayView from the dataBuffer
        // and compare the views.
        auto dataViewFromOutput = MakeSharedObject<NDArrayView>(viewShape, dataBuffer, viewShape.TotalSize(), device);
        BOOST_TEST(AreEqual(dataViewFromOutput, dataView), "The NDArrayView created from DataBuffer on GPU does not match the source one.");

        // Additional test: copy it out to a dense matrix on the CPU and verify the data
        std::vector<ElementType> anotherDenseBuffer(viewShape.TotalSize());
        NDArrayView denseCPUView(viewShape, anotherDenseBuffer.data(), anotherDenseBuffer.size(), DeviceDescriptor::CPUDevice());
        denseCPUView.CopyFrom(*dataViewFromOutput);
        BOOST_TEST(anotherDenseBuffer == data, "The data in the copied NDArrayView does not match the source data.");
    }
}

struct NDArrayViewFixture
{
    NDArrayViewFixture()
    {
        srand(1);
    }
};

BOOST_FIXTURE_TEST_SUITE(NDArrayViewSuite, NDArrayViewFixture)

BOOST_AUTO_TEST_CASE(CheckFloatNDArrayViewInCpu)
{
    if (ShouldRunOnCpu())
        TestNDArrayView<float>(GenerateNumOfAxes(10), DeviceDescriptor::CPUDevice());
}

BOOST_AUTO_TEST_CASE(CheckNDArrayViewInGpu)
{
    if (ShouldRunOnGpu())
    {
        TestNDArrayView<float>(0, DeviceDescriptor::GPUDevice(0));
        TestNDArrayView<double>(GenerateNumOfAxes(6), DeviceDescriptor::GPUDevice(0));
    }
}

BOOST_AUTO_TEST_CASE(CheckCscArrayViewInGpu)
{
    if (ShouldRunOnGpu())
    {
        TestSparseCSCArrayView<float>(1, DeviceDescriptor::GPUDevice(0));
        TestSparseCSCArrayView<double>(GenerateNumOfAxes(9), DeviceDescriptor::GPUDevice(0));
    }
}

BOOST_AUTO_TEST_CASE(CheckCscArrayViewInCpu)
{
    if (ShouldRunOnCpu())
    {
        TestSparseCSCArrayView<float>(2, DeviceDescriptor::CPUDevice());
        TestSparseCSCArrayView<float>(GenerateNumOfAxes(15), DeviceDescriptor::CPUDevice());
    }
}

BOOST_AUTO_TEST_CASE(CheckSparseCscDataBuffersInGpu)
{
    if (ShouldRunOnGpu())
    {
        TestSparseCSCDataBuffers<float>(1, DeviceDescriptor::GPUDevice(0));
        TestSparseCSCDataBuffers<double>(GenerateNumOfAxes(7), DeviceDescriptor::GPUDevice(0));
    }
}

BOOST_AUTO_TEST_CASE(CheckSparseCscDataBuffersInCpu)
{
    if (ShouldRunOnCpu())
    {
        TestSparseCSCDataBuffers<float>(2, DeviceDescriptor::CPUDevice());
        TestSparseCSCDataBuffers<float>(GenerateNumOfAxes(6), DeviceDescriptor::CPUDevice());
    }
}

BOOST_AUTO_TEST_CASE(CheckDataBufferInCpu)
{
    if (ShouldRunOnCpu())
        TestDataBuffer<float>(GenerateNumOfAxes(5), DeviceDescriptor::CPUDevice());
}

BOOST_AUTO_TEST_CASE(CheckDataBufferInGpu)
{
    if (ShouldRunOnGpu())
    {
        TestDataBuffer<float>(1, DeviceDescriptor::GPUDevice(0));
        TestDataBuffer<double>(GenerateNumOfAxes(7), DeviceDescriptor::GPUDevice(0));
    }
}

BOOST_AUTO_TEST_SUITE_END()

}}
back to top