Files
2026-07-13 12:47:05 +08:00

441 lines
20 KiB
C++

/* ******************************************************************************
*
*
* 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 <dnnl_types.h>
#include <ops/declarable/helpers/convolutions.h>
using namespace dnnl;
namespace sd {
namespace onednnUtils {
//////////////////////////////////////////////////////////////////////
void getDims(NDArray* array, const int rank, dnnl::memory::dims& mklDims) {
std::vector<int64_t> 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<int>& 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<size_t>(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<NDArray&>(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<int> permut;
if (!isNCHW) permut = rank == 4 ? std::vector<int>({0, 3, 1, 2}) : std::vector<int>({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<int, dnnl::memory> 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<int> permut;
if (!isNCHW) permut = rank == 4 ? std::vector<int>({0, 3, 1, 2}) : std::vector<int>({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<int, dnnl::memory> 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<dnnl::engine*>(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