Compartilhar via


Associação no DirectML

No DirectML, a associação refere-se à vinculação de recursos ao pipeline para que a GPU os utilize durante a inicialização e execução dos seus operadores de aprendizado de máquina. Esses recursos podem ser tensores de entrada e saída, por exemplo, bem como quaisquer recursos temporários ou persistentes de que o operador precise.

Este tópico aborda os detalhes conceituais e processuais da vinculação. Recomendamos que você também leia completamente a documentação das APIs que você chama, incluindo parâmetros e Comentários.

Ideias importantes em encadernação

A lista de passos abaixo contém uma descrição de alto nível de tarefas relacionadas à associação. Você precisa seguir essas etapas sempre que executar um dispatchable– um dispatchable é um inicializador de operador ou um operador compilado. Essas etapas introduzem as ideias, estruturas e métodos importantes envolvidos na associação DirectML.

As seções subsequentes neste tópico se aprofundam e explicam essas tarefas de associação com mais detalhes, com snippets de código ilustrativos extraídos do exemplo mínimo de código do aplicativo DirectML .

  • Chame IDMLDispatchable::GetBindingProperties no objeto despachável para determinar quantos descritores são necessários, assim como suas necessidades de recursos temporários/persistentes.
  • Crie uma pilha de descritores Direct3D 12 grande o suficiente para os descritores e associe-a ao pipeline.
  • Chame IDMLDevice::CreateBindingTable para criar uma tabela de associação DirectML para representar os recursos associados ao pipeline. Use a estrutura DML_BINDING_TABLE_DESC para descrever sua tabela de associação, incluindo o subconjunto dos descritores para os quais ela aponta na pilha de descritores.
  • Crie recursos temporários/persistentes como recursos de buffer do Direct3D 12, descreva-os com estruturas de DML_BUFFER_BINDING e DML_BINDING_DESC e adicione-os à tabela de associação.
  • Se o dispatchable for um operador compilado, então crie um buffer de elementos de tensores como um recurso de buffer do Direct3D 12. Preencha/carregue-o, descreva-o com estruturas DML_BUFFER_BINDING e DML_BINDING_DESC e adicione-o à tabela de associação.
  • Passe sua tabela de associação como um parâmetro ao chamar IDMLCommandRecorder::RecordDispatch.

Recuperar as propriedades de associação de um dispatchable

A estrutura DML_BINDING_PROPERTIES descreve as necessidades de associação de um operador dispatchable (inicializador de operador ou operador compilado). Essas propriedades relacionadas à associação incluem o número de descritores que você deve associar ao dispatchable, bem como o tamanho em bytes de qualquer recurso temporário e/ou persistente de que ele precise.

Observação

Mesmo para múltiplos operadores do mesmo tipo, não faça suposições sobre eles terem os mesmos requisitos de vinculação. Consulte as propriedades de associação para cada inicializador e operador que você criar.

Chame IDMLDispatchable::GetBindingProperties para recuperar um 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 = ...

O descriptorCount valor que você recupera aqui determina o tamanho (mínimo) do heap do descritor e da tabela de associação que você cria nos próximos dois passos.

DML_BINDING_PROPERTIES também contém um TemporaryResourceSize membro, que é o tamanho mínimo em bytes do recurso temporário que deve ser vinculado à tabela de ligação para este objeto despachável. Um valor zero significa que um recurso temporário não é necessário.

E um PersistentResourceSize membro, que é o tamanho mínimo em bytes do recurso persistente que deve ser vinculado à tabela de associação para este objeto despachável. Um valor zero significa que um recurso persistente não é necessário. Um recurso persistente, se necessário, deve ser fornecido durante a inicialização de um operador compilado (em que ele está associado como uma saída do inicializador do operador), bem como durante a execução. Há mais informações sobre isso mais adiante neste tópico. Somente os operadores compilados têm recursos persistentes— os inicializadores de operador sempre retornam um valor de 0 para esse membro.

