5.1. Object Lifetimes
TensorRT 的 API 是基于類的,其中一些類充當其他類的工廠。對于用戶擁有的對象,工廠對象的生命周期必須跨越它創建的對象的生命周期。例如, NetworkDefinition和BuilderConfig類是從構建器類創建的,這些類的對象應該在構建器工廠對象之前銷毀。
此規則的一個重要例外是從構建器創建引擎。創建引擎后,您可以銷毀構建器、網絡、解析器和構建配置并繼續使用引擎。
5.2. Error Handling and Logging
創建 TensorRT 頂級接口(builder、runtime 或 refitter)時,您必須提供Logger ( C++ 、 Python )接口的實現。記錄器用于診斷和信息性消息;它的詳細程度是可配置的。由于記錄器可用于在 TensorRT 生命周期的任何時間點傳回信息,因此它的生命周期必須跨越應用程序中對該接口的任何使用。實現也必須是線程安全的,因為 TensorRT 可以在內部使用工作線程。
對對象的 API 調用將使用與相應頂級接口關聯的記錄器。例如,在對ExecutionContext::enqueue()的調用中,執行上下文是從引擎創建的,該引擎是從運行時創建的,因此 TensorRT 將使用與該運行時關聯的記錄器。
錯誤處理的主要方法是ErrorRecorde ( C++ , Python ) 接口。您可以實現此接口,并將其附加到 API 對象以接收與該對象關聯的錯誤。對象的記錄器也將傳遞給它創建的任何其他記錄器 – 例如,如果您將錯誤記錄器附加到引擎,并從該引擎創建執行上下文,它將使用相同的記錄器。如果您隨后將新的錯誤記錄器附加到執行上下文,它將僅接收來自該上下文的錯誤。如果生成錯誤但沒有找到錯誤記錄器,它將通過關聯的記錄器發出。
請注意,CUDA 錯誤通常是異步的 – 因此,當執行多個推理或其他 CUDA 流在單個 CUDA 上下文中異步工作時,可能會在與生成它的執行上下文不同的執行上下文中觀察到異步 GPU 錯誤。
5.3 Memory
TensorRT 使用大量設備內存,即 GPU 可直接訪問的內存,而不是連接到 CPU 的主機內存。由于設備內存通常是一種受限資源,因此了解 TensorRT 如何使用它很重要。
5.3.1. The Build Phase
在構建期間,TensorRT 為時序層實現分配設備內存。一些實現可能會消耗大量臨時內存,尤其是在使用大張量的情況下。您可以通過構建器的maxWorkspace屬性控制最大臨時內存量。這默認為設備全局內存的完整大小,但可以在必要時進行限制。如果構建器發現由于工作空間不足而無法運行的適用內核,它將發出一條日志消息來指示這一點。
然而,即使工作空間相對較小,計時也需要為輸入、輸出和權重創建緩沖區。 TensorRT 對操作系統因此類分配而返回內存不足是穩健的,但在某些平臺上,操作系統可能會成功提供內存,隨后內存不足killer進程觀察到系統內存不足,并終止 TensorRT 。如果發生這種情況,請在重試之前盡可能多地釋放系統內存。
在構建階段,通常在主機內存中至少有兩個權重拷貝:來自原始網絡的權重拷貝,以及在構建引擎時作為引擎一部分包含的權重拷貝。此外,當 TensorRT 組合權重(例如卷積與批量歸一化)時,將創建額外的臨時權重張量。
5.3.2. The Runtime Phase
在運行時,TensorRT 使用相對較少的主機內存,但可以使用大量的設備內存。
引擎在反序列化時分配設備內存來存儲模型權重。由于序列化引擎幾乎都是權重,因此它的大小非常接近權重所需的設備內存量。
ExecutionContext使用兩種設備內存:
一些層實現所需的持久內存——例如,一些卷積實現使用邊緣掩碼,并且這種狀態不能像權重那樣在上下文之間共享,因為它的大小取決于層輸入形狀,這可能因上下文而異。該內存在創建執行上下文時分配,并在其生命周期內持續。
暫存內存,用于在處理網絡時保存中間結果。該內存用于中間激活張量。它還用于層實現所需的臨時存儲,其邊界由IBuilderConfig::setMaxWorkspaceSize()控制。
您可以選擇通過ICudaEngine::createExecutionContextWithoutDeviceMemory()創建一個沒有暫存內存的執行上下文,并在網絡執行期間自行提供該內存。這允許您在未同時運行的多個上下文之間共享它,或者在推理未運行時用于其他用途。 ICudaEngine::getDeviceMemorySize()返回所需的暫存內存量。
構建器在構建網絡時發出有關執行上下文使用的持久內存和暫存內存量的信息,嚴重性為 kINFO 。檢查日志,消息類似于以下內容:
[08/12/2021-17:39:11] [I] [TRT] Total Host Persistent Memory: 106528 [08/12/2021-17:39:11] [I] [TRT] Total Device Persistent Memory: 29785600 [08/12/2021-17:39:11] [I] [TRT] Total Scratch Memory: 9970688
默認情況下,TensorRT 直接從 CUDA 分配設備內存。但是,您可以將 TensorRT 的IGpuAllocator ( C++ 、 Python )接口的實現附加到構建器或運行時,并自行管理設備內存。如果您的應用程序希望控制所有 GPU 內存并子分配給 TensorRT,而不是讓 TensorRT 直接從 CUDA 分配,這將非常有用。
TensorRT 的依賴項( cuDNN和cuBLAS )會占用大量設備內存。 TensorRT 允許您通過構建器配置中的TacticSources ( C++ 、 Python )屬性控制這些庫是否用于推理。請注意,某些層實現需要這些庫,因此當它們被排除時,網絡可能無法編譯。
CUDA 基礎設施和 TensorRT 的設備代碼也會消耗設備內存。內存量因平臺、設備和 TensorRT 版本而異。您可以使用cudaGetMemInfo來確定正在使用的設備內存總量。
注意:由于 CUDA 無法控制統一內存設備上的內存,因此cudaGetMemInfo返回的結果在這些平臺上可能不準確。
5.4. Threading
一般來說,TensorRT 對象不是線程安全的。預期的運行時并發模型是不同的線程將在不同的執行上下文上操作。上下文包含執行期間的網絡狀態(激活值等),因此在不同線程中同時使用上下文會導致未定義的行為。 為了支持這個模型,以下操作是線程安全的:
運行時或引擎上的非修改操作。
從 TensorRT 運行時反序列化引擎。
從引擎創建執行上下文。
注冊和注銷插件。
在不同線程中使用多個構建器沒有線程安全問題;但是,構建器使用時序來確定所提供參數的最快內核,并且使用具有相同 GPU 的多個構建器將擾亂時序和 TensorRT 構建最佳引擎的能力。使用多線程使用不同的 GPU 構建不存在此類問題。
5.5. Determinism
TensorRT builder 使用時間來找到最快的內核來實現給定的運算符。時序內核會受到噪聲的影響——GPU 上運行的其他工作、GPU 時鐘速度的波動等。時序噪聲意味著在構建器的連續運行中,可能不會選擇相同的實現。
AlgorithmSelector ( C++ , Python )接口允許您強制構建器為給定層選擇特定實現。您可以使用它來確保構建器從運行到運行選擇相同的內核。有關更多信息,請參閱算法選擇和可重現構建部分。
一旦構建了引擎,它就是確定性的:在相同的運行時環境中提供相同的輸入將產生相同的輸出。
關于作者
Ken He 是 NVIDIA 企業級開發者社區經理 & 高級講師,擁有多年的 GPU 和人工智能開發經驗。自 2017 年加入 NVIDIA 開發者社區以來,完成過上百場培訓,幫助上萬個開發者了解人工智能和 GPU 編程開發。在計算機視覺,高性能計算領域完成過多個獨立項目。并且,在機器人和無人機領域,有過豐富的研發經驗。對于圖像識別,目標的檢測與跟蹤完成過多種解決方案。曾經參與 GPU 版氣象模式GRAPES,是其主要研發者。
審核編輯:郭婷
-
NVIDIA
+關注
關注
14文章
4994瀏覽量
103195 -
gpu
+關注
關注
28文章
4743瀏覽量
129008 -
CUDA
+關注
關注
0文章
121瀏覽量
13641
發布評論請先 登錄
相關推薦
評論