news 2026/9/16 23:01:14

cuda-samples vectorAddMMAP 深度解析:用 cuMemMap 虚拟内存管理实现向量加法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
cuda-samples vectorAddMMAP 深度解析:用 cuMemMap 虚拟内存管理实现向量加法

cuda-samples vectorAddMMAP 深度解析:用 cuMemMap 虚拟内存管理实现向量加法

【免费下载链接】cuda-samplesSamples for CUDA Developers which demonstrates features in CUDA Toolkit项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples

导读

vectorAddMMAP是 NVIDIA cuda-samples 仓库中位于 cpp/0_Introduction/vectorAddMMAP 目录下的入门级示例,其核心目标是将 vectorAddDrv 中的传统设备内存分配(cuMemAlloc)替换为基于cuMemMap的虚拟内存映射分配。通过本文,你将掌握cuMemMap/cuMemCreate/cuMemSetAccess等 CUDA Driver API 虚拟内存管理接口的完整调用链,理解"物理内存属性与虚拟地址连续性解耦"的设计思想,并学会如何用虚拟地址区间(VA)组织跨设备驻留、多设备可访问的内存分配,同时保持程序原有访问结构不变。

示例概览:从 cuMemAlloc 到 cuMemMap

原 README 明确指出:该示例用 cuMemMap 分配的设备内存替换了 vectorAddDrv 示例中的设备分配。其核心意义在于——cuMemMapAPI 允许用户在保留内存访问的连续性的同时,自由指定内存的物理属性(例如内存驻留在哪个设备、以何种粒度分配),因此不需要改变程序原有的结构

对比两个示例的主机端代码可以更直观地看到差异:

  • vectorAddDrv.cpp 使用cuMemAlloc(&d_A, size)三行分配三个设备缓冲区;
  • vectorAddMMAP.cpp 改用simpleMallocMultiDeviceMmap(&d_A, &allocationSize, size, backingDevices, mappingDevices)进行映射式分配。

两者在分配完成之后的代码几乎完全一致(cuMemcpyHtoD拷贝输入、cuLaunchKernel启动内核、cuMemcpyDtoH取回结果),这恰好印证了 README 的描述:切换到cuMemMap不改变程序结构,只替换"分配"这一环节。

前置条件与支持平台

  • 前置条件:下载并安装对应平台的 CUDA Toolkit(官方提供的安装入口,仓库内代码依赖其驱动与头文件)。
  • 支持的 SM 架构:SM 5.0、5.2、5.3、6.0、6.1、7.0、7.2、7.5、8.0、8.6、8.7、8.9、9.0。
  • 支持的操作系统:Linux、Windows。
  • 支持的 CPU 架构:x86_64、ppc64le。
  • 特殊限制:该示例不支持 aarch64。在 CMakeLists.txt 中,当CMAKE_SYSTEM_PROCESSORaarch64时会打印 "Will not build sample vectorAddMMAP - not supported on aarch64" 并跳过构建。

此外,运行时代码还会检查设备属性CU_DEVICE_ATTRIBUTE_VIRTUAL_ADDRESS_MANAGEMENT_SUPPORTED,只有支持虚拟地址管理的设备才能使用这些 API(详见下文源码解析)。

涉及的核心 CUDA Driver API

README 列出了该示例使用的全部 Driver API,这里给出每个接口在本示例中的职责:

API在示例中的用途
cuInit初始化 CUDA Driver API 运行环境
cuDeviceGetCount统计系统中 GPU 数量,用于收集可作为后备(backing)设备的候选
cuDeviceGetAttribute查询CU_DEVICE_ATTRIBUTE_VIRTUAL_ADDRESS_MANAGEMENT_SUPPORTED,判断设备是否支持虚拟地址管理
cuDeviceCanAccessPeer判断其他设备能否与当前设备建立 P2P 互访,决定哪些设备可作为后备设备
cuCtxCreate在目标设备上创建 CUDA 上下文
cuCtxDestroy释放上下文
cuMemGetAllocationGranularity查询每个参与设备的最小分配粒度,取最大值作为统一粒度
cuMemAddressReserve预留一段连续的虚拟地址空间(VA 区间)
cuMemCreate按指定物理属性(Pinned + 设备位置)创建物理内存分配句柄
cuMemMap将物理分配映射到预留的 VA 区间
cuMemSetAccess为映射设备设置读写访问权限,实现跨设备可见性
cuMemRelease释放分配句柄(映射建立后句柄不再需要)
cuMemUnmap解除 VA 区间上的映射,释放物理后备存储
cuMemAddressFree释放 VA 区间,使其可被复用
cuModuleLoadData从 fatbin 二进制数据加载 CUDA 模块
cuModuleGetFunction从模块中获取内核函数句柄VecAdd_kernel
cuLaunchKernel启动向量加法内核
cuMemcpyHtoD/cuMemcpyDtoH主机与设备之间的数据拷贝

主机端主流程解析

vectorAddMMAP.cpp 的主函数流程如下:

  1. 初始化与设备选择cuInit(0)初始化驱动,findCudaDeviceDRV(定义于 Common/helper_cuda_drvapi.h)根据命令行-device=参数或按最高 Gflops 自动选择设备。
  2. 虚拟地址管理能力检查
