• <xmp id="om0om">
  • <table id="om0om"><noscript id="om0om"></noscript></table>
  • 生成式人工智能/大語言模型

    具有條件節點的 CUDA 圖的動態控制流

    CUDA 圖 可以顯著提高性能,因為驅動程序能夠使用任務和依賴關系的完整描述來優化執行。特別是在靜態工作流中,圖形可以提供難以置信的好處,其創建的開銷可以在多次連續啟動中分攤,從而提高整體性能。

    然而,幾乎所有的問題都涉及某種形式的決策,這可能需要分解圖并將控制權返回給 CPU,以決定下一步要啟動哪些工作。像這樣分解工作會損害 CUDA 進行優化的能力,占用 CPU 資源,并增加每次圖形啟動的開銷。

    從 CUDA 12.4 開始,CUDA Graphs 支持 條件節點,這使得圖形的部分能夠有條件地或重復地執行,而不需要將控制返回到 CPU。這釋放了 CPU 資源,使得更多的工作流能夠在單個圖形中表示,從而提高了計算效率。

    條件節點

    條件節點有兩種風格:

    • IF 節點:如果條件值為 true,則每次評估節點時執行一次主體。
    • WHILE 節點:只要條件值為 true,當對節點求值時,就會重復執行主體。

    條件節點是容器節點,類似于子圖節點,但節點中包含的圖的執行取決于條件變量的值。與節點關聯的條件值由必須在節點之前創建的句柄訪問,該句柄還可以指定在圖形的每個開始處應用的初始化。可以通過調用在 CUDA 內核中設置條件值的 cudaGraphSetConditional 函數。

    創建條件節點時,還會創建一個空圖,并將句柄返回給用戶。此圖與節點綁定,并將根據條件值執行。此條件體圖可以使用 圖形 API 或者通過使用捕獲異步 CUDA 調用的 cudaStreamBeginCaptureToGraph 函數。

    條件節點也可以嵌套。例如,可以使用包含條件 IF 節點的體圖創建條件 WHILE 節點。

    條件節點體圖可以包含以下任意一項:

    • 內核節點(CNP,當前不支持協同)
    • 空節點
    • 子圖節點
    • Memset 節點
    • Memcopy 節點
    • 條件節點

    這遞歸地應用于子圖和條件體。所有內核,包括嵌套條件句或任何級別的子圖中的內核,都必須屬于同一個 CUDA 上下文。Memcopies 和 memset 必須作用于可從條件節點的上下文訪問的內存。

    完整的樣品代碼可在 CUDA 樣本庫 中找到。下一節將通過一些示例來展示如何處理條件節點的方法。

    條件 IF 節點

    如果條件為非零,則無論何時評估 IF 節點,都將執行一次 IF 節點的體圖。圖 1 描述了一個圖,其中中間節點 B 是包含四節點圖的 IF 條件節點:

    Diagram depicting a decision workflow using three circular nodes with a conditional node (b) set to conditionally run its body graph.
    圖 1。條件 IF 節點

    為了顯示如何創建此圖,以下示例使用條件節點 B 上游的內核節點 A,根據該內核所做的工作結果設置條件的值。條件的主體是使用圖 API 填充的。

    首先,定義節點 A 內核。這個內核根據用戶執行的一些任意計算的結果來設置條件句柄。

    __global__ void setHandle(cudaGraphConditionalHandle handle)
    {
        unsigned int value = 0;
        // We could perform some work here and set value based on the result of that work.
        if (someCondition) {
            // Set ‘value’ to non-zero if we want the conditional body to execute
            value = 1;
        }
        cudaGraphSetConditional(handle, value);
    }

    接下來,定義一個函數來構建圖。此函數分配條件句柄、創建節點并填充條件圖的主體。為了清楚起見,省略了啟動和執行圖形的代碼。

    cudaGraph_t createGraph() {
        cudaGraph_t graph;
        cudaGraphNode_t node;
        void *kernelArgs[1];
     
        cudaGraphCreate(&graph, 0);
     
        cudaGraphConditionalHandle handle;
        cudaGraphConditionalHandleCreate(&handle, graph);
     
        // Use a kernel upstream of the conditional to set the handle value
        cudaGraphNodeParams kParams = { cudaGraphNodeTypeKernel };
        kParams.kernel.func         = (void *)setHandle;
        kParams.kernel.gridDim.x    = kParams.kernel.gridDim.y = kParams.kernel.gridDim.z = 1;
        kParams.kernel.blockDim.x   = kParams.kernel.blockDim.y = kParams.kernel.blockDim.z = 1;
        kParams.kernel.kernelParams = kernelArgs;
        kernelArgs[0] = &handle
        cudaGraphAddNode(&node, graph, NULL, 0, &kParams);
     
        cudaGraphNodeParams cParams = { cudaGraphNodeTypeConditional };
        cParams.conditional.handle = handle;
        cParams.conditional.type   = cudaGraphCondTypeIf;
        cParams.conditional.size   = 1;
        cudaGraphAddNode(&node, graph, &node, 1, &cParams);
     
        cudaGraph_t bodyGraph = cParams.conditional.phGraph_out[0];
     
        // Populate the body of the conditional node
        cudaGraphNode_t bodyNodes[4];
        cudaGraphNodeParams params[4] = { ... }; // Setup kernel parameters as needed.
        cudaGraphAddNode(&bodyNodes[0], bodyGraph, NULL, 0, &params[0]);
        cudaGraphAddNode(&bodyNodes[1], bodyGraph, &bodyNodes[0], 1, &params[1]);
        cudaGraphAddNode(&bodyNodes[2], bodyGraph, &bodyNodes[0], 1, &params[2]);
        cudaGraphAddNode(&bodyNodes[3], bodyGraph, &bodyNodes[1], 2, &params[3]);
     
        return graph;
    }

    條件 WHILE 節點

    只要條件為非零,WHILE 節點的體圖就會重復執行。將在執行節點時以及每次完成體圖后評估條件。下圖描述了一個三節點圖,其中中間節點 B 是包含三節點圖的 WHILE 條件節點。

    Diagram depicting a decision workflow using three circular nodes with a conditional node (b) set to loop over its body graph.
    圖 2:條件 WHILE 節點

    為了了解如何創建此圖,以下示例將句柄的默認值設置為非零值,以便在默認情況下執行 WHILE 循環。將默認值設置為非零,并將條件值保留在條件上游的內核中不修改,這有效地產生了一個 do-while 循環,其中條件體總是至少執行一次。創建 WHILE 循環,其中循環體僅在條件為 true 時執行,需要執行一些計算并在節點 a 中適當設置條件句柄。

    在上一個示例中,條件主體由圖 API 填充。在本例中,使用流捕獲填充條件的主體。

    第一步是定義一個內核,在每次執行條件體時設置條件值。在本例中,句柄是基于遞減計數器的值設置的。

    __global__ void loopKernel(cudaGraphConditionalHandle handle)
    {
        static int count = 10;
        cudaGraphSetConditional(handle, --count ? 1 : 0);
    }

    接下來,定義一個函數來構建圖。此函數分配條件句柄、創建節點并填充條件圖的主體。為了清楚起見,省略了啟動和執行圖形的代碼。

    cudaGraph_t createGraph() {
        cudaGraph_t graph;
        cudaGraphNode_t nodes[3];
     
        cudaGraphCreate(&graph, 0);
     
        // Insert kernel node A
        cudaGraphNodeParams params = ...;
     
        cudaGraphAddNode(&nodes[0], graph, NULL, 0, &params);
     
        cudaGraphConditionalHandle handle;
        cudaGraphConditionalHandleCreate(&handle, graph, 1, cudaGraphCondAssignDefault);
     
        // Insert conditional node B
        cudaGraphNodeParams cParams = { cudaGraphNodeTypeConditional };
        cParams.conditional.handle = handle;
        cParams.conditional.type   = cudaGraphCondTypeWhile;
        cParams.conditional.size   = 1;
        cudaGraphAddNode(&nodes[1], graph, &nodes[0], 1, &cParams);
     
        cudaGraph_t bodyGraph = cParams.conditional.phGraph_out[0];
        cudaStream_t captureStream;
        cudaStreamCreate(&captureStream);
         
        // Fill out body graph with stream capture.
        cudaStreamBeginCaptureToGraph(captureStream,
                                      bodyGraph,
                                      nullptr,
                                      nullptr,
                                      0,
                                      cudaStreamCaptureModeRelaxed);
        myKernel1<<<..., captureStream>>>(...);
        myKernel2<<<..., captureStream>>>(...);
        loopKernel<<<1, 1, 0, captureStream>>>(handle);
        cudaStreamEndCapture(captureStream, nullptr);
        cudaStreamDestroy(captureStream);
     
        // Insert kernel node C.
        params = ...;
        cudaGraphAddNode(&nodes[2], graph, &nodes[1], 1, &params);
     
        return graph;
    }

    此示例使用 cudaStreamBeginCaptureToGraph,這是 CUDA 12.3 中新增加的一個 API,它使流捕獲能夠將節點插入到現有的圖中。借助這個 API,可以將多個單獨的捕獲組合到一個圖形對象中。此外,這個 API 還允許填充與條件節點一起創建的條件體圖對象。

    結論

    CUDA Graphs 為靜態工作流提供了難以置信的好處,在靜態工作流中,圖形創建的開銷可以在多次連續啟動中分攤。消除對圖形的分割并將控制權返回給 CPU,以決定優先啟動哪個,這有助于減少 CPU 開銷和延遲。使用具有條件節點的 CUDA 圖形可以有條件地或重復地執行圖形的部分,而無需將控制權返回給 CPU。這釋放了 CPU 資源,并使單個圖形能夠表示更復雜的工作流。

    欲了解更多關于條件節點的信息,請參閱 CUDA 編程指南。要探索簡單、完整的示例代碼,請訪問 NVIDIA/cuda-samples 在 GitHub 上。同時,您也可以加入 NVIDIA 開發者 CUDA 論壇 中的討論。

    ?

    +2

    標簽

    人人超碰97caoporen国产