/
githubmirror
/
incubator-mxnet
Обзор
Документация
Войти
/
githubmirror
/
incubator-mxnet
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/cpp/include/test_dnnl.h
643 строки
24 KB
mozga
[Master] Decouple OneDNN data structures in MXNet C++ API (#20624)
01 апр 2022, 20:12
Не верифицирован
01 апр 2022, 20:12
08737b2
Код
Авторство
О чём код?
/* * 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_