Bindning i DirectML

I DirectML refererar bindning till fastsättningen av resurser till pipelinen som GPU:n ska använda under initieringen och körningen av dina maskininlärningsoperationer. Dessa resurser kan vara in- och utdata tensorer, till exempel, samt eventuella tillfälliga eller beständiga resurser som operatorn behöver.

Det här avsnittet beskriver bindningens konceptuella och procedurmässiga detaljer. Vi rekommenderar att du också fullt ut läser dokumentationen för de API:er som du anropar, inklusive parametrar och kommentarer.

Viktiga idéer i bokbinderi

Listan med steg nedan innehåller en beskrivning på hög nivå av bindningsrelaterade uppgifter. Du måste följa dessa steg varje gång du kör en dispatchbar—en dispatchbar är antingen en operator-initierare eller en kompilerad operator. De här stegen introducerar viktiga idéer, strukturer och metoder som ingår i DirectML-bindning.

Efterföljande avsnitt i det här avsnittet går djupare och förklarar dessa bindningsuppgifter mer detaljerat, med illustrativa kodfragment som hämtats från det minimala directML-programkodexemplet .

  • Anropa IDMLDispatchable::GetBindingProperties på den sändningsbara filen för att avgöra hur många deskriptorer den behöver och även dess tillfälliga/beständiga resursbehov.
  • Skapa en Direct3D 12-beskrivande heap som är tillräckligt stor för deskriptorerna och binda den till pipelinen.
  • Anropa IDMLDevice::CreateBindingTable för att skapa en DirectML-bindningstabell som representerar de resurser som är bundna till pipelinen. Använd DML_BINDING_TABLE_DESC strukturen för att beskriva din bindningstabell, inklusive delmängden av deskriptorer som den pekar på i deskriptorheapen.
  • Skapa tillfälliga/beständiga resurser som Direct3D 12-buffertresurser, beskriv dem med DML_BUFFER_BINDING och DML_BINDING_DESC strukturer och lägg till dem i bindningstabellen.
  • Om dispatchable är en kompilerad operator skapar du en buffert med tensor-element som en Direct3D 12-buffertresurs. Fyll i/ladda upp den, beskriv den med DML_BUFFER_BINDING och DML_BINDING_DESC strukturer och lägg till den i bindningstabellen.
  • Skicka bindningstabellen som en parameter när du anropar IDMLCommandRecorder::RecordDispatch.

Hämta bindningsegenskaperna för en sändningsbar

Den DML_BINDING_PROPERTIES strukturen beskriver bindningsbehoven för en sändningsbar (operatorinitierare eller kompilerad operator). Dessa bindningsrelaterade egenskaper inkluderar antalet deskriptorer som du bör binda till det sändningsbara objektet, samt storleken i byte för alla tillfälliga och/eller beständiga resurser som behövs.

Anmärkning

Även för flera operatorer av samma typ ska du inte göra antaganden om att de har samma bindningskrav. Fråga bindningsegenskaperna för varje initierare och operator som du skapar.

Anropa IDMLDispatchable::GetBindingProperties för att hämta en DML_BINDING_PROPERTIES.

winrt::com_ptr<::IDMLCompiledOperator> dmlCompiledOperator;
// Code to create and compile a DirectML operator goes here.

DML_BINDING_PROPERTIES executeDmlBindingProperties{
    dmlCompiledOperator->GetBindingProperties()
};

winrt::com_ptr<::IDMLOperatorInitializer> dmlOperatorInitializer;
// Code to create a DirectML operator initializer goes here.

DML_BINDING_PROPERTIES initializeDmlBindingProperties{
    dmlOperatorInitializer->GetBindingProperties()
};

UINT descriptorCount = ...

Värdet descriptorCount som du hämtar här avgör, (minsta), storleken på beskrivningshögen och bindningstabellen som du skapar i de kommande två stegen.

DML_BINDING_PROPERTIES innehåller också en TemporaryResourceSize medlem, vilket är den minsta storleken i byte för den tillfälliga resursen som måste bindas till bindningstabellen för det här sändningsbara objektet. Värdet noll innebär att en tillfällig resurs inte krävs.

