blob: 166074e29fa74256f4342d85e4226718e033d741 [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_dnnl.h
* \brief helper functions to test dnnl.
* \author Alex Zai
*/
#ifndef TEST_DNNL_H_
#define TEST_DNNL_H_
#if MXNET_USE_ONEDNN == 1
#include <set>
#include <string>
#include <vector>
#include "../../../3rdparty/googletest/googletest/include/gtest/gtest.h"
#include "../../../3rdparty/onednn/include/dnnl_types.h"
#include "../../../src/operator/nn/dnnl/dnnl_base-inl.h"
using namespace mxnet;
inline static dnnl::memory::desc GetMemDesc(const mxnet::TShape s,
const int dtype,
const dnnl::memory::format_tag format_tag) {
dnnl::memory::dims dims(s.ndim());
for (size_t i = 0; i < dims.size(); i++)
dims[i] = s[i];
dnnl::memory::desc desc{dims, get_dnnl_type(dtype), format_tag};
return desc;
}
inline static dnnl::memory::desc GetExpandedMemDesc(dnnl::memory::desc md,
const float scale,
const int dim = 0) {
CHECK(dim < md.data.ndims) << "dimension cannot be larger than total dimensions of input";
mxnet::TShape s(md.data.ndims, -1);
for (size_t i = 0; i < md.data.ndims; i++)
s[i] = md.data.dims[i];
s[dim] = static_cast<int64_t>(s[dim] * scale);
return GetMemDesc(s,
mshadow::DataType<mshadow::default_real_t>::kFlag,
static_cast<dnnl::memory::format_tag>(GetDefaultFormat(md)));
}
struct TestArrayShapes {
std::vector<mxnet::TShape> shapes;
std::vector<dnnl::memory::desc> mds;
};
// Init arrays with the default layout.
inline static void InitDefaultArray(NDArray* arr, bool is_rand = false, int max = 50) {
const TBlob& blob = arr->data();
mshadow::default_real_t* data = blob.dptr<mshadow::default_real_t>();
int size = blob.Size();
for (int i = 0; i < size; i++)
if (is_rand) {
data[i] = (std::rand() % (max * 2)) - max;
} else {
data[i] = i % (max * 2) - max;
}
}
// Init arrays with the specified layout.
inline static void InitDNNLArray(NDArray* arr,
const dnnl::memory::desc& desc,
bool is_rand = false,
int max = 50) {
InitDefaultArray(arr, is_rand, max);
arr->DNNLDataReorderAsync(&desc);
arr->WaitToRead();
}
inline static bool IsSameShape(const dnnl::memory::desc& desc, const mxnet::TShape& shape) {
if (desc.data.ndims != shape.ndim())
return false;
for (size_t i = 0; i < shape.ndim(); i++)
if (desc.data.dims[i] != shape[i])
return false;
return true;
}
// This function gets special DNNL formats without knowing the specific
// hardware configuration. Certainly, it potentially misses some format if
// it's specific for certain array shapes. It covers at least one special format
// for each of the formats: nchw, oihw, goihw.
// To test the logic of the code in NDArray, these formats should be enough.
inline static std::vector<dnnl::memory::format_tag> GetDNNLFormat(size_t num_dims, int dtype) {
if (num_dims == 4) {
dnnl::memory::dims data_dims{1, 3, 224, 224};
dnnl::memory::desc data_md{data_dims, get_dnnl_type(dtype), dnnl::memory::format_tag::any};
dnnl::memory::dims weight_dims{96, 3, 11, 11};
dnnl::memory::desc weight_md{weight_dims, get_dnnl_type(dtype), dnnl::memory::format_tag::any};
dnnl::memory::dims output_dims{1, 96, 54, 54};
dnnl::memory::desc out_md{output_dims, get_dnnl_type(dtype), dnnl::memory::format_tag::any};
dnnl::memory::dims strides{4, 4};
dnnl::memory::dims padding{0, 0};
dnnl::convolution_forward::desc desc(dnnl::prop_kind::forward_training,
dnnl::algorithm::convolution_direct,
data_md,
weight_md,
out_md,
strides,
padding,
padding);
dnnl::convolution_forward::primitive_desc pd(desc, CpuEngine::Get()->get_engine());
while (pd.dst_desc().get_size() != GetMemDescSize(out_md) ||
pd.src_desc().get_size() != GetMemDescSize(data_md) ||
pd.weights_desc().get_size() != GetMemDescSize(weight_md)) {
CHECK(pd.next_impl()) << "No implementation";
}
std::vector<dnnl::memory::format_tag> ret(1);
ret[0] = static_cast<dnnl::memory::format_tag>(GetDefaultFormat(pd.dst_desc()));
printf("format: %d \n", static_cast<int>(ret[0]));
return ret;
} else if (num_dims == 5) {
dnnl::memory::dims data_dims{1, 32, 112, 112};
dnnl::memory::desc data_md{data_dims, get_dnnl_type(dtype), dnnl::memory::format_tag::any};
dnnl::memory::dims weight_dims{32, 1, 1, 3, 3};
dnnl::memory::desc weight_md{weight_dims, get_dnnl_type(dtype), dnnl::memory::format_tag::any};
dnnl::memory::dims output_dims{1, 32, 112, 112};
dnnl::memory::desc out_md{output_dims, get_dnnl_type(dtype), dnnl::memory::format_tag::any};
dnnl::memory::dims strides{1, 1};
dnnl::memory::dims padding{1, 1};
dnnl::convolution_forward::desc desc(dnnl::prop_kind::forward_training,
dnnl::algorithm::convolution_direct,
data_md,
weight_md,
out_md,
strides,
padding,
padding);
dnnl::convolution_forward::primitive_desc pd(desc, CpuEngine::Get()->get_engine());
while (pd.dst_desc().get_size() != GetMemDescSize(out_md) ||
pd.src_desc().get_size() != GetMemDescSize(data_md) ||
pd.weights_desc().get_size() != GetMemDescSize(weight_md)) {
CHECK(pd.next_impl()) << "No implementation";
}
std::vector<dnnl::memory::format_tag> ret(1);
ret[0] = static_cast<dnnl::memory::format_tag>(GetDefaultFormat(pd.weights_desc()));
printf("format: %d\n", static_cast<int>(ret[0]));
return ret;
} else {
return std::vector<dnnl::memory::format_tag>();
}
}
inline static TestArrayShapes GetTestArrayShapes(bool spatial_data_format = false) {
int dtype = mshadow::DataType<mshadow::default_real_t>::kFlag;
mxnet::ShapeVector shapes;
std::vector<dnnl::memory::desc> mds;
{
// 1D
mxnet::TShape s(1, -1);
s[0] = 279936;
shapes.push_back(s);
mds.push_back(GetMemDesc(s, dtype, dnnl::memory::format_tag::x));
s[0] = 34848;
shapes.push_back(s);
mds.push_back(GetMemDesc(s, dtype, dnnl::memory::format_tag::x));
}
{
// 2D
mxnet::TShape s(2, -1);
s[0] = 96;
s[1] = 2916;
shapes.push_back(s);
mds.push_back(GetMemDesc(s, dtype, dnnl::memory::format_tag::nc));
s[0] = 96;
s[1] = 363;
shapes.push_back(s);
mds.push_back(GetMemDesc(s, dtype, dnnl::memory::format_tag::nc));
}
{
// 4D
mxnet::TShape s1(4, -1);
s1[0] = 10;
s1[1] = 96;
s1[2] = 54;
s1[3] = 54;
shapes.push_back(s1);
mds.push_back(GetMemDesc(s1, dtype, dnnl::memory::format_tag::nchw));
mxnet::TShape s2(4, -1);
s2[0] = 96;
s2[1] = 3;
s2[2] = 11;
s2[3] = 11;
shapes.push_back(s2);
mds.push_back(GetMemDesc(s2, dtype, dnnl::memory::format_tag::oihw));
std::vector<dnnl::memory::format_tag> formats = GetDNNLFormat(4, dtype);
if (!spatial_data_format) {
mds.push_back(GetMemDesc(s1, dtype, formats[0]));
}
}
{
// 5D
mxnet::TShape s(5, -1);
s[0] = 96;
s[1] = 1;
s[2] = 3;
s[3] = 11;
s[4] = 11;
shapes.push_back(s);
mds.push_back(GetMemDesc(s, dtype, dnnl::memory::format_tag::goihw));
std::vector<dnnl::memory::format_tag> formats = GetDNNLFormat(5, dtype);
if (!spatial_data_format) {
mds.push_back(GetMemDesc(s, dtype, formats[0]));
}
}
TestArrayShapes ret;
ret.shapes = shapes;
ret.mds = mds;
return ret;
}
struct NDArrayAttrs {
NDArray arr;
std::string desc;
NDArrayAttrs(NDArray arr, std::string desc) : arr(arr), desc(desc) {}
};
struct OpAttrs {
nnvm::NodeAttrs attrs;
std::vector<DispatchMode> dispatches;
std::set<OpReqType> requests;
std::unordered_set<int> accept_dims;
int num_inputs;
int num_outputs;
int input_types;
int output_types;
};
enum ArrayTypes {
Normal = 1,
DNNL = 2,
DNNLDiffShape = 4,
DNNLDiffDim = 8,
NormalReshaped = 16,
DNNLReshaped = 32,
DNNLReshapedDiffShape = 64,
DNNLReshapedDiffDim = 128,
NormalReused = 256,
DNNLReused = 512,
DNNLReusedDiffDim = 1024,
NormalReshapedReused = 2048,
NormalReusedDiffDtype = 4096,
All = 8191,
};
inline NDArray CreateKernelNDArray(mxnet::TShape kernel,
int num_filters,
mxnet::TShape input,
bool is_deconv = false) {
CHECK_EQ(kernel.ndim(), 2) << "dnnl only supports 2d filters on 4d inputs";
mxnet::TShape target_shape(4, -1);
target_shape[0] = is_deconv ? input[1] : num_filters;
target_shape[1] = is_deconv ? num_filters : input[1];
target_shape[2] = kernel[0];
target_shape[3] = kernel[1];
int dtype = mshadow::DataType<mshadow::default_real_t>::kFlag;
NDArray arr(target_shape, Context());
auto pd = GetMemDesc(target_shape, dtype, dnnl::memory::format_tag::nchw);
InitDNNLArray(&arr, pd);
return arr;
}
inline NDArray CreateBiasNDArray(mxnet::TShape target_shape) {
int dtype = mshadow::DataType<mshadow::default_real_t>::kFlag;
NDArray arr(target_shape, Context());
auto pd = GetMemDesc(target_shape, dtype, dnnl::memory::format_tag::x);
InitDNNLArray(&arr, pd);
return arr;
}
inline int CalculateWidthConvOutput(int width, int kernel, int padding, int stride) {
return (width - kernel + 2 * padding) / stride + 1;
}
inline int CalculateWidthDeconvOutput(int width, int kernel, int padding, int stride) {
return stride * (width - 1) + kernel - 2 * padding;
}
inline std::string CreateShapeString(int value, int dim) {
std::stringstream ss;
ss << "(";
for (int i = 0; i < dim; i++) {
ss << value;
if (i != dim - 1)
ss << ",";
}
ss << ")";
return ss.str();
}
inline void PrintVerifyMsg(const NDArrayAttrs& arr1, const NDArrayAttrs& arr2) {
mxnet::TShape t1 = arr1.arr.shape();
mxnet::TShape t2 = arr2.arr.shape();
std::stringstream ss;
std::cout << "Verifying: " << arr1.desc.c_str() << " " << t1 << " with " << arr2.desc.c_str()
<< " " << t2 << "\n";
}
/*
* We want to get a few types of NDArrays for testing:
* 1. Normal NDArray
* 2. Normal NDArray with DNNL layout (output from an DNNL operator)
* 3. Normal NDArray with DNNL layout whose DNNL memory may have different
* dimensions from the NDArray (result of DNNLDataReorderAsync). However, this
* type of NDArrays only exists for weight arrays. I don't think we should
* pass them to all operators.
* In the inference mode, the DNNL memory in the weight array will be
* reordered to 5 dimensions.
* 4. Reshaped/sliced NDArray
* 5. Reshaped/sliced NDArray with DNNL layout (reshape/slice from Normal NDArray
* with DNNL layout)
* 6. Reshaped/sliced NDArray with DNNL layout whose DNNL memory may have
* different dimensions from the NDArray (result of DNNLDataReorderAsync).
* However, this type of NDArrays only exists for weight arrays. I don't think
* we should pass them to all operators.
* In the inference mode, the DNNL memory in the weight array will be
* reordered to 5 dimensions.
*
* num_inputs / dim arguments used to scale shape (used for concat backwards to enlarge input
* shapes)
*/
inline std::vector<NDArrayAttrs> GetTestInputArrays(int types = ArrayTypes::All,
bool rand = false,
std::vector<float> scale = {1},
bool spatial_data_format = false,
int max = 50) {
TestArrayShapes tas = GetTestArrayShapes(spatial_data_format);
std::vector<mxnet::TShape> shapes = tas.shapes;
std::vector<dnnl::memory::desc> mds = tas.mds;
std::vector<NDArrayAttrs> in_arrs;
std::string desc_str;
int slice_amount = scale[0];
for (auto shape : shapes) {
if (scale.size() > shape.ndim())
continue;
for (size_t dim = 0; dim < scale.size(); ++dim)
shape[dim] = static_cast<int>(round(shape[dim] * scale[dim]));
// Type 1.
NDArray arr(shape, Context());
if (types & ArrayTypes::Normal) {
InitDefaultArray(&arr, rand, max);
in_arrs.emplace_back(arr, "Normal NDArray");
}
// Type 4
arr = NDArray(shape, Context());
if (types & ArrayTypes::NormalReshaped) {
InitDefaultArray(&arr, rand, max);
in_arrs.emplace_back(arr.Slice(slice_amount, arr.shape()[0] - slice_amount),
"Reshaped Normal NDArray");
}
for (auto md : mds) {
for (size_t dim = 0; dim < scale.size(); ++dim) {
// preserve if matching layout else just expand on 0 dim
if (shape.ndim() == md.data.ndims)
md = GetExpandedMemDesc(md, scale[dim], dim);
else
md = GetExpandedMemDesc(md, scale[dim]);
}
if (shape.Size() != md.get_size() / sizeof(mshadow::default_real_t))
continue;
// Type 2, 3.
arr = NDArray(shape, Context());
if (shape.ndim() == md.data.ndims && IsSameShape(md, shape) && types & ArrayTypes::DNNL) {
desc_str = "oneDNN NDArray";
InitDNNLArray(&arr, md, rand, max);
in_arrs.emplace_back(arr, desc_str);
} else if (shape.ndim() == md.data.ndims && !IsSameShape(md, shape) &&
types & ArrayTypes::DNNLDiffShape) {
desc_str = "oneDNN NDArray with different shape";
InitDNNLArray(&arr, md, rand, max);
in_arrs.emplace_back(arr, desc_str);
} else if (shape.ndim() != md.data.ndims && types & ArrayTypes::DNNLDiffDim) {
std::stringstream ss;
ss << "oneDNN NDArray with different dim " << shape.ndim() << "/" << md.data.ndims;
desc_str = ss.str();
InitDNNLArray(&arr, md, rand, max);
in_arrs.emplace_back(arr, desc_str);
}
// Type 5, 6.
arr = NDArray(shape, Context());
if (shape.ndim() == md.data.ndims && IsSameShape(md, shape) &&
types & ArrayTypes::DNNLReshaped) {
desc_str = "Reshaped oneDNN NDArray";
InitDNNLArray(&arr, md, rand, max);
in_arrs.emplace_back(arr.Slice(slice_amount, arr.shape()[0] - slice_amount), desc_str);
} else if (shape.ndim() == md.data.ndims && !IsSameShape(md, shape) &&
types & ArrayTypes::DNNLReshapedDiffShape) {
desc_str = "Reshaped oneDNN NDArray with different shape";
InitDNNLArray(&arr, md, rand, max);
in_arrs.emplace_back(arr.Slice(slice_amount, arr.shape()[0] - slice_amount), desc_str);
} else if (shape.ndim() != md.data.ndims && types & ArrayTypes::DNNLReshapedDiffDim) {
std::stringstream ss;
ss << "oneDNN NDArray with different dim " << shape.ndim() << "/" << md.data.ndims;
desc_str = ss.str();
InitDNNLArray(&arr, md, rand, max);
in_arrs.emplace_back(arr.Slice(slice_amount, arr.shape()[0] - slice_amount), desc_str);
}
}
}
return in_arrs;
}
/*
* We want to get a few types of NDArrays for testing:
* 1. Normal NDArray
* 2. Normal NDArray with DNNL layout (output from an DNNL operator)
* 3. Normal NDArray with DNNL layout whose DNNL memory may have different
* dimensions from the NDArray (result of DNNLDataReorderAsync). However, this
* type of NDArrays only exists for weight arrays. I don't think we should
* pass them to all operators.
* In the inference mode, the DNNL memory in the weight array will be
* reordered to 5 dimensions.
* 4. Reshaped/sliced NDArray
* 5. Reused NDArray (this is created by the MXNet executor). This type of
* NDArrays can only be used as output arrays.
* 6. Reused NDArray converted from an array with a different data type.
* 7. Reused reshaped/sliced NDArray.
* 8. Reused NDArray with DNNL layout.
* 9. Reused NDArray with DNNL layout of different dimensions.
*
* Optional num_inputs / dim args can be passed to modify input shape (used for Concat test)
*/
inline std::vector<NDArrayAttrs> GetTestOutputArrays(const mxnet::TShape& shp,
const std::vector<dnnl::memory::desc>& mds,
std::vector<float> scale = {1},
bool rand = true,
int types = ArrayTypes::All,
int max = 50) {
mxnet::TShape shape = shp;
for (int dim = 0; dim < scale.size(); dim++)
shape[dim] = static_cast<int>(shape[dim] * scale[dim]);
std::vector<NDArrayAttrs> in_arrs;
std::string desc_str;
// Type 1.
NDArray arr(shape, Context());
if (types & ArrayTypes::Normal) {
in_arrs.emplace_back(arr, "Normal NDArray");
InitDefaultArray(&in_arrs.back().arr, rand, max);
}
mxnet::TShape tmp_shape = shape;
if (types & ArrayTypes::NormalReshaped) {
// Type 4.
tmp_shape[0] = shape[0] * 2;
NDArray arr0(tmp_shape, Context());
InitDefaultArray(&arr0, rand, max);
in_arrs.emplace_back(arr0.Slice(1, shape[0] + 1), "Reshaped NDArray");
}
mxnet::TShape s(1, -1);
if (types & ArrayTypes::NormalReused) {
// Type 5.
// Get a reused version.
s[0] = shape.Size();
NDArray arr1(s, Context());
arr1 = arr1.AsArray(shape, arr1.dtype());
InitDefaultArray(&arr1, rand, max);
in_arrs.emplace_back(arr1, "Reused NDArray");
}
if (types & ArrayTypes::NormalReusedDiffDtype) {
// Type 6.
s[0] = shape.Size() * GetTypeSize(mshadow::default_type_flag);
NDArray arr2(s, Context(), true, mshadow::kUint8);
arr2 = arr2.AsArray(shape, mshadow::default_type_flag);
InitDefaultArray(&arr2, rand, max);
in_arrs.emplace_back(arr2, "Reused NDArray with diff data type");
}
if (types & ArrayTypes::NormalReshapedReused) {
// Type 7
s[0] = shape.Size() * GetTypeSize(mshadow::default_type_flag) * 2;
NDArray arr3(s, Context(), true, mshadow::kUint8);
tmp_shape[0] = shape[0] * 2;
arr3 = arr3.AsArray(tmp_shape, mshadow::default_type_flag);
InitDefaultArray(&arr3, rand, max);
in_arrs.emplace_back(arr3.Slice(1, shape[0] + 1), "Reused+Reshaped NDArray");
}
for (auto md : mds) {
if (shape.Size() != md.get_size() / sizeof(mshadow::default_real_t))
continue;
if (scale.size() > md.data.ndims)
continue;
for (int dim = 0; dim < scale.size(); dim++)
md = GetExpandedMemDesc(md, scale[dim]);
// Type 2, 3.
arr = NDArray(shape, Context());
desc_str = "oneDNN NDArray";
if (shape.ndim() != md.data.ndims) {
std::stringstream ss;
ss << "oneDNN NDArray with different memory layout " << shape.ndim() << "/" << md.data.ndims;
desc_str = ss.str();
}
if ((types & ArrayTypes::DNNL && shape.ndim() == md.data.ndims) ||
(types & ArrayTypes::DNNLDiffDim && shape.ndim() != md.data.ndims)) {
in_arrs.emplace_back(arr, desc_str);
InitDNNLArray(&in_arrs.back().arr, md, rand, max);
}
// Type 8, 9.
// Get a reused version.
mxnet::TShape s(1, -1);
s[0] = shape.Size();
NDArray arr = NDArray(s, Context());
arr = arr.AsArray(shape, arr.dtype());
InitDNNLArray(&arr, md, rand, max);
desc_str = "Reused oneDNN NDArray";
if (shape.ndim() != md.data.ndims) {
std::stringstream ss;
ss << "Reused oneDNN NDArray with different memory layout " << shape.ndim() << "/"
<< md.data.ndims;
desc_str = ss.str();
}
if ((types & ArrayTypes::DNNLReused && shape.ndim() == md.data.ndims) ||
(types & ArrayTypes::DNNLReusedDiffDim && shape.ndim() != md.data.ndims)) {
in_arrs.emplace_back(arr, desc_str);
}
}
return in_arrs;
}
/*
* Determines axis ndarrays are concatenated by
* Used to verify concat/concat backwards operator
*/
inline int GetDim(mxnet::TShape input_shape, mxnet::TShape output_shape) {
CHECK(input_shape.Size() != output_shape.Size());
for (size_t i = 0; i < input_shape.ndim(); i++) {
if (input_shape[i] != output_shape[i])
return i;
}
return -1;
}
/*
* Calculates the size of continuous block of array inside larger concatenated array
* Used to verify concat/concat backwards operator
*/
inline int GetBlockSize(mxnet::TShape shape, int dim) {
int block_size = 1;
for (int i = shape.ndim() - 1; i >= dim; i--)
block_size *= shape[i];
return block_size;
}
inline int CalculateWidthPoolOutput(int width, int kernel, int padding, int stride) {
return (width - kernel + 2 * padding) / stride + 1;
}
using VerifyFunc = std::function<void(const std::vector<NDArray*>& in_arrs,
const std::vector<NDArray*>& out_arrs)>;
inline void VerifyAddRequest(const std::vector<NDArray*>& in_arrs,
const std::vector<NDArray*>& original_outputs,
const std::vector<NDArray*>& new_outputs,
VerifyFunc verify_fn) {
CHECK(original_outputs.size() == new_outputs.size());
std::vector<NDArray*> tmp_outputs;
NDArray tmp;
for (size_t i = 0; i < new_outputs.size(); i++) {
tmp = new_outputs[i]->Reorder2Default() - original_outputs[i]->Reorder2Default();
tmp_outputs.push_back(&tmp);
}
Engine::Get()->WaitForAll();
verify_fn(in_arrs, tmp_outputs);
}
inline void VerifyCopyResult(const std::vector<NDArray*>& in_arrs,
const std::vector<NDArray*>& out_arrs) {
NDArray tmp1 = in_arrs[0]->Reorder2Default();
NDArray tmp2 = out_arrs[0]->Reorder2Default();
EXPECT_EQ(tmp1.shape().Size(), tmp2.shape().Size());
TBlob d1 = tmp1.data();
TBlob d2 = tmp2.data();
EXPECT_EQ(memcmp(d1.dptr_, d2.dptr_, tmp1.shape().Size() * sizeof(mshadow::default_real_t)), 0);
}
inline void VerifySumResult(const std::vector<NDArray*>& in_arrs,
const std::vector<NDArray*>& out_arrs) {
NDArray in1 = in_arrs[0]->Reorder2Default();
NDArray in2 = in_arrs[1]->Reorder2Default();
NDArray out = out_arrs[0]->Reorder2Default();
EXPECT_EQ(in1.shape().Size(), in2.shape().Size());
EXPECT_EQ(in1.shape().Size(), out.shape().Size());
mshadow::default_real_t* d1 = in1.data().dptr<mshadow::default_real_t>();
mshadow::default_real_t* d2 = in2.data().dptr<mshadow::default_real_t>();
mshadow::default_real_t* o = out.data().dptr<mshadow::default_real_t>();
for (size_t i = 0; i < in1.shape().Size(); i++)
ASSERT_EQ(d1[i] + d2[i], o[i]);
}
#endif // MXNET_USE_ONEDNN == 1
#endif // TEST_DNNL_H_