Programmatic Dependent Launch 深入 (PDL Deep Dive)

重點總覽

Programmatic Dependent Launch(PDL)讓「相依的 secondary kernel」可以在它所依賴的 primary kernel 尚未執行完成前,就先在同一條 CUDA stream 中提早啟動,藉此把 secondary kernel 與 primary kernel 不相依的部分重疊執行,並隱藏 secondary kernel 的 launch latency。需要 compute capability 9.0 以上才能提供真正的重疊執行。

項目 重點
機制目的 讓 dependent secondary kernel 在 primary kernel 完成前提早 launch,重疊執行不相依的工作
適用情境 secondary kernel 有大量「不依賴 primary 結果」的工作(如 preamble:清零 buffer、載入常數)
硬體需求 compute capability 9.0 以上才提供 overlapping execution
Primary 端 API cudaTriggerProgrammaticLaunchCompletion():宣告可放行 secondary
Secondary 端 API cudaGridDependencySynchronize():等待 primary 結果寫回 global memory
Launch 方式 secondary 須用 extensible launch API cudaLaunchKernelEx + launch attribute
關鍵 attribute cudaLaunchAttributeProgrammaticStreamSerialization(允許提早 launch)
安全性 重疊是「機會性」(opportunistic),不保證並行;不可依賴並行,否則可能 deadlock
CUDA Graphs 可經 stream capture,或用 edge data(cudaGraphDependencyTypeProgrammatic)表達
一句話定義

PDL = 在同一 stream 中,相依的後續 grid 不必等前一個 grid 完全跑完才 launch,而是由前一個 grid 主動「觸發」放行,後續 grid 再用 grid 同步點確認結果可用。

Background:為何相依 grid 能提早啟動

CUDA 應用透過在 GPU 上 launch 並執行多個 kernel 來使用 GPU。傳統時間軸上,secondary_kernelprimary_kernel 執行完成後才 launch,這種序列化執行通常是必要的,因為 secondary 依賴 primary 產生的結果資料。

傳統序列化:
  primary_kernel  [=========]
  secondary_kernel            [preamble][dependent work]
                              ^ 等 primary 完成才 launch

PDL 重疊執行:
  primary_kernel  [=========]
  secondary_kernel      [preamble][sync][dependent work]
                        ^ 提早 launch,preamble 與 primary 重疊

PDL 對 CUDA kernel launch API 做了改動以達成上述重疊,這些 API 需要至少 compute capability 9.0 才能提供 overlapping execution。

API Description:trigger 與 grid sync

在 PDL 中,primary 與 secondary kernel 被 launch 到同一條 CUDA stream。分工如下:

__global__ void primary_kernel() {
    // 在啟動 secondary 之前應完成的初始工作
    // 觸發 secondary kernel
    cudaTriggerProgrammaticLaunchCompletion();
    // 可與 secondary kernel 同時進行的工作
}

__global__ void secondary_kernel() {
    // 不相依的工作(preamble,可與 primary 重疊)
    // 會阻塞,直到所有相依的 primary kernel 完成並把結果 flush 到 global memory
    cudaGridDependencySynchronize();
    // 相依的工作(需要 primary 結果)
}

cudaTriggerProgrammaticLaunchCompletion 放在 primary「初始工作之後」,之後的程式碼便可與 secondary 同時進行;cudaGridDependencySynchronize 是 secondary 取得 primary 結果前的屏障。

secondary kernel 用 extensible launch API 並設定 cudaLaunchAttributeProgrammaticStreamSerialization attribute:

