Green Contexts

重點總覽

項目 重點
Green Context (GC) 本質 一個輕量 context,建立時即綁定一組特定 GPU 資源(目前是 SMs 與 work queues);GPU 工作只能用其 provisioned 的資源
核心價值 降低/控制共用資源造成的干擾,確保 latency-sensitive kernel 隨時有 SM 可立即啟動;亦可快速測試「用較少 SM」的效果
無需改 kernel 只改 host 端:建立 green context + 在該 GC 上建立 stream。kernel 與啟動幾何皆不變
執行緒模型 CUDA 13.1 起以 execution context (EC) 抽象暴露在 runtime;EC 可對應 primary context 或 green context,本節兩詞交替使用
建議用法 green context runtime 暴露後,強烈建議直接用 CUDA runtime API(本節僅用 runtime API)
device resource cudaDevResource,綁定特定 device;三型別:SM、workqueue config、pre-existing workqueue
resource descriptor cudaDevResourceDesc_t,封裝一或多個 resource;GC 只能存取其建立時 descriptor 內的資源
建立四步驟 1) 取得資源 → 2) split SM 資源(+ 選配 WQ)→ 3) 產生 descriptor → 4) cudaGreenCtxCreate
launch work cudaExecutionCtxStreamCreate 建立屬於 GC 的 stream,於其上 <<<>>> 啟動的 kernel 僅能用該 GC 資源
重要限制 即使分開 provisioned SM 與 WQ,並不保證 真正並行;只是「移除阻礙並行的因素」

Green Context 是什麼

green context (GC) 是一個 輕量 context,從建立那一刻起就與一組特定 GPU 資源綁定。使用者可在建立時 partition GPU 資源(目前是 streaming multiprocessors, SMs 與 work queues, WQs),使得 targeting 該 GC 的 GPU 工作 只能使用其 provisioned 的 SM 與 work queue。如此可減少或更好地控制因共用資源造成的干擾。一個應用程式可擁有多個 green context。

Important

一個 execution context 目前可對應 primary context(runtime API 使用者一直隱式互動的 context)或 green context。本節在指 green context 時,「execution context」與「green context」交替使用。runtime 已暴露 green context 後,強烈建議直接用 CUDA runtime API

Motivation / 何時使用

啟動 CUDA kernel 時,使用者 無法直接控制 kernel 會在幾顆 SM 上執行,只能透過改變 launch geometry(或任何影響每 SM 最大 active block 數的因素)間接影響。當多個 kernel 在 GPU 上並行(不同 stream 或 CUDA graph 的一部分),它們也可能 爭用相同 SM 資源

green context 透過 partition SM 資源 解決此問題:某 GC 只能用建立時 provisioned 的特定 SM。

無 Green Context(kernel A 先佔滿全部 SM)
SM% │████████████████ A ███████████──┐
    │                    B 被延遲 →   └─B 等 A ramp down 才能開始
    └───────────────────────────────────────► time

有 Green Context(A 限 80% SM,B 保留專屬 SM)
SM% │████████ A ████████████████████
    │░░░░ B 立即開始(有專屬 SM)░░░░
    └───────────────────────────────────────► time

Work Queues 的角色

SM 是可 provisioned 的一種資源;另一種是 work queues (WQs)。把 work queue 想成一個黑箱資源抽象,它(連同其他因素)會影響 GPU 工作的並行度。

Warning

即使為各 GC 分開 provisioned SM 與 work queue,仍不保證 獨立 GPU 工作真正並行執行。最好把 Green Contexts 整節技術視為「移除可能阻礙並行的因素(降低潛在干擾)」,而非保證並行。

GC vs MIG vs MPS

機制 切分粒度 何時設定 SM 是否固定 與 GC 關係
Green Context 單一 process 內,per-context 程式執行中、建立 GC 時 是,固定那 N 顆 SM 最輕量(多數結構由 primary context 共享)
MIG 把 GPU 靜態切成多個 instance(「小 GPU」) 程式啟動前 instance 級 可在某 MIG instance 內再用 GC(以該 instance 資源 partition)
MPS(動態) process 級,active thread percentage 上限 啟動前以環境變數 否,任意 N 顆且隨時間變 不同 process;GC 在單一 process 內也適用
MPS(靜態,13.1 起) process 級靜態 partition 程式啟動時指定 仍是 process 級;GC 可單一 process 內,且 GC 允許 SM oversubscription,MPS 靜態不允許

Ease of Use(最小改動)

