blob: 8a53298f48117cc1c1899e0847ee465cac400774 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef TEST_NDARRAY_UTILS_H_
#define TEST_NDARRAY_UTILS_H_
#include <unistd.h>
#include <dmlc/logging.h>
#include <gtest/gtest.h>
#include <mxnet/engine.h>
#include <mxnet/ndarray.h>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <string>
#include <map>
#include "test_util.h"
#include "test_op.h"
namespace mxnet {
namespace test {
#define ROW_SPARSE_IDX_TYPE mshadow::kInt64
using namespace mxnet;
#define TEST_DTYPE float
#define TEST_ITYPE int32_t
inline void CheckDataRegion(const TBlob &src, const TBlob &dst) {
auto size = src.shape_.Size() * mshadow::mshadow_sizeof(src.type_flag_);
auto equals = memcmp(src.dptr_, dst.dptr_, size);
EXPECT_EQ(equals, 0);
}
inline unsigned gen_rand_seed() {
time_t timer;
::time(&timer);
return static_cast<unsigned>(timer);
}
inline float RandFloat() {
static unsigned seed = gen_rand_seed();
double v = rand_r(&seed) * 1.0 / RAND_MAX;
return static_cast<float>(v);
}
// Get an NDArray with provided indices, prepared for a RowSparse NDArray.
inline NDArray RspIdxND(const mxnet::TShape shape, const Context ctx,
const std::vector<TEST_ITYPE> &values) {
NDArray nd(shape, ctx, false, ROW_SPARSE_IDX_TYPE);
size_t num_val = values.size();
MSHADOW_TYPE_SWITCH(nd.dtype(), DType, {
auto tensor = nd.data().FlatTo1D<cpu, DType>();
for (size_t i = 0; i < num_val; i++) {
tensor[i] = values[i];
}
});
return nd;
}
// Get a dense NDArray with provided values.
inline NDArray DnsND(const mxnet::TShape shape, const Context ctx, std::vector<TEST_DTYPE> vs) {
NDArray nd(shape, ctx, false);
size_t num_val = shape.Size();
// generate random values
while (vs.size() < num_val) {
auto v = RandFloat();
vs.emplace_back(v);
}
CHECK_EQ(vs.size(), nd.shape().Size());
MSHADOW_TYPE_SWITCH(nd.dtype(), DType, {
auto tensor = nd.data().FlatTo1D<cpu, DType>();
for (size_t i = 0; i < num_val; i++) {
tensor[i] = vs[i];
}
});
return nd;
}
template<typename xpu>
static void inline CopyBlob(mshadow::Stream<xpu> *s,
const TBlob& dest_blob,
const TBlob& src_blob) {
using namespace mshadow;
using namespace mshadow::expr;
CHECK_EQ(src_blob.type_flag_, dest_blob.type_flag_);
CHECK_EQ(src_blob.shape_, dest_blob.shape_);
MSHADOW_TYPE_SWITCH(src_blob.type_flag_, DType, {
// Check if the pointers are the same (in-place operation needs no copy)
if (src_blob.dptr<DType>() != dest_blob.dptr<DType>()) {
mshadow::Copy(dest_blob.FlatTo1D<xpu, DType>(s), src_blob.FlatTo1D<xpu, DType>(s), s);
}
});
}
// Get a RowSparse NDArray with provided indices and values
inline NDArray RspND(const mxnet::TShape shape,
const Context ctx,
const std::vector<TEST_ITYPE> idx,
std::vector<TEST_DTYPE> vals) {
CHECK(shape.ndim() <= 2) << "High dimensional row sparse not implemented yet";
index_t num_rows = idx.size();
index_t num_cols = vals.size() / idx.size();
// create index NDArray
NDArray index = RspIdxND(mshadow::Shape1(num_rows), ctx, idx);
print(&std::cout, "index", index);
CHECK_EQ(vals.size() % idx.size(), 0);
// create value NDArray
NDArray data = DnsND(mshadow::Shape2(num_rows, num_cols), ctx, vals);
print(&std::cout, "data", data);
// create result nd
mxnet::ShapeVector aux_shapes = {mshadow::Shape1(num_rows)};
NDArray nd(kRowSparseStorage, shape, ctx, false, mshadow::default_type_flag,
{}, aux_shapes);
mshadow::Stream<cpu> *s = nullptr;
CopyBlob(s, nd.aux_data(rowsparse::kIdx), index.data());
CopyBlob(s, nd.data(), data.data());
print(&std::cout, "nd", nd);
return nd;
}
/*! \brief Array - utility class to construct sparse arrays
* \warning This class is not meant to run in a production environment. Since it is for unit tests only,
* simplicity has been chosen over performance.
**/
template<typename DType>
class Array {
typedef std::map<size_t, std::map<size_t, DType> > TItems;
static constexpr double EPSILON = 1e-5;
static const char *st2str(const NDArrayStorageType storageType) {
switch (storageType) {
case kDefaultStorage:
return "kDefaultStorage";
case kRowSparseStorage:
return "kRowSparseStorage";
case kCSRStorage:
return "kCSRStorage";
case kUndefinedStorage:
return "kUndefinedStorage";
default:
LOG(FATAL) << "Unsupported storage type: " << storageType;
return "<INVALID>";
}
}
/*! \brief Remove all zero entries */
void Prune() {
for (typename TItems::iterator i = items_.begin(), e = items_.end();
i != e;) {
const size_t y = i->first;
std::map<size_t, DType> &m = i->second;
++i;
for (typename std::map<size_t, DType>::const_iterator j = m.begin(), jn = m.end();
j != jn;) {
const size_t x = j->first;
const DType v = j->second;
++j;
if (IsZero(v)) {
m.erase(x);
}
}
if (m.empty()) {
items_.erase(y);
}
}
}
/*! \brief Create a dense NDArray from our mapped data */
NDArray CreateDense(const Context& ctx) const {
NDArray array(shape_, Context::CPU(-1));
TBlob data = array.data();
DType *p_data = data.dptr<DType>();
memset(p_data, 0, array.shape().Size() * sizeof(DType));
for (typename TItems::const_iterator i = items_.begin(), e = items_.end();
i != e; ++i) {
const size_t y = i->first;
const std::map<size_t, DType> &m = i->second;
for (typename std::map<size_t, DType>::const_iterator j = m.begin(), jn = m.end();
j != jn; ++j) {
const size_t x = j->first;
const DType v = j->second;
if (!IsZero(v)) {
const size_t offset = mxnet::test::offset(shape_, {y, x});
p_data[offset] = v;
}
}
}
if (ctx.dev_type == Context::kGPU) {
NDArray argpu(shape_, ctx);
CopyFromTo(array, &argpu);
return argpu;
} else {
return array;
}
}
public:
Array() = default;
explicit Array(const mxnet::TShape &shape)
: shape_(shape) {}
explicit Array(const NDArray &arr)
: shape_(arr.shape()) {
Load(arr);
}
void clear() {
items_.clear();
shape_ = mxnet::TShape(0);
}
static inline bool IsNear(const DType v1, const DType v2) { return fabs(v2 - v1) <= EPSILON; }
static inline bool IsZero(const DType v) { return IsNear(v, DType(0)); }
/*! Index into value maps via: [y][x] (row, col) */
std::map<size_t, DType> &operator[](const size_t idx) { return items_[idx]; }
const std::map<size_t, DType> &operator[](const size_t idx) const {
typename TItems::const_iterator i = items_.find(idx);
if (i != items_.end()) {
return i->second;
}
CHECK(false) << "Attempt to access a non-existent key in a constant map";
return *static_cast<std::map<size_t, DType> *>(nullptr);
}
bool Contains(const size_t row, const size_t col) const {
typename TItems::const_iterator i = items_.find(row);
if (i != items_.end()) {
typename std::map<size_t, DType>::const_iterator j = i->second.find(col);
if (j != i->second.end()) {
return true;
}
}
return false;
}
/*! \brief Convert from one storage type NDArray to another */
static NDArray Convert(const Context& ctx, const NDArray& src,
const NDArrayStorageType storageType) {
std::unique_ptr<NDArray> pArray(
storageType == kDefaultStorage
? new NDArray(src.shape(), ctx)
: new NDArray(storageType, src.shape(), ctx));
OpContext opContext;
MXNET_CUDA_ONLY(std::unique_ptr<test::op::GPUStreamScope> gpuScope;);
switch (ctx.dev_type) {
#if MNXNET_USE_CUDA
case Context::kGPU:
gpuScope.reset(new test::op::GPUStreamScope(&opContext));
mxnet::op::CastStorageComputeImpl<gpu>(s, src, dest);
break;
#endif // MNXNET_USE_CUDA
default: { // CPU
OpContext op_ctx;
mxnet::op::CastStorageComputeImpl<cpu>(op_ctx, src, *pArray);
break;
}
}
return *pArray;
}
/*! \brief Return NDArray of given storage type representing the value maps */
NDArray Save(const Context& ctx, const NDArrayStorageType storageType) const {
switch (storageType) {
case kDefaultStorage:
return CreateDense(ctx);
case kRowSparseStorage:
case kCSRStorage:
return Convert(ctx, CreateDense(ctx), storageType);
case kUndefinedStorage:
default:
LOG(ERROR) << "Unsupported storage type: " << storageType;
return NDArray(mxnet::TShape(0), ctx);
}
}
void Load(NDArray array) {
clear();
shape_ = array.shape();
if (array.storage_type() != kDefaultStorage) {
array = Convert(array.ctx(), array, kDefaultStorage);
}
#if MXNET_USE_CUDA
if (array.ctx().dev_type == Context::kGPU) {
NDArray tmp(array.shape(), Context::CPU(-1));
CopyFromTo(array, &tmp);
array = tmp;
}
#endif // MXNET_USE_CUDA
const TBlob blob = array.data();
DType *p = blob.dptr<DType>();
CHECK_EQ(shape_.ndim(), 2U);
for (size_t row = 0, nrow = shape_[0]; row < nrow; ++row) {
for (size_t col = 0, ncol = shape_[1]; col < ncol; ++col) {
const size_t off = test::offset(shape_, {row, col});
if (!IsZero(p[off])) {
(*this)[row][col] = p[off];
}
}
}
}
void print() const {
for (typename TItems::const_iterator i = items_.begin(), e = items_.end();
i != e; ++i) {
const size_t y = i->first;
const std::map<size_t, DType> &m = i->second;
CHECK_EQ(m.empty(), false); // How did it get to have an empty map?
for (typename std::map<size_t, DType>::const_iterator j = m.begin(), jn = m.end();
j != jn; ++j) {
const size_t x = j->first;
const DType v = j->second;
if (!IsZero(v)) {
std::cout << "[row=" << y << ", col=" << x << "]: " << v << std::endl;
}
}
}
std::cout << std::flush;
}
private:
mxnet::TShape shape_;
TItems items_;
};
template<typename StreamType>
inline StreamType& print_dense(StreamType *_os, const std::string& label, const NDArray& arr) {
MSHADOW_TYPE_SWITCH(arr.data().type_flag_, DType, {
print(_os, label, test::Array<DType>(arr).Save(arr.ctx(), kDefaultStorage))
<< std::endl;
});
return *_os;
}
} // namespace test
} // namespace mxnet
#endif // TEST_NDARRAY_UTILS_H_