Se você chamar IDMLDispatchable::GetBindingProperties em um inicializador de operador antes e depois de uma chamada para IDMLOperatorInitializer::Reset, os dois conjuntos de propriedades de associação recuperados não serão garantidos como idênticos.

Descrever, criar e vincular um heap de descritor

Em termos de descritores, sua responsabilidade começa e termina com o próprio monte de descritor. O próprio DirectML cuida da criação e gerenciamento dos descritores dentro do heap fornecido por você.

Portanto, use uma estrutura D3D12_DESCRIPTOR_HEAP_DESC para descrever um heap grande o suficiente para o número de descritores de que o dispatchable precisa. Em seguida, crie-o com ID3D12Device::CreateDescriptorHeap. Por fim, chame ID3D12GraphicsCommandList::SetDescriptorHeaps para vincular o heap do descritor ao pipeline.

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()
);

Descrever e criar uma tabela de associação

Uma tabela de associação do DirectML representa os recursos que você liga ao pipeline para que um "dispatchable" possa utilizá-los. Esses recursos podem ser tensores de entrada e saída (ou outros parâmetros) para um operador ou podem ser vários recursos persistentes e temporários com os quais um dispatchable funciona.

Use a estrutura DML_BINDING_TABLE_DESC para descrever sua tabela de vinculação, incluindo o dispatchable para o qual a tabela de vinculação representará as vinculações e o intervalo de descritores (do heap de descritores que você acabou de criar) que você deseja que a tabela de vinculação se refira (e na qual o DirectML pode gravar descritores). O descriptorCount valor (uma das propriedades de associação que recuperamos na primeira etapa) informa qual é o tamanho mínimo, em descritores, da tabela de associação necessária para o objeto expediível. Aqui, usamos esse valor para indicar o número máximo de descritores que o DirectML tem permissão para gravar em nosso heap, desde o início das alças de descritor de CPU e GPU fornecidas.

Em seguida, chame IDMLDevice::CreateBindingTable para criar a tabela de associação DirectML. Em etapas posteriores, depois de criarmos mais recursos para o dispatchable, adicionaremos esses recursos à tabela de associação.

Em vez de passar uma DML_BINDING_TABLE_DESC para essa chamada, você pode passar nullptr, indicando uma tabela de associação vazia.

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()
    )
);

A ordem na qual o DirectML grava descritores no heap não é especificada, portanto, seu aplicativo deve tomar cuidado para não substituir os descritores encapsulados pela tabela de associação. Os identificadores de descritor de CPU e GPU fornecidos podem vir de heaps diferentes, no entanto, é responsabilidade do aplicativo garantir que todo o intervalo de descritores referenciado pelo identificador do descritor de CPU seja copiado para o intervalo referenciado pelo identificador do descritor de GPU antes da execução usando essa tabela de associação. O heap de descritor de onde as alças são fornecidas deve ter o tipo D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV. Além disso, o heap referenciado pelo GPUDescriptorHandle deve ser um heap de descritor visível ao sombreador.

Você pode redefinir uma tabela de vinculação para remover quaisquer recursos que você tenha adicionado a ela e, ao mesmo tempo, alterar qualquer propriedade definida em sua DML_BINDING_TABLE_DESC inicial (para abranger um novo intervalo de descritores ou reutilizá-la para um diferente item despachável). Basta fazer as alterações na estrutura de descrição e chamar IDMLBindingTable::Reset.

dmlBindingTableDesc.Dispatchable = pIDMLCompiledOperator.get();

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

Descrever e associar quaisquer recursos temporários/persistentes

A estrutura DML_BINDING_PROPERTIES que preenchemos quando recuperamos as propriedades de associação de nosso dispatchable contém o tamanho em bytes de qualquer recurso temporário e/ou persistente de que o dispatchable precisa. Se um desses tamanhos não for zero, crie um recurso de buffer Direct3D 12 e adicione-o à tabela de vínculos.

No exemplo de código abaixo, criamos um recurso temporário (temporaryResourceSize bytes em tamanho) para o dispatchable. Descrevemos como desejamos associar o recurso e, em seguida, adicionamos essa associação à tabela de associação.

