blob: 2cb590230ee198ee885058ef80821ceedfb4e5c2 [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.
*/
/*!
* \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_