Och en PersistentResourceSize medlem, som är den minsta storleken i byte för den beständiga resursen som måste bindas till bindningstabellen för det här sändningsbara objektet. Värdet noll innebär att en beständig resurs inte krävs. En beständig resurs, om en behövs, måste anges under initieringen av en kompilerad operator (där den är bunden som utdata från operatorinitieraren) samt under körningen. Det finns mer om detta senare i det här avsnittet. Endast kompilerade operatorer har beständiga resurser – operatorinitierare returnerar alltid värdet 0 för den här medlemmen.

Om du anropar IDMLDispatchable::GetBindingProperties på en operatorinitierare både före och efter ett anrop till IDMLOperatorInitializer::Reset är de två uppsättningarna med bindningsegenskaper som hämtats inte garanterat identiska.

Beskriv, skapa och bind en deskriptorkö

När det gäller deskriptorer börjar och slutar ditt ansvar med själva deskriptorheapen. DirectML sköter själv att skapa och hantera deskriptorerna i den heap som du tillhandahåller.

Använd därför en D3D12_DESCRIPTOR_HEAP_DESC struktur för att beskriva en hög som är tillräckligt stor för det antal deskriptorer som den sändningsbara behöver. Skapa den sedan med ID3D12Enhet::CreateDescriptorHeap. Och slutligen anropa ID3D12GraphicsCommandList::SetDescriptorHeaps för att koppla din deskriptorhög till pipelinesystemet.

winrt::com_ptr<::ID3D12DescriptorHeap> d3D12DescriptorHeap;

D3D12_DESCRIPTOR_HEAP_DESC descriptorHeapDescription{};
descriptorHeapDescription.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
descriptorHeapDescription.NumDescriptors = descriptorCount;
descriptorHeapDescription.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;

winrt::check_hresult(
    d3D12Device->CreateDescriptorHeap(
        &descriptorHeapDescription,
        _uuidof(d3D12DescriptorHeap),
        d3D12DescriptorHeap.put_void()
    )
);

std::array<ID3D12DescriptorHeap*, 1> d3D12DescriptorHeaps{ d3D12DescriptorHeap.get() };
d3D12GraphicsCommandList->SetDescriptorHeaps(
    static_cast<UINT>(d3D12DescriptorHeaps.size()),
    d3D12DescriptorHeaps.data()
);

Beskriva och skapa en bindningstabell

En DirectML-bindningstabell representerar de resurser som du binder till en pipeline för ett anrop som kan använda dem. Dessa resurser kan vara indata- och utdata tensorer (eller andra parametrar) för en operator, eller så kan de vara olika beständiga och tillfälliga resurser som en sändningsbar funktion fungerar med.

Använd strukturen DML_BINDING_TABLE_DESC för att beskriva din bindningstabell, inklusive den dispatchbara som bindningstabellen ska representera bindningarna för, och intervallet med deskriptorer (från deskriptorshögen som du just skapade) som du vill att bindningstabellen ska referera till (och till vilken DirectML kan skriva deskriptorer). Värdet descriptorCount (en av bindningsegenskaperna som vi hämtade i det första steget) anger vilken minsta storlek som i beskrivningar är för bindningstabellen som krävs för det sändningsbara objektet. Här använder vi det värdet för att ange det maximala antalet deskriptorer som DirectML får skriva till vår heap, från de angivna startpunkterna för både CPU- och GPU-deskriptorhandtag.

Anropa sedan IDMLDevice::CreateBindingTable för att skapa DirectML-bindningstabellen. I senare steg lägger vi till dessa resurser i bindningstabellen när vi har skapat ytterligare resurser för det sändningsbara objektet.

I stället för att skicka en DML_BINDING_TABLE_DESC till det här anropet kan du skicka nullptr, vilket anger en tom bindningstabell.