Como estamos associando um único recurso de buffer, descrevemos nossa associação com uma estrutura de DML_BUFFER_BINDING . Nessa estrutura, especificamos o recurso de buffer do Direct3D 12 (o recurso deve ter a dimensão D3D12_RESOURCE_DIMENSION_BUFFER), assim como um deslocamento e um tamanho definidos no buffer. Também é possível descrever uma associação para uma matriz de buffers (em vez de para um único buffer) e a estrutura DML_BUFFER_ARRAY_BINDING existe para essa finalidade.

Para abstrair a distinção entre uma associação de buffer e uma associação de matriz de buffer, usamos a estrutura DML_BINDING_DESC . Você pode definir o Type membro do DML_BINDING_DESC como DML_BINDING_TYPE_BUFFER ou DML_BINDING_TYPE_BUFFER_ARRAY. Em seguida, você pode definir o Desc membro para apontar para um DML_BUFFER_BINDING ou para um DML_BUFFER_ARRAY_BINDING, dependendo de Type.

Estamos lidando com o recurso temporário neste exemplo, portanto, o adicionamos à tabela de associação com uma chamada para 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);

Um recurso temporário (se necessário) é uma memória temporária utilizada internamente durante a execução do operador, de modo que você não precisa se preocupar com seu conteúdo. Também não é necessário mantê-lo ao redor depois que sua chamada para IDMLCommandRecorder::RecordDispatch foi concluída na GPU. Isso significa que seu aplicativo pode liberar ou substituir o recurso temporário entre as execuções do operador compilado. O intervalo de buffer fornecido a ser associado como recurso temporário deve ter seu deslocamento inicial alinhado ao DML_TEMPORARY_BUFFER_ALIGNMENT. O tipo do heap subjacente ao buffer deve ser D3D12_HEAP_TYPE_DEFAULT.

Se o dispatchable relatar um tamanho diferente de zero para seu recurso persistente de longa duração, porém, o procedimento será um pouco diferente. Você deve criar um buffer e descrever uma associação seguindo o mesmo padrão mostrado acima. Mas adicione-a à tabela de associação do inicializador do operador com uma chamada para IDMLBindingTable::BindOutputs, porque é trabalho do inicializador do operador inicializar o recurso persistente. Em seguida, adicione-a à tabela de associação do operador compilado com uma chamada para IDMLBindingTable::BindPersistentResource. Consulte o exemplo mínimo de código do aplicativo DirectML para ver esse fluxo de trabalho em ação. O conteúdo e o tempo de vida do recurso persistente devem persistir enquanto o operador compilado persistir. Ou seja, se um operador exigir um recurso persistente, o aplicativo deverá fornecê-lo durante a inicialização e, posteriormente, também fornecê-lo a todas as execuções futuras do operador sem modificar seu conteúdo. O recurso persistente normalmente é usado pelo DirectML para armazenar tabelas de pesquisa ou outros dados de longa duração computados durante a inicialização de um operador e reutilizados em execuções futuras desse operador. O intervalo de buffer fornecido a ser associado, pois o buffer persistente deve ter seu deslocamento inicial alinhado ao DML_PERSISTENT_BUFFER_ALIGNMENT. O tipo do heap subjacente ao buffer deve ser D3D12_HEAP_TYPE_DEFAULT.

Descrever e associar tensores

Se você estiver lidando com um operador compilado (em vez de com um inicializador de operador), precisará associar recursos de entrada e saída (para tensores e outros parâmetros) à tabela de associação do operador. O número de associações deve corresponder exatamente ao número de entradas do operador, incluindo tensores opcionais. Os tensores de entrada e saída específicos e outros parâmetros que um operador usa estão documentados no tópico desse operador (por exemplo, DML_ELEMENT_WISE_IDENTITY_OPERATOR_DESC).

