| /* |
| * 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 dnnl_test.cc |
| * \brief test functions in dnnl. |
| * \author Da Zheng |
| */ |
| |
| #if MXNET_USE_ONEDNN == 1 |
| |
| #include <dnnl_types.h> |
| |
| #include <climits> |
| #include <cmath> |
| #include <set> |
| |
| #include "../../src/operator/nn/dnnl/dnnl_base-inl.h" |
| #include "../include/test_dnnl.h" |
| #include "gtest/gtest.h" |
| #include "mxnet/imperative.h" |
| |
| using namespace mxnet; |
| |
| #if __GNUC__ >= 5 |
| bool test_mem_align(void* mem, size_t size, size_t alignment, size_t space) { |
| void *ret1, *ret2; |
| size_t space1, space2; |
| space1 = space; |
| space2 = space; |
| ret1 = mxnet::AlignMem(mem, size, alignment, &space1); |
| ret2 = std::align(alignment, size, mem, space2); |
| EXPECT_EQ(ret1, ret2); |
| EXPECT_EQ(space1, space2); |
| return ret1 == ret2; |
| } |
| #endif |
| |
| TEST(DNNL_UTIL_FUNC, AlignMem) { |
| #if __GNUC__ >= 5 |
| size_t alignment = 4096; |
| void* mem; |
| size_t size, space; |
| // When mem has been aligned. |
| mem = reinterpret_cast<void*>(0x10000); |
| size = 1000; |
| space = 10000; |
| test_mem_align(mem, size, alignment, space); |
| |
| // When mem isn't aligned and we have enough space for alignment. |
| mem = reinterpret_cast<void*>(0x10010); |
| size = 1000; |
| space = 10000; |
| test_mem_align(mem, size, alignment, space); |
| |
| // When mem isn't aligned and we don't have enough memory for alignment |
| mem = reinterpret_cast<void*>(0x10010); |
| size = 1000; |
| space = 1001; |
| test_mem_align(mem, size, alignment, space); |
| |
| for (size_t i = 0; i < 10000; i++) { |
| mem = reinterpret_cast<void*>(random()); |
| size = random() % 2000; |
| space = random() % 2000; |
| test_mem_align(mem, size, alignment, space); |
| } |
| #else |
| // std::align is not supported in GCC < 5.0, this test case will be checked |
| // with newer version |
| LOG(INFO) << "Skipped for GCC " << __GNUC__ << "." << __GNUC_MINOR__; |
| #endif |
| } |
| |
| static void VerifyDefMem(const dnnl::memory& mem) { |
| dnnl::memory::desc desc = mem.get_desc(); |
| mshadow::default_real_t* data = static_cast<mshadow::default_real_t*>(mem.get_data_handle()); |
| size_t size = desc.get_size() / sizeof(mshadow::default_real_t); |
| size_t num_same = 0; |
| for (int i = 0; i < size; i++) |
| num_same += data[i] == static_cast<mshadow::default_real_t>(i % 100 - 50); |
| EXPECT_EQ(num_same, size); |
| } |
| |
| TEST(DNNL_UTIL_FUNC, MemFormat) { |
| // Check whether the number of format is correct. |
| CHECK_EQ(dnnl_format_tag_last, 514); |
| CHECK_EQ(dnnl_nchw, 5); |
| CHECK_EQ(dnnl_oihw, 5); |
| } |
| |
| static void VerifyMem(const dnnl::memory& mem) { |
| dnnl::memory::desc desc = mem.get_desc(); |
| dnnl::memory::dims dims(desc.data.ndims); |
| for (size_t i = 0; i < dims.size(); i++) |
| dims[i] = desc.data.dims[i]; |
| dnnl::memory::desc new_desc{dims, |
| static_cast<dnnl::memory::data_type>(desc.data.data_type), |
| static_cast<dnnl::memory::format_tag>(GetDefaultFormat(desc))}; |
| |
| if (desc == new_desc) { |
| VerifyDefMem(mem); |
| } else { |
| dnnl::memory* src_mem = const_cast<dnnl::memory*>(&mem); |
| dnnl::memory new_mem(new_desc, CpuEngine::Get()->get_engine()); |
| |
| dnnl::stream s(CpuEngine::Get()->get_engine()); |
| dnnl::reorder(*src_mem, new_mem).execute(s, *src_mem, new_mem); |
| |
| VerifyDefMem(new_mem); |
| } |
| } |
| |
| TEST(DNNL_NDArray, GetDataReorder) { |
| TestArrayShapes tas = GetTestArrayShapes(); |
| mxnet::ShapeVector shapes = tas.shapes; |
| std::vector<dnnl::memory::desc> mds = tas.mds; |
| |
| // Reorder from the default to any other layout. |
| for (auto s : shapes) { |
| NDArray arr(s, Context()); |
| InitDefaultArray(&arr); |
| for (auto md : mds) { |
| if (s.Size() == md.get_size() / sizeof(mshadow::default_real_t)) { |
| const dnnl::memory* mem = arr.GetDNNLDataReorder(&md); |
| printf("reorder from ("); |
| for (size_t i = 0; i < s.ndim(); i++) |
| printf("%ld, ", s[i]); |
| printf(") to ("); |
| for (int i = 0; i < md.data.ndims; i++) |
| printf("%ld, ", md.data.dims[i]); |
| printf("), format: %d\n", static_cast<int>(GetDefaultFormat(md))); |
| DNNLStream::Get()->Submit(false); |
| VerifyMem(*mem); |
| DNNLStream::Get()->Cleanup(); |
| } |
| } |
| } |
| |
| // Reorder from a special layout to another layout. |
| for (auto s : shapes) { |
| for (auto md : mds) { |
| if (md.get_size() / sizeof(mshadow::default_real_t) == s.Size()) { |
| NDArray arr(s, Context()); |
| // There is possibility that the dimensions of an NDArray doesn't match |
| // with the DNNL memory inside. |
| printf("Init array ("); |
| for (size_t i = 0; i < s.ndim(); i++) |
| printf("%ld, ", s[i]); |
| printf(") with oneDNN memory ("); |
| for (int i = 0; i < md.data.ndims; i++) |
| printf("%ld, ", md.data.dims[i]); |
| printf("), format: %d\n", static_cast<int>(GetDefaultFormat(md))); |
| InitDNNLArray(&arr, md); |
| for (auto to_md : mds) { |
| if (to_md.get_size() / sizeof(mshadow::default_real_t) == s.Size()) { |
| const dnnl::memory* mem = arr.GetDNNLDataReorder(&to_md); |
| printf("reorder from ("); |
| for (size_t i = 0; i < s.ndim(); i++) |
| printf("%ld, ", s[i]); |
| printf("), format: %d to (", static_cast<int>(GetDefaultFormat(to_md))); |
| for (int i = 0; i < to_md.data.ndims; i++) |
| printf("%ld, ", to_md.data.dims[i]); |
| printf("), format: %d\n", static_cast<int>(GetDefaultFormat(to_md))); |
| DNNLStream::Get()->Submit(false); |
| VerifyMem(*mem); |
| DNNLStream::Get()->Cleanup(); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| TEST(DNNL_BASE, DNNLMemorySum) { |
| std::vector<NDArrayAttrs> in_arrs = GetTestInputArrays(); |
| std::vector<NDArrayAttrs> in_arrs2 = GetTestInputArrays(ArrayTypes::All, true); |
| TestArrayShapes tas = GetTestArrayShapes(); |
| std::vector<dnnl::memory::desc> mds = tas.mds; |
| |
| for (int i = 0; i < in_arrs.size(); i++) { |
| auto in_arr = in_arrs[i]; |
| auto in_arr2 = in_arrs2[i]; |
| if (!SupportDNNL(in_arr.arr)) |
| continue; |
| if (in_arr.arr.IsDNNLData() && in_arr.arr.IsView()) { |
| continue; |
| } |
| std::vector<NDArrayAttrs> out_arrs = GetTestOutputArrays(in_arr.arr.shape(), mds); |
| for (auto& out_arr : out_arrs) { |
| auto in_mem1 = in_arr.arr.GetDNNLData(); |
| auto in_mem2 = in_arr2.arr.GetDNNLData(); |
| if (out_arr.arr.IsView()) |
| continue; |
| auto out_mem = out_arr.arr.GetDNNLData(); |
| PrintVerifyMsg(in_arr, in_arr); |
| op::DNNLMemorySum(*in_mem1, *in_mem2, *out_mem); |
| DNNLStream::Get()->Submit(); |
| VerifySumResult({&in_arr.arr, &in_arr2.arr}, {&out_arr.arr}); |
| } |
| } |
| |
| // in place |
| for (int i = 0; i < in_arrs.size(); i++) { |
| auto in_arr = in_arrs[i]; |
| auto in_arr2 = in_arrs2[i]; |
| if (!SupportDNNL(in_arr.arr)) |
| continue; |
| if (in_arr.arr.IsDNNLData() && in_arr.arr.IsView()) { |
| continue; |
| } |
| auto input_mem = in_arr.arr.GetDNNLData(); |
| auto input_mem2 = in_arr2.arr.GetDNNLData(); |
| NDArrayAttrs orig_arr(in_arr.arr.Copy(in_arr.arr.ctx()), "In Place Copy"); |
| orig_arr.arr.WaitToRead(); |
| PrintVerifyMsg(orig_arr, in_arr); |
| InitDNNLArray(&orig_arr.arr, input_mem->get_desc()); |
| orig_arr.arr.CopyFrom(*input_mem); |
| op::DNNLMemorySum(*input_mem, *input_mem2, *input_mem); |
| DNNLStream::Get()->Submit(); |
| VerifySumResult({&orig_arr.arr, &in_arr2.arr}, {&in_arr.arr}); |
| } |
| } |
| |
| TEST(DNNL_BASE, CreateDNNLMem) { |
| std::vector<NDArrayAttrs> in_arrs = GetTestInputArrays(); |
| std::vector<NDArrayAttrs> in_arrs2 = GetTestInputArrays(ArrayTypes::All, true); |
| TestArrayShapes tas = GetTestArrayShapes(); |
| std::vector<dnnl::memory::desc> mds = tas.mds; |
| DNNLStream* stream = DNNLStream::Get(); |
| |
| // kWriteTo |
| for (int i = 0; i < in_arrs.size(); i++) { |
| auto in_arr = in_arrs[i]; |
| auto in_arr2 = in_arrs2[i]; |
| if (!SupportDNNL(in_arr.arr)) |
| continue; |
| if (in_arr.arr.IsDNNLData() && in_arr.arr.IsView()) { |
| continue; |
| } |
| std::vector<NDArrayAttrs> out_arrs = GetTestOutputArrays(in_arr.arr.shape(), mds); |
| for (auto& out_arr : out_arrs) { |
| auto in_mem = in_arr.arr.GetDNNLData(); |
| auto in_mem2 = in_arr2.arr.GetDNNLData(); |
| NDArray orig_output = out_arr.arr.Copy(out_arr.arr.ctx()); |
| orig_output.WaitToRead(); |
| PrintVerifyMsg(in_arr, out_arr); |
| auto out_mem = out_arr.arr.GetDNNLData(); |
| auto output_mem_t = CreateDNNLMem(out_arr.arr, out_mem->get_desc(), kWriteTo); |
| op::DNNLMemorySum(*in_mem, *in_mem2, *output_mem_t.second); |
| CommitOutput(out_arr.arr, output_mem_t); |
| stream->Submit(); |
| VerifySumResult({&in_arr.arr, &in_arr2.arr}, {&out_arr.arr}); |
| } |
| } |
| |
| // kWriteInPlace |
| for (int i = 0; i < in_arrs.size(); i++) { |
| auto in_arr = in_arrs[i]; |
| auto in_arr2 = in_arrs2[i]; |
| if (!SupportDNNL(in_arr.arr)) |
| continue; |
| if (in_arr.arr.IsDNNLData() && in_arr.arr.IsView()) { |
| continue; |
| } |
| auto input_mem = in_arr.arr.GetDNNLData(); |
| auto input_mem2 = in_arr2.arr.GetDNNLData(); |
| NDArrayAttrs orig_arr(in_arr.arr.Copy(in_arr.arr.ctx()), "In Place Copy"); |
| orig_arr.arr.WaitToRead(); |
| PrintVerifyMsg(orig_arr, in_arr); |
| InitDNNLArray(&orig_arr.arr, input_mem->get_desc()); |
| orig_arr.arr.CopyFrom(*input_mem); |
| auto output_mem_t = |
| CreateDNNLMem(in_arr.arr, input_mem->get_desc(), kWriteInplace, &in_arr.arr); |
| op::DNNLMemorySum(*input_mem, *input_mem2, *output_mem_t.second); |
| CommitOutput(in_arr.arr, output_mem_t); |
| stream->Submit(); |
| VerifySumResult({&orig_arr.arr, &in_arr2.arr}, {&in_arr.arr}); |
| } |
| |
| // kAddTo |
| for (int i = 0; i < in_arrs.size(); i++) { |
| auto in_arr = in_arrs[i]; |
| auto in_arr2 = in_arrs2[i]; |
| if (!SupportDNNL(in_arr.arr)) |
| continue; |
| if (in_arr.arr.IsDNNLData() && in_arr.arr.IsView()) { |
| continue; |
| } |
| std::vector<NDArrayAttrs> out_arrs = GetTestOutputArrays(in_arr.arr.shape(), mds); |
| for (auto& out_arr : out_arrs) { |
| auto in_mem = in_arr.arr.GetDNNLData(); |
| auto in_mem2 = in_arr2.arr.GetDNNLData(); |
| NDArray orig_output = out_arr.arr.Copy(out_arr.arr.ctx()); |
| orig_output.WaitToRead(); |
| PrintVerifyMsg(in_arr, out_arr); |
| auto out_mem = out_arr.arr.GetDNNLData(); |
| auto output_mem_t = CreateDNNLMem(out_arr.arr, out_mem->get_desc(), kAddTo); |
| op::DNNLMemorySum(*in_mem, *in_mem2, *output_mem_t.second); |
| CommitOutput(out_arr.arr, output_mem_t); |
| stream->Submit(); |
| VerifyAddRequest( |
| {&in_arr.arr, &in_arr2.arr}, {&orig_output}, {&out_arr.arr}, VerifySumResult); |
| } |
| } |
| |
| // kNullOp |
| for (int i = 0; i < in_arrs.size(); i++) { |
| auto in_arr = in_arrs[i]; |
| auto in_arr2 = in_arrs2[i]; |
| if (!SupportDNNL(in_arr.arr)) |
| continue; |
| if (in_arr.arr.IsDNNLData() && in_arr.arr.IsView()) { |
| continue; |
| } |
| auto input_mem = in_arr.arr.GetDNNLData(); |
| auto input_mem2 = in_arr2.arr.GetDNNLData(); |
| NDArrayAttrs orig_arr(in_arr.arr.Copy(in_arr.arr.ctx()), "In Place Copy"); |
| orig_arr.arr.WaitToRead(); |
| PrintVerifyMsg(orig_arr, in_arr); |
| InitDNNLArray(&orig_arr.arr, input_mem->get_desc()); |
| orig_arr.arr.CopyFrom(*input_mem); |
| auto output_mem_t = CreateDNNLMem(in_arr.arr, input_mem->get_desc(), kNullOp); |
| op::DNNLMemorySum(*input_mem, *input_mem2, *output_mem_t.second); |
| CommitOutput(in_arr.arr, output_mem_t); |
| stream->Submit(); |
| // original and input should be the same since noop |
| VerifyCopyResult({&orig_arr.arr}, {&in_arr.arr}); |
| } |
| } |
| |
| TEST(DNNL_NDArray, GetTestInputArraysConcat) { |
| auto in_arrs = GetTestInputArrays(); |
| for (int dim = 0; dim < 5; dim++) { |
| for (int num_inputs = 2; num_inputs < 5; num_inputs++) { |
| std::vector<float> scale_vector(dim + 1); |
| for (size_t i = 0; i < dim + 1; ++i) |
| scale_vector[i] = 1; |
| scale_vector[dim] = num_inputs; |
| std::vector<NDArrayAttrs> expanded_arrs = |
| GetTestInputArrays(ArrayTypes::All, false, scale_vector); |
| int i = 0; |
| for (auto& arr : in_arrs) { |
| if (dim >= arr.arr.shape().ndim()) |
| continue; |
| auto ex_arr = expanded_arrs[i]; |
| PrintVerifyMsg(arr, ex_arr); |
| EXPECT_EQ(arr.arr.shape().Size() * num_inputs, ex_arr.arr.shape().Size()); |
| EXPECT_EQ(arr.arr.shape()[dim] * num_inputs, ex_arr.arr.shape()[dim]); |
| i++; |
| } |
| } |
| } |
| } |
| |
| TEST(DNNL_NDArray, GetTestOutputArraysConcat) { |
| auto shapes_pds = GetTestArrayShapes(); |
| std::vector<mxnet::TShape> shapes = shapes_pds.shapes; |
| std::vector<dnnl::memory::desc> mds = shapes_pds.mds; |
| for (auto& shape : shapes) { |
| for (int dim = 0; dim < 5; dim++) { |
| for (int num_inputs = 2; num_inputs < 5; num_inputs++) { |
| if (shape.ndim() <= dim) |
| continue; |
| std::cout << "Extending " << shape << " dim " << dim << " and " << num_inputs |
| << "num_inputs\n"; |
| std::vector<float> scale_vector(shape.ndim()); |
| for (int i = 0; i < shape.ndim(); i++) |
| scale_vector[i] = 1; |
| scale_vector[dim] = num_inputs; |
| auto output_arrs = GetTestOutputArrays(shape, mds, scale_vector); |
| for (auto& out_arr : output_arrs) { |
| auto out_shape = out_arr.arr.shape(); |
| EXPECT_EQ(shape.Size() * num_inputs, out_arr.arr.shape().Size()); |
| EXPECT_EQ(shape[dim] * num_inputs, out_arr.arr.shape()[dim]); |
| } |
| } |
| } |
| } |
| } |
| |
| TEST(DNNL_NDArray, CopyFrom) { |
| TestArrayShapes tas = GetTestArrayShapes(); |
| std::vector<dnnl::memory::desc> mds = tas.mds; |
| |
| std::vector<NDArrayAttrs> in_arrs = GetTestInputArrays(); |
| for (auto& in_arr : in_arrs) { |
| if (in_arr.arr.IsDNNLData() && in_arr.arr.IsView()) |
| continue; |
| std::vector<NDArrayAttrs> out_arrs = GetTestOutputArrays(in_arr.arr.shape(), mds); |
| for (auto& out_arr : out_arrs) { |
| const dnnl::memory* mem = in_arr.arr.GetDNNLData(); |
| out_arr.arr.CopyFrom(*mem); |
| DNNLStream::Get()->Submit(); |
| std::vector<NDArray*> inputs(1); |
| inputs[0] = &in_arr.arr; |
| VerifyCopyResult(inputs, {&out_arr.arr}); |
| } |
| } |
| } |
| |
| #endif // MXNET_USE_ONEDNN == 1 |