DML_BINDING_TABLE_DESC dmlBindingTableDesc{};
dmlBindingTableDesc.Dispatchable = dmlOperatorInitializer.get();
dmlBindingTableDesc.CPUDescriptorHandle = d3D12DescriptorHeap->GetCPUDescriptorHandleForHeapStart();
dmlBindingTableDesc.GPUDescriptorHandle = d3D12DescriptorHeap->GetGPUDescriptorHandleForHeapStart();
dmlBindingTableDesc.SizeInDescriptors = descriptorCount;

winrt::com_ptr<::IDMLBindingTable> dmlBindingTable;
winrt::check_hresult(
    dmlDevice->CreateBindingTable(
        &dmlBindingTableDesc,
        __uuidof(dmlBindingTable),
        dmlBindingTable.put_void()
    )
);

Ordningen i vilken DirectML skriver deskriptorer i heapen är ospecificerad, så ditt program måste vara noga med att inte skriva över de beskrivningar som omsluts av bindningstabellen. De angivna CPU- och GPU-beskrivningshandtagen kan komma från olika högar, men det är ditt programs ansvar att se till att hela deskriptorintervallet som refereras till av CPU-beskrivningshandtaget kopieras till det intervall som anges av GPU-beskrivningshandtaget före exekvering med den här bindningstabellen. Den beskrivande heap som handtagen levereras från måste ha typ D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV. Dessutom måste heapen som avses av GPUDescriptorHandle vara en shader-synlig deskriptorheap.

Du kan återställa en bindningstabell för att ta bort alla resurser som du har lagt till i den, samtidigt som du ändrar alla egenskaper som du har angett på den ursprungliga DML_BINDING_TABLE_DESC (om du vill omsluta ett nytt intervall med deskriptorer eller för att återanvända den för en annan sändningsbar fil). Gör bara ändringarna i beskrivningsstrukturen och anropa IDMLBindingTable::Reset.

dmlBindingTableDesc.Dispatchable = pIDMLCompiledOperator.get();

winrt::check_hresult(
    pIDMLBindingTable->Reset(
        &dmlBindingTableDesc
    )
);

Beskriva och binda eventuella tillfälliga/beständiga resurser

Den DML_BINDING_PROPERTIES struktur som vi fyllde i när vi hämtade bindningsegenskaperna för vår sändningsbara innehåller storleken i byte för alla tillfälliga och/eller beständiga resurser som den sändningsbara resursen behöver. Om någon av dessa storlekar inte är noll skapar du en Direct3D 12-buffertresurs och lägger till den i bindningstabellen.

I kodexemplet nedan skapar vi en tillfällig resurs (temporaryResourceSize byte i storlek) för den sändningsbara resursen. Vi beskriver hur vi vill binda resursen och sedan lägger vi till bindningen i bindningstabellen.

Eftersom vi binder en enda buffertresurs beskriver vi vår bindning med en DML_BUFFER_BINDING struktur. I den strukturen anger vi Direct3D 12-buffertresursen (resursen måste ha dimensionen D3D12_RESOURCE_DIMENSION_BUFFER) samt en förskjutning och storlek i bufferten. Det går också att beskriva en bindning för en matris med buffertar (i stället för för en enda buffert) och den DML_BUFFER_ARRAY_BINDING strukturen finns för det ändamålet.

För att ta bort skillnaden mellan en buffertbindning och en buffertmatrisbindning använder vi den DML_BINDING_DESC strukturen. Du kan ange medlemmen i TypeDML_BINDING_DESC till antingen DML_BINDING_TYPE_BUFFER eller DML_BINDING_TYPE_BUFFER_ARRAY. Och du kan sedan ange Desc att medlemmen ska peka på antingen en DML_BUFFER_BINDING eller till en DML_BUFFER_ARRAY_BINDING, beroende på Type.

Vi hanterar den tillfälliga resursen i det här exemplet, så vi lägger till den i bindningstabellen med ett anrop till IDMLBindingTable::BindTemporaryResource.

D3D12_HEAP_PROPERTIES defaultHeapProperties{ CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT) };
winrt::com_ptr<::ID3D12Resource> temporaryBuffer;

