Fix slice.

This commit is contained in:
Dwayne Robinson 2020-03-27 00:48:06 -07:00
parent 13dabd97b6
commit ccb840ac99
5 changed files with 124 additions and 117 deletions

View file

@ -13,49 +13,36 @@ public:
: DmlOperator(kernelInfo),
SliceHelperBase(kernelInfo, kernelInfo.GetTensorShapeDescription(), opsetVersion)
{
uint32_t minInputCount = (opsetVersion < 10) ? 1 : 3;
ML_CHECK_VALID_ARGUMENT(kernelInfo.GetInputCount() >= minInputCount);
const uint32_t inputCount = kernelInfo.GetInputCount();
ML_CHECK_VALID_ARGUMENT((opsetVersion < 10 && inputCount == 1)
|| (opsetVersion == 10 && inputCount >= 3 && inputCount <= 5));
ML_CHECK_VALID_ARGUMENT(kernelInfo.GetOutputCount() == 1);
// TODO (23108599): Slice V10 introduces an optional "Steps" input which the kernel does not yet support.
THROW_HR_IF(E_NOTIMPL, kernelInfo.GetInputCount() > 4);
std::vector<std::optional<uint32_t>> kernelInputIndices = { 0 };
std::vector<std::optional<uint32_t>> kernelInputIndices = { 0 }; // Only bind GPU to first 'data' tensor.
DmlOperator::Initialize(kernelInfo, kernelInputIndices);
assert(m_inputTensorDescs[0].GetDimensionCount() >= gsl::narrow_cast<uint32_t>(m_offsets.size()));
assert(m_inputTensorDescs[0].GetDimensionCount() >= gsl::narrow_cast<uint32_t>(m_sizes.size()));
assert(m_inputTensorDescs[0].GetDimensionCount() >= gsl::narrow_cast<uint32_t>(m_strides.size()));
const uint32_t inputTensorRank = m_inputTensorDescs[0].GetDimensionCount();
assert(inputTensorRank >= gsl::narrow_cast<uint32_t>(m_offsets.size()));
assert(inputTensorRank >= gsl::narrow_cast<uint32_t>(m_sizes.size()));
assert(inputTensorRank >= gsl::narrow_cast<uint32_t>(m_strides.size()));
// Pad the parameters to respect DML's requirements
m_offsets.insert(
m_offsets.begin(),
m_inputTensorDescs[0].GetDimensionCount() - gsl::narrow_cast<uint32_t>(m_offsets.size()),
0);
m_sizes.insert(
m_sizes.begin(),
m_inputTensorDescs[0].GetDimensionCount() - gsl::narrow_cast<uint32_t>(m_sizes.size()),
1);
m_strides.insert(
m_strides.begin(),
m_inputTensorDescs[0].GetDimensionCount() - gsl::narrow_cast<uint32_t>(m_strides.size()),
1);
FillWithLeadingValues(/*inout*/ m_offsets, m_inputTensorDescs[0].GetDimensionCount(), 0u);
FillWithLeadingValues(/*inout*/ m_sizes, m_inputTensorDescs[0].GetDimensionCount(), 1u);
FillWithLeadingValues(/*inout*/ m_strides, m_inputTensorDescs[0].GetDimensionCount(), 1);
std::vector<DML_TENSOR_DESC> inputDescs = GetDmlInputDescs();
std::vector<DML_TENSOR_DESC> outputDescs = GetDmlOutputDescs();
DML_SLICE_OPERATOR_DESC sliceDesc = {};
DML_SLICE1_OPERATOR_DESC sliceDesc = {};
sliceDesc.InputTensor = inputDescs.data();
sliceDesc.OutputTensor = outputDescs.data();
sliceDesc.DimensionCount = gsl::narrow_cast<uint32_t>(m_offsets.size());
sliceDesc.Offsets = m_offsets.data();
sliceDesc.Sizes = m_sizes.data();
sliceDesc.Strides = m_strides.data();
sliceDesc.InputWindowOffsets = m_offsets.data();
sliceDesc.InputWindowSizes = m_sizes.data();
sliceDesc.InputWindowStrides = m_strides.data();
DML_OPERATOR_DESC opDesc = { DML_OPERATOR_SLICE, &sliceDesc };
DML_OPERATOR_DESC opDesc = { DML_OPERATOR_SLICE1, &sliceDesc };
SetDmlOperatorDesc(opDesc, kernelInfo);
}
};
@ -73,9 +60,10 @@ public:
void CALLBACK QuerySlice(IMLOperatorSupportQueryContextPrivate* context, bool *isSupported)
{
*isSupported = (context->GetInputCount() <= 4);
*isSupported = (context->GetInputCount() <= 5);
}
DML_OP_DEFINE_CREATION_FUNCTION(Slice7, DmlOperatorSliceTemplate<7>);
DML_OP_DEFINE_CREATION_FUNCTION(Slice10, DmlOperatorSliceTemplate<10>);
DML_OP_DEFINE_CREATION_FUNCTION(Slice11, DmlOperatorSliceTemplate<10>);
} // namespace Dml

