CUDA 编程指南 13.4.2 · 非官方中文译本
连续阅读
原著发布于 2026 年 9 月 10 日对照 NVIDIA 英文原文对应英文网页

4.7. 局部性域

局部性域是 GPU 的一部分,其中包含流式多处理器(SM)和设备内存。应用程序可以在某个局部性域中分配设备内存,并创建一个具有同一局部性域中 SM 资源的绿色上下文。将计算放置在其所访问的内存附近,可以提高具有多个局部性域的设备上的性能。计算局部化 API 在计算资源局部化中介绍,内存局部化在分配局部化设备内存中介绍。

CUDA 通过从零开始的局部性域 ID标识设备的局部性域。局部性域 ID 只有与其设备 ID 结合使用时才有意义。对 SM 资源和内存进行局部化时使用相同的 ID,使应用程序能够请求将两者放置在匹配的位置。

4.7.1. 发现局部性域

使用cudaDeviceGetAttribute,并指定以下cudaDeviceAttr值:

  • cudaDevAttrLocalityDomainCount返回设备上的局部性域数量。

  • cudaDevAttrLocalityDomainMultiprocessorCount返回每个局部性域中的 SM 数量。

应用程序应查询这些属性,而不是假定某种特定的拓扑结构。设备上可用的局部性域数量受系统配置影响。在不同情形下,相同类型的设备可能呈现不同数量的局部性域(第 4.7.5.3 节)。所有设备,包括仅有一个可用局部性域的设备,都可以使用局部性域 API。在仅有一个局部性域的设备上,这些 API 与面向整个设备的操作相同。使用设备属性以编程方式查询设备的局部性域属性:

int localityDomainCount = 0;
int smsPerLocalityDomain = 0;

cudaDeviceGetAttribute(&localityDomainCount, cudaDevAttrLocalityDomainCount, device);
cudaDeviceGetAttribute(&smsPerLocalityDomain, cudaDevAttrLocalityDomainMultiprocessorCount, device);

4.7.2. 计算资源局部化

要将 SM 资源局部化到特定的局部性域,请使用 CUDA 绿色上下文 API。SM 的放置通过第 4.6 节中介绍的绿色上下文资源 API 来表达。构建局部化 SM 分区的主要步骤如下:

  1. 使用cudaDeviceGetDevResource获取设备的cudaDevResourceTypeSm资源。

  2. 使用cudaDevSmResourceGroupParams描述局部化 SM 组(详细信息)。

  3. 将组参数传递给cudaDevSmResourceSplit。

  4. 使用cudaDevResourceGenerateDesc生成资源描述符,使用cudaGreenCtxCreate创建绿色上下文,最后通过cudaExecutionCtxStreamCreate创建流。

  5. 在该流上启动核函数。

典型的绿色上下文创建过程在第 4.6.2 节中介绍。为使 SM 资源局部化而必须添加的内容如下所示:

要描述局部化 SM 组,请在flags中设置cudaDevSmResourceGroupLocalityDomainId,并在localityDomainId中设置 ID。有效 ID 介于0和cudaDevAttrLocalityDomainCount - 1之间。

cudaDevResource smResource;
cudaDeviceGetDevResource(device, &smResource, cudaDevResourceTypeSm);

{
    cudaDevSmResourceGroupParams params = {};
    // all fields in params must be zero-initialized
    params.flags = cudaDevSmResourceGroupLocalityDomainId;
    params.localityDomainId = localityDomainId;

    cudaDevResource result;
    cudaDevSmResourceSplit(&result, 1, &smResource, nullptr, 0, &params);
}

可以在请求局部性域的同时,在params中指定多个约束。例如:

smCount约束可以为0(发现模式)或正值。在发现模式下,API 在params.smCount中返回该局部性域中的 SM 数量。对于完整、未分区的设备资源,此值与cudaDevAttrLocalityDomainMultiprocessorCount相同。当smCount为正值时,API 最多分配cudaDevAttrLocalityDomainMultiprocessorCount所指定的数量。更大的smCount请求将失败。

当coscheduledSmCount为0时,API 不会根据协同调度能力限制 SM,因此可使用最大数量的局部化 SM。之后可以通过cudaOccupancyMaxPotentialClusterSize动态查询该分区的簇能力。当coscheduledSmCount非零时,API 将仅选择满足所请求的coscheduledSmCount的局部化 SM。

默认情况下,result中的所有 SM 都必须满足局部性域约束以及指定的任何附加约束。要放宽此行为,请将cudaDevSmResourceGroupBackfill添加到params.flags。启用回填后,API 首先从所请求的局部性域填充该组,然后使用不归属于任何局部性域的 SM,最后使用其他局部性域中的 SM。如果在所请求的局部性域中找不到任何 SM,该调用将失败并返回cudaErrorInvalidResourceConfiguration。