D3D12_RESOURCE_DESC temporaryBufferDesc{ CD3DX12_RESOURCE_DESC::Buffer(temporaryResourceSize) };
winrt::check_hresult(
    d3D12Device->CreateCommittedResource(
        &defaultHeapProperties,
        D3D12_HEAP_FLAG_NONE,
        &temporaryBufferDesc,
        D3D12_RESOURCE_STATE_COMMON,
        nullptr,
        __uuidof(temporaryBuffer),
        temporaryBuffer.put_void()
    )
);

DML_BUFFER_BINDING bufferBinding{ temporaryBuffer.get(), 0, temporaryResourceSize };
DML_BINDING_DESC bindingDesc{ DML_BINDING_TYPE_BUFFER, &bufferBinding };
dmlBindingTable->BindTemporaryResource(&bindingDesc);

En tillfällig resurs (om en resurs behövs) är ett minnesminne som används internt under körningen av operatorn, så du behöver inte bry dig om innehållet. Du behöver inte heller behålla det efter anropet till IDMLCommandRecorder::RecordDispatch har slutförts på GPU:n. Det innebär att ditt program kan släppa eller skriva över den tillfälliga resursen mellan sändningarna av den kompilerade operatorn. Det angivna buffertintervallet som ska bindas som den tillfälliga resursen måste ha sin startförskjutning justerad till DML_TEMPORARY_BUFFER_ALIGNMENT. Typen av heap som ligger bakom bufferten måste vara D3D12_HEAP_TYPE_DEFAULT.

Om den distribuerbara enheten rapporterar en icke-nollstorlek för dess mer långlivade persistenta resurs, är proceduren dock lite annorlunda. Du bör skapa en buffert och beskriva en bindning enligt samma mönster som ovan. Men lägg till den i operatorinitierarens bindningstabell med ett anrop till IDMLBindingTable::BindOutputs, eftersom det är operatorinitierarens jobb att initiera den beständiga resursen. Lägg sedan till den i den kompilerade operatorns bindningstabell med ett anrop till IDMLBindingTable::BindPersistentResource. Se det minsta exemplet på DirectML-programkod för att se det här arbetsflödet i praktiken. Den beständiga resursens innehåll och livslängd måste finnas kvar så länge som den kompilerade operatorn gör det. Om en operatör kräver en beständig resurs måste programmet ange den under initieringen och därefter även ange den till alla framtida körningar av operatorn utan att ändra innehållet. Den beständiga resursen används vanligtvis av DirectML för att lagra uppslagstabeller eller andra långlivade data som beräknas under initieringen av en operator och återanvänds vid framtida körningar av operatorn. Det angivna buffertintervallet som ska bindas som den beständiga bufferten måste ha sin startförskjutning justerad till DML_PERSISTENT_BUFFER_ALIGNMENT. Typen av heap som ligger bakom bufferten måste vara D3D12_HEAP_TYPE_DEFAULT.

Beskriva och binda tensorer

Om du har att göra med en kompilerad operator (i stället för en operatorinitierare) måste du binda in- och utdataresurser (för tensorer och andra parametrar) till operatorns bindningstabell. Antalet bindningar måste exakt matcha antalet indata för operatorn, inklusive valfria tensorer. De specifika in- och utdata-tensorer och andra parametrar som en operator tar dokumenteras i ämnet för den operatorn (till exempel DML_ELEMENT_WISE_IDENTITY_OPERATOR_DESC).

En tensorresurs är en buffert som innehåller tensorns enskilda elementvärden. Du laddar upp och läser upp en sådan buffert till/från GPU:n med hjälp av vanliga Direct3D 12-tekniker (Ladda upp resurser och läsa tillbaka data via en buffert). Se det minimala exemplet på DirectML-programkod för att se de här teknikerna i praktiken.

Beskriv slutligen dina indata- och utdataresursbindningar med DML_BUFFER_BINDING och DML_BINDING_DESC strukturer och lägg sedan till dem i den kompilerade operatorns bindningstabell med anrop till IDMLBindingTable::BindInputs och IDMLBindingTable::BindOutputs. När du anropar en IDMLBindingTable::Bind*- metod skriver DirectML en eller flera deskriptorer i intervallet med CPU-beskrivningar.

