ORT DirectML EP for Iron release, ONNX 1.5 (part 2) (#5263)

* Merged PR 5195856: Fix broken cases of zero size tensors in Cast/Reduce

 MaskRCNN failed when `Cast` tried to execute `Xor` with emptiness (zero in dimensions). This is perfectly legal and should be treated as a nop.

Ultimately DML itself should treat this case as a nop, just like how C's `memcpy` treats 0 count as a nop, but I'm just addressing it in ORT now, as enabling it in DML would impact more operators to be consistent (probably should incrementally add a flag to tensor validation so operators can be opted in gradually).

Corresponding WindowsAI PR: https://microsoft.visualstudio.com/WindowsAI/_git/WindowsAI/pullrequest/5195850

Related work items: #27469839, #28761382

* Merged PR 5201369: Remove copy of initializers added in DMLXP refactor

When used in ORT, a common method shouldn't copy and return initializer data

Related work items: #29514403

Co-authored-by: Justin Stoecker <justoeck@microsoft.com>
Co-authored-by: Jeff Bloomfield <jeffbloo@microsoft.com>
This commit is contained in:
Dwayne Robinson 2020-09-23 01:56:19 -07:00 committed by Tianlei Wu
parent eb75b492cc
commit b648fe5f74
10 changed files with 41 additions and 18 deletions

View file

@ -89,7 +89,7 @@ namespace Dml
m_nonOwnedGraphInputsFromInitializers.resize(graphInputCount);
std::vector<Microsoft::WRL::ComPtr<ID3D12Resource>> initializeResourceRefs;
GraphKernelHelper::PopulateInputBindings(
GraphKernelHelper::ProcessInputData(
m_provider.Get(),
m_winmlProvider.Get(),
m_inputsConstant,
@ -101,6 +101,7 @@ namespace Dml
initInputResources,
m_nonOwnedGraphInputsFromInitializers,
initializeResourceRefs,
nullptr,
transferredInitializerMap);
DML_GRAPH_DESC dmlGraphDesc = {};

View file

@ -133,7 +133,7 @@ namespace GraphKernelHelper
return true;
};
std::vector<std::vector<std::byte>> PopulateInputBindings(
void ProcessInputData(
Dml::IExecutionProvider* provider,
IWinmlExecutionProvider* winmlProvider,
const std::vector<uint8_t>& inputsConstant,
@ -145,10 +145,9 @@ namespace GraphKernelHelper
_Out_ std::vector<ComPtr<ID3D12Resource>>& initInputResources,
_Out_ std::vector<ComPtr<ID3D12Resource>>& nonOwnedGraphInputsFromInitializers,
_Out_ std::vector<ComPtr<ID3D12Resource>>& initializeResourceRefs,
_Out_opt_ std::vector<std::vector<std::byte>>* inputRawData,
_Inout_ std::unordered_map<std::string, onnx::TensorProto>& transferredInitializerMap)
{
std::vector<std::vector<std::byte>> inputRawData;
const uint32_t graphInputCount = kernelInfo.GetInputCount();
// Determine the last input which uses an initializer, so initializers can be freed incrementally
// while processing each input in order.
@ -166,6 +165,7 @@ namespace GraphKernelHelper
for (const DML_INPUT_GRAPH_EDGE_DESC& edge : graphDesc.inputEdges) {
inputsUsed[edge.GraphInputIndex] = true;
}
for (uint32_t i = 0; i < initInputBindings.size(); i++)
{
// If the input isn't actually used by the graph, nothing ever needs to be bound (either for
@ -173,7 +173,12 @@ namespace GraphKernelHelper
if (!inputsUsed[i])
{
transferredInitializerMap.erase(fusedNodeInputDefs[i]->Name());
inputRawData.push_back(std::vector<std::byte>());
if (inputRawData)
{
inputRawData->push_back(std::vector<std::byte>());
}
continue;
}
@ -202,7 +207,10 @@ namespace GraphKernelHelper
// Tensor sizes in DML must be a multiple of 4 bytes large.
tensorByteSize = AlignToPow2<size_t>(tensorByteSize, 4);
inputRawData.push_back(std::vector<std::byte>(tensorPtr, tensorPtr + tensorByteSize));
if (inputRawData)
{
inputRawData->push_back(std::vector<std::byte>(tensorPtr, tensorPtr + tensorByteSize));
}
if (!inputsConstant[i])
{
@ -243,8 +251,12 @@ namespace GraphKernelHelper
THROW_HR_IF(E_UNEXPECTED, !kernelInfo.TryGetConstantInput(i, &inputTensor));
const std::byte* tensorData = reinterpret_cast<const std::byte*>(inputTensor->DataRaw());
inputRawData.push_back(
std::vector<std::byte>(tensorData, tensorData + inputTensor->SizeInBytes()));
if (inputRawData)
{
inputRawData->push_back(
std::vector<std::byte>(tensorData, tensorData + inputTensor->SizeInBytes()));
}
uint64_t allocId;
UnwrapTensor(winmlProvider, inputTensor, &initInputBindings[i].Buffer, &allocId);
@ -253,15 +265,14 @@ namespace GraphKernelHelper
initInputBindings[i].Buffer->Release(); // Avoid holding an additional reference
initInputResources.push_back(initInputBindings[i].Buffer);
}
else
else if (inputRawData)
{
inputRawData.push_back(std::vector<std::byte>());
inputRawData->push_back(std::vector<std::byte>());
}
}
// All initializers should have been consumed and freed above
assert(transferredInitializerMap.empty());
return inputRawData;
}
void ConvertGraphDesc(

View file

@ -39,7 +39,7 @@ namespace GraphKernelHelper
const onnxruntime::ConstPointerContainer<std::vector<onnxruntime::NodeArg*>>& fusedNodeInputDefs,
const std::unordered_map<std::string, onnx::TensorProto>& transferredInitializerMap);
std::vector<std::vector<std::byte>> PopulateInputBindings(
void ProcessInputData(
Dml::IExecutionProvider* provider,
IWinmlExecutionProvider* winmlProvider,
const std::vector<uint8_t>& inputsConstant,
@ -51,6 +51,7 @@ namespace GraphKernelHelper
_Out_ std::vector<ComPtr<ID3D12Resource>>& initInputResources,
_Out_ std::vector<ComPtr<ID3D12Resource>>& nonOwnedGraphInputsFromInitializers,
_Out_ std::vector<ComPtr<ID3D12Resource>>& initializeResourceRefs,
_Out_opt_ std::vector<std::vector<std::byte>>* inputRawData,
_Inout_ std::unordered_map<std::string, onnx::TensorProto>& transferredInitializerMap);
void ConvertGraphDesc(

View file

@ -483,7 +483,7 @@ namespace Dml
(requiredInputCount == std::nullopt || *requiredInputCount == node.InputDefs().size()))
{
*isDmlGraphNode = true;
graphNodeProperty.first->second.internalRegInfo = internalRegInfo;
graphNodeProperty.first->second.internalRegInfo = internalRegInfo;
}
}
}

View file

@ -336,6 +336,11 @@ namespace Dml
ComPtr<IDMLCompiledOperator> DmlOperator::InitializeZeroInt64Tensor(uint64_t tensorSizeInBytes)
{
if (tensorSizeInBytes == 0)
{
return nullptr; // No work to do.
}
// This fun little solution uses DML's element-wise shader with XOR to zero the memory of the passed-in
// tensor. This requires that the tensor's memory has been initialized (i.e. raw_mutable_data has been
// called, and there is a size to the tensor). The tensor is XOR'd with itself to produce zeros,

View file

@ -79,6 +79,8 @@ namespace Dml
// will read the full 64-bit values. This means it is necessary to zero out the memory to ensure there
// are no uninitialized values in the upper 32-bit portion of the tensor memory.
//
// It returns nullptr if there is no work to do (0 bytes).
//
ComPtr<IDMLCompiledOperator> InitializeZeroInt64Tensor(uint64_t tensorSizeInBytes);
void ExecuteZeroInt64Tensor(IDMLCompiledOperator* compiledOperator, IMLOperatorTensor* tensor);

View file

@ -42,9 +42,9 @@ public:
std::vector<IMLOperatorTensor*> outputTensors = GetOutputTensorsForExecute(kernelContext);
// Zero the output tensor's memory for 64-bit integer emulation with strides.
if (m_toDataType == MLOperatorTensorDataType::UInt64 || m_toDataType == MLOperatorTensorDataType::Int64)
if (m_zeroOperator)
{
assert(m_zeroOperator);
assert(m_toDataType == MLOperatorTensorDataType::UInt64 || m_toDataType == MLOperatorTensorDataType::Int64);
ExecuteZeroInt64Tensor(m_zeroOperator.Get(), outputTensors[0]);
}

View file

@ -58,7 +58,10 @@ public:
std::vector<IMLOperatorTensor*> inputTensors = GetInputTensorsForExecute(kernelContext);
std::vector<IMLOperatorTensor*> outputTensors = GetOutputTensorsForExecute(kernelContext);
ExecuteZeroInt64Tensor(m_zeroOperator.Get(), outputTensors[1]);
if (m_zeroOperator)
{
ExecuteZeroInt64Tensor(m_zeroOperator.Get(), outputTensors[1]);
}
THROW_IF_FAILED(m_executionProvider->ExecuteOperator(
m_compiledOperator.Get(),

View file

@ -215,7 +215,7 @@ namespace Dml
return std::nullopt;
}
// All fused ops just have "Fused" prepended to their name
// All fused ops just have "Fused" prepended to their name (e.g. "Conv" -> "FusedConv").
std::string fusedOpType = std::string("Fused").append(candidateOpType);
return FusedOpProperties{ std::move(fusedOpType), onnxruntime::kMSDmlDomain };

View file

@ -46,7 +46,7 @@ namespace Dml
gsl::span<const uint32_t> GetSizes() const { return { m_sizes, m_sizes + m_bufferTensorDesc.DimensionCount }; }
gsl::span<const uint32_t> GetStrides() const;
inline UINT64 GetBufferSizeInBytes() const
inline uint64_t GetBufferSizeInBytes() const
{
assert(m_tensorType == DML_TENSOR_TYPE_BUFFER);
return m_bufferTensorDesc.TotalTensorSizeInBytes;