Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В DirectML привязка относится к привязке ресурсов к конвейеру для использования GPU во время инициализации и выполнения операторов машинного обучения. Эти ресурсы могут быть входными и выходными тензорами, например временными или постоянными ресурсами, которыми нуждается оператор.
В этом разделе рассматриваются концептуальные и процедурные сведения о привязке. Мы рекомендуем также полностью ознакомиться с документацией по вызываемым API, включая параметры и примечания.
Важные идеи в связывании
Список шагов ниже содержит высокоуровневое описание задач, связанных с привязкой. Каждый раз при выполнении диспатчера — либо инициализатора оператора, либо скомпилированного оператора, необходимо следовать этим шагам. В этих шагах представлены важные идеи, структуры и методы, участвующие в привязке DirectML.
В последующих разделах этого раздела более подробно описаны эти задачи привязки с помощью иллюстрирующих фрагментов кода, взятых из минимального примера кода приложения DirectML .
- Вызовите IDMLDispatchable::GetBindingProperties на диспетчируемом объекте, чтобы определить, сколько дескрипторов требуется, а также его временные/постоянные ресурсы.
- Создайте кучу дескрипторов Direct3D 12 достаточно большую для дескрипторов и привяжите её к конвейеру.
- Вызовите IDMLDevice::CreateBindingTable, чтобы создать таблицу привязки DirectML для представления ресурсов, привязанных к конвейеру. Используйте структуру DML_BINDING_TABLE_DESC для описания таблицы привязки, включая подмножество дескрипторов, на которые он указывает в куче дескриптора.
- Создайте временные и постоянные ресурсы в качестве буферных ресурсов Direct3D 12, опишите их с помощью DML_BUFFER_BINDING и DML_BINDING_DESC структур и добавьте их в таблицу привязки.
- Если диспетчеризуемый является скомпилированным оператором, создайте буфер тензорных элементов в виде ресурса буфера Direct3D 12. Заполните или загрузите его, опишите его с использованием структур DML_BUFFER_BINDING и DML_BINDING_DESC, и добавьте его в таблицу привязки.
- Передайте таблицу привязки в качестве параметра при вызове IDMLCommandRecorder::RecordDispatch.
Получение свойств привязки отправляемого объекта
Структура DML_BINDING_PROPERTIES описывает потребности привязки исполняемых объектов (инициализатора оператора или скомпилированного оператора). Эти свойства, связанные с привязкой, включают количество дескрипторов, которые необходимо привязать к соответствующему объекту, а также размер в байтах любого временного и/или постоянного ресурса, который требуется.
Замечание
Даже для нескольких операторов одного типа не предполагайте, что они имеют одинаковые требования к привязке. Запросите свойства привязки для каждого создаваемого инициализатора и оператора.
Вызовите IDMLDispatchable::GetBindingProperties, чтобы получить 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 = ...
Значение descriptorCount , полученное здесь, определяет (минимальный) размер кучи дескриптора и таблицы привязки, создаваемой на следующих двух шагах.
DML_BINDING_PROPERTIES также содержит TemporaryResourceSize элемент, который является минимальным размером в байтах временного ресурса, который должен быть привязан к таблице привязки для этого объекта, который можно отправить. Значение нуля означает, что временный ресурс не требуется.
Элемент PersistentResourceSize, который является минимальным размером в байтах сохраняемого ресурса, который должен быть привязан к таблице привязки для этого объекта, готового к выполнению. Значение нуля означает, что постоянный ресурс не требуется. Постоянный ресурс при необходимости должен быть предоставлен во время инициализации скомпилированного оператора (где он привязан в качестве выходных данных инициализатора оператора), а также во время выполнения. Дополнительные сведения об этом см. далее в этой статье. Только скомпилированные операторы имеют постоянные ресурсы— инициализаторы операторов всегда возвращают значение 0 для этого элемента.
Если вы вызываете IDMLDispatchable::GetBindingProperties для инициализатора оператора до и после вызова IDMLOperatorInitializer::Reset, то два набора свойств привязки, извлеченных, не гарантированы идентичны.
Описать, создать и привязать кучу дескрипторов
С точки зрения дескрипторов, ваша ответственность начинается и заканчивается самой кучей дескрипторов. Сам DirectML заботится о создании дескрипторов и управлении ими внутри предоставленной вами кучи.
Таким образом, используйте структуру D3D12_DESCRIPTOR_HEAP_DESC , чтобы описать кучу достаточно большой для количества дескрипторов, необходимых для отправки. Затем создайте его с помощью метода ID3D12Device::CreateDescriptorHeap. И, наконец, вызовите ID3D12GraphicsCommandList::SetDescriptorHeaps для привязки кучи дескриптора к конвейеру.
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()
);
Опишите и создайте таблицу привязки
Таблица привязки DirectML представляет собой набор ресурсов, которые вы связываете с конвейером для использования в процессе выполнения. Эти ресурсы могут быть входными и выходными тензорами (или другими параметрами) для оператора или могут быть различными постоянными и временными ресурсами, с которыми работает диспетчер.
Используйте структуру DML_BINDING_TABLE_DESC , чтобы описать таблицу привязки, включая диспетчеризацию, для которой она будет представлять привязки, и диапазон дескрипторов (из только что созданной кучи дескрипторов), к которой должна ссылаться таблица привязки (и в которую DirectML может записывать эти дескрипторы). Значение descriptorCount (одно из свойств привязки, которые мы извлекли на первом шаге) сообщает нам, какой минимальный размер имеется в дескрипторах таблицы привязки, необходимой для объекта с возможностью отправки. Здесь мы используем это значение, чтобы указать максимальное количество дескрипторов, которые DirectML разрешено записывать в нашу кучу, начиная от начальных значений предоставленных дескрипторов как для ЦП, так и для GPU.
Затем вызовите IDMLDevice::CreateBindingTable , чтобы создать таблицу привязки DirectML. На более поздних этапах, после того как мы создадим дополнительные ресурсы для распределения, мы добавим эти ресурсы в таблицу привязки.
Вместо передачи DML_BINDING_TABLE_DESC этому вызову, вы можете передать nullptr, указывающий на пустую таблицу привязки.
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()
)
);
Порядок, в котором DirectML записывает дескрипторы в кучу, не указан, поэтому приложение должно не перезаписать дескрипторы, упакованные в таблицу привязки. Предоставленные дескрипторы ЦП и GPU могут поступать из разных куч, однако приложение несет ответственность за то, чтобы весь диапазон дескрипторов, на который указывает дескриптор ЦП, был скопирован в диапазон, указанный дескриптором GPU, до использования этой таблицы привязок. Куча дескриптора, из которой предоставляются дескрипторы, должна иметь тип D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV. Кроме того, куча, на которую ссылается GPUDescriptorHandle, должна быть шейдер-видимой дескрипторной кучей.
Вы можете сбросить таблицу привязки, чтобы удалить все ресурсы, добавленные в неё, и одновременно изменить любое свойство, установленное в исходном DML_BINDING_TABLE_DESC (например, для охвата нового диапазона дескрипторов или повторного использования для другой задачи в диспетчеризации). Просто внесите изменения в структуру описания и вызовите IDMLBindingTable::Reset.
dmlBindingTableDesc.Dispatchable = pIDMLCompiledOperator.get();
winrt::check_hresult(
pIDMLBindingTable->Reset(
&dmlBindingTableDesc
)
);
Описание и привязка временных и постоянных ресурсов
Структура DML_BINDING_PROPERTIES, которую мы заполняли при извлечении свойств привязки для нашего объекта, способного к диспетчеризации, содержит размер в байтах любого временного или постоянного ресурса, необходимых для его работы. Если любой из этих размеров не равен нулю, создайте ресурс буфера Direct3D 12 и добавьте его в таблицу привязки.
В приведенном ниже примере кода мы создадим временный ресурс (размером temporaryResourceSize байтов) для диспетчеризуемого объекта. Мы описываем, как мы хотим привязать ресурс, а затем добавим ее в таблицу привязки.
Поскольку мы привязываем один ресурс буфера, мы описываем нашу привязку с помощью структуры DML_BUFFER_BINDING. В этой структуре мы указываем ресурс буфера Direct3D 12 (ресурс должен иметь измерение D3D12_RESOURCE_DIMENSION_BUFFER), а также смещение и размер буфера. Кроме того, можно описать привязку для массива буферов (а не для одного буфера), а DML_BUFFER_ARRAY_BINDING структуру существует для этой цели.
Чтобы абстрагировать различие между привязкой буфера и привязкой буферного массива, мы используем структуру DML_BINDING_DESC . Вы можете установить элемент Type членов DML_BINDING_DESC в значение DML_BINDING_TYPE_BUFFER или DML_BINDING_TYPE_BUFFER_ARRAY. Затем можно установить Desc член, чтобы он указывал на DML_BUFFER_BINDING или на DML_BUFFER_ARRAY_BINDING, в зависимости от Type.
Мы имеем дело с временным ресурсом в этом примере, поэтому мы добавим его в таблицу привязки с вызовом 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);
Временный ресурс (если он требуется) — это оперативная память, используемая внутренне во время выполнения оператора, поэтому вам не нужно беспокоиться о её содержимом. Кроме того, после вызова IDMLCommandRecorder::RecordDispatch не нужно хранить его на GPU. Это означает, что приложение может освободить или перезаписать временный ресурс между вызовами скомпилированного оператора. Указанный диапазон буфера, привязанный к временному ресурсу, должен иметь его начальное смещение, выровненное по DML_TEMPORARY_BUFFER_ALIGNMENT. Тип кучи, лежащей в основе буфера, должен быть D3D12_HEAP_TYPE_DEFAULT.
Если диспетчер сообщает о ненулевом размере для его постоянно работающего ресурса с более длительным сроком службы, то процедура немного отличается. Необходимо создать буфер и описать привязку, следуя тому же образцу, как показано выше. Но добавьте его в таблицу привязки оператора с вызовом IDMLBindingTable::BindOutputs, так как это задание инициализатора оператора для инициализации постоянного ресурса. Затем добавьте его в таблицу привязки скомпилированного оператора с вызовом IDMLBindingTable::BindPersistentResource. Ознакомьтесь с минимальным примером кода приложения DirectML , чтобы увидеть этот рабочий процесс в действии. Содержимое и время существования постоянного ресурса должны сохраняться до тех пор, пока существует скомпилированный оператор. То есть, если оператору требуется постоянный ресурс, приложение должно предоставить его во время инициализации и впоследствии предоставить его всем будущим выполнениям оператора без изменения его содержимого. Постоянный ресурс обычно используется DirectML для хранения таблиц подстановки или других длительных данных, вычисляемых во время инициализации оператора и повторно используемых в будущих выполнениях этого оператора. Указанный диапазон буфера, привязанный к постоянному буферу, должен иметь смещение начала, выровненное по DML_PERSISTENT_BUFFER_ALIGNMENT. Тип кучи, лежащей в основе буфера, должен быть D3D12_HEAP_TYPE_DEFAULT.
Описание и привязка любых тензоров
Если вы используете скомпилированный оператор (а не с инициализатором оператора), необходимо привязать входные и выходные ресурсы (для тензоров и других параметров) к таблице привязки оператора. Количество связей должно точно соответствовать количеству входных данных оператора, включая необязательные тензоры. Определенные входные и выходные тензоры и другие параметры, которые принимает оператор, описаны в разделе для этого оператора (например, DML_ELEMENT_WISE_IDENTITY_OPERATOR_DESC).
Ресурс тензора — это буфер, содержащий значения отдельных элементов тензора. Вы отправляете и считываете обратно такой буфер в GPU с помощью обычных методов Direct3D 12 (отправка ресурсов и чтение данных обратно через буфер). Ознакомьтесь с минимальным примером кода приложения DirectML , чтобы увидеть эти методы в действии.
Наконец, опишите привязки входных и выходных ресурсов с помощью структур DML_BUFFER_BINDING и DML_BINDING_DESC, а затем добавьте их в таблицу привязки скомпилированного оператора с вызовами IDMLBindingTable::BindInputs и IDMLBindingTable::BindOutputs. При вызове метода IDMLBindingTable::Bind* DirectML записывает один или несколько дескрипторов в диапазон дескрипторов ЦП.
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);
Одним из шагов при создании оператора DirectML (см. idMLDevice::CreateOperator) является объявление одной или нескольких структур DML_BUFFER_TENSOR_DESC для описания буферов данных тензора, которые принимает и возвращает оператор. Кроме типа и размера буфера тензора, можно также указать флаг DML_TENSOR_FLAG_OWNED_BY_DML .
DML_TENSOR_FLAG_OWNED_BY_DML указывает, что данные тензора должны принадлежать и управляться DirectML. DirectML создает копию данных тензора во время инициализации оператора и сохраняет его в постоянном ресурсе. Это позволяет DirectML выполнять переформатирование данных тензора в другие, более эффективные формы. Установка этого флага может повысить производительность, но обычно это полезно только для тензоров, данные которых не изменяются в течение времени существования оператора (например, тензоры веса). Флаг может использоваться только для входных тензоров. Если флаг задан в определенном описании тензора, соответствующий тензор должен быть привязан к таблице привязки во время инициализации оператора, а не во время выполнения (что приведет к ошибке). Это противоположность поведения по умолчанию (поведение без флага DML_TENSOR_FLAG_OWNED_BY_DML), где тензор, как ожидается, будет привязан во время выполнения, а не во время инициализации. Все ресурсы, привязанные к DirectML, должны быть ресурсами по умолчанию или ресурсами пользовательской кучи.
Дополнительные сведения см. в разделе IDMLBindingTable::BindInputs и IDMLBindingTable::BindOutputs.
Выполнить диспетчируемое
Передайте таблицу привязки в качестве параметра при вызове IDMLCommandRecorder::RecordDispatch.
При использовании таблицы привязки во время вызова IDMLCommandRecorder::RecordDispatch DirectML привязывает соответствующие дескрипторы GPU к конвейеру. Обработчики дескрипторов ЦП и GPU не обязаны указывать на одни и те же записи в куче дескрипторов, однако ваша программа должна обеспечить, чтобы весь диапазон дескрипторов, на который указывает обработчик дескрипторов ЦП, был скопирован в диапазон, на который указывает обработчик дескрипторов GPU, до выполнения с использованием этой таблицы привязки.
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()
);
Наконец, закройте список команд Direct3D 12 и отправьте его для выполнения, как и любой другой список команд.
Перед выполнением RecordDispatch на GPU необходимо перенести все связанные ресурсы в состояние D3D12_RESOURCE_STATE_UNORDERED_ACCESS или в состояние, неявно промотируемое в D3D12_RESOURCE_STATE_UNORDERED_ACCESS, например D3D12_RESOURCE_STATE_COMMON. После завершения этого вызова ресурсы остаются в состоянии D3D12_RESOURCE_STATE_UNORDERED_ACCESS . Единственным исключением являются кучи для загрузки, привязанных при выполнении инициализатора оператора, когда у одного или нескольких тензоров установлен флаг DML_TENSOR_FLAG_OWNED_BY_DML. В этом случае все загрузочные кучи, предназначенные для ввода, должны находиться в состоянии D3D12_RESOURCE_STATE_GENERIC_READ и останутся в этом состоянии, как требуется для всех загрузочных куч. Если DML_EXECUTION_FLAG_DESCRIPTORS_VOLATILE не задано при компиляции оператора, все привязки необходимо задать в таблице привязки перед вызовом RecordDispatch , в противном случае поведение не определено. В противном случае, если оператор поддерживает позднюю привязку, привязка ресурсов может быть отложена до отправки списка команд Direct3D 12 в очередь команд для выполнения.
RecordDispatch логически действует как вызов ID3D12GraphicsCommandList::Dispatch. Таким образом, неупорядоченные барьеры представления доступа (UAV) необходимы для обеспечения правильного порядка при наличии зависимостей данных между диспетчерами. Этот метод не вставляет барьеры для UAV на входные и выходные ресурсы. Приложение должно гарантировать, что правильные барьеры UAV выполняются для любых входных данных, если их содержимое зависит от предыдущей отправки, а также для любых выходных данных, если есть последующие отправки, которые зависят от этих выходных данных.
Время существования и синхронизация дескрипторов и таблицы привязки
Хорошее представление привязки в DirectML можно представить так: в фоновом режиме таблица привязки DirectML создает и управляет дескрипторами неупорядоченного доступа (UAV) внутри предоставленной кучи дескрипторов. Поэтому все обычные правила Direct3D 12 относятся к синхронизации доступа как к этой куче, так и к её дескрипторам. Это ответственность вашего приложения за правильную синхронизацию между работой ЦП и GPU, используюющей таблицу привязки.
Таблица привязки не может перезаписать дескриптор, пока дескриптор используется (например, по предыдущему кадру). Таким образом, если вы хотите повторно использовать уже привязанную кучу дескрипторов (например, снова вызвав Bind* на таблице привязки, указывающей на неё, или вручную перезаписав кучу дескрипторов), вам следует подождать, пока диспатчер, который в настоящее время использует эту кучу дескрипторов, не завершит выполнение на GPU. Таблица привязки не поддерживает надежную ссылку на кучу дескрипторов, в которую она записывается, поэтому вы не должны освобождать основную шейдер-видимую кучу дескрипторов, пока вся работа, использующая эту таблицу привязки, не завершит выполнение на GPU.
С другой стороны, хотя таблица привязки указывает на кучу дескрипторов и управляет ею, сама таблица не содержит ни одну из этих областей памяти. Таким образом, вы можете освободить или сбросить таблицу привязки в любое время после вызова IDMLCommandRecorder::RecordDispatch с ним (вам не нужно ждать завершения этого вызова на GPU, пока базовые дескрипторы остаются допустимыми).
Таблица привязки не содержит надежных ссылок на ресурсы, связанные с ним, — приложение должно убедиться, что ресурсы не удаляются во время использования GPU. Кроме того, таблица привязки не является потокобезопасной. Приложение не должно вызывать методы в таблице привязки одновременно из разных потоков без синхронизации.
И учитывайте, что в любом случае повторная привязка необходима только при изменении связанных ресурсов. Если вам не нужно изменять связанные ресурсы, можно привязать один раз при запуске и передать ту же таблицу привязки при каждом вызове RecordDispatch.
Для переключения рабочих нагрузок машинного обучения и отрисовки просто убедитесь, что таблицы привязки каждого кадра указывают на диапазоны кучи дескриптора, которые еще не используются на GPU.
При необходимости укажите привязки операторов для позднего связывания
Если вы работаете с компилируемым оператором (а не с инициализатором оператора), у вас есть возможность указать позднюю привязку для оператора. Без поздней привязки необходимо задать все привязки в таблице привязки перед записью оператора в список команд. С помощью поздней привязки можно задать (или изменить) привязки к операторам, которые вы уже записали в список команд, прежде чем он был отправлен в очередь команд.
Чтобы указать позднюю привязку, вызовите IDMLDevice::CompileOperator с аргументом flagsDML_EXECUTION_FLAG_DESCRIPTORS_VOLATILE.