diff --git a/inference-engine/src/cldnn_engine/ops/constant.cpp b/inference-engine/src/cldnn_engine/ops/constant.cpp index 26e8c2845ee..3e8648ddc0a 100644 --- a/inference-engine/src/cldnn_engine/ops/constant.cpp +++ b/inference-engine/src/cldnn_engine/ops/constant.cpp @@ -22,35 +22,6 @@ namespace CLDNNPlugin { -struct ConstProperties { - bool isWeights; - bool hasGroupDimension; - bool reversedChannelsOrder; -}; - -static ConstProperties getConstProperties(const std::shared_ptr& op) { - for (size_t i = 0; i < op->get_output_size(); i++) { - auto outTensors = op->get_output_target_inputs(i); - for (auto& t : outTensors) { - auto outOp = t.get_node(); - if (dynamic_cast(outOp)) { - return {t.get_index() == 1, false, false}; - } else if (dynamic_cast(outOp)) { - return {t.get_index() == 1, false, false}; - } else if (auto castedOp = dynamic_cast(outOp)) { - return {t.get_index() == 2, castedOp->get_group() > 1, false}; - } else if (dynamic_cast(outOp)) { - return {t.get_index() == 1, true, false}; - } else if (dynamic_cast(outOp)) { - return {t.get_index() == 1, false, true}; - } else if (dynamic_cast(outOp)) { - return {t.get_index() == 1, true, true}; - } - } - } - return {false, false, false}; -} - static cldnn::tensor getConstTensor(const ngraph::Shape constDims) { cldnn::tensor constTensor; switch (constDims.size()) { @@ -78,71 +49,103 @@ static cldnn::tensor getConstTensor(const ngraph::Shape constDims) { return constTensor; } +struct ConstProperties { + bool needsBatchInterpretation; + bool swapOI; + bool hasGroupDimension; +}; + +static void createClDnnConstant(Program& p, const ngraph::Shape& constDims, const std::shared_ptr& op, const ConstProperties& props); + static void CreateConstantOp(Program& p, const std::shared_ptr& op) { - auto constDims = op->get_shape(); - cldnn::tensor constTensor = getConstTensor(constDims); + const auto& constDims = op->get_shape(); + auto constUsers = op->get_output_target_inputs(0); + size_t numConstUsers = constUsers.size(); + + std::unordered_map, ConstProperties> consts = { + {op, {false, false, false}} + }; + + // handleConvWeights function is executed when one of the constant users is ConvolutionBackpropData or GroupConvolutionBackpropData. + // In that case, we mark that constant's O and I dimensions need to be swapped. + auto handleConvWeights = [&op] (ngraph::Node* conv, std::unordered_map, ConstProperties>& consts, + size_t& numConstUsers, bool hasGroupDimension) { + // If constant has multiple users - create its copy and replace 'conv' weights with the copy. + // This is to make sure that dimension change doesn't break other users of the constant node. + // It is a shallow copy, but that's fine since in createClDnnConstant + // every constant created here, gets memcopied to a brand new cldnn::memory. + if (numConstUsers > 1) { + auto constant = std::make_shared(*(op.get())); + conv->input(1).replace_source_output(constant); + consts.insert({constant, {false, true, hasGroupDimension}}); + numConstUsers--; + } else { + consts[op].swapOI = true; + consts[op].hasGroupDimension = hasGroupDimension; + } + }; // WA to inconsistency between input and const 1d tensors // For Concat along batch we go with batch interpretation // For Gather input we go with batch interpretation - bool needsBatchInterpretation = false; - if (constDims.size() == 1) { - for (size_t i = 0; i < op->get_output_size(); i++) { - auto outTensors = op->get_output_target_inputs(i); - - for (auto& t : outTensors) { - auto outOp = t.get_node(); - if (auto castedOp = dynamic_cast(outOp)) { - if (castedOp->get_axis() == 0) { - needsBatchInterpretation = true; - break; - } - } else if (ngraph::op::is_binary_elementwise_arithmetic(outOp) || - ngraph::op::is_binary_elementwise_logical(outOp) || - ngraph::is_type(outOp)) { - bool all_inputs_1d = true; - for (size_t j = 0; j < outOp->get_input_size(); j++) { - auto& in_shape = outOp->get_input_shape(j); - if (in_shape.size() > 1) - all_inputs_1d = false; - } - needsBatchInterpretation = all_inputs_1d; - break; - } else if (ngraph::is_type(outOp) || - ngraph::is_type(outOp) || - ngraph::is_type(outOp)) { - needsBatchInterpretation = true; - break; - } + // Also check if constant users is a backprop convolution - in that case O and I need to be swapped. + for (auto& node : constUsers) { + auto outOp = node.get_node(); + if (auto castedOp = dynamic_cast(outOp)) { + if (castedOp->get_axis() == 0) { + consts[op].needsBatchInterpretation = constDims.size() == 1; } + } else if (ngraph::op::is_binary_elementwise_arithmetic(outOp) || + ngraph::op::is_binary_elementwise_logical(outOp) || + ngraph::is_type(outOp)) { + bool all_inputs_1d = true; + for (size_t j = 0; j < outOp->get_input_size(); j++) { + auto& in_shape = outOp->get_input_shape(j); + if (in_shape.size() > 1) + all_inputs_1d = false; + } + consts[op].needsBatchInterpretation = all_inputs_1d && constDims.size() == 1; + } else if (ngraph::is_type(outOp) || + ngraph::is_type(outOp) || + ngraph::is_type(outOp)) { + consts[op].needsBatchInterpretation = constDims.size() == 1; + } else if (ngraph::is_type(outOp) && node.get_index() == 1) { + handleConvWeights(outOp, consts, numConstUsers, false); + } else if (ngraph::is_type(outOp) && node.get_index() == 1) { + handleConvWeights(outOp, consts, numConstUsers, true); } } - if (needsBatchInterpretation) { + for (auto& it : consts) { + createClDnnConstant(p, constDims, it.first, it.second); + } +} + +void createClDnnConstant(Program& p, const ngraph::Shape& constDims, const std::shared_ptr& op, const ConstProperties& props) { + cldnn::tensor constTensor = getConstTensor(constDims); + auto constFormat = DefaultFormatForDims(constDims.size()); + + if (props.needsBatchInterpretation) { constTensor.batch[0] = constTensor.count(); constTensor.feature[0] = 1; } - auto constFormat = DefaultFormatForDims(op->get_output_shape(0).size()); - auto prop = getConstProperties(op); - // If constDims has a dimension = 0, then create tensor with single value // TODO: check if dim=0 is a valid case if (std::accumulate(constDims.begin(), constDims.end(), 1, std::multiplies()) == 0) constTensor = cldnn::tensor{1}; // Swap O and I dimensions to match expected deconvolution weights format - bool swap_oi = prop.isWeights && prop.reversedChannelsOrder; size_t inputFeatureElements = 1; size_t outputFeatureElements = 1; size_t groups = 1; - if (swap_oi) { - size_t expected_min_rank = 2 + (prop.hasGroupDimension ? 1 : 0); + auto newDims = constDims; + if (props.swapOI) { + size_t expected_min_rank = 2 + (props.hasGroupDimension ? 1 : 0); if (expected_min_rank > constDims.size()) IE_THROW() << "Invalid constant properties or shape"; - auto newDims = constDims; - if (prop.hasGroupDimension) { + if (props.hasGroupDimension) { std::swap(newDims[2], newDims[1]); inputFeatureElements = newDims[2]; outputFeatureElements = newDims[1]; @@ -164,8 +167,7 @@ static void CreateConstantOp(Program& p, const std::shared_ptrget_data_ptr(); - - auto bufIter = p.blobMemCache.find(std::make_pair(data, constDims)); + auto bufIter = p.blobMemCache.find(std::make_pair(data, newDims)); if (bufIter != p.blobMemCache.end()) { constPrimID = bufIter->second; @@ -181,9 +183,9 @@ static void CreateConstantOp(Program& p, const std::shared_ptrget_friendly_name())); - p.blobMemCache[std::make_pair(data, constDims)] = initialconstPrimID; + p.blobMemCache[std::make_pair(data, newDims)] = initialconstPrimID; constPrimID = initialconstPrimID; } diff --git a/inference-engine/tests/functional/plugin/gpu/subgraph_tests/shared_constant.cpp b/inference-engine/tests/functional/plugin/gpu/subgraph_tests/shared_constant.cpp new file mode 100644 index 00000000000..f106acae24f --- /dev/null +++ b/inference-engine/tests/functional/plugin/gpu/subgraph_tests/shared_constant.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +namespace { + +using namespace ngraph; + +// Validate scenario where a single Constant has multiple users (like one constant is used for Convolution, ConvolutionBackpropData, Multiply, etc.) +class SharedConstant : virtual public LayerTestsUtils::LayerTestsCommon { +protected: + void SetUp() override { + targetDevice = CommonTestUtils::DEVICE_GPU; + auto type = element::f32; + Shape constShape{4, 1, 3, 3}; + Shape convInputShape{1, 1, 5, 5}; + Shape convBackpropInputShape{1, 4, 5, 5}; + Shape constGroupConvBackpropShape{2, 2, 3, 3, 3}; + auto constant = opset8::Constant::create(type, constShape, {1}); + auto input1 = std::make_shared(type, convInputShape); + auto conv = std::make_shared(input1, constant, Strides{1, 1}, CoordinateDiff{0, 0}, CoordinateDiff{0, 0}, Strides{1, 1}); + auto input2 = std::make_shared(type, convBackpropInputShape); + auto convBprop = std::make_shared(input2, constant, Strides{1, 1}, + CoordinateDiff{0, 0}, CoordinateDiff{0, 0}, Strides{1, 1}); + auto input3 = std::make_shared(type, convBackpropInputShape); + auto constantGroupConv = opset8::Constant::create(type, constGroupConvBackpropShape, {1}); + auto groupConvBprop = std::make_shared(input3, constantGroupConv, Strides{1, 1}, + CoordinateDiff{0, 0}, CoordinateDiff{0, 0}, Strides{1, 1}); + auto input4 = std::make_shared(type, constShape); + auto mul = std::make_shared(input4, constant); + auto input5 = std::make_shared(type, constGroupConvBackpropShape); + auto mul2 = std::make_shared(input5, constantGroupConv); + function = std::make_shared(NodeVector{convBprop, conv, groupConvBprop, mul2, mul}, + ParameterVector{input1, input2, input3, input4, input5}); + } +}; + +TEST_F(SharedConstant, smoke_SharedConstant) { + Run(); +} + +} // namespace