View file

@ -101,6 +101,7 @@ DML_OP_EXTERN_CREATION_FUNCTION(Tile);
DML_OP_EXTERN_CREATION_FUNCTION(Concat);
DML_OP_EXTERN_CREATION_FUNCTION(Slice7);
DML_OP_EXTERN_CREATION_FUNCTION(Slice10);
DML_OP_EXTERN_CREATION_FUNCTION(Slice11);
DML_OP_EXTERN_CREATION_FUNCTION(Pad);
DML_OP_EXTERN_CREATION_FUNCTION(SpaceToDepth);
DML_OP_EXTERN_CREATION_FUNCTION(DepthToSpace);
@ -214,6 +215,7 @@ const static char* const typeNameListCast[2] = { "T1", "T2" };
const static char* const typeNameListIsNan[2] = { "T1", "T2" };
const static char* const typeNameListConstantOfShape[2] = { "T1", "T2" };
const static char* const typeNameListScatterGather[2] = { "T", "Tind" };
const static char* const typeNameListSlice10[2] = { "T", "Tind" };
const static char* const typeNameListQuantize[2] = { "T1", "T2" };
const static char* const typeNameListWhere[2] = { "B", "T" };
const static char* const typeNameListOneHot[3] = { "T1", "T2", "T3" };
@ -228,6 +230,7 @@ const static SupportedTensorDataTypes supportedTypeListTopK[2] = {SupportedTenso
const static SupportedTensorDataTypes supportedTypeListIndices[1] = { SupportedTensorDataTypes::Int32|SupportedTensorDataTypes::Int64 };
const static SupportedTensorDataTypes supportedTypeListCast[2] = { SupportedTensorDataTypes::AllScalars, SupportedTensorDataTypes::Scalars8to32 };
const static SupportedTensorDataTypes supportedTypeListScatterGather[2] = { SupportedTensorDataTypes::NumericDefault, SupportedTensorDataTypes::Int32 | SupportedTensorDataTypes::Int64 };
const static SupportedTensorDataTypes supportedTypeListSlice10[2] = { SupportedTensorDataTypes::NumericDefault, SupportedTensorDataTypes::Int32 | SupportedTensorDataTypes::Int64 };
const static SupportedTensorDataTypes supportedTypeListQuantizeLinear[2] = { SupportedTensorDataTypes::Float32 | SupportedTensorDataTypes::Int32, SupportedTensorDataTypes::UInt8 | SupportedTensorDataTypes::Int8 };
const static SupportedTensorDataTypes supportedTypeListDequantizeLinear[2] = { SupportedTensorDataTypes::Float32, SupportedTensorDataTypes::UInt8 | SupportedTensorDataTypes::Int8 | SupportedTensorDataTypes::Int32 };
const static SupportedTensorDataTypes supportedTypeListQuantize[2] = { SupportedTensorDataTypes::Float32, SupportedTensorDataTypes::UInt8 };
@ -297,7 +300,8 @@ const static OperatorRegistrationInformation operatorRegistrationInformationTabl
{REG_INFO( 7, Transpose, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 7, Concat, typeNameListDefault, supportedTypeListNumericDefault, DmGraphSupport::Supported)},
{REG_INFO_VER( 7, Slice, typeNameListDefault, supportedTypeListNumericDefault, DmGraphSupport::Supported)},
{REG_INFO_VER( 10, Slice, typeNameListDefault, supportedTypeListNumericDefault, DmGraphSupport::Supported, {1, 2, 3}, std::nullopt, QuerySlice)},
{REG_INFO_VER( 10, Slice, typeNameListSlice10, supportedTypeListSlice10, DmGraphSupport::Supported, {1, 2, 3, 4}, std::nullopt, QuerySlice)},
{REG_INFO_VER( 11, Slice, typeNameListSlice10, supportedTypeListSlice10, DmGraphSupport::Supported, {1, 2, 3, 4}, std::nullopt, QuerySlice)},
{REG_INFO( 7, Pad, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 7, SpaceToDepth, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 7, DepthToSpace, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
@ -473,7 +477,6 @@ const static OperatorRegistrationInformation operatorRegistrationInformationTabl
{REG_INFO( 11, Scan, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 11, ScatterElements, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 11, ScatterND, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 11, Slice, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 11, Softmax, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 11, Split, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},
{REG_INFO( 11, Squeeze, typeNameListDefault, supportedTypeListFloat16to32, DmGraphSupport::Supported)},

View file

@ -563,6 +563,20 @@ public:
return m_impl->GetOutputCount();
}
// Returns true if an input to the operator is valid.
// This returns false for optional omitted inputs and invalid indices.
bool IsInputValid(uint32_t inputIndex) const noexcept
{
return m_impl->IsInputValid(inputIndex);
}
// Returns true if an output to the operator is valid.
// This returns false for optional omitted inputs and invalid indices.
bool IsOutputValid(uint32_t inputIndex) const noexcept
{
return m_impl->IsOutputValid(inputIndex);
}
MLOperatorEdgeDescription GetInputEdgeDescription(uint32_t inputIndex) const
{
MLOperatorEdgeDescription ret;

View file

@ -32,21 +32,51 @@ void HandleNegativeAxes(gsl::span<int32_t> onnxAxes, uint32_t dimCount)
}
}
void FillWithLeadingValues(/*inout*/ std::vector<uint32_t>& values, uint32_t minimumElementCount, uint32_t fillValue)
void ReadCpuLocalTensorIntoInt32(
const MLOperatorTensor& tensor,
std::vector<int32_t>& result
)
{
// e.g.
// input = [6,7]
// elementCount = 4
// fillValue = 1
// output = [1,1,6,7]
result.clear();
ML_CHECK_VALID_ARGUMENT(tensor.IsCpuData(), "Tensor must be CPU Tensor.");
const size_t oldElementCount = values.size();
const size_t newElementCount = std::max(size_t(minimumElementCount), oldElementCount);
const size_t fillCount = newElementCount - oldElementCount;
const std::vector<uint32_t>& tensorDimensions = tensor.GetShape();
const uint32_t elementCount = ComputeElementCountFromDimensions(tensorDimensions);
values.resize(newElementCount);
std::copy_backward(values.data(), values.data() + oldElementCount, values.data() + fillCount);
std::fill_n(values.data(), fillCount, fillValue);
switch (tensor.GetTensorDataType())
{
case MLOperatorTensorDataType::Int32:
{
const int32_t* data = tensor.GetData<int32_t>();
result.assign(data, data + elementCount);
}
break;
case MLOperatorTensorDataType::Int64:
{
const int64_t* data = tensor.GetData<int64_t>();
for (auto d : gsl::make_span(data, data + elementCount))
{
result.push_back(gsl::narrow_cast<int32_t>(d));
}
}
break;
default:
ML_INVALID_ARGUMENT("Expecting CPU local tensor of type int32 or int64.");
break;
}
}
void DowncastDimensions(gsl::span<const int64_t> inputDimensions, std::vector<DimensionType>& outputDimensions)
{
outputDimensions.reserve(inputDimensions.size());
outputDimensions.clear();
for (int64_t dim : inputDimensions)
{
outputDimensions.push_back(gsl::narrow_cast<uint32_t>(std::clamp<int64_t>(dim, INT32_MIN, INT32_MAX)));
}
}
int64_t ReadAsInt64(MLOperatorTensorDataType tensorDataType, const void* p)
@ -1070,10 +1100,7 @@ int64_t ReadAsInt64(MLOperatorTensorDataType tensorDataType, const void* p)
// First element of shape tensor is how many dims to expand to.
std::vector<uint32_t> desiredTensorShape;
for (int64_t dim : gsl::make_span(shapeData, dimCount))
{
desiredTensorShape.push_back(gsl::narrow_cast<uint32_t>(dim));
}
DowncastDimensions(gsl::make_span(shapeData, dimCount), /*out*/ desiredTensorShape);
// Determine the broadcasted input shape.
outputDimensions = OperatorHelper::BroadcastTensorShape(actualInputTensorShape, desiredTensorShape);
@ -1098,10 +1125,7 @@ int64_t ReadAsInt64(MLOperatorTensorDataType tensorDataType, const void* p)
// First element of shape tensor is how many dims to expand to.
std::vector<uint32_t> desiredTensorShape;
for (int64_t dim : gsl::make_span(shapeData, dimCount))
{
desiredTensorShape.push_back(gsl::narrow_cast<uint32_t>(dim));
}
DowncastDimensions(gsl::make_span(shapeData, dimCount), /*out*/ desiredTensorShape);
return { std::move(EdgeShapes(desiredTensorShape)) };
}

View file

@ -75,10 +75,28 @@ void RemoveValuesByIndex(gsl::span<const uint32_t> indices, bool keepOneValue, /
values.resize(newValuesCount);
}
void FillWithLeadingValues(/*inout*/ std::vector<uint32_t>& values, uint32_t minimumElementCount, uint32_t fillValue);
template <typename T>
void FillWithLeadingValues(/*inout*/ std::vector<T>& values, uint32_t minimumElementCount, T fillValue)
{
// e.g.
// input = [6,7]
// elementCount = 4
// fillValue = 1
// output = [1,1,6,7]
const size_t oldElementCount = values.size();
const size_t newElementCount = std::max(size_t(minimumElementCount), oldElementCount);
const size_t fillCount = newElementCount - oldElementCount;
values.resize(newElementCount);
std::copy_backward(values.data(), values.data() + oldElementCount, values.data() + fillCount);
std::fill_n(values.data(), fillCount, fillValue);
}
int64_t ReadAsInt64(MLOperatorTensorDataType tensorDataType, const void* p);
void ReadCpuLocalTensorIntoInt32(const MLOperatorTensor& tensor, std::vector<int32_t>& result);
class EdgeShapes {
public:
EdgeShapes() = default;
@ -503,56 +521,25 @@ class SplitHelper {
class SliceHelperBase
{
public:
template<typename Info_t, typename Index_t>
template<typename Info_t>
void ReadIndexTensors(
const Info_t& operatorInfo,
std::vector<int32_t>& starts,
std::vector<int32_t>& ends,
std::vector<int32_t>& axes,
std::vector<int32_t>& steps
)
/*out*/ std::vector<int32_t>& starts,
/*out*/ std::vector<int32_t>& ends,
/*out*/ std::vector<int32_t>& axes,
/*out*/ std::vector<int32_t>& steps
)
{
// Get starts, ends, optional axes and optional steps from constant inputs.
MLOperatorTensor startsTensor = operatorInfo.GetConstantInputTensor(1);
const std::vector<uint32_t>& startsTensorDimensions = startsTensor.GetShape();
size_t dimCount = startsTensorDimensions[0];
const Index_t* startsData = startsTensor.GetData<Index_t>();
for (size_t i = 0; i < dimCount; ++i)
// Get starts, ends, optional axes, and optional steps from constant inputs.
ReadCpuLocalTensorIntoInt32(operatorInfo.GetConstantInputTensor(1), /*out*/ starts);
ReadCpuLocalTensorIntoInt32(operatorInfo.GetConstantInputTensor(2), /*out*/ ends);
if (operatorInfo.IsInputValid(3))
{
starts.push_back(gsl::narrow_cast<int32_t>(startsData[i]));
ReadCpuLocalTensorIntoInt32(operatorInfo.GetConstantInputTensor(3), /*out*/ axes);
}
MLOperatorTensor endsTensor = operatorInfo.GetConstantInputTensor(2);
const std::vector<uint32_t>& endsTensorDimensions = endsTensor.GetShape();
dimCount = endsTensorDimensions[0];
const Index_t* endsData = endsTensor.GetData<Index_t>();
for (size_t i = 0; i < dimCount; ++i)
if (operatorInfo.IsInputValid(4))
{
ends.push_back(gsl::narrow_cast<int32_t>(endsData[i]));
}
uint32_t inputCount = operatorInfo.GetInputCount();
if (inputCount > 3)
{
MLOperatorTensor axesTensor = operatorInfo.GetConstantInputTensor(3);
const std::vector<uint32_t>& axesTensorDimensions = axesTensor.GetShape();
dimCount = axesTensorDimensions[0];
const Index_t* axesData = axesTensor.GetData<Index_t>();
for (size_t i = 0; i < dimCount; ++i)
{
axes.push_back(gsl::narrow_cast<int32_t>(axesData[i]));
}
}
if (inputCount > 4)
{
MLOperatorTensor stepsTensor = operatorInfo.GetConstantInputTensor(4);
const std::vector<uint32_t>& stepsTensorDimensions = stepsTensor.GetShape();
dimCount = stepsTensorDimensions[0];
const Index_t* stepsData = stepsTensor.GetData<Index_t>();
for (size_t i = 0; i < dimCount; ++i)
{
steps.push_back(gsl::narrow_cast<int32_t>(stepsData[i]));
}
ReadCpuLocalTensorIntoInt32(operatorInfo.GetConstantInputTensor(4), /*out*/ steps);
}
}
@ -567,29 +554,23 @@ public:
std::vector<int32_t> ends;
std::vector<int32_t> axes;
std::vector<int32_t> steps;
if (opsetVersion == 7)
{
// Get starts, ends and axes from attributes
// Read starts, ends, and axes from attributes.
starts = operatorInfo.GetOptionalAttributeVectorInt32(AttrName::Starts);
ends = operatorInfo.GetOptionalAttributeVectorInt32(AttrName::Ends);
axes = operatorInfo.GetOptionalAttributeVectorInt32(AttrName::Axes);
}
else if (opsetVersion == 10)
{
if (operatorInfo.GetConstantInputTensor(1).GetTensorDataType() == MLOperatorTensorDataType::Int32)
{
ReadIndexTensors<Info_t, int32_t>(operatorInfo, starts, ends, axes, steps);
}
else
{
THROW_HR_IF(E_INVALIDARG, operatorInfo.GetConstantInputTensor(1).GetTensorDataType() != MLOperatorTensorDataType::Int64);
ReadIndexTensors<Info_t, int64_t>(operatorInfo, starts, ends, axes, steps);
}
// Read starts, ends, and axes from tensors.
ReadIndexTensors(operatorInfo, /*out*/ starts, /*out*/ ends, /*out*/ axes, /*out*/ steps);
}
const uint32_t dimCount = gsl::narrow_cast<int32_t>(inputDimensions.size());
HandleNegativeAxes(/*inout*/ axes, dimCount);
const uint32_t inputDimensionCount = gsl::narrow_cast<int32_t>(inputDimensions.size());
HandleNegativeAxes(/*inout*/ axes, inputDimensionCount);
ML_CHECK_VALID_ARGUMENT(starts.size() == ends.size(), "'starts' must equal 'ends' in size.");
ML_CHECK_VALID_ARGUMENT(axes.empty() || starts.size() == axes.size(), "'axes' must equal 'starts' in size, or 'axes' must be empty.");
@ -604,18 +585,14 @@ public:
// Clamp selected dimensions to given 'starts' and 'ends'.
for (int i = 0, ci = gsl::narrow_cast<int>(starts.size()); i < ci; ++i)
{
int dimIndex = i;
if (!axes.empty())
{
dimIndex = axes[i];
}
int dimIndex = axes.empty() ? i : axes[i];
ML_CHECK_VALID_ARGUMENT(dimIndex < inputDimensions.size(), "'axes' must be valid with within actual input dimensions.");
// Positive values are offsets from 0.
// Negative values are offsets from the dimension's size.
int dim = gsl::narrow_cast<int>(inputDimensions[dimIndex]);
int start = (starts[i] < 0) ? (starts[i] + dim) : starts[i];
int end = (ends[i] < 0) ? (ends[i] + dim) : ends[i];
int start = (starts[i] < 0 && starts[i] > INT_MIN) ? (starts[i] + dim) : starts[i];
int end = (ends[i] < 0 && ends[i] < INT_MAX) ? (ends[i] + dim) : ends[i];
// Clamp the dimensions to the slice extents.
// Clamp negative numbers to 0, per case test_slice_start_out_of_bounds.
@ -1195,6 +1172,7 @@ using ShapeInferenceHelper_Transpose = TransposeHelper;
using ShapeInferenceHelper_Concat = ConcatHelper;
using ShapeInferenceHelper_Slice7 = SliceHelper;
using ShapeInferenceHelper_Slice10 = Slice10Helper;
using ShapeInferenceHelper_Slice11 = Slice10Helper; // No functional change from 10.
using ShapeInferenceHelper_Pad = PaddingHelper;
using ShapeInferenceHelper_SpaceToDepth = SpaceToDepthHelper;
using ShapeInferenceHelper_DepthToSpace = DepthToSpaceHelper;