原本只能靠 launch geometry 間接影響 SM 用量:

int gpu_device_index = 0;  // GPU ordinal
cudaSetDevice(gpu_device_index);
cudaStream_t strm1, strm2;
cudaStreamCreateWithFlags(&strm1, cudaStreamNonBlocking);
cudaStreamCreateWithFlags(&strm2, cudaStreamNonBlocking);
// 無法控制各 stream 上 kernel 能用幾顆 SM
code_that_launches_kernels_on_streams(strm1, strm2);

CUDA 13.1 起,只要加幾行 host code、不改 kernel,即可控制每個 stream 上 kernel 可用的 SM:

// 1) 取得全部 SM 資源
cudaDevResource initial_GPU_SM_resources{};
cudaDeviceGetDevResource(gpu_device_index, &initial_GPU_SM_resources,
                         cudaDevResourceTypeSm);
// 2) split:一組 16 SM、一組 8 SM
cudaDevSmResource result[2]{{}, {}};
cudaDevSmResourceGroupParams group_params[2] = {
  {.smCount=16, .coscheduledSmCount=0, .preferredCoscheduledSmCount=0, .flags=0},
  {.smCount=8,  .coscheduledSmCount=0, .preferredCoscheduledSmCount=0, .flags=0}};
cudaDevSmResourceSplit(&result[0], 2, &initial_GPU_SM_resources, nullptr, 0,
                       &group_params[0]);
// 3) 各自產生 descriptor
cudaDevResourceDesc_t resource_desc1{}, resource_desc2{};
cudaDevResourceGenerateDesc(&resource_desc1, &result[0], 1);
cudaDevResourceGenerateDesc(&resource_desc2, &result[1], 1);
// 4) 建立兩個 green context
cudaExecutionContext_t my_green_ctx1{}, my_green_ctx2{};
cudaGreenCtxCreate(&my_green_ctx1, resource_desc1, gpu_device_index, 0);
cudaGreenCtxCreate(&my_green_ctx2, resource_desc2, gpu_device_index, 0);
// 唯一需改的呼叫端:改用 EC 版 stream 建立 API
cudaStream_t strm1, strm2;
cudaExecutionCtxStreamCreate(&strm1, my_green_ctx1, cudaStreamDefault, 0);
cudaExecutionCtxStreamCreate(&strm2, my_green_ctx2, cudaStreamDefault, 0);
// 其餘不變:strm1 上 kernel 最多 16 SM、strm2 上最多 8 SM
code_that_launches_kernels_on_streams(strm1, strm2);

唯一的呼叫端差異是 cudaExecutionCtxStreamCreate 取代 cudaStreamCreateWithFlags,函式內部與 kernel 完全不動。

Tip

許多 execution context API(如上例)接受 顯式的 cudaExecutionContext_t handle,因此 忽略 呼叫 thread 的 current context。這是從過去依賴 thread-local state (TLS) 的隱式 context 程式設計,轉向 顯式 context-based 程式設計,語意更清楚也帶來額外好處。

Device Resource 與 Resource Descriptor

green context 的核心是綁定特定 device 的 device resource (cudaDevResource);資源可被組合並封裝進 descriptor (cudaDevResourceDesc_t)。GC 只能存取建立時 descriptor 內封裝的資源。

