Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En DirectML, el enlace hace referencia a los datos adjuntos de los recursos a la canalización para que la GPU la use durante la inicialización y ejecución de los operadores de aprendizaje automático. Estos recursos pueden ser tensores de entrada y salida, por ejemplo, así como cualquier recurso temporal o persistente que el operador necesite.
En este tema se tratan los detalles conceptuales y de procedimientos del enlace. Se recomienda leer completamente la documentación de las API a las que se llama, incluidos los parámetros y los comentarios.
Ideas importantes sobre el enlace
La lista de los pasos siguientes contiene una descripción general de las tareas relacionadas con el enlace. Debe seguir estos pasos cada vez que ejecute un distribuidor: un distribuidor es un inicializador de operador o un operador compilado. Estos pasos presentan las ideas, estructuras y métodos importantes implicados en la vinculación de DirectML.
Las secciones posteriores de este tema profundizan y explican estas tareas de enlace con más detalle, con fragmentos de código ilustrativos tomados del ejemplo mínimo de código de la aplicación DirectML .
- Llame a IDMLDispatchable::GetBindingProperties en el despachable para determinar cuántos descriptores necesita y también sus necesidades de recursos temporales y persistentes.
- Cree una pila de descriptores de Direct3D 12 lo suficientemente grande para los descriptores y vincúlela a la tubería.
- Llame a IDMLDevice::CreateBindingTable para crear una tabla de enlace de DirectML para representar los recursos enlazados a la canalización. Use la estructura DML_BINDING_TABLE_DESC para describir la tabla de enlace, incluido el subconjunto de los descriptores a los que apunta en el montón del descriptor.
- Cree recursos temporales o persistentes como recursos de búfer de Direct3D 12 y descríbalos con las estructuras DML_BUFFER_BINDING y DML_BINDING_DESC, y agréguelos a la tabla de enlace.
- Si el elemento para enviar es un operador compilado, cree un búfer de elementos tensor como recurso de búfer de Direct3D 12. Rellene o cárguelo, descríbalo con estructuras DML_BUFFER_BINDING y DML_BINDING_DESC, y agréguelo a la tabla de enlace.
- Pase la tabla de enlace como parámetro al llamar a IDMLCommandRecorder::RecordDispatch.
Recuperación de las propiedades de enlace de un objeto que se va a enviar
La estructura DML_BINDING_PROPERTIES describe las necesidades de vinculación de un operador despachable (inicializador de operador o operador compilado). Estas propiedades relacionadas con el enlace incluyen el número de descriptores que debe enlazar al distribuidor, así como el tamaño en bytes de cualquier recurso temporal o persistente que necesite.
Nota:
Incluso para varios operadores del mismo tipo, no asuma que tienen los mismos requisitos de enlace. Consulte las propiedades de enlace para cada inicializador y operador que cree.
Llame a IDMLDispatchable::GetBindingProperties para recuperar un 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 = ...
El valor descriptorCount
que recupera aquí determina el tamaño (mínimo) del montón de descriptores y de la tabla de enlace que se crea en los dos pasos siguientes.
DML_BINDING_PROPERTIES también contiene un miembro TemporaryResourceSize
, que es el tamaño mínimo en bytes del recurso temporal que se debe enlazar a la tabla de enlace para este objeto de envío. Un valor de cero significa que no se requiere un recurso temporal.
Y un miembro PersistentResourceSize
, que es el tamaño mínimo en bytes del recurso persistente que se debe enlazar a la tabla de enlace para este objeto de envío. Un valor de cero significa que no se requiere un recurso persistente. Un recurso persistente, si es necesario, debe proporcionarse durante la inicialización de un operador compilado (donde se enlaza como salida del inicializador del operador) así como durante la ejecución. Hay más información sobre esto más adelante en este tema. Solo los operadores compilados tienen recursos persistentes: los inicializadores de operador siempre devuelven un valor de 0 para este miembro.
Si llama a IDMLDispatchable::GetBindingProperties en un inicializador de operador antes y después de una llamada a IDMLOperatorInitializer::Reset, no se garantiza que los dos conjuntos de propiedades de enlace recuperados sean idénticos.
Describir, crear y vincular un conjunto de descriptores
En términos de descriptores, su responsabilidad comienza y termina con la propia pila de descriptores. DirectML se encarga de crear y administrar los descriptores dentro de la pila que proporcione.
Por tanto, use una estructura D3D12_DESCRIPTOR_HEAP_DESC para describir un montón suficientemente grande para el número de descriptores que necesita el objeto de envío. A continuación, créelo con ID3D12Device::CreateDescriptorHeap. Y, por último, llame a ID3D12GraphicsCommandList::SetDescriptorHeaps para asociar el montón de descriptores a la canalización.
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()
);
Describir y crear una tabla de enlace
Una tabla de enlace de DirectML representa los recursos que se enlazan a la canalización para que un elemento de envío los use. Esos recursos podrían ser tensores de entrada y salida (u otros parámetros) para un operador, o podrían ser varios recursos persistentes y temporales con los que funciona un despachable.
Use la estructura DML_BINDING_TABLE_DESC para describir su tabla de enlace, incluyendo el elemento ejecutable para el cual la tabla de enlace representará los enlaces y el rango de descriptores (de la pila de descriptores que acaba de crear) a los que desea que haga referencia la tabla de enlace (y en los cuales DirectML pueda escribir descriptores). El valor descriptorCount
(una de las propiedades de enlace que recuperamos en el primer paso) nos indica cuál es el tamaño mínimo, en descriptores, de la tabla de enlace necesaria para el objeto despachado. Aquí, usamos ese valor para indicar el número máximo de descriptores que DirectML puede escribir en nuestro heap, comenzando desde los manejadores de descriptores de CPU y GPU proporcionados.
A continuación, llame a IDMLDevice::CreateBindingTable para crear la tabla de enlace de DirectML. En pasos posteriores, después de que hayamos creado más recursos para el elemento de envío, agregaremos esos recursos a la tabla de enlace.
En lugar de pasar un DML_BINDING_TABLE_DESC a esta llamada, puede pasar nullptr
, lo que indica una tabla de enlace vacía.
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()
)
);
El orden en el que DirectML escribe descriptores en el montón no se especifica, por lo que la aplicación debe tener cuidado de no sobrescribir los descriptores encapsulados por la tabla de enlace. Los identificadores de descriptores de CPU y GPU proporcionados pueden provenir de distintos montones; pero es responsabilidad de la aplicación asegurarse de que todo el intervalo de descriptores al que hace referencia el identificador del descriptor de CPU se copia en el intervalo al que hace referencia el identificador del descriptor de GPU antes de la ejecución mediante esta tabla de enlace. El montón de descriptores desde el que se proporcionan los identificadores debe tener el tipo D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV. Además, el montón al que hace referencia GPUDescriptorHandle
debe ser un montón de descriptores visibles para el sombreador.
Puede restablecer una tabla de enlace para quitar los recursos que le haya agregado, al tiempo que cambia cualquier propiedad que haya establecido en su DML_BINDING_TABLE_DESC inicial (para encapsular un nuevo rango de descriptores o reutilizarlo para otro elemento de envío). Solo tiene que realizar los cambios en la estructura de descripción y llamar a IDMLBindingTable::Reset.
dmlBindingTableDesc.Dispatchable = pIDMLCompiledOperator.get();
winrt::check_hresult(
pIDMLBindingTable->Reset(
&dmlBindingTableDesc
)
);
Describir y enlazar los recursos temporales o persistentes
La estructura DML_BINDING_PROPERTIES que rellenamos cuando recuperamos las propiedades de enlace de nuestro distribuidor contiene el tamaño en bytes de cualquier recurso temporal o persistente que necesite el distribuidor. Si cualquiera de estos tamaños es distinto de cero, cree un recurso de búfer de Direct3D 12 y agréguelo a la tabla de enlace.
En el ejemplo de código siguiente, creamos un recurso temporal (temporaryResourceSize
bytes de tamaño) para el elemento de envío. Se describe cómo deseamos enlazar el recurso y, a continuación, se agrega ese enlace a la tabla de enlace.
Dado que estamos enlazando un único recurso de búfer, describimos nuestro enlace con una estructura DML_BUFFER_BINDING. En esa estructura, especificamos el recurso de búfer Direct3D 12 (el recurso debe tener D3D12_RESOURCE_DIMENSION_BUFFER de dimensión), así como un desplazamiento y un tamaño dentro del búfer. También es posible describir un enlace para una matriz de búferes (en lugar de para un único búfer) y la estructura de DML_BUFFER_ARRAY_BINDING existe para ese propósito.
Para abstraer la distinción entre un enlace de búfer y un enlace de matriz de búfer, usamos la estructura DML_BINDING_DESC . Puede establecer el miembro Type
de DML_BINDING_DESC en DML_BINDING_TYPE_BUFFER o DML_BINDING_TYPE_BUFFER_ARRAY. Y después puede establecer el miembro Desc
para que apunte a una instancia de DML_BUFFER_BINDING o DML_BUFFER_ARRAY_BINDING, en función de Type
.
Estamos tratando con el recurso temporal en este ejemplo, por lo que lo agregamos a la tabla de enlace con una llamada a 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);
Un recurso temporal (si es necesario) es la memoria temporal que se usa internamente durante la ejecución del operador, por lo que no es necesario preocuparse por su contenido. Tampoco es necesario conservarlo una vez que la llamada a IDMLCommandRecorder::RecordDispatch se haya completado en la GPU. Esto significa que la aplicación puede liberar o sobrescribir el recurso temporal entre envíos del operador compilado. El rango de búfer proporcionado que se va a enlazar como recurso temporal debe tener su desplazamiento inicial alineado con DML_TEMPORARY_BUFFER_ALIGNMENT. El tipo del montón subyacente del búfer debe ser D3D12_HEAP_TYPE_DEFAULT.
Si el elemento de envío notifica un tamaño distinto de cero para su recurso persistente más duradero, el procedimiento es un poco diferente. Debe crear un búfer y describir un enlace siguiendo el mismo patrón que se muestra anteriormente. Pero agréguelo a la tabla de enlace del inicializador del operador con una llamada a IDMLBindingTable::BindOutputs, ya que es el trabajo del inicializador del operador para inicializar el recurso persistente. A continuación, agréguelo a la tabla de enlace del operador compilado con una llamada a IDMLBindingTable::BindPersistentResource. Consulte el ejemplo mínimo de código de la aplicación DirectML para ver este flujo de trabajo en acción. El contenido y la duración del recurso persistente deben conservarse siempre que el operador compilado lo haga. Es decir, si un operador requiere un recurso persistente, la aplicación debe proporcionarla durante la inicialización y, posteriormente, proporcionarla a todas las ejecuciones futuras del operador sin modificar su contenido. DirectML suele usar el recurso persistente para almacenar tablas de búsqueda u otros datos de larga duración que se calculan durante la inicialización de un operador y se reutilizan en futuras ejecuciones de ese operador. El intervalo de búfer proporcionado que se va a enlazar como búfer persistente debe tener su desplazamiento inicial alineado con DML_PERSISTENT_BUFFER_ALIGNMENT. El tipo del montón subyacente del búfer debe ser D3D12_HEAP_TYPE_DEFAULT.
Describir y vincular los tensores
Si trabaja con un operador compilado (en lugar de con un inicializador de operador), debe enlazar los recursos de entrada y salida (para tensores y otros parámetros) a la tabla de enlace del operador. El número de enlaces debe coincidir exactamente con el número de entradas del operador, incluidos los tensores opcionales. Los tensores de entrada y salida concretos y otros parámetros que toma un operador se documentan en el tema de ese operador (por ejemplo, DML_ELEMENT_WISE_IDENTITY_OPERATOR_DESC).
Un recurso tensor es un búfer que contiene los valores de elemento individuales del tensor. Puede cargar y leer de nuevo este búfer hacia o desde la GPU mediante las técnicas normales de Direct3D 12 (cargar recursos y leer datos a través de un búfer). Consulte el ejemplo mínimo de código de la aplicación DirectML para ver estas técnicas en acción.
Por último, describa los enlaces de recursos de entrada y salida con estructuras de DML_BUFFER_BINDING y DML_BINDING_DESC y, a continuación, agréguelos a la tabla de enlace del operador compilado con llamadas a IDMLBindingTable::BindInputs e IDMLBindingTable::BindOutputs. Cuando se llama a un método IDMLBindingTable::Bind*, DirectML escribe uno o varios descriptores en el intervalo de descriptores 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);
Uno de los pasos para crear un operador DirectML (vea IDMLDevice::CreateOperator) consiste en declarar una o varias estructuras de DML_BUFFER_TENSOR_DESC para describir los búferes de datos tensor que toma y devuelve el operador. Además del tipo y el tamaño del búfer de tensor, opcionalmente puede especificar la bandera DML_TENSOR_FLAG_OWNED_BY_DML.
DML_TENSOR_FLAG_OWNED_BY_DML indica que los datos tensor deben ser propiedad y administrados por DirectML. DirectML realiza una copia de los datos de tensor durante la inicialización del operador y los almacena en el recurso persistente. Esto permite que DirectML realice el reformateo de los datos del tensor de otras formas más eficaces. Establecer esta marca puede aumentar el rendimiento, pero normalmente solo es útil para tensores cuyos datos no cambian durante la vigencia del operador (por ejemplo, tensores de peso). Y la bandera solo se puede usar en tensores de entrada. Cuando la marca se establece en una descripción determinada del tensor, el tensor correspondiente debe enlazarse a la tabla de enlace durante la inicialización del operador y no durante la ejecución (lo que provocará un error). Es lo contrario al comportamiento predeterminado (el comportamiento sin la marca DML_TENSOR_FLAG_OWNED_BY_DML) donde se espera que el tensor esté enlazado durante la ejecución y no durante la inicialización. Todos los recursos enlazados a DirectML deben ser recursos de montón DEFAULT o CUSTOM.
Para obtener más información, consulta IDMLBindingTable::BindInputs e IDMLBindingTable::BindOutputs.
Ejecución del distribuidor
Pase la tabla de enlace como parámetro al llamar a IDMLCommandRecorder::RecordDispatch.
Cuando se usa la tabla de enlace durante una llamada a IDMLCommandRecorder::RecordDispatch, DirectML enlaza los descriptores de GPU correspondientes a la canalización. No es necesario que los identificadores de descriptores de CPU y GPU apunten a las mismas entradas de un montón de descriptores; sin embargo, es responsabilidad de la aplicación asegurarse de que todo el intervalo de descriptores al que hace referencia el identificador del descriptor de CPU se copia en el intervalo al que hace referencia el identificador del descriptor de GPU antes de la ejecución mediante esta tabla de enlace.
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 último, cierre la lista de comandos de Direct3D 12 y envíela para su ejecución como haría con cualquier otra lista de comandos.
Antes de la ejecución de RecordDispatch en la GPU, debe realizar la transición de todos los recursos enlazados al estado D3D12_RESOURCE_STATE_UNORDERED_ACCESS o a un estado que se puede promover implícitamente a D3D12_RESOURCE_STATE_UNORDERED_ACCESS, como D3D12_RESOURCE_STATE_COMMON. Una vez completada esta llamada, los recursos permanecen en el estado D3D12_RESOURCE_STATE_UNORDERED_ACCESS. La única excepción a esto es para los montones de carga enlazados al ejecutar un inicializador de operador y mientras que uno o varios tensores tienen establecida la marca DML_TENSOR_FLAG_OWNED_BY_DML. En ese caso, los montones de carga enlazados a la entrada deben estar en el estado D3D12_RESOURCE_STATE_GENERIC_READ y permanecerán en ese estado, como se exige para todos los montones de carga. Si DML_EXECUTION_FLAG_DESCRIPTORS_VOLATILE no se estableció al compilar el operador, todos los enlaces deben establecerse en la tabla de enlace antes de llamar a RecordDispatch, de lo contrario, el comportamiento será indefinido. De lo contrario, si un operador admite la vinculación tardía, el vinculado de recursos se puede aplazar hasta que se someta la lista de comandos de Direct3D 12 a la cola de comandos para su ejecución.
RecordDispatch actúa como una llamada a ID3D12GraphicsCommandList::Dispatch. Por tanto, las barreras de vistas de acceso desordenado (UAV) son necesarias para garantizar el orden correcto si hay dependencias de datos entre envíos. Este método no inserta barreras UAV en los recursos de entrada ni salida. La aplicación debe asegurarse de que las barreras de UAV correctas se aplican a cualquier entrada si su contenido depende de un despacho ascendente y a cualquier salida si hay despachos descendentes que dependen de esas salidas.
Vida útil y sincronización de descriptores y tabla de vinculación
Un buen modelo mental del enlace en DirectML es que, en segundo plano, la propia tabla de enlace de DirectML crea y administra descriptores de vista de acceso desordenado (UAV) dentro del montón de descriptores que proporcione. Por lo tanto, todas las reglas habituales de Direct3D 12 se aplican para la sincronización del acceso a ese heap y a sus descriptores. Es responsabilidad de la aplicación realizar la sincronización correcta entre el trabajo de CPU y GPU que usa una tabla de enlace.
Una tabla de enlace no puede sobrescribir un descriptor mientras el descriptor está en uso (por ejemplo, un marco anterior). Por lo tanto, si desea reutilizar un montón de descriptores ya enlazados (por ejemplo, llamando a Bind* de nuevo en una tabla de enlace que apunte a él o sobrescribiendo el montón de descriptores manualmente), debe esperar a que el objeto ejecutable que está utilizando actualmente el montón de descriptores termine de ejecutarse en la GPU. Una tabla de enlace no mantiene una referencia sólida en el montón de descriptores en el que escribe, por lo que no se debe liberar el montón de descriptores visible para el sombreador hasta que todo el trabajo que use esa tabla de enlace haya completado su ejecución en la GPU.
Por otro lado, mientras que una tabla de enlace especifica y administra un montón de descriptores, la tabla no contiene ninguna de esas memorias. Por lo tanto, puede liberar o restablecer una tabla de enlace en cualquier momento después de llamar a IDMLCommandRecorder::RecordDispatch con ella (no es necesario esperar a que se complete esa llamada en la GPU, siempre y cuando los descriptores subyacentes sigan siendo válidos).
La tabla de enlace no mantiene referencias seguras en ningún recurso enlazado mediante él: la aplicación debe asegurarse de que los recursos no se eliminan mientras la GPU sigue usando. Además, una tabla de enlace no es segura para subprocesos: la aplicación no debe llamar a métodos en una tabla de enlace simultáneamente desde diferentes subprocesos sin sincronización.
Y tenga en cuenta que, en cualquier caso, el reenlace solo es necesario cuando cambian los recursos enlazados. Si no necesita cambiar los recursos enlazados, puede enlazar una vez al inicio y pasar la misma tabla de enlace cada vez que llame a RecordDispatch.
Para intercalar cargas de trabajo de aprendizaje automático y renderizado, asegúrese de que las tablas de enlace de cada fotograma apunten a intervalos del montón de descriptores que aún no están en uso en la GPU.
Opcionalmente, especifique enlaces de operador enlazados en tiempo de ejecución
Si trabaja con un operador compilado (en lugar de un inicializador de operador), tiene la opción de especificar el enlace dinámico para el operador. Sin el enlace dinámico, debe establecer todos los enlaces de la tabla de enlace antes de registrar un operador en una lista de comandos. Con el enlace dinámico, puede establecer (o cambiar) enlaces en operadores que ya ha registrado en una lista de comandos, antes de que se haya enviado a la cola de comandos.
Para especificar la vinculación tardía, llame a IDMLDevice::CompileOperator con un flags
argumento de DML_EXECUTION_FLAG_DESCRIPTORS_VOLATILE.