• <xmp id="om0om">
  • <table id="om0om"><noscript id="om0om"></noscript></table>
  • 3 月 19 日下午 2 點,鎖定 NVIDIA AI 網絡中文專場。立即注冊觀看
    計算機視覺/視頻分析

    適用于基于 NVIDIA 的 PC 的端到端 AI : ONNX 和 DirectML

    這篇文章是關于優化端到端人工智能.

    雖然 NVIDIA 硬件可以以難以置信的速度處理構成神經網絡的單個操作,但確保您正確使用這些工具是很重要的。在 ONNX 中使用 ONNX Runtime 或 TensorRT 等開箱即用的工具通常會給您帶來良好的性能,但既然您可以擁有出色的性能,為什么還要滿足于良好的性能呢?

    在這篇文章中,我討論了一個常見的場景,即帶有 DirectML 后端的 ONNX Runtime 。這是構建 WinML 的兩個主要組件。當在 WinML 之外使用時,它們可以在支持運算符集以及支持 DML 以外的后端(如 TensorRT )方面提供極大的靈活性。

    為了獲得 ONNX Runtime 和 DML 的出色性能,通常值得超越基本實現。從使用 ONNX Runtime 時的常見場景開始。

    To use ONNX runtime, you must load, pre-process, and convert data, run inference on the data, and then post-process and convert data again.
    圖 1 。使用 ONNX Runtime 所需的步驟
    • 一些圖像數據是從磁盤加載的。
    • int8 圖像數據以某種方式進行預處理,例如縮放并轉換為 float16 。
    • 圖像數據被加載到 GPU 上。
    • 對圖像數據進行推理。
    • 將結果加載回 CPU 。
    • 結果經過后處理,或者可能發送到另一個模型。

    這里有幾個問題。如果從頭開始使用 ONNX Runtime ,則提供指向系統( CPU )內存中數據的指針。當您通過調用Ort::Session::Run(...)以及當推理完成時將數據傳輸回系統( CPU )存儲器。

    雖然從實現的角度來看,這聽起來很方便,但您可能在推理之前有一個預處理階段,在推理之后有一個后處理階段。使用當前的工作流程,您必須對 CPU 上的數據進行預處理和后處理,或者在 ONNX Runtime 第二次將所有數據傳輸回 GPU 進行推理之前,往返于 GPU ‘。

    一個更好的方法是將原始數據加載到 GPU ,無論是完整加載還是分塊加載,先對 GPU ‘執行預處理步驟(稍后將對此進行詳細介紹),然后將其保留在 GPU 上,以便進行推理。通過這種方式,您已經利用 GPU 的大規模并行能力來執行預處理步驟,并減少了初始傳輸的大小,因為您現在正在傳輸 int8 數據,而不是 float16 數據。

    理論上這一切都很好,但如何使用 ONNX Runtime 和 DirectML 將其付諸實踐?為此,您必須深入研究 DirectX 12 , DirectML 就是基于它構建的。

    有關我在本文中討論的實現的更多信息,請參閱code example以及評論。

    DirectX12

    與 OpenGL 和 CUDA 相比, DirectX 12 可能有些冗長。對于渲染圖形,可能有很多管道狀態需要管理。您只需要使用計算管道,這要簡單得多。無論如何, DirectX 12 與其他任何 API 或 SDK 一樣,都有學習曲線,但既不陡峭也不冗長。

    DirectX12 通過公開較低級別的構造來實現對 GPU 的快速且高度可配置的訪問,您可以使用這些構造來控制在 GPU ‘上安排工作的時間和方式。帶 DML 的 ONNX Runtime 已經使用了它,但您希望訪問 ONNX 和 DML 正在使用的相同資源,以便使用它們執行預處理和傳輸。

    DirectX 12 公開了名為命令隊列。您可以將命令記錄到 CPU 上的這些隊列中,并將它們發送到要調度的 GPU 。這些命令可以多次運行,而無需重新記錄。通過創建多個隊列,您可以在 GPU 上并行執行多個作業。通常情況下,單個推理可能不會使 GPU 上的處理器飽和,并且您可能能夠同時做多件事。稍后將對此進行詳細介紹。

    以下是 DirectX 12 工作流的高級視圖:

    • 獲取圖形卡(適配器)的參考。
    • 創建對圖形設備的邏輯引用。您可以使用它來分配內存和發出命令。
    • 從設備中獲取對命令隊列的引用。
    • 編寫用于預處理的計算著色器,這比您想象的要簡單。
    • 創建計算管道狀態對象。
    • 在設備上分配一些內存用于輸入和輸出。您可以隨時在該內存之間進行傳輸。
    • 將命令添加到命令隊列中。
    • 執行隊列。

    您創建的隊列與 ONNX Runtime 提供給 DirectML 的隊列相同。您可以構建新的高性能功能,作為 ONNX Runtime 已經提供的功能的擴展。

    您將要學習的內容適用于 ONNX 和 DirectML ,以及許多其他計算任務。

    獲取圖形卡的參考

    根據您的系統類型,您可能有一個圖形卡、多個圖形卡,或者根本沒有圖形卡。

    要做的第一件事是查詢系統,以發現您可以玩什么。這使您能夠通過 Direct X Graphics interface ( DXGI )獲取與實際物理設備的接口。通過此物理設備接口,您可以創建對邏輯設備使您可以訪問 DirectX 運行所需的設備內存和命令隊列。

    有幾種類型的命令隊列可用于不同的任務,例如渲染、復制和計算工作。可以并行執行一些任務,例如復制和計算工作。有關詳細信息,請參閱代碼示例.

    設置 ONNX 運行時

    DirectML is one of the many backends available for ONNX Runtime. Others include CUDA, cuDNN, TensorRT, and OpenVINO.
    圖 2 :將 ONNX Runtime 與后端一起使用

    要在此項目中將 ONNX Runtime 與 DirectML 一起使用,請首先設置對邏輯設備的引用。然后,使用邏輯 DirectX12 設備創建對 DML 設備的引用。您還可以創建一個隊列供 DML 使用。然后,當您為 DML 執行提供程序創建會話選項結構時,您可以使用擴展表單,使用先前創建的 DirectX12 構造來創建 ORT 會話。

        Ort::SessionOptions opts;
        OrtSessionOptionsAppendExecutionProviderEx_DML(
    opts, m_dml_device.Get(),
    m_copy_queue->GetD3D12CmdQueue().Get())

    現在,您可以使用SessionOptions對象

    創建會話后,您現在可以開始初始化資源,將輸入數據傳遞給模型,并從模型接收輸出數據。要做到這一點,您可以在模型中查詢它所期望的張量形狀和格式。

    內存和內存傳輸

    如果從基本實現中使用 ONNX Runtime ,則輸入和輸出數據將在 CPU 內存中啟動, ONNX Run 將管理與 GPU 之間的傳輸。在簡單的情況下,例如當對圖像的整體進行推理時,這可能是可以的。

    然而,在實踐中,大多數大圖像都被分解成瓦片,可能有一些重疊并按順序處理。在這種情況下,通過自己管理轉移可以獲得相當大的性能提升。

    When transfers happen in parallel to inference, you can have a performance improvement.
    圖 3 。通過啟用轉移以進行重疊推理,最大限度地提高您的 GPU
    • 您可以控制何時進行轉賬。
    • 您可以與其他計算工作并行執行傳輸。

    DirectX 12 的存儲器接口是靈活的,可以以各種方式使用以執行傳輸。在執行數據傳輸方面,為您提供最大粒度的方法是自己暫存內存。

    Although you can write directly to the CPU the data is slow to access from the GPU; however, you cannot write to data local to the GPU.
    圖 4 。與 GPU 相比,向 CPU 書寫的利弊
    • 為暫存內存創建專用隊列:
      • 類型: D3D12 _ COMMAND _ LIST _ type _ COPY
    • 創建兩個 ID3D12Resource 對象:
      • D3D12 _ HEAP _ TYPE _ UPLOAD :從主機可見。
      • D3D12 _ HEAP _ TYPE _ DEFAULT : GPU 的本地。
      • 使用已提交或已放置的資源:
        • 提交的資源: DX12 為您創建和管理堆。
        • 放置的資源:您提供堆。用于子分配。
    • 創建一個命令列表并發送一個復制命令。這將執行從主機到設備的復制。

    在 GPU 上獲得數據后,創建一個引用它的視圖對象和一個綁定到此內存的 Ort-Value 對象。然而,您并沒有將原始傳輸的數據按原樣輸入到模型中,因為還有一個更重要的步驟需要執行。

    更快的預處理

    現在,您可以控制數據何時以及如何傳輸到 GPU 。現在,您還可以了解如何將預處理和后處理移動到 GPU 。

    在大多數計算機視覺應用程序中,以整數格式(如 RGB8 )提供一些輸入,并將其轉換為縮放和偏置的浮點表示是很常見的。

    如果您使用開箱即用的 ONNX Runtime 和 DML ,則很難在 GPU 上執行此操作,因為數據在 CPU 上開始和結束其行程。現在,您可以自己執行這些傳輸,從而可以控制內存的生命周期。您還可以將此預處理和后處理轉移到自定義計算過程中,并將其作為端到端推理管道的一部分運行。

    您必須做的是在轉移到 GPU 之后但在運行推理之前插入一個計算步驟。本例中的計算步驟獲取傳輸到 GPU 的 RGB8 整數數據,并將其傳遞給執行縮放和偏移的計算內核(著色器)。在這樣做的同時,它還將數據轉換為模型所需精度的浮點值。為了獲得最佳性能這里應該使用 FP16.

    必須對數據執行的所有操作都是就地操作,因為輸入中的每個像素都對其執行了相同的操作,并且不依賴于其任何鄰居。這種類型的工作很容易并行執行,因此它是利用 GPU 的力量的絕佳候選者。

    要使用 DirectX 12 運行計算著色器,請創建所謂的管道狀態對象。對于圖形渲染來說,這可能是一個相當復雜的過程,但對于計算處理來說,它要簡單得多。

    管道狀態對象本質上預編譯在 GPU 上執行某些工作所需的所有狀態,包括運行的著色器字節碼和要使用的資源的綁定。

    The compute pipeline contains steps for transferring, converting to float + scale + bias, and inference.
    圖 5 。管道狀態對象

    首先創建一個名為根簽名,這與函數簽名類似,因為它描述了管道的屬性和輸出。然后,您可以使用這個根簽名來創建管道狀態對象本身,為輸入和輸出提供實際的緩沖區綁定。

    創建管道后,創建一個命令緩沖區并記錄運行計算著色器所需的命令。有關詳細信息,請參閱代碼示例.

    同步和利用更多并行性

    Between transfers, data must be preprocessed, denoised, and post-processed.
    圖 6 。可以并行完成的其他任務

    NVIDIA 硬件可以并行執行一些不同的任務,在執行任何計算工作的同時,顯著地執行與 GPU 的并行傳輸。當 DML 模型在 GPU 上執行時,它是計算工作。

    我建議您設置端到端管道,以便一批推理工作(例如瓦片)可以執行推理,而下一批推理任務則轉移到 GPU ,以便它可以下一步運行。事實上,如果 GPU 上有足夠的可用資源,甚至可以并行運行多個瓦片的實際計算或推理部分。

    為了在處理中發生這些重疊,計算或傳輸工作必須在它們自己的隊列中執行,其中一些隊列可以相互并行運行。這就提出了同步。如果在某些數據的一個隊列中運行傳輸,而在另一個隊列上運行推理,則必須確保在必須運行任何計算或推理步驟時數據已完成傳輸。

    同步可以通過多種方式從 CPU 側和 GPU 側執行,但您希望 CPU ‘盡可能少地進行交互。使用資源壁壘這導致隊列等待,直到滿足由屏障設置的條件為止。您使用兩個障礙:

    資源轉換障礙

    請記住,您正在將數據從主機傳輸到設備。傳輸數據時,目標緩沖區處于可以從 CPU 向其傳輸數據的狀態。當綁定到管道時,這可能不是它所處的最佳狀態,因此必須提供轉換。

    這一要求取決于硬件平臺,但需要轉換才能使 DirectX12 的使用有效。

    UAV 屏障

    這種類型的屏障只是阻塞隊列,直到所有數據都完成傳輸。通過以這種方式使用屏障,您可以讓 GPU 等待,而 CPU 根本不會參與并提高性能。

    CD3DX12_RESOURCE_BARRIER barrier2 = CD3DX12_RESOURCE_BARRIER::UAV(
            m_ort_input_buffer->GetD3DResource().Get()
        );

    創建兩個屏障后,一步將它們添加到命令列表中。

    CD3DX12_RESOURCE_BARRIER barriers[2]  = {barrier1, barrier2};
    m_cmd_list_stage_input->ResourceBarrier(2, barriers);

    你現在可以把所有的部分放在一起了。您已經看到,您不僅可以創建和管理可用于調度傳輸和計算工作的資源,還可以創建并管理這些資源的調度時間。

    現在您只需要兩個隊列:

    • 傳輸隊列:用于調度傳輸命令。
    • 計算隊列:用于調度預處理和后處理命令以及實際 ONNX 運行時會話本身。

    您還需要為每個記錄命令的命令列表。

    傳輸和計算之間必須有一些同步,以確保在傳輸數據的工作開始之前傳輸已經完成。這里有一個優化的機會。

    NVIDIA 硬件完全是并行的,它可以同時執行傳輸和計算等操作。當您處理單個作業時,幾乎沒有機會將轉移與計算重疊,因為您必須等待轉移完成后才能開始計算。

    通常,在圖像處理作業的情況下,您會將作業拆分為瓦片。對于大型圖像,很可能沒有足夠的設備內存來在一次運行中執行工作。通過將每個瓦片視為要執行的一系列任務來使用這種并行性。然后,您可以在任何時候“飛行”幾個瓦片,每個關鍵階段之間都有一個同步點:

    • 第一個磁貼:將數據復制回 CPU 內存。
    • 第二個磁貼:運行推理和計算工作。
    • 第三個瓦片:正在將數據復制到 GPU 內存。

    這三項任務都可以并行進行。甚至可能存在這樣的情況,即如果沒有使 GPU 飽和,則可以在一定程度的重疊的情況下進行一個以上的計算工作。

    結論

    我在這篇文章中涵蓋了很多內容。要想實際理解這些方法的機制,唯一的方法就是動手。我鼓勵你們花時間試驗example code,使用從導出的 ONNX NVIDIA DL Designer.

    有關執行過程中發生的事情的更多信息,請參閱代碼注釋。

    ?

    0

    標簽

    人人超碰97caoporen国产