ConvTranspose asymmetric padding

* Temporarily reverse the extra padding location in case of
SAME_UPPER vs SAME_LOWER for convTranspose to match with onnxruntime.
* In case of importing asymmetric padding convTranspose, use
symmetric pads by alter the output_shape and pads, and attach a slice
node afterwards to enable cudnn.
* Fix a bug in slice/squeeze attribute axes export.
This commit is contained in:
Bowen Bao 2018-11-13 11:46:59 -08:00
Родитель 196df1a143
Коммит d1d113322c
5 изменённых файлов: 82 добавлений и 40 удалений

Просмотреть файл

@ -535,7 +535,6 @@ private:
static onnx::TypeProto ToTypeProto(const NDShape& shape, bool hasBatchAxis = false, bool hasSequenceAxis = false, bool doReverseShape = true);
static onnx::TypeProto ToTypeProto(const std::vector<bool>& shape);
static onnx::TypeProto ToTypeProto(const std::vector<int64_t>& shape, bool doReverseVec = true);
static onnx::TypeProto ToTypeProto(const std::vector<Axis>& axes);
//
// Convert TypeProto, NDShape and various std::vector types to std::vector
@ -544,7 +543,6 @@ private:
static std::vector<int64_t> ToINTS(const NDShape& shape, bool hasBatchAxis = false);
static std::vector<int64_t> ToINTS(const std::vector<bool>& shape);
static std::vector<int64_t> ToINTS(const std::vector<int>& shape, bool doReverseVec = true);
static std::vector<int64_t> ToINTS(const std::vector<Axis>& axes);
static std::vector<float> INTSToVecFloat(const std::vector<int64_t> &ints);
static std::vector<int64_t> ConvertPermutationCNTKToONNX(const std::vector<Axis> &axes, bool hasBatchAxis);
@ -1688,23 +1686,6 @@ onnx::TypeProto CNTKToONNXHelper::ToTypeProto(const std::vector<int64_t>& shape,
return newShape;
}
onnx::TypeProto CNTKToONNXHelper::ToTypeProto(const std::vector<Axis>& axes)
{
std::vector<int> axesValue;
for (auto axis : axes)
{
axesValue.push_back(ToIndex(axis));
}
std::sort(axesValue.begin(), axesValue.end());
onnx::TypeProto newShape = MakeTypeProtoWithShape();
for (auto dimension : axesValue)
newShape.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(dimension);
return newShape;
}
// this method is to undo an idempotent convertion in sanitize_permutation:
// Find the permutation such that when it is applied to the reverse
// of an input gives the reverse of perm applied to the input
@ -1768,11 +1749,6 @@ std::vector<int64_t> CNTKToONNXHelper::ToINTS(const std::vector<int>& shape,
return ToINTS(ToTypeProto(Cast<int, int64_t>(shape), doReverseVec));
}
std::vector<int64_t> CNTKToONNXHelper::ToINTS(const std::vector<Axis>& axes)
{
return ToINTS(ToTypeProto(axes));
}
bool IsUnSupportedLayerNormalization(const FunctionPtr src)
{
std::string cntkOpName = ToLegacyString(ToUTF8(src->OpName()));
@ -5542,7 +5518,7 @@ void CNTKToONNXHelper::CopyAttributes(const FunctionPtr& src, onnxruntime::Node*
if (src->Attributes().Contains(L"axisVec"))
{
std::vector<Axis> sliceAxes = AsVector<Axis>(src->Attributes()[L"axisVec"].Value<std::vector<DictionaryValue>>());
node->AddAttribute(attributesMap[L"axes"], ToINTS(sliceAxes));
node->AddAttribute(attributesMap[L"axes"], ConvertAxesToOnnx(sliceAxes, src->Inputs()[0]));
beginIndex = AsVector<int>(src->Attributes()[L"beginIndexVec"].Value<std::vector<DictionaryValue>>());
endIndex = AsVector<int>(src->Attributes()[L"endIndexVec"].Value<std::vector<DictionaryValue>>());
@ -5661,7 +5637,10 @@ void CNTKToONNXHelper::CopyAttributes(const FunctionPtr& src, onnxruntime::Node*
{
axes.push_back((Axis)(src->Attributes()[L"axis"].Value<Axis>()));
}
node->AddAttribute("axes", ToINTS(axes));
if (axes.size() > 0)
{
node->AddAttribute("axes", ConvertAxesToOnnx(axes, src->Inputs()[0]));
}
}
else if (src->OpName() == L"Gather")
{

Просмотреть файл

@ -1431,6 +1431,8 @@ ConvAutoPadType ONNXToCNTKHelper::ConvertStrToConvAutoPadType(const string &str)
return ConvAutoPadType::SAME_UPPER;
else if (str == "SAME_LOWER" || str == "same_lower")
return ConvAutoPadType::SAME_LOWER;
else if (str == "NOTSET" || str == "notset")
return ConvAutoPadType::NOTSET;
else
LogicError("Unknown value for %s attribute: %s", "auto_pad", str.c_str());
}
@ -3322,7 +3324,10 @@ FunctionPtr ONNXToCNTKHelper::CreateCNTKConvTransposeNode(const Node *node, cons
outputShape = GetNamedAttributeAsShape(node, "output_shape", /*hasBatchAxis=*/false);
if ((outputShape.Rank() != numSpatialDim) && (outputShape.Rank() != numSpatialDim + 2))
LogicError("ConvTranspose node's output shape attribute is of unexpected length. It should be either equal to input shape length, or input shape length - 2");
padsPair = CalcPaddingFromOutputShape(inputShape, kernelShape, strides, outputShape, outputPadding, /*isSameUpper=*/false);
// NOTE: The following is for ONNX V1.3 opset8. It is subject to change in future versions.
// For convTranspose, extra pad location is flipped compared to conv/pooling. Thus the flag 'isSameUpper' is flipped to 'notSameUpper'.
const bool notSameUpper = ConvertStrToConvAutoPadType(GetNamedAttributeAsString(node, "auto_pad", "SAME_UPPER")) != ConvAutoPadType::SAME_UPPER;
padsPair = CalcPaddingFromOutputShape(inputShape, kernelShape, strides, outputShape, outputPadding, notSameUpper);
}
else if (USE_PADS)
{
@ -3345,9 +3350,10 @@ FunctionPtr ONNXToCNTKHelper::CreateCNTKConvTransposeNode(const Node *node, cons
case ConvAutoPadType::SAME_UPPER:
case ConvAutoPadType::SAME_LOWER:
{
const bool isSameUpper = auto_pad == ConvAutoPadType::SAME_UPPER;
const bool notSameUpper = auto_pad != ConvAutoPadType::SAME_UPPER;
auto outputPadding = (HasNamedAttribute(node, "output_padding")) ? GetNamedAttributeAsInt64Vec(node, "output_padding") : std::vector<int64_t>(numSpatialDim, 0);
padsPair = CalcPaddingFromOutputShape(inputShape, kernelShape, strides, outputShape, outputPadding, isSameUpper);
// For convTranspose, extra pad location is flipped compared to conv/pooling. Thus the flag 'isSameUpper' is flipped to 'notSameUpper'.
padsPair = CalcPaddingFromOutputShape(inputShape, kernelShape, strides, outputShape, outputPadding, notSameUpper);
break;
}
case ConvAutoPadType::VALID:
@ -3366,6 +3372,8 @@ FunctionPtr ONNXToCNTKHelper::CreateCNTKConvTransposeNode(const Node *node, cons
padsPair.first.push_back(0);
padsPair.second.push_back(0);
if (padsPair.first.size() != padsPair.second.size())
LogicError("ConvTranspose: producing uneven lower/upper pads rank: lower(%zu), upper(%zu). ", padsPair.first.size(), padsPair.second.size());
// CNTK only accepts outputShape in the format of
// case 1: [0]. Empty shape which tells CNTK to infer output shape from other inputs.
@ -3396,18 +3404,59 @@ FunctionPtr ONNXToCNTKHelper::CreateCNTKConvTransposeNode(const Node *node, cons
LogicError("ConvTranspose: unable to produce CNTK compatible output shape from given ONNX node. ");
}
// cuDNN couldn't support cases of asymmetric pad values.
// Solution: increase the outputShape with size of extra pads, and add a slice node after convTranspose to remove the padded values.
std::vector<int> extraUpperPads(outputShape.Rank(), 0);
if (extraUpperPads.size() > 1)
{
assert(padsPair.first.size() == padsPair.second.size());
if (padsPair.first.size() != outputShape.Rank())
LogicError("ConvTranspose: producing uneven pads rank and outputShape rank: pads(%zu), outputShape(%zu). ", padsPair.first.size(), outputShape.Rank());
for (int idx = 0; idx < outputShape.Rank() - 1; ++idx)
{
if (padsPair.second[idx] != padsPair.first[idx])
{
extraUpperPads[idx] = padsPair.second[idx] - padsPair.first[idx];
if (extraUpperPads[idx] > 0)
padsPair.second[idx] -= extraUpperPads[idx];
else
padsPair.first[idx] += extraUpperPads[idx];
outputShape[idx] += abs(extraUpperPads[idx]);
}
}
}
FunctionPtr cntkConvFunction = CreateCNTKConvTransposeNode(inputOperand, convolutionMap,
strides, sharing, padsPair.first, padsPair.second, outputShape,
dilation, reductionRank, maxTempMemSizeInSamples, node->Name());
if (std::any_of(extraUpperPads.begin(), extraUpperPads.end(), [](int i) { return i != 0; }))
{
// Add slice node to remove output values that are considered padded.
std::vector<Axis> axes;
std::vector<int> beginIndices;
std::vector<int> endIndices;
for (int idx = 0; idx < extraUpperPads.size() - 1; ++idx)
{
if (extraUpperPads[idx] != 0)
{
int extraUpperPad = extraUpperPads[idx];
axes.push_back(Axis(idx));
beginIndices.push_back(extraUpperPad > 0 ? 0 : -extraUpperPad);
endIndices.push_back(extraUpperPad > 0 ? outputShape[idx] - extraUpperPad : outputShape[idx]);
}
}
cntkConvFunction = Slice(cntkConvFunction, axes, beginIndices, endIndices);
}
// If Bias is specified in the ONNX node.
if (inputs.size() == 3)
{
NDShape shape({ 1, 1, inputs[2].Shape()[0] });
return Plus(cntkConvFunction, Reshape(inputs[2], shape));
cntkConvFunction = Plus(cntkConvFunction, Reshape(inputs[2], shape));
}
else
return cntkConvFunction;
return cntkConvFunction;
}
FunctionPtr ONNXToCNTKHelper::CreateCNTKConvTransposeNode(const Variable& inputOperand, const Variable& convolutionMap, const NDShape& strides,

Просмотреть файл

@ -22,6 +22,7 @@ namespace CNTK
VALID = 0,
SAME_UPPER = 1,
SAME_LOWER = 2,
NOTSET = 3,
};
}

Просмотреть файл

@ -760,9 +760,9 @@ bool CuDnnConvolutionEngineFactory<ElemType>::IsSupported(DEVICEID_TYPE deviceId
auto lowerPad = geometry->GetLowerPad(i);
auto upperPad = geometry->GetUpperPad(i);
auto stride = geometry->GetStride(i);
if (kernel[i] % 2 == 0 && lowerPad < upperPad && stride < input[i])
if (lowerPad < upperPad && stride < input[i])
{
fprintf(stderr, "WARNING: Detected asymmetric padding issue with even kernel size and lowerPad (%d) < higherPad (%d) (i=%d), cuDNN will not be able to produce correct result. Switch to reference engine (VERY SLOW). \n", lowerPad, upperPad, i);
fprintf(stderr, "WARNING: Detected asymmetric padding issue with lowerPad (%d) < higherPad (%d) (i=%d), cuDNN will not be able to produce correct result. Switch to reference engine (VERY SLOW). \n", lowerPad, upperPad, i);
retVal = false;
break;
}

Просмотреть файл

@ -1739,9 +1739,14 @@ def test_Slice(tmpdir, dtype):
model = C.slice(x1, 0, 1, 2)
verify_one_input(model, data, tmpdir, 'Slice_1')
model = C.slice(x1, [0,1], [1,0], [2,1]);
model = C.slice(x1, [0,1], [1,0], [2,1])
verify_one_input(model, data, tmpdir, 'Slice2_1')
data = np.asarray([[[1,1,1,1],[2,2,2,2],[3,3,2,2]], [[4,4,5,5], [5,5,6,6], [6,6,7,7]]],dtype=dtype)
x1 = C.input_variable((2,3,4))
model = C.slice(x1, [1,2], [1,0],[2,1])
verify_one_input(model, data, tmpdir, 'Slice3_1')
#Sequence.Slice
@pytest.mark.parametrize("beginIndex, endIndex", (
(-2, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-4, 2), (0, 1), (1, 2)))
@ -1827,11 +1832,19 @@ def test_Softsign(tmpdir, dtype):
verify_no_input(model, tmpdir, 'Softsign_0')
#Squeeze
#def test_Squeeze(tmpdir):
# x0 = np.arange(12).reshape((2, 2, 1, 3)).astype('f')
# x = C.input_variable((2, 1, 3))
# model = C.squeeze(x)
# verify_one_input(model, x0, tmpdir, 'Squeeze_0')
def test_Squeeze(tmpdir):
pytest.skip('TODO: need to bump ONNX CI version. ')
x0 = np.arange(6).reshape((1, 2, 1, 3)).astype('f')
x = C.input_variable((2, 1, 3))
model = C.squeeze(x, [1])
verify_one_input(model, x0, tmpdir, 'Squeeze_0')
def test_Squeeze_without_axes(tmpdir):
pytest.skip('ONNX should update attribute axes to be optional.')
x0 = np.arange(6).reshape((1, 2, 1, 3)).astype('f')
x = C.input_variable((2, 1, 3))
model = C.squeeze(x)
verify_one_input(model, x0, tmpdir, 'Squeeze_without_axes_0')
#Sum
@pytest.mark.parametrize("dtype", DType_Config)