checkCudaErrors( cuDeviceGetAttribute(&attributeVal, CU_DEVICE_ATTRIBUTE_VIRTUAL_ADDRESS_MANAGEMENT_SUPPORTED, cuDevice)); printf("Device %d VIRTUAL ADDRESS MANAGEMENT SUPPORTED = %d.\n", cuDevice, attributeVal); if (attributeVal == 0) { printf("Device %d doesn't support VIRTUAL ADDRESS MANAGEMENT.\n", cuDevice); exit(EXIT_WAIVED); }

若设备不支持虚拟地址管理,程序以EXIT_WAIVED(值为 2,定义于 Common/helper_cuda_drvapi.h)退出,表示跳过而非失败。

  1. 收集后备设备getBackingDevices(cuDevice)(vectorAddMMAP.cpp)通过cuDeviceGetCount遍历所有设备,用cuDeviceCanAccessPeer筛出与当前设备支持 P2P 互访的设备,再用虚拟地址管理属性做二次过滤,最终得到可为其分配物理内存的设备列表。
  2. 创建上下文与加载模块cuCtxCreate创建上下文;findFatbinPath定位构建期生成的vectorAdd_kernel64.fatbin(默认宏FATBIN_FILE);cuModuleLoadData加载二进制模块,cuModuleGetFunction获取VecAdd_kernel函数句柄。
  3. 分配设备内存:三次调用simpleMallocMultiDeviceMmapd_Ad_Bd_C分配虚拟连续、可被mappingDevices读写的内存。
  4. 数据搬运与内核启动cuMemcpyHtoD拷贝输入;以threadsPerBlock = 256blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock的网格配置,通过参数数组void *args[] = {&d_A, &d_B, &d_C, &N}调用cuLaunchKernel
  5. 结果校验cuMemcpyDtoH取回h_C,逐元素校验fabs(h_C[i] - (h_A[i] + h_B[i])) > 1e-7f,全部通过则输出Result = PASS
  6. 清理CleanupNoFailure依次simpleFreeMultiDeviceMmap释放设备内存、free主机内存、cuModuleUnload卸载模块、cuCtxDestroy销毁上下文。

多设备 mmap 分配实现:simpleMallocMultiDeviceMmap

核心分配逻辑封装在 multidevicealloc_memmap.cpp 的simpleMallocMultiDeviceMmap中,接口声明见 multidevicealloc_memmap.hpp。函数参数含义:

  • dptr(输出):预留的虚拟地址起始值;
  • allocationSize(输出):实际预留的 VA 空间大小,释放时必须传回;
  • size(输入):期望的最小分配字节数,会向上取整;
  • residentDevices(输入):物理内存需要跨哪些设备条带化驻留(stripe);
  • mappingDevices(输入):哪些设备需要读写这块内存;
  • align(输入,默认 0):额外对齐要求。

虚拟地址布局

头文件注释给出了 VA 映射的可视化布局:

v-stripeSize-v v-rounding -v +-----------------------------------------+ | D1 | D2 | D3 | +-----------------------------------------+ ^-- dptr ^-- dptr + size

每个residentDevices中的设备获得等长的条带(stripe),末尾多余空间用于满足所有设备的最小粒度要求。

实现步骤与关键点

  1. 构造分配属性CUmemAllocationProp prop设置type = CU_MEM_ALLOCATION_TYPE_PINNEDlocation.type = CU_MEM_LOCATION_TYPE_DEVICE,即创建设备锁页(pinned)内存。
  2. 计算统一最小粒度:对所有residentDevicesmappingDevices调用cuMemGetAllocationGranularity(..., CU_MEM_ALLOC_GRANULARITY_MINIMUM),取各设备粒度的最大值作为min_granularity
  3. 向上取整size = round_up(size, residentDevices.size() * min_granularity),使总大小可被设备数整除且每个条带都满足粒度;stripeSize = size / residentDevices.size();通过allocationSize把取整后的大小回传给调用方(释放时使用)。
  4. 预留 VA 区间cuMemAddressReserve(dptr, size, align, 0, 0)预留连续虚拟地址。
  5. 逐设备创建并映射:循环中对每个residentDevices[idx]设置prop.location.idcuMemCreate(&allocationHandle, stripeSize, &prop, 0)创建物理分配,cuMemMap(*dptr + stripeSize * idx, stripeSize, 0, allocationHandle, 0)映射到对应 VA 偏移,随后立即cuMemRelease(allocationHandle)——映射建立后句柄不再需要,物理内存由映射保持存活。
  6. 设置跨设备访问权限:为每个mappingDevices构造CUmemAccessDesclocation.type = CU_MEM_LOCATION_TYPE_DEVICEflags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE),一次cuMemSetAccess(*dptr, size, descriptors, count)为整个 VA 区间授予读写权限。
  7. 失败回滚:任一步失败跳转done标签,若*dptr非空则调用simpleFreeMultiDeviceMmap清理已建立的映射。