DML_BUFFER_BINDING inputBufferBinding{ inputBuffer.get(), 0, tensorBufferSize };
DML_BINDING_DESC inputBindingDesc{ DML_BINDING_TYPE_BUFFER, &inputBufferBinding };
dmlBindingTable->BindInputs(1, &inputBindingDesc);

DML_BUFFER_BINDING outputBufferBinding{ outputBuffer.get(), 0, tensorBufferSize };
DML_BINDING_DESC outputBindingDesc{ DML_BINDING_TYPE_BUFFER, &outputBufferBinding };
dmlBindingTable->BindOutputs(1, &outputBindingDesc);

Ett av stegen i att skapa en DirectML-operator (se IDMLDevice::CreateOperator) är att deklarera en eller flera DML_BUFFER_TENSOR_DESC strukturer för att beskriva de tensordatabuffertar som operatorn tar och returnerar. Förutom tensorbuffertens typ och storlek kan du också ange flaggan DML_TENSOR_FLAG_OWNED_BY_DML .

DML_TENSOR_FLAG_OWNED_BY_DML anger att tensor-data ska ägas och hanteras av DirectML. DirectML gör en kopia av tensordata under initieringen av operatorn och lagrar dem i den beständiga resursen. På så sätt kan DirectML utföra omformatering av tensordata i andra, effektivare former. Att ange den här flaggan kan öka prestandan, men det är vanligtvis bara användbart för tensorer vars data inte ändras under operatorns livslängd (till exempel vikt tensorer). Och flaggan får endast användas på ingresstensorer. När flaggan är satt på en viss tensorbeskrivning måste motsvarande tensor vara bunden till bindningstabellen under operatörinitieringen och inte under körningen (vilket resulterar i ett fel). Det är motsatsen till standardbeteendet (beteendet utan flaggan DML_TENSOR_FLAG_OWNED_BY_DML), där förväntas tensorn vara bunden under körningen, inte under initieringen. Alla resurser som är bundna till DirectML måste vara STANDARD- eller CUSTOM-heapresurser.

Mer information finns i IDMLBindingTable::BindInputs och IDMLBindingTable::BindOutputs.

Utför den sändningsbara enheten

Skicka bindningstabellen som en parameter när du anropar IDMLCommandRecorder::RecordDispatch.

När du använder bindningstabellen under ett anrop till IDMLCommandRecorder::RecordDispatch binder DirectML motsvarande GPU-beskrivningar till pipelinen. CPU- och GPU-beskrivningshandtag behöver inte peka på samma poster i en deskriptorhögen, men det är då applikationens ansvar att se till att hela deskriptorintervallet som refereras till av CPU-beskrivningshandtaget kopieras till det intervall som GPU-beskrivningshandtaget refererar till före exekvering när denna bindningstabell används.

winrt::com_ptr<::ID3D12GraphicsCommandList> d3D12GraphicsCommandList;
// Code to create a Direct3D 12 command list goes here.

winrt::com_ptr<::IDMLCommandRecorder> dmlCommandRecorder;
// Code to create a DirectML command recorder goes here.

dmlCommandRecorder->RecordDispatch(
    d3D12GraphicsCommandList.get(),
    dmlOperatorInitializer.get(),
    dmlBindingTable.get()
);

Stäng slutligen din Direct3D 12-kommandolista och skicka den för körning som du skulle göra med andra kommandolistor.

Innan du kör RecordDispatch på GPU:n måste du överföra alla bundna resurser till D3D12_RESOURCE_STATE_UNORDERED_ACCESS tillstånd eller till ett tillstånd som implicit kan befordras till D3D12_RESOURCE_STATE_UNORDERED_ACCESS, till exempel D3D12_RESOURCE_STATE_COMMON. När det här anropet har slutförts förblir resurserna i D3D12_RESOURCE_STATE_UNORDERED_ACCESS-läge. Det enda undantaget till detta är för uppladdningshögar som är bundna när en operatorinitierare körs och när en eller flera tensorer har flaggan DML_TENSOR_FLAG_OWNED_BY_DML inställd. I så fall måste alla uppladdningshögar som är bundna till indata vara i D3D12_RESOURCE_STATE_GENERIC_READ tillstånd och förbli i det tillståndet, vilket krävs av alla uppladdningshögar. Om DML_EXECUTION_FLAG_DESCRIPTORS_VOLATILE inte angavs när operatorn kompilerades måste alla bindningar anges i bindningstabellen innan RecordDispatch anropas, annars är beteendet odefinierat. Om en operatör har stöd för sen bindning kan annars bindningen av resurser skjutas upp tills direct3D 12-kommandolistan skickas till kommandokön för körning.