Um recurso tensor é um buffer que contém os valores de elemento individuais do tensor. Você carrega e lê de volta esse buffer de/para a GPU usando as técnicas regulares do Direct3D 12 (carregar recursos e ler dados de volta por meio de um buffer). Consulte o exemplo mínimo de código do aplicativo DirectML para ver essas técnicas em ação.

Por fim, descreva as associações de recursos de entrada e saída com estruturas DML_BUFFER_BINDING e DML_BINDING_DESC e adicione-as à tabela de associação do operador compilado com chamadas para IDMLBindingTable::BindInputs e IDMLBindingTable::BindOutputs. Quando você chama um método IDMLBindingTable::Bind*, o DirectML grava um ou mais descritores no intervalo de descritores de CPU.

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);

Uma das etapas na criação de um operador DirectML (consulte IDMLDevice::CreateOperator) é declarar uma ou mais estruturas DML_BUFFER_TENSOR_DESC para descrever os buffers de dados tensor que o operador usa e retorna. Além do tipo e tamanho do buffer do tensor, você também pode especificar opcionalmente a flag DML_TENSOR_FLAG_OWNED_BY_DML.

DML_TENSOR_FLAG_OWNED_BY_DML indica que os dados tensores devem ser de propriedade e gerenciados pelo DirectML. O DirectML faz uma cópia dos dados tensores durante a inicialização do operador e os armazena no recurso persistente. Isso permite que o DirectML execute a reformatação dos dados tensor em outras formas mais eficientes. Definir esse sinalizador pode aumentar o desempenho, mas normalmente só é útil para tensores cujos dados não são alterados durante o tempo de vida do operador (por exemplo, tensores de peso). E o sinalizador só pode ser usado em tensores de entrada. Quando o sinalizador é definido em uma descrição específica do tensor, o tensor correspondente deve ser associado à tabela de associação durante a inicialização do operador e não durante a execução (o que resultará em um erro). Isso é o oposto do comportamento padrão (o comportamento sem o flag DML_TENSOR_FLAG_OWNED_BY_DML), em que se espera que o tensor seja vinculado durante a execução e não durante a inicialização. Todos os recursos associados ao DirectML devem ser recursos de heap PADRÃO ou, inclusive, PERSONALIZADOS.

Para obter mais informações, consulte IDMLBindingTable::BindInputs e IDMLBindingTable::BindOutputs.

Executar o dispatchable

Passe sua tabela de associação como um parâmetro ao chamar IDMLCommandRecorder::RecordDispatch.

Quando você usa a tabela de associação durante uma chamada para IDMLCommandRecorder::RecordDispatch, o DirectML associa os descritores de GPU correspondentes ao pipeline. Não é necessário que os identificadores de descritor da CPU e da GPU apontem para as mesmas entradas em um heap de descritor. No entanto, é responsabilidade do aplicativo garantir que todo o intervalo de descritores referenciado pelo identificador de descritor da CPU seja copiado para o intervalo referenciado pelo identificador de descritor da GPU antes da execução, utilizando essa tabela de vinculação.

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()
);

Por fim, feche a lista de comandos do Direct3D 12 e envie-a para execução como faria com qualquer outra lista de comandos.

Antes da execução do RecordDispatch na GPU, você deve fazer a transição de todos os recursos associados para o estado D3D12_RESOURCE_STATE_UNORDERED_ACCESS ou para um estado implicitamente promovível para D3D12_RESOURCE_STATE_UNORDERED_ACCESS, como D3D12_RESOURCE_STATE_COMMON. Após a conclusão dessa chamada, os recursos permanecerão no estado D3D12_RESOURCE_STATE_UNORDERED_ACCESS. A única exceção a isso é para carregar heaps associados ao executar um inicializador de operador e, enquanto um ou mais tensores tiverem o sinalizador DML_TENSOR_FLAG_OWNED_BY_DML definido. Nesse caso, todos os heaps de carregamento associados à entrada devem estar no estado D3D12_RESOURCE_STATE_GENERIC_READ e permanecerão nesse estado, conforme exigido por todos os heaps de upload. Se DML_EXECUTION_FLAG_DESCRIPTORS_VOLATILE não tiver sido definido ao compilar o operador, todas as associações deverão ser definidas na tabela de associação antes que RecordDispatch seja chamado, caso contrário, o comportamento será indefinido. Caso contrário, se um operador der suporte à associação tardia, a associação de recursos poderá ser adiada até que a lista de comandos do Direct3D 12 seja enviada para a fila de comandos para execução.