所有未在result中返回的未使用 SM 都会在 API 的remainder参数中返回。

result的cudaDevResource.sm.flags和cudaDevResource.sm.localityDomainId报告资源的放置情况。将 SM 资源组合到一个描述符中时,这些资源的约束必须匹配,包括flags和localityDomainId的值。

以下代码查询局部性域的数量,并为每个局部性域构建一个流。

std::vector<cudaExecutionContext_t> contexts(localityDomainCount);
std::vector<cudaStream_t> streams(localityDomainCount);

{
    std::vector<cudaDevSmResourceGroupParams> params(localityDomainCount);
    std::vector<cudaDevResource> result(localityDomainCount);

    for (int i = 0; i < localityDomainCount; i++) {
        params[i] = {};
        params[i].flags = cudaDevSmResourceGroupLocalityDomainId;
        params[i].localityDomainId = i;
    }

    // A single split produces non-overlapping resources for all domains.
    cudaDevSmResourceSplit(result.data(), localityDomainCount, &smResource, nullptr, 0, params.data());

    for (int i = 0; i < localityDomainCount; i++) {
        cudaDevResourceDesc_t desc;
        cudaDevResourceGenerateDesc(&desc, &result[i], 1);
        cudaGreenCtxCreate(&contexts[i], desc, device, 0);
        cudaExecutionCtxStreamCreate(&streams[i], contexts[i], cudaStreamDefault, 0);
    }
}

streams中的每个流都在具有对应索引的局部性域中的 SM 上执行。不再需要这些流时,使用cudaStreamDestroy销毁流,然后使用cudaExecutionCtxDestroy销毁其上下文。

4.7.3. 分配局部化设备内存

CUDA 使用type为cudaMemLocationTypeDeviceLocalityDomain的cudaMemLocation来表示局部化内存位置。对于这种位置类型,请初始化cudaMemLocation.localized的两个成员:

  • deviceId标识 GPU。

  • localityDomainId标识该 GPU 上的一个局部性域。

运行时 API 将此位置用于流有序内存池(第 4.3 节)。驱动程序 API 还将相应的CUmemLocation用于虚拟内存管理分配(第 4.17 节)。

通过其他机制(例如cudaMalloc或统一内存)创建的分配无法局部化。MLOPart 模式下的MPS(多进程服务)可用于对依赖cudaMalloc的应用程序透明地进行局部化。

4.7.3.1. 创建局部化内存池

要创建局部化内存池,请在cudaMemPoolProps中设置位置并调用cudaMemPoolCreate:

cudaMemPool_t pool {};
cudaMemPoolProps props {};

props.allocType = cudaMemAllocationTypePinned;
props.location.type = cudaMemLocationTypeDeviceLocalityDomain;
props.location.localized.deviceId = device;
props.location.localized.localityDomainId = localityDomainId;

cudaMemPoolCreate(&pool, &props);

使用cudaMallocFromPoolAsync从该池分配内存,并使用cudaFreeAsync释放分配。在为该局部性域选择默认或当前页锁定内存池时,也可以将该位置传递给cudaMemGetDefaultMemPool、cudaMemGetMemPool和cudaMemSetMemPool。有关通用内存池编程模型,请参见内存池。

4.7.3.2. 创建局部化 VMM 分配

驱动程序 API 可以将cuMemCreate创建的物理内存放置在某个局部性域中。将CUmemAllocationProp::location.type设置为CU_MEM_LOCATION_TYPE_DEVICE_LOCALITY_DOMAIN,并初始化location.localized.deviceId和location.localized.localityDomainId。所生成的物理分配按与其他 VMM 分配相同的方式进行预留、映射和访问授权。通过cuMemSetAccess设置的访问控制以整个设备为粒度,而不是以单个局部性域为粒度。有关完整的分配顺序和粒度要求,请参见局部化 VMM 分配一节。

托管内存无法局部化:使用cudaMemLocationTypeDeviceLocalityDomain请求cudaMemAllocationTypeManaged将返回CUDA_ERROR_NOT_SUPPORTED。同样,cudaMemPrefetchAsync和cudaMemAdvise不接受设备局部性域作为位置,并将返回CUDA_ERROR_NOT_SUPPORTED。

有关局部性域与统一内存之间的关系,请参见局部性域与统一内存。

4.7.4. 查询内存放置情况

使用cudaMemPoolGetAttribute并指定cudaMemPoolAttrLocalityDomainId来查询池。对于局部化池,它返回局部性域 ID;对于非局部化池,它返回-1。

使用cudaPointerGetAttributes通过指针查询分配。返回的cudaPointerAttributes.localityDomainOrdinal字段包含该分配的局部性域序号;如果该分配未局部化,则为-1:

cudaPointerAttributes attributes {};
cudaPointerGetAttributes(&attributes, ptr);
int localityDomainId = attributes.localityDomainOrdinal;