RecordDispatch fungerar logiskt som ett anrop till ID3D12GraphicsCommandList::Dispatch. Därför är Osorterade åtkomstvybarriärer (UAV) nödvändiga för att säkerställa rätt ordning om det finns databeroenden mellan sändningarna. Den här metoden infogar inte UAV-hinder för indata eller utdataresurser. Ditt program måste se till att korrekta UAV-barriärer utförs på alla indata om deras innehåll är beroende av en tidigare sändning, och på alla utdata om det finns senare sändningar som är beroende av dessa utdata.

Livslängd och synkronisering av deskriptorer och bindningstabell

En bra mental modell för bindning i DirectML är att själva DirectML-bindningstabellen i bakgrunden skapar och hanterar osorterade åtkomstvy (UAV) deskriptorer i den deskriptorhög som du tillhandahåller. Så alla de vanliga Direct3D 12-reglerna gäller för synkronisering av åtkomst till den här heapen och dess deskriptorer. Det är programmets ansvar att utföra korrekt synkronisering mellan processor- och GPU-arbetet som använder en bindningstabell.

En bindningstabell kan inte skriva över en beskrivning medan beskrivningen används (till exempel med en tidigare ram). Så om du vill återanvända en redan bunden deskriptorheap (till exempel genom att anropa Bind* igen på en bindningstabell som pekar på den eller genom att skriva över deskriptorheapen manuellt), bör du vänta tills det dispatchbara objektet som för närvarande använder deskriptorheapen har slutfört sin körning på GPU:n. En bindningstabell bibehåller inte en stark referens till den beskrivningsheapen som den skriver till, så du får inte släppa den skugg-synliga beskrivningsheapen förrän allt arbete som använder bindningstabellen har avslutats på GPU:n.

Å andra sidan, medan en bindningstabell anger och hanterar en deskriptortabell innehåller tabellen inte själv något av det minnet. Så du kan släppa eller återställa en bindningstabell när som helst efter att du har anropat IDMLCommandRecorder::RecordDispatch med den (du behöver inte vänta tills det anropet har slutförts på GPU:n, så länge de underliggande beskrivningarna förblir giltiga).

Bindningstabellen behåller inte starka referenser på några resurser som är bundna med hjälp av den. Programmet måste se till att resurserna inte tas bort när de fortfarande används av GPU:n. En bindningstabell är inte heller trådsäker – programmet får inte anropa metoder i en bindningstabell samtidigt från olika trådar utan synkronisering.

Och tänk på att ombindning i vilket fall som helst bara är nödvändigt när du ändrar vilka resurser som är bundna. Om du inte behöver ändra de bundna resurserna kan du binda en gång vid start och skicka samma bindningstabell varje gång du anropar RecordDispatch.

För att blanda maskininlärning och rendering av arbetsbelastningar, se bara till att bindningstabellerna för varje bildruta pekar på intervall av deskriptorheapen som inte redan används på GPU:n.

Du kan också ange sent bundna operatorbindningar

Om du har att göra med en kompilerad operator (i stället för med en operatorinitierare) har du möjlighet att ange sen bindning för operatorn. Utan sen bindning måste du ange alla bindningar i bindningstabellen innan du registrerar en operator i en kommandolista. Med sen bindning kan du ange (eller ändra) bindningar för operatorer som du redan har registrerat i en kommandolista innan de har skickats till kommandokön.

För att ange sen bindning, anropa IDMLDevice::CompileOperator med ett flags argument av DML_EXECUTION_FLAG_DESCRIPTORS_VOLATILE.

Se även