/* * 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 #include #include #include #include #include #include #include #include #include #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(timer); } inline float RandFloat() { static unsigned seed = gen_rand_seed(); double v = rand_r(&seed) * 1.0 / RAND_MAX; return static_cast(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& 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(); 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 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(); for (size_t i = 0; i < num_val; i++) { tensor[i] = vs[i]; } }); return nd; } template static void inline CopyBlob(mshadow::Stream* 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() != dest_blob.dptr()) { mshadow::Copy(dest_blob.FlatTo1D(s), src_blob.FlatTo1D(s), s); } }); } // Get a RowSparse NDArray with provided indices and values inline NDArray RspND(const mxnet::TShape shape, const Context ctx, const std::vector idx, std::vector 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* 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 class Array { typedef std::map > 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 ""; } } /*! \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& m = i->second; ++i; for (typename std::map::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(); 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& m = i->second; for (typename std::map::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& operator[](const size_t idx) { return items_[idx]; } const std::map& 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*>(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::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 pArray(storageType == kDefaultStorage ? new NDArray(src.shape(), ctx) : new NDArray(storageType, src.shape(), ctx)); OpContext opContext; MXNET_CUDA_ONLY(std::unique_ptr gpuScope;); switch (ctx.dev_type) { #if MNXNET_USE_CUDA case Context::kGPU: gpuScope.reset(new test::op::GPUStreamScope(&opContext)); mxnet::op::CastStorageComputeImpl(s, src, dest); break; #endif // MNXNET_USE_CUDA default: { // CPU OpContext op_ctx; mxnet::op::CastStorageComputeImpl(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(); 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& m = i->second; CHECK_EQ(m.empty(), false); // How did it get to have an empty map? for (typename std::map::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 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(arr).Save(arr.ctx(), kDefaultStorage)) << std::endl; }); return *_os; } } // namespace test } // namespace mxnet #endif // TEST_NDARRAY_UTILS_H_