cudaLaunchAttribute attribute[1];
attribute[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
attribute[0].val.programmaticStreamSerializationAllowed = 1;
configSecondary.attrs = attribute;
configSecondary.numAttrs = 1;
primary_kernel<<<grid_dim, block_dim, 0, stream>>>();
cudaLaunchKernelEx(&configSecondary, secondary_kernel);

設定 programmaticStreamSerializationAllowed = 1 後,CUDA driver 便可以安全地提早 launch secondary,不必等待 primary 完成與其 memory flush。

兩個 device 端函式的角色

cudaTriggerProgrammaticLaunchCompletion 是 primary 端「主動放行」的訊號;cudaGridDependencySynchronize 是 secondary 端「確認結果就緒」的屏障。兩者搭配,才能既提早 launch、又保證資料正確。

Trigger 觸發時機與安全性

CUDA driver 在「所有 primary thread block 都已 launch 並執行了 cudaTriggerProgrammaticLaunchCompletion」時,可以 launch secondary kernel。

trigger 觸發路徑:
  primary 顯式呼叫 cudaTriggerProgrammaticLaunchCompletion()
        └─► driver 可 launch secondary(所有 primary block 已 launch+trigger)
  primary 未呼叫 trigger
        └─► 隱式:所有 primary thread block 退出後才觸發
重疊是機會性的,不可依賴

這些方法只是「提供」primary 與 secondary 並行執行的機會,此行為是 opportunistic、不保證真的並行。以此方式依賴並行執行是不安全的,可能導致 deadlock。換言之:可重疊→效能加分;但程式邏輯的正確性絕不能建立在「一定會並行」的假設上。

一定要做 grid 同步

即使 primary 顯式 trigger,secondary 仍可能在 primary 資料可見前 launch。少了 cudaGridDependencySynchronize(或等效手段),secondary 的相依工作就可能讀到尚未寫回 global memory 的舊資料。

Use in CUDA Graphs

PDL 可在 CUDA Graphs 中使用,途徑有二:經由 stream capture,或直接用 edge data

// edge data 設定(連接 upstream → downstream kernel node)
cudaGraphEdgeData edgeData;
edgeData.type      = cudaGraphDependencyTypeProgrammatic;
edgeData.from_port = cudaGraphKernelNodePortProgrammatic;        // 或 ...PortLaunchCompletion

把 edge type 設為 Programmatic,等於在 graph 中宣告「downstream 對 upstream 有 PDL 相依」,使 downstream 的 cudaGridDependencySynchronize() 能感知 upstream。

Stream 屬性 ↔ Graph edge 對應

stream capture 下,三種 stream 端設定會轉成對應的 graph edge:

Stream 端設定(attribute) from_port
cudaLaunchAttributeProgrammaticStreamSerializationprogrammaticStreamSerializationAllowed = 1 cudaGraphKernelNodePortProgrammatic
cudaLaunchAttributeProgrammaticEventtriggerAtBlockStart = 0 cudaGraphKernelNodePortProgrammatic
cudaLaunchAttributeProgrammaticEventtriggerAtBlockStart = 1 cudaGraphKernelNodePortLaunchCompletion

三者的 edgeData.type 皆為 cudaGraphDependencyTypeProgrammatic。重點是 triggerAtBlockStart 的值(0 或 1)決定了 outgoing port 是 Programmatic 還是 LaunchCompletion

            triggerAtBlockStart = 0  ─►  from_port = cudaGraphKernelNodePortProgrammatic
edgeData
type =      triggerAtBlockStart = 1  ─►  from_port = cudaGraphKernelNodePortLaunchCompletion
Programmatic
            StreamSerialization      ─►  from_port = cudaGraphKernelNodePortProgrammatic
兩種表達方式等價

在 stream 中用 launch attribute(ProgrammaticStreamSerialization / ProgrammaticEvent)表達的 PDL 相依,stream capture 後會被轉成帶 cudaGraphDependencyTypeProgrammatic 的 graph edge;也可以略過 capture,直接以 edge data 建構 graph。

考試/測驗重點

主題 必記重點
PDL 定義 同一 stream 內,相依 secondary 可在 primary 完成前提早 launch
硬體需求 compute capability 9.0 以上才提供 overlapping execution
Primary 端函式 cudaTriggerProgrammaticLaunchCompletion(所有 block 都應呼叫)
Secondary 端函式 cudaGridDependencySynchronize(等 primary 結果 flush 到 global memory)
Launch API secondary 須用 extensible launch API:cudaLaunchKernelEx
啟用 attribute cudaLaunchAttributeProgrammaticStreamSerialization,programmaticStreamSerializationAllowed = 1
未呼叫 trigger 時 隱式在所有 primary thread block 退出後觸發
資料可見性陷阱 secondary 可能在 primary 資料可見前 launch,故必須做 grid sync
並行性質 opportunistic/不保證並行;依賴並行不安全、可能 deadlock
Graph edge type cudaGraphDependencyTypeProgrammatic(連接兩 kernel node)
Graph outgoing port cudaGraphKernelNodePortProgrammatic 或 cudaGraphKernelNodePortLaunchCompletion
triggerAtBlockStart 對應 0 ⟶ Programmatic port;1 ⟶ LaunchCompletion port
易混淆點

trigger(cudaTriggerProgrammaticLaunchCompletion)是 primary「放行」訊號;sync(cudaGridDependencySynchronize)是 secondary「等資料」屏障。兩者方向相反、缺一不可。