值得注意的源码注释(vectorAddMMAP.cpp)强调:即使后备设备与映射设备不同,也无需调用cuCtxEnablePeerAccess,因为cuMemSetAccess显式指定了跨设备映射,但该调用仍受cuDeviceCanAccessPeer约束(这正是先前收集backingDevices时先做 P2P 检查的原因)。

释放实现:simpleFreeMultiDeviceMmap

multidevicealloc_memmap.cpp 中的释放分两步:

  1. cuMemUnmap(dptr, size):解除整个 VA 区间上的映射。由于句柄此前已通过cuMemRelease释放且这是唯一引用该后备存储的映射,后备物理内存在此处被自动释放;此后访问该 VA 区间将触发错误(fault)。
  2. cuMemAddressFree(dptr, size):归还虚拟地址区间,使其可被后续cuMemAddressReserve或其他操作系统级分配(如mallocmmap)复用。

设备端内核

内核定义在 vectorAdd_kernel.cu,是经典的逐元素向量加法:

extern "C" __global__ void VecAdd_kernel(const float *A, const float *B, float *C, int N) { int i = blockDim.x * blockIdx.x + threadIdx.x; if (i < N) C[i] = A[i] + B[i]; }

extern "C"保证函数名不被名字改编(name mangling),从而可被主机端cuModuleGetFunction(&vecAdd_kernel, cuModule, "VecAdd_kernel")精确查找到。

构建与运行

构建配置位于 CMakeLists.txt:

  • 要求 CMake 3.20+,启用C CXX CUDA三种语言,通过find_package(CUDAToolkit REQUIRED)定位 CUDA 工具包。
  • 默认架构列表为75 80 86 87 89 90 100 110 120,并通过add_custom_command调用nvcc ... -fatbin把 vectorAdd_kernel.cu 编译为vectorAdd_kernel64.fatbin(fatbin 文件生成后由findFatbinPath在运行时定位加载)。
  • 可执行文件由vectorAddMMAP.cppmultidevicealloc_memmap.cpp组成,链接CUDA::cuda_driver
  • 该目录通过 cpp/0_Introduction/CMakeLists.txt 中的add_subdirectory(vectorAddMMAP)接入整个仓库的构建体系。

典型构建与运行方式(在仓库根目录):

mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make vectorAddMMAP ./vectorAddMMAP # 使用性能最优设备 ./vectorAddMMAP -device=0 # 显式指定设备编号

运行输出示例:设备信息、VIRTUAL ADDRESS MANAGEMENT SUPPORTED = 1、fatbin 加载路径,最终打印Result = PASS(校验失败则打印Result = FAIL并以非零码退出)。

总结

vectorAddMMAP以最简的向量加法为载体,系统展示了 CUDA 虚拟内存管理(VMM)四大步骤:预留 VA(cuMemAddressReserve)→ 创建物理分配(cuMemCreate)→ 映射(cuMemMap)→ 设置访问(cuMemSetAccess,并完整覆盖了释放路径(cuMemUnmap+cuMemAddressFree)。它与vectorAddDrv的对照关系、与 simpleP2P 等 P2P 示例的能力边界,为读者理解"物理属性可定制、虚拟访问仍连续"的现代 CUDA 内存模型提供了可编译、可运行、可逐步断点跟读的实践起点。

【免费下载链接】cuda-samplesSamples for CUDA Developers which demonstrates features in CUDA Toolkit项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 23:01:14

LeetCode 149:用gcd归一化斜率,O(n²)哈希解共线点问题

最近刷题的时候&#xff0c;我一直在用腾讯元宝网页版里的 DeepSeek 当陪练。说实话&#xff0c;之前我对“用大模型辅助刷算法题”这件事挺保守的&#xff0c;总觉得会变成“抄答案工具”&#xff0c;直到碰到 LeetCode 149 这道题&#xff0c;发现让 AI 讲思路、帮我分析边界…

作者头像 李华
网站建设 2026/9/16 23:01:06

互联网平台盈利模式与抽成机制深度解析

1. 互联网商业模型解析互联网行业的盈利模式与传统行业有着本质区别。作为从业十余年的互联网商业分析师&#xff0c;我发现许多刚入行的朋友对互联网企业的收支结构存在认知偏差。以平台型互联网公司为例&#xff0c;其核心收入来源通常包含以下几个部分&#xff1a;广告收入&…

作者头像 李华
网站建设 2026/9/16 22:58:53

XXE注入漏洞原理与利用:从XML外部实体到Apache POI漏洞解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 22:57:32

MATLAB中SOM聚类实战:从原理、参数调优到误差评估

简介&#xff1a;SOM&#xff08;自组织映射&#xff09;是一种基于竞争学习的无监督神经网络&#xff0c;常用于非线性降维与数据可视化。以MATLAB为环境的SOM聚类资源&#xff0c;专为希望掌握SOM原理并快速上手的初学者设计&#xff0c;通过鱼类种类特征数据&#xff0c;演示…

作者头像 李华
网站建设 2026/9/16 22:56:35

xcodebuild + simctl 实现iOS模拟器自动化打包与安装全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华