Inside NVIDIA’s cuDNN Graph API: Fusion, Autotuning, and Plan Reuse with cuDNN Frontend
https://www.marktechpost.com/2026/09/15/inside-nvidias-cudnn-graph-api-fusion-autotuning-and-plan-reuse-with-cudnn-frontend/📌 跳過框架直接對話 cuDNN:用 Graph API 動手做 kernel 融合與自動調參
TL;DR:一份可在 Colab 跑的教學,示範如何用 cuDNN Frontend 手動搭圖、自選引擎、量化融合與調參帶來的效能差異。
平常呼叫 torch.nn.functional.conv2d 時,背後其實是 PyTorch 在幫你決定要用 cuDNN 的哪個引擎執行。這篇教學把這層決策攤開給你看:自己描述運算圖、自己選引擎、自己付出或省下編譯與 launch 的成本。
🧩 統一的五步驟建構流程
教學中的每個 kernel 都用同一套流程處理:先用維度與 strides 宣告 tensor,把運算串接上去,接著跑五步驟的 build pipeline——validate、build operation graph、create execution plans、check support、build plans——最後對一組指標構成的 variant pack 執行運算。全程在單一 Colab GPU 上跑,並拿 PyTorch 的對應運算做為 reference 驗證正確性、同時量測效能代價。
第一步是解決環境問題:讓 cuDNN Frontend 的動態載入器找得到 libcudnn.so。做法是先讓 PyTorch 載入它內建的 cuDNN,再手動 preload 對應的共享物件,讓 Frontend 自己的 dlopen 可以解析到 process 中已經存在的 library。接著依 GPU 的 compute capability 選擇 bfloat16 或 float16,建立 cuDNN handle,並定義好 tensor 描述、圖建構、workspace 配置與以事件為基礎的 benchmark 等後續共用的輔助函式。
🧩 從單一融合 kernel 到不信任預設引擎
第一個示範圖是 convolution 接 bias add 再接 ReLU,三個運算全部融合成單一 kernel。全程使用 channels_last 排列,因為這正是 cuDNN 的 tensor-core 引擎偏好的 NHWC strides,並明確指定輸出的維度與 strides,確保結果以相同佈局寫回。驗證輸出與 torch.nn.functional.conv2d 一致後,再把融合圖拿去跟 PyTorch 分開執行 convolution 與 activation 兩個 kernel 的做法做效能比較。
接著重新搭建同一個 convolution,但這次不採用預設的 heuristic,改為向 heuristic mode A、B、FALLBACK 都索取候選 plan,並用 build_plan_policy.ALL 全部編譯出來。逐一走訪每個候選 plan、配置各自需要的 workspace、用 execute_plan_at_index 計時,印出每個引擎的吞吐量與 workspace 大小。最快與最慢引擎之間的落差,正是這個練習想凸顯的重點:比起接受預設選擇,自己動手調參到底能換來多少額外效能。
🧩 更完整的 epilogue、attention 與 plan 重用
教學接著搭建一個 batched matmul,並掛上完整的 epilogue:以 host scalar 傳入的 alpha scale、bias add、activation,再加上對結果做 AMAX reduction。把 AMAX 計算塞進同一個 kernel,正是 FP8 訓練會用到的模式,可以在不對輸出多做一次掃描的情況下,順手收集下一步量化所需的 scale factor。跟 PyTorch 的 baddbmm + activation + amax 這串鏈式呼叫比較後可以看出,加速的來源主要是省掉了 epilogue 之間的記憶體存取,而不是矩陣乘法本身變快。
再來是一個帶 causal mask 的 fused scaled dot-product attention 圖,拿去跟 torch.nn.functional.scaled_dot_product_attention 核對,並限定只在 SM80(Ampere 架構)以上執行,因為融合 kernel 需要對應的硬體支援。教學也特別處理了寫法上的相容性問題,因為 cuDNN Frontend 在 1.x 系列版本之間,已經逐漸從 use_causal_mask 走向 diagonal_alignment 與 bound arguments 的寫法。
最後示範把已經建好的 matmul 圖序列化成 bytes,重新載入到一個全新的圖物件,並透過整數 UID 執行,讓 process 啟動之後可以完全跳過重新編譯的成本。
🧩 貼近生產環境的兩個實務技巧
教學收尾聚焦在兩個生產環境常見的考量。第一個是讓四個只有 batch size 不同的圖共用同一個 kernel cache,分別計時觀察後續 shape 是否能重用已編譯好的 kernel,不必再付一次 JIT 編譯的代價。第二個是把 convolution 的 plan 包進 CUDA graph 裡執行擷取,並把 cuDNN handle 的 stream 設定成 capture stream,讓整段運算進到 graph 裡,藉此量測 replay 到底省下多少每次迭代的 launch overhead。
⚠️ 這套 API 值得投入的場景
整份教學涵蓋的程式碼規模不大,只有一個 convolution、一個 matmul、一個 attention kernel,但涉及的技巧面向很廣。作者指出,這個 API 真正值得投入的地方,是那些框架層級沒有等效寫法的融合、shape 夠「熱」值得花時間自動調參,以及啟動與 launch 成本會主導總開銷的小型 kernel;許多情況下跟 PyTorch 效能打平,其實是因為 PyTorch 底層本來就在呼叫 cuDNN。
🎯 實務啟示
對想要壓榨 GPU 訓練或推論效能的工程師來說,這份教學提供了一套可以直接動手驗證的思路:哪些運算值得手動搭圖融合、如何用同一套程式碼跑過所有候選引擎找出實際最快的選項,以及如何透過 plan 序列化、kernel cache 共用與 CUDA graph 擷取,把編譯與 launch 成本挪出熱路徑。
🔗 來源
- 標題:Inside NVIDIA’s cuDNN Graph API: Fusion, Autotuning, and Plan Reuse with cuDNN Frontend
- 作者/機構:Sana Hassan, MarkTechPost
- 連結:https://www.marktechpost.com/2026/09/15/inside-nvidias-cudnn-graph-api-fusion-autotuning-and-plan-reuse-with-cudnn-frontend/
#cuDNN #NVIDIA #GPUOptimization #DeepLearning #KernelFusion #CUDA #Autotuning #Attention #FP8 #MLSystems
原始資料 MarkTechPost · 收集於 2026-09-16
摘要原文
In this tutorial , we work through the cuDNN Frontend ‘s graph API from below the framework: we describe a computation as a graph of operations, let cuDNN pick an engine to run it, and then take control of that choice ourselves. Every kernel we build here is expressed the same way: we declare tensors by their dimensions and strides, chain operations onto them, run the five-step build pipeline of validate, build operation graph, create execution plans, check support, and build plans, and then execute against a variant pack of pointers. We run it all on a single Colab GPU, checking each result against a PyTorch reference so we can see both that the fusion is correct and what it costs. The topics build on each other, moving from a single fused convolution to autotuning across engine configs, FP8-style epilogues, attention, plan serialization, dynamic shapes, and CUDA graph capture. We start by installing nvidia-cudnn-frontend and solving the problem that trips up most first runs: making libcudnn.so visible to the frontend’s dynamic loader. We force PyTorch to load its bundled cuDNN first and then preload the shared objects explicitly, so the frontend’s own dlopen resolves against a library already resident in the process. We then report the compute capability, pick bfloat16 or float16 accordingly, create the cuDNN handle, and define the helpers for tensor description, graph building, workspace allocation, and event-based benchmarking that the rest of the notebook reuses. We build our first graph, a convolution followed by a bias add and a ReLU, all fused into a single kernel. We keep every tensor in channels_last because that is what gives cuDNN the NHWC strides its tensor-core engines want, and we pin the output dimensions and strides explicitly so the result is written back in the same layout. We validate the output against torch.nn.functional.conv2d, then benchmark the fused graph against PyTorch running the convolution and activation as separate kernels. We rebuild the same convolution but stop trusting the heuristic, asking for plans from heuristic modes A, B, and FALLBACK and compiling all of them with build_plan_policy.ALL. We then walk the plan list, build each config, allocate its specific workspace, and time it with execute_plan_at_index, printing throughput and workspace size for every candidate. The spread between the fastest and slowest engine is the point of the exercise, because it tells us how much we gain by shipping an autotuned index instead of accepting the default pick. We move to a batched matmul and hang a full epilogue off it: an alpha scale supplied as a pass-by-value host scalar, a bias add, an activation, and an AMAX reduction over the result. The AMAX in the same kernel is the pattern that FP8 training relies on, since it collects the scale factor for the next quantization step without a second pass over the output. We compare against a PyTorch chain of baddbmm, activation, and amax, which makes clear that the speedup comes from eliminating epilogue memory traffic rather than from a faster GEMM. We build a fused scaled dot-product attention graph with causal masking and check it against torch.nn.functional.scaled_dot_product_attention, guarding the whole section behind an SM80 check because the fused kernels need Ampere or newer. We write the causal argument with fallbacks, since the frontend has moved from use_causal_mask toward diagonal_alignment and bound arguments across its 1.x releases. We then serialize a built matmul graph to bytes, reload it into a fresh graph object, and execute it via integer UIDs, which lets us skip the compilation cost entirely at process startup. We finish with two production concerns. First, we share a kernel cache across four graphs that differ only in batch size and time each build, so we can see later shapes reuse an already compiled kernel instead of paying the JIT cost again. Then we capture the convolution plan inside a CUDA graph, setting the cuDNN handle’s stream to the capture stream. So the work lands in the graph, and we measure how much per-iteration launch overhead the replay removes. In conclusion, what we built here was small in code but broad in scope: a convolution, a matmul, and an attention kernel, each expressed as a graph rather than a library call. Working at that level changed what we could decide. We chose which operations collapsed into a single kernel, so the bias adds, activations, and AMAX reductions we folded into the epilogues never wrote an intermediate to memory. We chose the engine ourselves instead of accepting a heuristic, and timing every candidate config told us what that choice was worth. We also chose when to pay for compilation, pushing it out of the hot path with serialized plans, a kernel cache shared across shapes, and CUDA graph capture. The checks against PyTorch mattered as much as the timings, since the places where we merely matched it were usually places where PyTorch was already calling cuDNN underneath. That marked out where this API earns its keep: fusions with no framework-level equivalent, shapes hot enough to justify autotuning, and small kernels where startup and launch costs dominate. Check out the FULL CODES here . All credit goes to the researcher of this project. Also, feel free to follow us on Twitter and don’t forget to join our 150k+ML SubReddit and Subscribe to our Newsletter . Wait! are you on telegram? now you can join us on telegram as well. Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us The post Inside NVIDIA’s cuDNN Graph API: Fusion, Autotuning, and Plan Reuse with cuDNN Frontend appeared first on MarkTechPost .
由 claude-code/sonnet 自動生成