/* ****************************************************************************** * * * This program and the accompanying materials are made available under the * terms of the Apache License, Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * 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. * * SPDX-License-Identifier: Apache-2.0 ******************************************************************************/ // // @author saudet // @author Yurii Shyrma (iuriish@yahoo.com) // #include "mkldnnUtils.h" #include #include using namespace dnnl; namespace sd { namespace onednnUtils { ////////////////////////////////////////////////////////////////////// void getDims(NDArray* array, const int rank, dnnl::memory::dims& mklDims) { std::vector vDims(rank); for (auto i = 0; i < rank; i++) { vDims[i] = array->sizeAt(i); } mklDims = dnnl::memory::dims(vDims); } ////////////////////////////////////////////////////////////////////// dnnl::memory::format_tag getFormat(NDArray& arr) { dnnl::memory::format_tag result; switch (arr.rankOf()) { case 1: result = dnnl::memory::format_tag::a; break; case 2: result = arr.ordering() == 'c' ? dnnl::memory::format_tag::ab : dnnl::memory::format_tag::ba; break; case 3: result = arr.ordering() == 'c' ? dnnl::memory::format_tag::abc : dnnl::memory::format_tag::cba; break; case 4: result = dnnl::memory::format_tag::abcd; break; case 5: result = dnnl::memory::format_tag::abcde; break; case 6: result = dnnl::memory::format_tag::abcdef; break; default: THROW_EXCEPTION("MKLDNN getFormat: do we really want to use arras with rank > 6 ?"); } return result; } ////////////////////////////////////////////////////////////////////// void setBlockStrides(NDArray& array, dnnl::memory::desc& mklMd, const std::vector& permut) { if ((array.rankOf() > 3 && array.ordering() == 'f') || !permut.empty()) { mklMd.data.format_kind = dnnl_blocked; // overrides format if (permut.empty()) for (auto i = 0; i < array.rankOf(); ++i) mklMd.data.format_desc.blocking.strides[i] = array.strideAt(i); else { if (static_cast(array.rankOf()) != permut.size()) THROW_EXCEPTION("mkldnnUtils::setBlockStrides: size of permut vector is not equal to array rank !"); for (auto i = 0; i < array.rankOf(); ++i) mklMd.data.format_desc.blocking.strides[i] = array.strideAt(permut[i]); } } } //////////////////////////////////////////////////////////////////////////////////////////////// dnnl::memory loadDataToMklStream(NDArray& array, const dnnl::engine& engine, const dnnl::stream& stream, const dnnl::memory::desc& user_md, const dnnl::memory::desc& primitive_md, dnnl::memory& arg) { auto user_mem = dnnl::memory(user_md, engine, const_cast(array).buffer()); const bool bReorder = primitive_md != user_mem.get_desc(); auto mkl_mem = bReorder ? dnnl::memory(primitive_md, engine) : user_mem; if (bReorder) dnnl::reorder(user_mem, mkl_mem).execute(stream, user_mem, mkl_mem); arg = mkl_mem; return user_mem; } ////////////////////////////////////////////////////////////////////// void poolingONEDNN(NDArray* input, NDArray* output, const sd::LongType kD, const sd::LongType kH, const sd::LongType kW, const sd::LongType sD, const sd::LongType sH, const sd::LongType sW, const sd::LongType pD, const sd::LongType pH, const sd::LongType pW, const int isNCHW, const dnnl::algorithm mode) { // unfortunately mkl dnn doesn't support any format (dnnl::memory::format_tag::any) for input const sd::LongType rank = input->rankOf(); sd::LongType bS, iC, iD, iH, iW, oC, oD, oH, oW, indIOioC, indIiH, indWoC, indWiC, indWkH, indOoH; dnnl::memory::dims strides, kernel, padding, padding_r, xDims, zDims; dnnl::memory::format_tag xzFrmat; const auto type = dnnl::memory::data_type::f32; if (rank == 4) { // 2d ops::ConvolutionUtils::getSizesAndIndexesConv2d(isNCHW, 0, *input, *output, bS, iC, iH, iW, oC, oH, oW, indIOioC, indIiH, indWiC, indWoC, indWkH, indOoH); strides = {sH, sW}; kernel = {kH, kW}; padding = {pH, pW}; padding_r = {(oH - 1) * sH - iH + kH - pH, (oW - 1) * sW - iW + kW - pW}; xDims = {bS, iC, iH, iW}; zDims = {bS, oC, oH, oW}; xzFrmat = isNCHW ? dnnl::memory::format_tag::nchw : dnnl::memory::format_tag::nhwc; } else { // 3d ops::ConvolutionUtils::getSizesAndIndexesConv3d(isNCHW, 0, *input, *output, bS, iC, iD, iH, iW, oC, oD, oH, oW, indIOioC, indIiH, indWiC, indWoC, indWkH); strides = {sD, sH, sW}; kernel = {kD, kH, kW}; padding = {pD, pH, pW}; padding_r = {(oD - 1) * sD - iD + kD - pD, (oH - 1) * sH - iH + kH - pH, (oW - 1) * sW - iW + kW - pW}; xDims = {bS, iC, iD, iH, iW}; zDims = {bS, oC, oD, oH, oW}; xzFrmat = isNCHW ? dnnl::memory::format_tag::ncdhw : dnnl::memory::format_tag::ndhwc; } std::vector permut; if (!isNCHW) permut = rank == 4 ? std::vector({0, 3, 1, 2}) : std::vector({0, 4, 1, 2, 3}); // memory descriptors for arrays // input dnnl::memory::desc x_mkl_md = dnnl::memory::desc(xDims, type, xzFrmat); dnnl::memory::desc x_user_md = dnnl::memory::desc(xDims, type, xzFrmat); onednnUtils::setBlockStrides(*input, x_user_md, permut); // output dnnl::memory::desc z_mkl_md = dnnl::memory::desc(zDims, type, dnnl::memory::format_tag::any); dnnl::memory::desc z_user_md = dnnl::memory::desc(zDims, type, xzFrmat); onednnUtils::setBlockStrides(*output, z_user_md, permut); auto engine = onednnUtils::getEngine(LaunchContext::defaultContext()->engine()); // operation primitive description dnnl::pooling_forward::desc op_desc(dnnl::prop_kind::forward_inference, mode, x_mkl_md, z_mkl_md, strides, kernel, padding, padding_r); dnnl::pooling_forward::primitive_desc op_prim_desc(op_desc, engine); // arguments (memory buffers) necessary for calculations std::unordered_map args; dnnl::stream stream(engine); // provide memory buffers and check whether reorder is required // input onednnUtils::loadDataToMklStream(*input, engine, stream, x_user_md, op_prim_desc.src_desc(), args[DNNL_ARG_SRC]); // output auto z_user_mem = onednnUtils::loadDataToMklStream(*output, engine, stream, z_user_md, op_prim_desc.dst_desc(), args[DNNL_ARG_DST]); // run calculations dnnl::pooling_forward(op_prim_desc).execute(stream, args); // reorder outputs if necessary if (op_prim_desc.dst_desc() != z_user_mem.get_desc()) dnnl::reorder(args[DNNL_ARG_DST], z_user_mem).execute(stream, args[DNNL_ARG_DST], z_user_mem); stream.wait(); } ////////////////////////////////////////////////////////////////////// void poolingBpONEDNN(NDArray* input, NDArray* gradO, NDArray* gradI, const sd::LongType kD, const sd::LongType kH, const sd::LongType kW, const sd::LongType sD, const sd::LongType sH, const sd::LongType sW, const sd::LongType pD, const sd::LongType pH, const sd::LongType pW, const int isNCHW, const dnnl::algorithm mode) { // unfortunately mkl dnn doesn't support any format (dnnl::memory::format_tag::any) for input const sd::LongType rank = input->rankOf(); sd::LongType bS, iC, iD, iH, iW, oC, oD, oH, oW, indIOioC, indIiH, indWoC, indWiC, indWkH, indOoH; dnnl::memory::dims strides, kernel, padding, padding_r, xDims, zDims; dnnl::memory::format_tag xzFrmat; const auto type = dnnl::memory::data_type::f32; if (rank == 4) { // 2d ops::ConvolutionUtils::getSizesAndIndexesConv2d(isNCHW, 0, *input, *gradO, bS, iC, iH, iW, oC, oH, oW, indIOioC, indIiH, indWiC, indWoC, indWkH, indOoH); strides = {sH, sW}; kernel = {kH, kW}; padding = {pH, pW}; padding_r = {(oH - 1) * sH - iH + kH - pH, (oW - 1) * sW - iW + kW - pW}; xDims = {bS, iC, iH, iW}; zDims = {bS, oC, oH, oW}; xzFrmat = isNCHW ? dnnl::memory::format_tag::nchw : dnnl::memory::format_tag::nhwc; } else { // 3d ops::ConvolutionUtils::getSizesAndIndexesConv3d(isNCHW, 0, *input, *gradO, bS, iC, iD, iH, iW, oC, oD, oH, oW, indIOioC, indIiH, indWiC, indWoC, indWkH); strides = {sD, sH, sW}; kernel = {kD, kH, kW}; padding = {pD, pH, pW}; padding_r = {(oD - 1) * sD - iD + kD - pD, (oH - 1) * sH - iH + kH - pH, (oW - 1) * sW - iW + kW - pW}; xDims = {bS, iC, iD, iH, iW}; zDims = {bS, oC, oD, oH, oW}; xzFrmat = isNCHW ? dnnl::memory::format_tag::ncdhw : dnnl::memory::format_tag::ndhwc; } std::vector permut; if (!isNCHW) permut = rank == 4 ? std::vector({0, 3, 1, 2}) : std::vector({0, 4, 1, 2, 3}); // memory descriptors for arrays // input dnnl::memory::desc x_mkl_md = dnnl::memory::desc(xDims, type, xzFrmat); dnnl::memory::desc x_user_md = dnnl::memory::desc(xDims, type, xzFrmat); onednnUtils::setBlockStrides(*input, x_user_md, permut); // gradO dnnl::memory::desc gradO_mkl_md = dnnl::memory::desc(zDims, type, dnnl::memory::format_tag::any); dnnl::memory::desc gradO_user_md = dnnl::memory::desc(zDims, type, xzFrmat); onednnUtils::setBlockStrides(*gradO, gradO_user_md, permut); // gradI dnnl::memory::desc gradI_mkl_md = dnnl::memory::desc(xDims, type, dnnl::memory::format_tag::any); dnnl::memory::desc gradI_user_md = dnnl::memory::desc(xDims, type, xzFrmat); onednnUtils::setBlockStrides(*gradI, gradI_user_md, permut); auto engine = onednnUtils::getEngine(LaunchContext::defaultContext()->engine()); dnnl::stream stream(engine); // forward primitive description dnnl::pooling_forward::desc op_ff_desc(dnnl::prop_kind::forward, mode, x_mkl_md, gradO_mkl_md, strides, kernel, padding, padding_r); dnnl::pooling_forward::primitive_desc op_ff_prim_desc(op_ff_desc, engine); // backward primitive description dnnl::pooling_backward::desc op_bp_desc(mode, gradI_mkl_md, gradO_mkl_md, strides, kernel, padding, padding_r); dnnl::pooling_backward::primitive_desc op_bp_prim_desc(op_bp_desc, engine, op_ff_prim_desc); // arguments (memory buffers) necessary for calculations std::unordered_map args; // gradO onednnUtils::loadDataToMklStream(*gradO, engine, stream, gradO_user_md, op_bp_prim_desc.diff_dst_desc(), args[DNNL_ARG_DIFF_DST]); // gradI auto gradI_user_mem = onednnUtils::loadDataToMklStream(*gradI, engine, stream, gradI_user_md, op_bp_prim_desc.diff_src_desc(), args[DNNL_ARG_DIFF_SRC]); if (mode == algorithm::pooling_max) { // input onednnUtils::loadDataToMklStream(*input, engine, stream, x_user_md, op_ff_prim_desc.src_desc(), args[DNNL_ARG_SRC]); // z auto z_mkl_mem = dnnl::memory(op_ff_prim_desc.dst_desc(), engine); args[DNNL_ARG_DST] = z_mkl_mem; // auxiliary memory allocation auto workspace = dnnl::memory(op_ff_prim_desc.workspace_desc(), engine); args[DNNL_ARG_WORKSPACE] = workspace; // run forward calculations dnnl::pooling_forward(op_ff_prim_desc).execute(stream, args); } // run backward calculations dnnl::pooling_backward(op_bp_prim_desc).execute(stream, args); // reorder gradI if necessary if (op_bp_prim_desc.diff_src_desc() != gradI_user_mem.get_desc()) dnnl::reorder(args[DNNL_ARG_DIFF_SRC], gradI_user_mem).execute(stream, args[DNNL_ARG_DIFF_SRC], gradI_user_mem); stream.wait(); } ////////////////////////////////////////////////////////////////////////// void getONEDNNMemoryDescLrn(NDArray* src, NDArray* diff_src, NDArray* dst, dnnl::memory::desc* lrn_src_md, dnnl::memory::desc* lrn_diff_src_md, dnnl::memory::desc* lrn_dst_md, dnnl::memory::desc* user_src_md, dnnl::memory::desc* user_diff_src_md, dnnl::memory::desc* user_dst_md, int axis) { const sd::LongType* shape = src->shapeInfo(); long rank = shape[0]; long dim1 = axis; // MKL-DNN supports only 1 axis, which has to be the "channel" one long dim2 = axis >= 2 ? 1 : 2; long dim3 = axis >= 3 ? 2 : 3; dnnl::memory::dims lrn_src_tz = {(int)shape[1], (int)shape[dim1 + 1], rank > 2 ? (int)shape[dim2 + 1] : 1, rank > 3 ? (int)shape[dim3 + 1] : 1}; auto type = dnnl::memory::data_type::f32; auto format = axis == 1 ? dnnl::memory::format_tag::nchw : dnnl::memory::format_tag::nhwc; auto supposed_to_be_any_format = format; // doesn't work with "any" if (src != nullptr && src->buffer() != nullptr && lrn_src_md != nullptr) { *lrn_src_md = dnnl::memory::desc({lrn_src_tz}, type, supposed_to_be_any_format); *user_src_md = dnnl::memory::desc({lrn_src_tz}, type, format); user_src_md->data.format_kind = dnnl_blocked; user_src_md->data.format_desc.blocking.strides[0] = src->stridesOf()[0]; user_src_md->data.format_desc.blocking.strides[1] = src->stridesOf()[dim1]; user_src_md->data.format_desc.blocking.strides[2] = rank > 2 ? src->stridesOf()[dim2] : 1; user_src_md->data.format_desc.blocking.strides[3] = rank > 3 ? src->stridesOf()[dim3] : 1; } if (diff_src != nullptr && diff_src->buffer() != nullptr && lrn_diff_src_md != nullptr) { *lrn_diff_src_md = dnnl::memory::desc({lrn_src_tz}, type, supposed_to_be_any_format); *user_diff_src_md = dnnl::memory::desc({lrn_src_tz}, type, format); user_diff_src_md->data.format_kind = dnnl_blocked; user_diff_src_md->data.format_desc.blocking.strides[0] = diff_src->stridesOf()[0]; user_diff_src_md->data.format_desc.blocking.strides[1] = diff_src->stridesOf()[dim1]; user_diff_src_md->data.format_desc.blocking.strides[2] = rank > 2 ? diff_src->stridesOf()[dim2] : 1; user_diff_src_md->data.format_desc.blocking.strides[3] = rank > 3 ? diff_src->stridesOf()[dim3] : 1; } if (dst != nullptr && dst->buffer() != nullptr && lrn_dst_md != nullptr) { *lrn_dst_md = dnnl::memory::desc({lrn_src_tz}, type, supposed_to_be_any_format); *user_dst_md = dnnl::memory::desc({lrn_src_tz}, type, format); user_dst_md->data.format_kind = dnnl_blocked; user_dst_md->data.format_desc.blocking.strides[0] = dst->stridesOf()[0]; user_dst_md->data.format_desc.blocking.strides[1] = dst->stridesOf()[dim1]; user_dst_md->data.format_desc.blocking.strides[2] = rank > 2 ? dst->stridesOf()[dim2] : 1; user_dst_md->data.format_desc.blocking.strides[3] = rank > 3 ? dst->stridesOf()[dim3] : 1; } } ////////////////////////////////////////////////////////////////////////// dnnl::engine& getEngine(void* ptr) { auto eng = reinterpret_cast(ptr); return *eng; } void checkPoolingONEDNN(Requirements& reqs, sd::graph::Context& block, sd::NDArray* in, sd::NDArray* out) { // replicate OneDNN check that was added since v1.8 // https://github.com/oneapi-src/oneDNN/blob/master/src/common/pooling.cpp#L108-L110 // if (str < 1 || dil < 0 || pad_l < 0 || pad_r + str < 0) return invalid_arguments; if (in->rankOf() > 4 && block.getIArguments()->size() > 12) { // pooling 3D sd::LongType kD = INT_ARG(0); // filter(kernel) depth sd::LongType kH = INT_ARG(1); // filter(kernel) height sd::LongType kW = INT_ARG(2); // filter(kernel) width sd::LongType sD = INT_ARG(3); // strides depth sd::LongType sH = INT_ARG(4); // strides height sd::LongType sW = INT_ARG(5); // strides width sd::LongType pD = INT_ARG(6); // paddings depth sd::LongType pH = INT_ARG(7); // paddings height sd::LongType pW = INT_ARG(8); // paddings width sd::LongType dD = INT_ARG(9); // dilations depth sd::LongType dH = INT_ARG(10); // dilations height sd::LongType dW = INT_ARG(11); // dilations width sd::LongType paddingMode = INT_ARG(12); // 1-SAME, 0-VALID // int extraParam0 = INT_ARG(13); // unnecessary for max case, required only for avg and pnorm cases int isNCDHW = block.getIArguments()->size() > 14 ? !INT_ARG(14) : 1; // 1-NDHWC, 0-NCDHW reqs.expectEq(makeInfoVariable(in->rankOf(), RANK_MSG_INPUT0), 5) && // stride >=1 reqs.expectGreaterEq(makeInfoVariable(sD, "strides#Depth"), 1) && reqs.expectGreaterEq(makeInfoVariable(sH, "strides#Height"), 1) && reqs.expectGreaterEq(makeInfoVariable(sW, "strides#Width"), 1) && // dilation >=0 reqs.expectGreaterEq(makeInfoVariable(dW, "dilation#Depth"), 0) && reqs.expectGreaterEq(makeInfoVariable(dH, "dilation#Height"), 0) && reqs.expectGreaterEq(makeInfoVariable(dW, "dilation#Width"), 0); if (reqs) { sd::LongType bS, iC, iD, iH, iW, oC, oD, oH, oW; // batch size, input channels, input depth/height/width, output channels, output depth/height/width; sd::LongType indIOioC, indIOioD, indWoC, indWiC, indWkD; // corresponding indexes ops::ConvolutionUtils::getSizesAndIndexesConv3d(isNCDHW, 0, *in, *out, bS, iC, iD, iH, iW, oC, oD, oH, oW, indIOioC, indIOioD, indWiC, indWoC, indWkD); if (paddingMode) // SAME ops::ConvolutionUtils::calcPadding3D(pD, pH, pW, oD, oH, oW, iD, iH, iW, kD, kH, kW, sD, sH, sW, dD, dH, dW); // pad_l >=0 reqs.expectGreaterEq(makeInfoVariable(pD, "padding_l#Depth"), 0) && reqs.expectGreaterEq(makeInfoVariable(pH, "padding_l#Height"), 0) && reqs.expectGreaterEq(makeInfoVariable(pW, "padding_l#Width"), 0) && // pad_r+ stride reqs.expectGreaterEq(makeInfoVariable(((oD - 1) * sD - iD + kD - pD) + sD, "padding_r#Depth + stride#Depth"), 0) && reqs.expectGreaterEq( makeInfoVariable(((oH - 1) * sH - iH + kH - pH) + sH, "padding_r#Height + stride#Height"), 0) && reqs.expectGreaterEq(makeInfoVariable(((oW - 1) * sW - iW + kW - pW) + sW, "padding_r#Width + stride#Width"), 0); } } else if (block.getIArguments()->size() > 8) { const int kH = INT_ARG(0); const int kW = INT_ARG(1); const int sH = INT_ARG(2); const int sW = INT_ARG(3); sd::LongType pH = INT_ARG(4); sd::LongType pW = INT_ARG(5); const int dH = INT_ARG(6); const int dW = INT_ARG(7); const int paddingMode = INT_ARG(8); const int isNCHW = block.getIArguments()->size() > 10 ? !INT_ARG(10) : 1; // INT_ARG(10): 1-NHWC, 0-NCHW reqs.expectEq(makeInfoVariable(in->rankOf(), RANK_MSG_INPUT0), 4) && // stride >=1 reqs.expectGreaterEq(makeInfoVariable(sH, "strides#Height"), 1) && reqs.expectGreaterEq(makeInfoVariable(sW, "strides#Width"), 1) && // dilation >=0 reqs.expectGreaterEq(makeInfoVariable(dH, "dilation#Height"), 0) && reqs.expectGreaterEq(makeInfoVariable(dW, "dilation#Width"), 0); if (reqs) { sd::LongType bS, iC, iD, iH, iW, oC, oD, oH, oW, indIOioC, indIiH, indWoC, indWiC, indWkH, indOoH; ops::ConvolutionUtils::getSizesAndIndexesConv2d(isNCHW, 0, *in, *out, bS, iC, iH, iW, oC, oH, oW, indIOioC, indIiH, indWiC, indWoC, indWkH, indOoH); if (paddingMode) { ops::ConvolutionUtils::calcPadding2D(pH, pW, oH, oW, iH, iW, kH, kW, sH, sW, dH, dW); } // pad_l >=0 reqs.expectGreaterEq(makeInfoVariable(pH, "padding_l#Height"), 0) && reqs.expectGreaterEq(makeInfoVariable(pW, "padding_l#Width"), 0) && // pad_r+ stride reqs.expectGreaterEq( makeInfoVariable(((oH - 1) * sH - iH + kH - pH) + sH, "padding_r#Height + stride#Height"), 0) && reqs.expectGreaterEq(makeInfoVariable(((oW - 1) * sW - iW + kW - pW) + sW, "padding_r#Width + stride#Width"), 0); } } return; } } // namespace onednnUtils } // namespace sd