| /* |
| * 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. |
| */ |
| |
| /*! |
| * \file test_util.h |
| * \brief unit test performance analysis functions |
| * \author Chris Olivier |
| */ |
| #ifndef TEST_UTIL_H_ |
| #define TEST_UTIL_H_ |
| |
| #include <gtest/gtest.h> |
| #include <mxnet/storage.h> |
| #include <mxnet/ndarray.h> |
| #include <string> |
| #include <vector> |
| #include <sstream> |
| #include <random> |
| |
| #include "../../../src/ndarray/ndarray_function.h" |
| |
| #if MXNET_USE_VTUNE |
| #include <ittnotify.h> |
| #endif |
| |
| namespace mxnet { |
| namespace test { |
| |
| extern bool unitTestsWithCuda; |
| extern bool debug_output; |
| extern bool quick_test; |
| extern bool performance_run; |
| extern bool csv; |
| extern bool thread_safety_force_cpu; |
| |
| template <typename DType> |
| inline size_t shapeMemorySize(const mxnet::TShape& shape) { |
| return shape.Size() * sizeof(DType); |
| } |
| |
| class BlobMemory { |
| public: |
| explicit inline BlobMemory(const bool isGPU) : isGPU_(isGPU) { |
| this->handle_.dptr = nullptr; |
| } |
| inline ~BlobMemory() { |
| Free(); |
| } |
| void* Alloc(const size_t size) { |
| CHECK_GT(size, 0U); // You've probably made a mistake |
| mxnet::Context context = isGPU_ ? mxnet::Context::GPU(0) : mxnet::Context{}; |
| Storage* storage = mxnet::Storage::Get(); |
| handle_ = storage->Alloc(size, context); |
| return handle_.dptr; |
| } |
| void Free() { |
| mxnet::Storage::Get()->DirectFree(handle_); |
| handle_.dptr = nullptr; |
| handle_.size = 0; |
| } |
| size_t Size() const { |
| return handle_.size; |
| } |
| |
| private: |
| const bool isGPU_; |
| Storage::Handle handle_; |
| }; |
| |
| class StandaloneBlob : public TBlob { |
| public: |
| inline StandaloneBlob(const mxnet::TShape& shape, const bool isGPU, const int dtype) |
| : TBlob(nullptr, shape, isGPU ? gpu::kDevMask : cpu::kDevMask, dtype), |
| memory_(std::make_shared<BlobMemory>(isGPU)) { |
| MSHADOW_TYPE_SWITCH( |
| dtype, DType, { this->dptr_ = memory_->Alloc(shapeMemorySize<DType>(shape)); }); |
| } |
| inline ~StandaloneBlob() { |
| this->dptr_ = nullptr; |
| } |
| inline size_t MemorySize() const { |
| return memory_->Size(); |
| } |
| |
| private: |
| /*! \brief Locally allocated memory block for this blob */ |
| std::shared_ptr<BlobMemory> memory_; |
| }; |
| |
| /*! |
| * \brief Access a TBlob's data on the CPU within the scope of this object |
| * Overloaded () operator returns the CPU-bound TBlob |
| * RAII will copy the data back to the GPU (if it was a GPU blob) |
| */ |
| class CAccessAsCPU { |
| public: |
| CAccessAsCPU(const RunContext& run_ctx, const TBlob& src, bool copy_back_result = true) |
| : run_ctx_(run_ctx), src_(src), copy_back_result_(copy_back_result) { |
| #if MXNET_USE_CUDA |
| if (run_ctx.ctx.dev_type == Context::kCPU) { |
| blob_ = src; |
| } else { |
| Context cpu_ctx, gpu_ctx = run_ctx.ctx; |
| cpu_ctx.dev_type = Context::kCPU; |
| cpu_ctx.dev_id = 0; |
| NDArray on_cpu(src.shape_, cpu_ctx, false, src_.type_flag_); |
| on_cpu.CheckAndAlloc(); |
| blob_ = on_cpu.data(); |
| run_ctx.get_stream<gpu>()->Wait(); |
| mxnet::ndarray::Copy<gpu, cpu>(src, &blob_, cpu_ctx, gpu_ctx, run_ctx); |
| run_ctx.get_stream<gpu>()->Wait(); |
| on_cpu_ = on_cpu; |
| } |
| #else |
| blob_ = src; |
| #endif |
| } |
| ~CAccessAsCPU() { |
| #if MXNET_USE_CUDA |
| if (copy_back_result_) { |
| // Copy back from GPU to CPU |
| if (run_ctx_.ctx.dev_type == Context::kGPU) { |
| Context cpu_ctx, gpu_ctx = run_ctx_.ctx; |
| cpu_ctx.dev_type = Context::kCPU; |
| cpu_ctx.dev_id = 0; |
| run_ctx_.get_stream<gpu>()->Wait(); |
| mxnet::ndarray::Copy<cpu, gpu>(blob_, &src_, gpu_ctx, cpu_ctx, run_ctx_); |
| run_ctx_.get_stream<gpu>()->Wait(); |
| } |
| } |
| #endif |
| } |
| inline const TBlob& operator()() const { |
| return blob_; |
| } |
| |
| private: |
| const RunContext run_ctx_; |
| TBlob src_; |
| const bool copy_back_result_; |
| NDArray on_cpu_; |
| TBlob blob_; |
| }; |
| |
| /*! |
| * \brief Access data blob as if on the CPU via a callback |
| * \tparam Type of callback Function to call with CPU-data NDArray |
| * \param src Source NDArray (on GPU or CPU) |
| * \param run_ctx Run context |
| * \param cb Callback Function to call with CPU-data NDArray |
| */ |
| template <typename CallbackFunction> |
| inline void AccessAsCPU(const NDArray& src, const RunContext& run_ctx, CallbackFunction cb) { |
| #if MXNET_USE_CUDA |
| if (src.ctx().dev_type == Context::kCPU) { |
| cb(src); |
| } else { |
| Context cpu_ctx, gpu_ctx = src.ctx(); |
| cpu_ctx.dev_type = Context::kCPU; |
| cpu_ctx.dev_id = 0; |
| NDArray on_cpu(src.shape(), cpu_ctx, false, src.dtype()); |
| on_cpu.CheckAndAlloc(); |
| TBlob tmp1 = on_cpu.data(); |
| run_ctx.get_stream<gpu>()->Wait(); |
| mxnet::ndarray::Copy<gpu, cpu>(src.data(), &tmp1, cpu_ctx, gpu_ctx, run_ctx); |
| run_ctx.get_stream<gpu>()->Wait(); |
| cb(on_cpu); |
| TBlob tmp2 = src.data(); |
| mxnet::ndarray::Copy<cpu, gpu>(on_cpu.data(), &tmp2, gpu_ctx, cpu_ctx, run_ctx); |
| run_ctx.get_stream<gpu>()->Wait(); |
| } |
| #else |
| cb(src); |
| #endif |
| } |
| |
| /*! |
| * \brief Access data blob as if on the CPU via a callback |
| * \tparam Type of callback Function to call with CPU-data NDArray |
| * \param src Source TBlob (on GPU or CPU) |
| * \param run_ctx Run context |
| * \param cb Callback Function to call with CPU-data TBlob |
| */ |
| template <typename CallbackFunction> |
| inline void AccessAsCPU(const TBlob& src, const RunContext& run_ctx, CallbackFunction cb) { |
| #if MXNET_USE_CUDA |
| if (run_ctx.ctx.dev_type == Context::kCPU) { |
| cb(src); |
| } else { |
| cb(CAccessAsCPU(run_ctx, src, true)()); |
| } |
| #else |
| cb(src); |
| #endif |
| } |
| |
| constexpr const size_t MPRINT_PRECISION = 5; |
| template <typename DType> |
| inline void fill(const RunContext& run_ctx, const TBlob& _blob, const DType val) { |
| AccessAsCPU(_blob, run_ctx, [val](const TBlob& blob) { |
| MSHADOW_TYPE_SWITCH(blob.type_flag_, DTypeX, { |
| DTypeX* p1 = blob.dptr<DTypeX>(); |
| for (size_t i = 0, n = blob.Size(); i < n; ++i) { |
| *p1++ = val; |
| } |
| }); |
| }); |
| } |
| |
| template <typename DType> |
| inline void try_fill(const RunContext& run_ctx, const TBlob* blob, const DType val) { |
| if (blob) { |
| fill(run_ctx, *blob, val); |
| } |
| } |
| |
| template <typename DType, typename Stream> |
| inline void dump(Stream* os, const TBlob& blob, const char* suffix = "f") { |
| DType* p1 = blob.dptr<DType>(); |
| for (size_t i = 0, n = blob.Size(); i < n; ++i) { |
| if (i) { |
| *os << ", "; |
| } |
| const DType val = *p1++; |
| |
| std::stringstream stream; |
| stream << val; |
| std::string ss = stream.str(); |
| if (suffix && *suffix == 'f') { |
| if (std::find(ss.begin(), ss.end(), '.') == ss.end()) { |
| ss += ".0"; |
| } |
| } |
| *os << ss << suffix; |
| } |
| } |
| |
| /*! \brief Return reference to data at position indexes */ |
| inline index_t getMult(const mxnet::TShape& shape, const index_t axis) { |
| return axis < shape.ndim() ? shape[axis] : 1; |
| } |
| |
| /*! \brief offset, given indices such as bn, channel, depth, row, column */ |
| inline index_t offset(const mxnet::TShape& shape, const std::vector<size_t>& indices) { |
| const size_t dim = shape.ndim(); |
| CHECK_LE(indices.size(), dim); |
| size_t offset = 0; |
| for (size_t i = 0; i < dim; ++i) { |
| offset *= shape[i]; |
| if (indices.size() > i) { |
| CHECK_LT(indices[i], shape[i]); |
| offset += indices[i]; |
| } |
| } |
| return offset; |
| } |
| |
| /*! \brief Return reference to data at position indexes */ |
| template <typename DType> |
| inline const DType& data_at(const TBlob* blob, const std::vector<size_t>& indices) { |
| return blob->dptr<DType>()[offset(blob->shape_, indices)]; |
| } |
| |
| /*! \brief Set data at position indexes */ |
| template <typename DType> |
| inline DType& data_ref(const TBlob* blob, const std::vector<size_t>& indices) { |
| return blob->dptr<DType>()[offset(blob->shape_, indices)]; |
| } |
| |
| inline std::string repeatedStr(const char* s, |
| const signed int count, |
| const bool trailSpace = false) { |
| if (count <= 0) { |
| return std::string(); |
| } else if (count == 1) { |
| std::stringstream str; |
| str << s << " "; |
| return str.str(); |
| } else { |
| std::stringstream str; |
| for (int x = 0; x < count; ++x) { |
| str << s; |
| } |
| if (trailSpace) { |
| str << " "; |
| } |
| return str.str(); |
| } |
| } |
| |
| /*! \brief Pretty print a shape with optional label */ |
| template <typename StreamType> |
| inline StreamType& print_shape(StreamType* _os, |
| const std::string& label, |
| const mxnet::TShape& shape, |
| const bool add_endl = true) { |
| if (!label.empty()) { |
| *_os << label << ": "; |
| } |
| *_os << "("; |
| for (size_t i = 0, n = shape.ndim(); i < n; ++i) { |
| if (i) { |
| *_os << ", "; |
| } |
| *_os << shape[i]; |
| } |
| *_os << ")"; |
| if (add_endl) { |
| *_os << std::endl; |
| } else { |
| *_os << " "; |
| } |
| return *_os << std::flush; |
| } |
| |
| /*! \brief Pretty print a 1D, 2D, or 3D blob */ |
| template <typename DType, typename StreamType> |
| inline StreamType& print_blob_(const RunContext& ctx, |
| StreamType* _os, |
| const TBlob& blob, |
| const bool doChannels = true, |
| const bool doBatches = true, |
| const bool add_endl = true) { |
| #if MXNET_USE_CUDA |
| if (blob.dev_mask() == gpu::kDevMask) { |
| return print_blob_<DType>( |
| ctx, _os, CAccessAsCPU(ctx, blob, false)(), doChannels, doBatches, add_endl); |
| } |
| #endif // MXNET_USE_CUDA |
| |
| StreamType& os = *_os; |
| const size_t dim = static_cast<size_t>(blob.ndim()); |
| |
| if (dim == 1) { |
| // probably a 1d tensor (mshadow::Tensor is deprecated) |
| TBlob changed(blob.dptr<DType>(), mxnet::TShape(3, -1), blob.dev_mask(), blob.dev_id()); |
| changed.shape_[0] = 1; |
| changed.shape_[1] = 1; |
| changed.shape_[2] = blob.shape_[0]; |
| return print_blob_<DType>(ctx, &os, changed, false, false, add_endl); |
| } else if (dim == 2) { |
| // probably a 2d tensor (mshadow::Tensor is deprecated) |
| TBlob changed(blob.dptr<DType>(), mxnet::TShape(4, -1), blob.dev_mask(), blob.dev_id()); |
| changed.shape_[0] = 1; |
| changed.shape_[1] = 1; |
| changed.shape_[2] = blob.shape_[0]; |
| changed.shape_[3] = blob.shape_[1]; |
| return print_blob_<DType>(ctx, &os, changed, false, false, add_endl); |
| } |
| CHECK_GE(dim, 3U) << "Invalid dimension zero (0)"; |
| |
| const size_t batchSize = blob.size(0); |
| |
| size_t channels = 1; |
| size_t depth = 1; |
| size_t height = 1; |
| size_t width = 1; |
| if (dim > 1) { |
| channels = blob.size(1); |
| if (dim > 2) { |
| if (dim == 3) { |
| width = blob.size(2); |
| } else if (dim == 4) { |
| height = blob.size(2); |
| width = blob.size(3); |
| } else { |
| depth = blob.size(2); |
| if (dim > 3) { |
| height = blob.size(3); |
| if (dim > 4) { |
| width = blob.size(4); |
| } |
| } |
| } |
| } |
| } |
| |
| for (size_t r = 0; r < height; ++r) { |
| for (size_t thisBatch = 0; thisBatch < batchSize; ++thisBatch) { |
| if (doBatches) { |
| std::stringstream ss; |
| if (doBatches && !thisBatch) { |
| os << "|"; |
| } |
| ss << "N" << thisBatch << "| "; |
| const std::string nns = ss.str(); |
| if (!r) { |
| os << nns; |
| } else { |
| os << repeatedStr(" ", nns.size()); |
| } |
| } |
| for (size_t thisChannel = 0; thisChannel < channels; ++thisChannel) { |
| os << "["; |
| for (size_t c = 0; c < width; ++c) { |
| if (c) { |
| os << ", "; |
| } |
| for (size_t dd = 0; dd < depth; ++dd) { |
| DType val; |
| switch (dim) { |
| case 3: |
| val = data_at<DType>(&blob, {thisBatch, thisChannel, c}); |
| break; |
| case 4: |
| val = data_at<DType>(&blob, {thisBatch, thisChannel, r, c}); |
| break; |
| case 5: |
| val = data_at<DType>(&blob, {thisBatch, thisChannel, dd, r, c}); |
| break; |
| default: |
| LOG(FATAL) << "Unsupported blob dimension" << dim; |
| val = DType(0); |
| break; |
| } |
| os << repeatedStr("(", dd); |
| os << std::fixed << std::setw(7) << std::setprecision(MPRINT_PRECISION) << std::right |
| << val << " "; |
| os << repeatedStr(")", dd, true); |
| } |
| } |
| os << "] "; |
| if (!doChannels) { |
| break; |
| } |
| } |
| if (!doBatches) { |
| break; |
| } else { |
| os << " |" << std::flush; |
| } |
| } |
| if (r < height - 1) { |
| os << std::endl; |
| } |
| } |
| if (!height) { |
| os << "[]"; |
| if (add_endl) { |
| os << std::endl; |
| } |
| } else if (!add_endl) { |
| os << " "; |
| } else { |
| os << std::endl; |
| } |
| os << std::flush; |
| return os; |
| } |
| |
| template <typename StreamType> |
| inline StreamType& print(const RunContext& ctx, |
| StreamType* _os, |
| const TBlob& blob, |
| const bool doChannels = true, |
| const bool doBatches = true, |
| const bool add_endl = true) { |
| MSHADOW_TYPE_SWITCH(blob.type_flag_, DType, { |
| print_blob_<DType>(ctx, _os, blob, doChannels, doBatches, add_endl); |
| }); |
| return *_os; |
| } |
| |
| template <typename StreamType> |
| inline StreamType& print(const RunContext& ctx, |
| StreamType* _os, |
| const std::string& label, |
| const TBlob& blob, |
| const bool doChannels = true, |
| bool doBatches = true, |
| const bool add_endl = true) { |
| if (!label.empty()) { |
| *_os << label << ": "; |
| } |
| return print(ctx, _os, blob, doChannels, doBatches, add_endl); |
| } |
| |
| template <typename StreamType> |
| inline StreamType& print(const RunContext& ctx, |
| StreamType* _os, |
| const std::string& label, |
| const NDArray& arr) { |
| if (!label.empty()) { |
| *_os << label << ": "; |
| } |
| switch (arr.storage_type()) { |
| case kRowSparseStorage: { |
| // data |
| const mxnet::TShape& shape = arr.shape(); |
| print_shape(_os, "[row_sparse] main shape", shape, false); |
| const mxnet::TShape& storage_shape = arr.storage_shape(); |
| const bool is_one_row = storage_shape[0] < 2; |
| print_shape(_os, "storage shape", storage_shape, false); |
| print(ctx, _os, arr.data(), true, true, !is_one_row); |
| |
| // indices |
| const mxnet::TShape& indices_shape = arr.aux_shape(rowsparse::kIdx); |
| print_shape(_os, "indices shape", indices_shape, false); |
| print(ctx, _os, arr.aux_data(rowsparse::kIdx), true, true, false) << std::endl; |
| break; |
| } |
| case kCSRStorage: { |
| // data |
| const mxnet::TShape& shape = arr.shape(); |
| print_shape(_os, "[CSR] main shape", shape, false); |
| const mxnet::TShape& storage_shape = arr.storage_shape(); |
| const bool is_one_row = storage_shape[0] < 2; |
| print_shape(_os, "storage shape", storage_shape, false); |
| print(ctx, _os, arr.data(), true, true, !is_one_row); |
| |
| // row ptrs |
| const mxnet::TShape& ind_ptr_shape = arr.aux_shape(csr::kIndPtr); |
| print_shape(_os, "row ptrs shape", ind_ptr_shape, false); |
| print(ctx, _os, arr.aux_data(csr::kIndPtr), true, true, false) << std::endl; |
| |
| // col indices |
| const mxnet::TShape& indices_shape = arr.aux_shape(csr::kIdx); |
| print_shape(_os, "col indices shape", indices_shape, false); |
| print(ctx, _os, arr.aux_data(csr::kIdx), true, true, false) << std::endl; |
| |
| break; |
| } |
| case kDefaultStorage: { |
| // data |
| const mxnet::TShape& shape = arr.shape(); |
| const bool is_one_row = shape[0] < 2; |
| print_shape(_os, "[dense] main shape", shape, !is_one_row); |
| print(ctx, _os, arr.data(), true, true, !is_one_row) << std::endl; |
| break; |
| } |
| default: |
| CHECK(false) << "Unsupported storage type:" << arr.storage_type(); |
| break; |
| } |
| return *_os << std::flush; |
| } |
| |
| inline void print(const RunContext& ctx, |
| const std::string& label, |
| const std::string& var, |
| const std::vector<NDArray>& arrays) { |
| std::cout << label << std::endl; |
| for (size_t x = 0, n = arrays.size(); x < n; ++x) { |
| std::stringstream ss; |
| ss << var << "[" << x << "]"; |
| test::print(ctx, &std::cout, ss.str(), arrays[x]); |
| } |
| } |
| |
| inline void print(const RunContext& ctx, |
| const std::string& label, |
| const std::string& var, |
| const std::vector<TBlob>& arrays) { |
| std::cout << label << std::endl; |
| for (size_t x = 0, n = arrays.size(); x < n; ++x) { |
| std::stringstream ss; |
| ss << var << "[" << x << "]"; |
| test::print(ctx, &std::cout, ss.str(), arrays[x], true, true, false); |
| } |
| } |
| |
| inline std::string demangle(const char* name) { |
| #if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) |
| int status = -4; // some arbitrary value to eliminate the compiler warning |
| std::unique_ptr<char, void (*)(void*)> res{abi::__cxa_demangle(name, nullptr, nullptr, &status), |
| &std::free}; |
| return status ? name : res.get(); |
| #else |
| return name; |
| #endif |
| } |
| |
| template <typename T> |
| inline std::string type_name() { |
| return demangle(typeid(T).name()); |
| } |
| |
| #define PRINT_NDARRAYS(__ctx$, __var) test::print(__ctx$, __FUNCTION__, #__var, __var) |
| #define PRINT_OP_AND_ARRAYS(__ctx$, __op, __var) \ |
| test::print(__ctx$, \ |
| __FUNCTION__, \ |
| static_cast<std::stringstream*>( \ |
| &(std::stringstream() << #__var << "<" << type_name<__op>() << ">")) \ |
| ->str(), \ |
| __var) |
| #define PRINT_OP2_AND_ARRAYS(__ctx$, __op1, __op2, __var) test::print(__ctx$, __FUNCTION__, \ |
| static_cast<std::stringstream *>(&(std::stringstream() << #__var << \ |
| "<" << type_name<__op1>().name()) << ", " \ |
| << type_name<__op2>() << ">"))->str(), __var) |
| |
| /*! \brief Fill blob with some pattern defined by the getNextData() callback |
| * Pattern fill in the defined order (important for analysis): |
| * 1D: batch item -> channel -> depth -> row -> col |
| * 2D: batch item -> channel -> row -> col |
| * 3D: batch item -> channel -> col |
| */ |
| template <typename GetNextData> |
| static inline void patternFill(const RunContext& run_ctx, |
| const TBlob* _blob, |
| GetNextData getNextData) { |
| AccessAsCPU(*_blob, run_ctx, [getNextData](const TBlob& blob) { |
| const size_t dim = static_cast<size_t>(blob.ndim()); |
| CHECK_LE(dim, 5U) << "Will need to handle above 3 dimensions (another for loop)"; |
| const size_t num = blob.size(0); |
| const size_t channels = dim > 1 ? blob.size(1) : 1; |
| const size_t depth = dim > 2 ? blob.size(2) : 1; |
| const size_t height = dim > 3 ? blob.size(3) : 1; |
| const size_t width = dim > 4 ? blob.size(4) : 1; |
| const size_t numberOfIndexes = blob.shape_.Size(); |
| for (size_t n = 0; n < num; ++n) { |
| if (dim > 1) { |
| for (size_t ch = 0; ch < channels; ++ch) { |
| if (dim > 2) { |
| for (size_t d = 0; d < depth; ++d) { |
| if (dim > 3) { |
| for (size_t row = 0; row < height; ++row) { |
| if (dim > 4) { |
| for (size_t col = 0; col < width; ++col) { |
| if (dim == 5) { |
| const size_t idx = test::offset(blob.shape_, {n, ch, d, row, col}); |
| CHECK_LT(idx, numberOfIndexes); |
| MSHADOW_TYPE_SWITCH(blob.type_flag_, ThisDataType, { |
| ThisDataType& f = blob.dptr<ThisDataType>()[idx]; |
| f = getNextData(); |
| }); |
| } else { |
| CHECK(dim <= 5) << "Unimplemented dimension: " << dim; |
| } |
| } |
| } else { |
| const size_t idx = test::offset(blob.shape_, {n, ch, d, row}); |
| CHECK_LT(idx, numberOfIndexes); |
| MSHADOW_TYPE_SWITCH(blob.type_flag_, ThisDataType, { |
| ThisDataType& f = blob.dptr<ThisDataType>()[idx]; |
| f = getNextData(); |
| }); |
| } |
| } |
| } else { |
| const size_t idx = test::offset(blob.shape_, {n, ch, d}); |
| CHECK_LT(idx, numberOfIndexes); |
| MSHADOW_TYPE_SWITCH(blob.type_flag_, ThisDataType, { |
| ThisDataType& f = blob.dptr<ThisDataType>()[idx]; |
| f = getNextData(); |
| }); |
| } |
| } |
| } else { |
| const size_t idx = test::offset(blob.shape_, {n, ch}); |
| CHECK_LT(idx, numberOfIndexes); |
| MSHADOW_TYPE_SWITCH(blob.type_flag_, ThisDataType, { |
| ThisDataType& f = blob.dptr<ThisDataType>()[idx]; |
| f = getNextData(); |
| }); |
| } |
| } |
| } else { |
| const size_t idx = test::offset(blob.shape_, {n}); |
| CHECK_LT(idx, numberOfIndexes); |
| MSHADOW_TYPE_SWITCH(blob.type_flag_, ThisDataType, { |
| ThisDataType& f = blob.dptr<ThisDataType>()[idx]; |
| f = getNextData(); |
| }); |
| } |
| } |
| }); |
| } |
| |
| /*! \brief Return a random number within a given range (inclusive) */ |
| template <class ScalarType> |
| inline ScalarType rangedRand(const ScalarType min, const ScalarType max) { |
| uint64_t num_bins = static_cast<uint64_t>(max + 1), num_rand = static_cast<uint64_t>(RAND_MAX), |
| bin_size = num_rand / num_bins, defect = num_rand % num_bins; |
| ScalarType x; |
| do { |
| x = std::rand(); |
| } while (num_rand - defect <= (uint64_t)x); |
| |
| return static_cast<ScalarType>(x / bin_size + min); |
| } |
| |
| /*! |
| * \brief Deterministically compare mxnet::TShape objects as less-than, |
| * for use in stl sorted key such as map and set |
| * \param s1 First shape |
| * \param s2 Second shape |
| * \return true if s1 is less than s2 |
| */ |
| inline bool operator<(const mxnet::TShape& s1, const mxnet::TShape& s2) { |
| if (s1.Size() == s2.Size()) { |
| if (s1.ndim() == s2.ndim()) { |
| for (size_t i = 0, n = s1.ndim(); i < n; ++i) { |
| if (s1[i] == s2[i]) { |
| continue; |
| } |
| return s1[i] < s2[i]; |
| } |
| return false; |
| } |
| return s1.ndim() < s2.ndim(); |
| } |
| return s1.Size() < s2.Size(); |
| } |
| |
| /*! |
| * \brief Deterministically compare a vector of mxnet::TShape objects as less-than, |
| * for use in stl sorted key such as map and set |
| * \param v1 First vector of shapes |
| * \param v2 Second vector of shapes |
| * \return true if v1 is less than v2 |
| */ |
| inline bool operator<(const std::vector<mxnet::TShape>& v1, const std::vector<mxnet::TShape>& v2) { |
| if (v1.size() == v2.size()) { |
| for (size_t i = 0, n = v1.size(); i < n; ++i) { |
| if (v1[i] == v2[i]) { |
| continue; |
| } |
| return v1[i] < v2[i]; |
| } |
| return false; |
| } |
| return v1.size() < v2.size(); |
| } |
| |
| /*! |
| * \brief std::less compare structure for compating vectors of shapes for stl sorted containers |
| */ |
| struct less_shapevect { |
| bool operator()(const std::vector<mxnet::TShape>& v1, |
| const std::vector<mxnet::TShape>& v2) const { |
| if (v1.size() == v2.size()) { |
| for (size_t i = 0, n = v1.size(); i < n; ++i) { |
| if (v1[i] == v2[i]) { |
| continue; |
| } |
| return v1[i] < v2[i]; |
| } |
| return false; |
| } |
| return v1.size() < v2.size(); |
| } |
| }; |
| |
| inline std::string pretty_num(uint64_t val) { |
| if (!test::csv) { |
| std::string res, s = std::to_string(val); |
| size_t ctr = 0; |
| for (int i = static_cast<int>(s.size()) - 1; i >= 0; --i, ++ctr) { |
| if (ctr && (ctr % 3) == 0) { |
| res += ","; |
| } |
| res.push_back(s[i]); |
| } |
| std::reverse(res.begin(), res.end()); |
| return res; |
| } else { |
| return std::to_string(val); |
| } |
| } |
| |
| /*! \brief Change a value during the scope of this declaration */ |
| template <typename T> |
| struct ScopeSet { |
| inline ScopeSet(T* var, const T tempValue) : var_(*var), saveValue_(var) { |
| *var = tempValue; |
| } |
| inline ~ScopeSet() { |
| var_ = saveValue_; |
| } |
| T& var_; |
| T saveValue_; |
| }; |
| |
| static inline void AssertEqual(const std::vector<NDArray*>& in_arrs, |
| const std::vector<NDArray*>& out_arrs, |
| float rtol = 1e-5, |
| float atol = 1e-8, |
| bool test_first_only = false) { |
| for (size_t j = 0; j < in_arrs.size(); ++j) { |
| // When test_all is fir |
| if (test_first_only && j == 1) { |
| return; |
| } |
| NDArray tmp1 = *in_arrs[j]; |
| NDArray tmp2 = *out_arrs[j]; |
| if (tmp1.ctx().dev_type == mxnet::Context::kGPU) { |
| tmp1 = tmp1.Copy(mxnet::Context::CPU(0)); |
| tmp2 = tmp2.Copy(mxnet::Context::CPU(0)); |
| tmp1.WaitToRead(); |
| tmp2.WaitToRead(); |
| } |
| #if MXNET_USE_ONEDNN == 1 |
| tmp1 = tmp1.Reorder2Default(); |
| tmp2 = tmp2.Reorder2Default(); |
| #endif |
| EXPECT_EQ(tmp1.shape().Size(), tmp2.shape().Size()); |
| TBlob blob1 = tmp1.data(); |
| TBlob blob2 = tmp2.data(); |
| mshadow::default_real_t* d1 = static_cast<mshadow::default_real_t*>(blob1.dptr_); |
| mshadow::default_real_t* d2 = static_cast<mshadow::default_real_t*>(blob2.dptr_); |
| for (int i = 0; i < tmp1.shape().Size(); i++) { |
| float abs_err = fabs((d1[i]) - (d2[i])); |
| ASSERT_LE(abs_err, (atol + rtol * fabs(d2[i]))) |
| << "index: " << i << ", " << d1[i] << " vs " << d2[i]; |
| } |
| } |
| } |
| |
| } // namespace test |
| } // namespace mxnet |
| |
| #if defined(_MSC_VER) |
| inline void usleep(__int64 usec) { |
| HANDLE timer; |
| LARGE_INTEGER ft; |
| |
| // Convert to 100 nanosecond interval, negative value indicates relative time |
| ft.QuadPart = -(10 * usec); |
| |
| timer = CreateWaitableTimer(NULL, TRUE, NULL); |
| SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); |
| WaitForSingleObject(timer, INFINITE); |
| CloseHandle(timer); |
| } |
| #endif // _WIN32 |
| |
| #endif // TEST_UTIL_H_ |