struct {
  enum cudaDevResourceType type;
  union {
    struct cudaDevSmResource              sm;        // SM 型
    struct cudaDevWorkqueueConfigResource wqConfig;  // WQ config 型
    struct cudaDevWorkqueueResource       wq;        // pre-existing WQ 型
  };
};
Struct 關鍵欄位 由誰設定
cudaDevSmResource smCount(SM 數)、minSmPartitionSize(partition 最小 SM 數)、smCoscheduledAlignment(保證同 GPC 共排的 SM 數,與 cluster 相關;flags=0 時 smCount 為其倍數)、flags(0 或 cudaDevSmResourceGroupBackfill 由 split API 或 cudaDeviceGetDevResource 填,使用者勿直接設
cudaDevWorkqueueConfigResource devicewqConcurrencyLimit(避免 false dependency 的預期 stream-ordered workload 數)、sharingScope 由使用者設(除 cudaDeviceGetDevResource 取得者外,無類似 split 的產生 API)
cudaDevWorkqueueResource(pre-existing WQ) 無使用者可設欄位 cudaDeviceGetDevResource 取得

建立 Green Context:四步驟

Step 1            Step 2                    Step 3                Step 4
取得資源    →    split SM 資源        →    產生 descriptor   →   建立 GC
GetDevResource   SplitByCount / Split      GenerateDesc          GreenCtxCreate
(device/EC/      (+ 選配 WQ config)        (封裝一或多 resource) (provision 資源)
 stream)
                                                                     │
                            建立屬於 GC 的 stream ◄───────────────────┘
                            ExecutionCtxStreamCreate → <<< >>> 只用該 GC 資源

Step 1:取得可用 GPU 資源

三個可能起點:device、execution context、stream。

// device:
cudaError_t cudaDeviceGetDevResource(int device, cudaDevResource* resource,
                                     cudaDevResourceType type);
// execution context:
cudaError_t cudaExecutionCtxGetDevResource(cudaExecutionContext_t ctx,
                                           cudaDevResource* resource,
                                           cudaDevResourceType type);
// stream(只支援 SM 型):
cudaError_t cudaStreamGetDevResource(cudaStream_t hStream,
                                     cudaDevResource* resource,
                                     cudaDevResourceType type);

Step 2:partition SM 資源

把可用 SM 資源 靜態 split 成一或多個 partition(可能留下一個 remaining partition)。兩個 split API 都僅適用 SM 型資源:

API 能力 signature 要點
cudaDevSmResourceSplitByCount 只能建 同質 group(每組 minCount SM)+ 可選 remaining result, *nbGroups, input, remaining, useFlags, minCount
cudaDevSmResourceSplit 可建 異質 group(單次呼叫不同 SM 數)+ 可選 remainder result, nbGroups, input, remainder, flags, groupParams

SplitByCount:請求把 input 切成 *nbGroups 組、每組 minCount SM。實際結果的 *nbGroups 可能變小(≤ 請求值)、實際 N 可能變大(≥ minCount),因架構特定的 granularity/alignment 要求。CC 9.0 範例:每 partition 最少 8 SM、SM 數須為 8 的倍數(useFlags=0)。

unsigned int min_SM_count = 8;
unsigned int actual_split_groups = 5;  // 可能被更新
cudaDevResource actual_split_result[5] = {{}, {}, {}, {}, {}};
cudaDevResource remaining_partition = {};
cudaDevSmResourceSplitByCount(&actual_split_result[0], &actual_split_groups,
                              &avail_resources, &remaining_partition,
                              0 /*useFlags*/, min_SM_count);

Split(異質):單次呼叫產生 non-overlapping 異質 partition,放入 result 陣列,各組 SM 數可不同但 絕不為 0。每組以 cudaDevSmResourceGroupParams 指定需求:

欄位 作用
smCount 該組 SM 數 0 = discovery mode(探索);非 0 須為 2 的倍數且在 [2, input.sm.smCount],且 flags=0 時為實際 coscheduledSmCount 的倍數
coscheduledSmCount 為發動 cluster 而「共排」的 SM 數(CC 9.0+),影響組大小與可支援 cluster 尺寸 0 = 用該架構預設;非 0 須為 2 的倍數且 ≤ 上限
preferredCoscheduledSmCount hint:嘗試把多個 coscheduledSmCount 的組合併成更大組(供 CC 10.0+ 的 preferred cluster dim) 0 = 預設;非 0 須為實際 coscheduledSmCount 的倍數
flags 是否允許 backfill 0(resulting SM 為 coscheduled 倍數)或 cudaDevSmResourceGroupBackfill(盡量塞滿,backfilled SM 不提供 coschedule 保證)
Tip

不用 cluster 時:coscheduledSmCount/preferredCoscheduledSmCount 用最小值 2 或直接設 0 即可。用 cluster 時:依架構最大可攜 cluster 大小選 coscheduledSmCount;在 CC 10.0 (Blackwell)+ 用 preferred cluster 時,preferredCoscheduledSmCount 選 > 2 的值。backfill 組仍可支援 cluster(至少保證一個 coscheduledSmCount 大小)。

Step 2(續):加入 workqueue 資源

若也要指定 WQ 資源,須 顯式 建立(無類似 split 的 API):

cudaDevResource split_result[2] = {{}, {}};
// split_result[0] 由 split API 填(nbGroups=1);最後一個是 WQ 資源
split_result[1].type = cudaDevResourceTypeWorkqueueConfig;
split_result[1].wqConfig.device = 0;                                     // device ordinal
split_result[1].wqConfig.sharingScope = cudaDevWorkqueueConfigScopeGreenCtxBalanced;
split_result[1].wqConfig.wqConcurrencyLimit = 4;                         // hint:最多 4 並行

wqConcurrencyLimit = 4 提示 driver 預期最多 4 個並行 stream-ordered workload;driver 盡量在可能時尊重此 hint 分配 work queue。

Step 3:建立 Resource Descriptor

cudaError_t cudaDevResourceGenerateDesc(cudaDevResourceDesc_t* phDesc,
                                        cudaDevResource* resources,
                                        unsigned int nbResources);
// 把 actual_split_result[2] 到 [4] 共 3 個資源封裝進一個 descriptor
cudaDevResourceDesc_t resource_desc;
cudaDevResourceGenerateDesc(&resource_desc, &actual_split_result[2], 3);

成功條件:

Step 4:建立 Green Context

cudaError_t cudaGreenCtxCreate(cudaExecutionContext_t* phCtx,
                               cudaDevResourceDesc_t desc,
                               int device, unsigned int flags);
cudaSetDevice(current_device);            // 或 cudaInitDevice,先初始化 primary context
cudaExecutionContext_t green_ctx{};
cudaGreenCtxCreate(&green_ctx, resource_desc, current_device, 0);

多 GC 與 oversubscription:多數情況各 GC 有 非重疊 的 SM 集合(如 GC1 用 result[2..4]、GC2 用 result[0..1],每顆 SM 只屬一個 GC)。但 SM oversubscription 也可能(如 GC2 用 result[0..2],則 result[2] 的 SM 被兩個 GC 共用)。

Warning

SM oversubscription 應 逐案謹慎使用。被 oversubscribed 的 SM 同時 provisioned 給多個 GC,可能再次引入干擾,違背 GC「降低干擾」的初衷。

Launching Work

在 GC 上啟動 kernel,先用 cudaExecutionCtxStreamCreate 為該 GC 建立 stream,於其上 <<<>>>cudaLaunchKernel 啟動的 kernel 只能用該 stream 的 EC 可用資源

cudaStream_t green_ctx_stream;
int priority = 0;
cudaExecutionCtxStreamCreate(&green_ctx_stream, green_ctx,
                             cudaStreamDefault, priority);
my_kernel<<<grid_dim, block_dim, 0, green_ctx_stream>>>();
cudaGetLastError();

CUDA Graphs 的細節

Warning

對 CUDA graph 中的 kernel,graph 被 launch 到的 stream 不決定 SM 資源——該 stream 僅用於 dependency tracking。節點的 EC 是在 node 建立時 決定的。

Thread Block Clusters

帶 thread block cluster 的 kernel 可像一般 kernel 一樣在 GC stream 上啟動,用該 GC 的 provisioned 資源。Step 2 已說明 split 時如何指定 coscheduled SM 數以利 cluster。仍應用 occupancy API 求最大可行 cluster 大小:

cudaLaunchConfig_t config = {0};
config.gridDim = grid_dim;          // 須為 cluster dim 的倍數
config.blockDim = block_dim;
config.dynamicSmemBytes = expected_dynamic_shared_mem;
cudaLaunchAttribute attribute[1];
attribute[0].id = cudaLaunchAttributeClusterDimension;
attribute[0].val.clusterDim = {1, 1, 1};
config.attrs = attribute; config.numAttrs = 1;
config.stream = gc_stream;          // 必須傳該 kernel 會用的 stream
int max_potential_cluster_size = 0;
cudaOccupancyMaxPotentialClusterSize(&max_potential_cluster_size, cluster_kernel, &config);
int num_clusters = 0;
cudaOccupancyMaxActiveClusters(&num_clusters, cluster_kernel, &config);

驗證 GC 使用

Warning

工具顯示的是 kernel 可存取 的 SM 數,不是 kernel 實際跑在幾顆 SM 上。實際用量取決於 kernel 本身(launch geometry 等)與同時段的其他工作。

Additional Execution Context APIs

API 用途
cudaExecutionCtxRecordEvent(ctx, event) 錄一個 event,捕捉該 EC 此刻全部 work/activity;比對多 stream 的 EC 用 cudaEventRecord 方便
cudaExecutionCtxWaitEvent(ctx, event) 未來 提交給該 EC 的所有 work 等待 event 中捕捉的 work;比逐 stream cudaStreamWaitEvent 方便
cudaExecutionCtxSynchronize(ctx) CPU 端阻塞,直到該 EC 完成全部 work
cudaExecutionCtxGetDevice(...) 取得 EC 關聯的 device
cudaExecutionCtxGetId(...) 取得 EC 的唯一識別碼
cudaExecutionCtxDestroy(ctx) 銷毀顯式建立的 EC
cudaDeviceGetExecutionCtx(...) 取得 device 的 primary context(作為 EC)
Important

若傳給 cudaExecutionCtxSynchronize 的 EC 不是cudaGreenCtxCreate 建立,而是由 cudaDeviceGetExecutionCtx 取得(即 device 的 primary context),則該呼叫會 連同同一 device 上所有已建立的 green context 一起同步

完整範例(critical kernel 提早完成)

情境:兩個 kernel 跑在兩條 non-blocking stream。先在 strm1 啟動長跑 kernel delay_kernel_us(block 數超過 SM 數、需多波),短暫等待後在 strm2 啟動短而關鍵的 critical_kernel。量測兩者 GPU duration 與「CPU 啟動到完成」時間。

無 GC(critical 在高 priority stream,但仍須等部分 block)
stream13 ████████ delay_kernel ███████████████████
stream14         ⟵ 等 0.9ms ⟶ ███ critical ███          ← 被延遲

有 GC(GC2=112 SM 給 delay,GC3=16 SM 給 critical,無重疊)
GC2(112SM) ████████ delay_kernel ██████████████████████ ← 變慢
GC3( 16SM) ███ critical ███                                ← 幾乎即時開始
Tip

用「delay kernel」當長跑 kernel 的 proxy:每個 thread block 跑固定微秒數,且 block 數超過 GPU 可用 SM 數,以保證需要多波。

考試/測驗重點

主題 必記重點
GC 定義 輕量 context,建立時綁定特定 GPU 資源(SMs / work queues);不需改 kernel,只改 host 端
暴露時程 最早 Driver API;CUDA 13.1 起以 execution context (EC) 抽象暴露於 runtime;建議直接用 runtime API
EC 對應 EC = primary context 或 green context;許多 EC API 接受顯式 handle / 忽略 thread current context
三種 resource type cudaDevResourceTypeSm / cudaDevResourceTypeWorkqueueConfig / cudaDevResourceTypeWorkqueue(+ Invalid)
stream 限制 cudaStreamGetDevResource 只支援 SM 型;stream 只能關聯 SM 型資源,EC 可關聯多型
建立四步驟 GetDevResource → SmResourceSplit(ByCount) → GenerateDesc → GreenCtxCreate(flags=0)
SplitByCount vs Split ByCount 只能同質 group;Split 可單次建異質 group + discovery mode(smCount=0)
split 結果保證 結果 smCount 為 2 的倍數、在 [2, input.smCount];flags=0 時為 actual coscheduledSmCount 的倍數
coscheduledSmCount 為發動 cluster 共排的 SM 數(CC 9.0+);preferredCoscheduledSmCount 供 CC 10.0+ preferred cluster dim(hint)
backfill flag cudaDevSmResourceGroupBackfill:盡量塞滿 SM,backfilled SM 無 coschedule 保證
descriptor 限制 同 device / 同次 split 且同 coscheduledSmCount / 至多一個 WQ 資源 / 陣列須連續
WQ 資源 須顯式設 typedevicewqConcurrencyLimitsharingScope;無 split 類產生 API
sharingScope DeviceCtx(預設,全共享)/ GreenCtxBalanced(盡量非重疊 WQ,concurrencyLimit 為 hint)
launch cudaExecutionCtxStreamCreate 建 GC stream;GC stream 預設 flag 等同 cudaStreamNonBlocking
CUDA graph graph 的 launch stream 不決定 SM;node EC 在建 node 時設;graph API 用 cudaGraphAddNode.kernel.ctx,避免 cudaGraphAddKernelNode
occupancy + GC 傳 GC stream 給 cudaOccupancyMaxPotentialClusterSize / MaxActiveClusters 才會納入 GC 的 SM
primary ctx 同步 cudaExecutionCtxSynchronize 對 primary context 會連同該 device 全部 GC 一起同步
重大保證 即使分開 provisioned SM/WQ,不保證 真正並行;只是移除阻礙並行的因素
GC vs MIG/MPS GC 固定特定 N 顆 SM、單 process 適用、最輕量、允許 SM oversubscription;MPS 靜態不允許 oversubscription
oversubscription 同一 SM 可 provisioned 給多 GC,須逐案謹慎使用
工具顯示語意 Nsight 顯示的是 kernel 可存取 的 SM 數,非實際使用數