RecordDispatch age logicamente como uma chamada para ID3D12GraphicsCommandList::Dispatch. Dessa forma, as barreiras de UAV (exibição de acesso não ordenada) são necessárias para garantir a ordenação correta se houver dependências de dados entre expedições. Esse método não insere barreiras UAV nos recursos de entrada nem de saída. Seu aplicativo deve garantir que as barreiras UAV corretas sejam executadas em qualquer entrada se o conteúdo depender de um envio a montante, e em qualquer saída se houver envios a jusante que dependam dessas saídas.

Tempo de vida e sincronização de descritores e tabela de associação

Um bom modelo mental de vinculação no DirectML é que, nos bastidores, a própria tabela de vinculação do DirectML está criando e gerenciando descritores de UAV (exibição de acesso não ordenado) dentro do heap de descritores que você fornece. Portanto, todas as regras usuais do Direct3D 12 se aplicam à sincronização do acesso a esse heap e aos seus descritores. É responsabilidade do aplicativo executar a sincronização correta entre o trabalho de CPU e GPU que usa uma tabela de associação.

Uma tabela de vinculação não pode substituir um descritor enquanto o descritor estiver em uso (por um quadro anterior, por exemplo). Portanto, se você quiser reutilizar um heap de descritor já associado (por exemplo, chamando Bind* novamente em uma tabela de associação que aponte para ela ou substituindo o heap de descritor manualmente), deverá aguardar o dispatchable que está usando o heap do descritor para concluir a execução na GPU. Uma tabela de associação não mantém uma referência forte no heap do descritor no qual ela grava, portanto, você não deve liberar o heap de descritor visível do sombreador de suporte até que todo o trabalho usando essa tabela de associação tenha concluído a execução na GPU.

Por outro lado, enquanto uma tabela de associação especifica e gerencia um heap de descritor, a tabela em si não contém nenhuma dessa memória. Portanto, você pode liberar ou redefinir uma tabela de associação a qualquer momento depois de chamar IDMLCommandRecorder::RecordDispatch com ela (você não precisa esperar que essa chamada seja concluída na GPU, desde que os descritores subjacentes permaneçam válidos).

A tabela de associação não mantém referências fortes em nenhum recurso associado usando-a– seu aplicativo deve garantir que os recursos não sejam excluídos enquanto ainda estiverem em uso pela GPU. Além disso, uma tabela de associação não é thread-safe – seu aplicativo não deve chamar métodos em uma tabela de associação simultaneamente de threads diferentes sem sincronização.

E considere que, em qualquer caso, a revinculação é necessária somente quando você altera quais recursos estão vinculados. Se você não precisar alterar os recursos associados, poderá associar uma vez na inicialização e passar a mesma tabela de associação sempre que chamar RecordDispatch.

Para intercalar tarefas de aprendizado de máquina e renderização, basta garantir que as tabelas de associação de cada quadro apontem para intervalos do heap de descritor que ainda não estão em uso na GPU.

Opcionalmente, especifique associações de operador com limite tardio

Se você estiver lidando com um operador compilado (em vez de com um inicializador de operador), terá a opção de especificar a associação tardia para o operador. Sem vinculação tardia, você deve definir todas as vinculações na tabela de vinculação antes de registrar um operador em uma lista de comandos. Com a associação tardia, você pode definir (ou alterar) associações em operadores que você já registrou em uma lista de comandos, antes que ela seja enviada para a fila de comandos.

Para especificar a ligação tardia, chame IDMLDevice::CompileOperator com um argumento flags de DML_EXECUTION_FLAG_DESCRIPTORS_VOLATILE.

Consulte também

  • de IA do Windows