4.7.5. 编程指导

要使工作负载局部化,请使用与核函数的内存分配位于同一局部性域的计算资源来启动核函数。

仅对计算资源或仅对内存资源进行局部化通常不会提高性能。

如果核函数必须使用来自多个局部性域的内存,请测量计算局部化是否有益。

4.7.5.1. 采用分叉/汇合模式的局部化核函数

分叉/汇合模式使用事件和流等待来表达并行区域。应用程序使用cudaExecutionCtxStreamCreate从每个局部化绿色上下文创建一个流。一个单独的主流协调这些局部化流。使用cudaEventCreateWithFlags为主流创建一个分叉事件,并为每个局部化流创建一个汇合事件;当事件仅用于排序时,应禁用计时。

要进行分叉,请在父流上记录一个事件,并让每个局部化流等待该事件。因此,先前提交到父流的工作在局部化工作之前执行。将独立的工作提交到各个局部化流,并使用来自其对应局部性域的内存。

要进行汇合,请在每个局部化流的工作之后记录一个事件,并让父流等待所有这些事件。随后提交到父流的工作便会在局部化工作之后执行。

produceInputs<<<grid, block, 0, parentStream>>>(...);
cudaEventRecord(forkEvent, parentStream);

// localizedStreams[i] belongs to the green context for locality domain i.
for (int i = 0; i < localityDomainCount; i++) {
    cudaStreamWaitEvent(localizedStreams[i], forkEvent);
    processPartition<<<grid, block, 0, localizedStreams[i]>>>(...);
    cudaEventRecord(joinEvents[i], localizedStreams[i]);
}

for (int i = 0; i < localityDomainCount; i++) {
    cudaStreamWaitEvent(parentStream, joinEvents[i]);
}
consumeResults<<<grid, block, 0, parentStream>>>(...);

4.7.5.2. CUDA 图

CUDA 图(第 4.2 节)通过流捕获和手动图构建来支持局部化。对于某些应用程序,CUDA 图可能比基于流的编程提供更好的性能。

4.7.5.2.1. 流捕获

可以将分叉/汇合模式捕获为 CUDA 图。在父流上开始捕获,记录分叉事件,并在每个局部化流中等待该分叉事件。等待分叉事件会将局部化流纳入与父流相同的捕获图。提交到局部化流的操作成为并行的图分支。在局部化流上启动所有工作后,记录汇合事件,并在父流中等待所有汇合事件。这些等待会将所有分支重新连接到父流。在调用cudaStreamEndCapture之前,每个参与的流都必须汇合回起始流。

cudaStreamBeginCapture(parentStream, cudaStreamCaptureModeGlobal);

// Record forkEvent, submit work to each localized stream, record the
// joinEvents, and make parentStream wait as in the fork/join example above.

cudaGraph_t graph;
cudaStreamEndCapture(parentStream, &graph);

每个子流的局部性限制都会由捕获的核函数节点保留。即使在不同的流上启动可执行图,图重放仍使用原始的局部化 SM。图启动流提供排序,但不会替换节点中记录的执行资源。

4.7.5.2.2. 图构建

可以显式构建相同的图拓扑。在cudaKernelNodeParamsV2.ctx中使用相应的局部化执行上下文,为每个局部性域添加一个核函数节点。

cudaGraph_t graph = nullptr;
cudaGraphCreate(&graph, 0);

std::vector<cudaGraphNode_t> localizedNodes(localityDomainCount);
for (int i = 0; i < localityDomainCount; i++) {
    cudaGraphNodeParams params = { CU_GRAPH_NODE_TYPE_KERNEL };
    params.kernel.kern = kernel;
    params.kernel.gridDim = grid;
    params.kernel.blockDim = block;
    params.kernel.kernelParams = kernelArgs[i];
    params.kernel.ctx = localizedContexts[i];

    cudaGraphAddNode(&localizedNodes[i], graph, nullptr, nullptr, 0, &params);
}

4.7.5.3. 兼容性注意事项

vGPU 和 MIG 设备不支持局部化,并且仅报告 1 个局部性域。

Windows 上的 CUDA 不支持局部化,并且仅报告 1 个局部性域。

CUDA Checkpoint 不保证局部化绿色上下文在另一台设备上恢复时仍保留其局部化属性。应用程序在恢复后可能出现性能下降。

MLOPart 模式下的MPS(多进程服务)将每个 MLOPart 设备呈现为具有单个局部性域的 CUDA 设备。MPS 静态分区将整个设备视为单个局部性域。

原著 © 2007 至 2026 NVIDIA Corporation 及其关联公司。保留所有权利。
中文译本仅作学习参考,非 NVIDIA 官方出版物。技术含义有疑问时请核对NVIDIA 英文原文。
关于本译本

输入关键词搜索全书。