ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

mxnet.rtc 运行时 CUDA 编译指南:在 MXNet 中直接编写并启动自定义 CUDA Kernel

mxnet.rtc 运行时 CUDA 编译指南:在 MXNet 中直接编写并启动自定义 CUDA Kernel 深度学习人工智能机器学习分布式训练【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址https://gitcode.com/gh_mirrors/mx/mxnet点击查看免费下载导读mxnet.rtc是 Apache MXNet 面向 GPU 用户的「运行时 CUDA 编译Runtime CUDA Compilation, RTC」模块它允许你在 Python 中直接书写 CUDA C/C 内核源码在运行时通过 NVRTC 编译成 PTX再与 MXNet 的 NDArray 和自动调度引擎无缝衔接并启动执行全程无需重编译 MXNet、无需编写 C 插件或自定义算子。读完本文你将掌握CudaModule/CudaKernel的完整用法、extern C与exports两种导出方式的取舍、签名解析与参数类型规则、编译选项与 SASS 直编的底层原理并能把自定义 CUDA Kernel 直接作用于mx.nd.NDArray复用 MXNet 的引擎调度与依赖管理。本文以官方 API 文档 docs/python_docs/python/api/rtc/index.rst其内容由 python/mxnet/rtc.py 的 docstring 承载为骨架结合源码与测试展开。1. rtc 是什么面向 MXNet 的运行时 CUDA 编译接口mxnet.rtc在 MXNet Python 包中通过 python/mxnet/init.py 的from . import rtc导出其模块 docstring 自述为 Interface to runtime cuda kernel compile modulepython/mxnet/rtc.py。它解决的核心问题很直接当标准算子无法覆盖某些自定义 GPU 计算时传统做法是编译进自定义算子涉及 C/CUDA 代码、MSHADOW_XINLINE宏与整个 MXNet 重编译而 rtc 让你把 CUDA 源码作为字符串传入在运行期完成「编译 → 加载 → 启动」全流程编译借助 NVIDIA NVRTClibnvrtc在运行时把 CUDA C 源码编译为 PTX或特定架构的 SASS/cubin。加载通过 CUDA Driver APIcuModuleLoadDataEx把编译产物加载为 CUmodule。启动通过cuLaunchKernel按用户指定的 grid/block 维度启动内核并同步等待完成。这一整套逻辑的 C 实现位于 src/common/rtc.cc 与 include/mxnet/rtc.h全部在MXNET_USE_CUDA宏保护下即该功能仅在启用 CUDA 的构建中可用从 include/mxnet/rtc.h 的CHECK_EQ(ctx.dev_mask(), Context::kGPU)也可确认内核只能在 NVIDIA GPU 上启动。Python 层通过 include/mxnet/c_api.h 暴露的MXRtcCudaModuleCreate/MXRtcCudaKernelCreate/MXRtcCudaKernelCall等 C API 与底层对接。从源码结构看rtc模块对外只暴露两个类CudaModule编译并持有 CUDA 源码对应的模块与CudaKernel由CudaModule.get_kernel产生、负责启动的 kernel 句柄本文其余章节将围绕这两个类的完整使用展开。2. 快速上手第一个 rtc 内核axpy 示例CudaModule的 docstringpython/mxnet/rtc.py给出了一个完整的端到端示例。在 CUDA 7.5 时代也兼容所有后续版本最稳妥的写法是用extern C修饰内核以避免 C 名称修饰name mangling导致无法按原名查找import mxnet as mx source r extern C __global__ void axpy(const float *x, float *y, float alpha) { int i threadIdx.x blockIdx.x * blockDim.x; y[i] alpha * x[i]; } module mx.rtc.CudaModule(source) func module.get_kernel(axpy, const float *x, float *y, float alpha) x mx.nd.ones((10,), ctxmx.gpu(0)) y mx.nd.zeros((10,), ctxmx.gpu(0)) func.launch([x, y, 3.0], mx.gpu(0), (1, 1, 1), (10, 1, 1)) print(y)运行后y的每个元素都为3.0。这个例子覆盖了 rtc 的全部核心流程步骤代码说明编写内核源码source r...原始字符串r避免转义问题内核需为__global__函数编译模块module mx.rtc.CudaModule(source)构造即触发 NVRTC 编译获取内核func module.get_kernel(axpy, ...)按名称与签名获取CudaKernel准备数据x/y为mx.gpu(0)上的 NDArray指针型参数必须是 NDArray启动内核func.launch(args, ctx, grid, block)指定三维 grid/block 尺寸2.1 参数命名与作用CudaModule构造函数的三个参数python/mxnet/rtc.pysource : str— 完整的 CUDA 源码字符串是唯一必填参数。options : tuple of str— 传给 NVRTC 的编译选项例如-I/usr/local/cuda/include用于向 include path 追加 CUDA 头文件目录也可直接传单个字符串构造时若检测到string_types会自动包装成元组。多个选项可组合使用。exports : tuple of str— 需要按名称导出的内核名仅 CUDA 8.0 支持详见第 3 节同样支持字符串自动包装。一个典型的实际用法是配合mxnet.util.get_rtc_compile_opts获取针对当前 GPU 架构的编译选项详见第 5 节例如官方 GPU 测试 tests/python/gpu/test_operator_gpu.py 中module mx.rtc.CudaModule(source, optionscompile_opts)的写法。3. 两种内核导出方式extern C 与 exportsC 会对函数名做名称修饰name mangling导致内核的实际符号名与源码中的函数名不一致。mxnet.rtc提供了两条解决路径这也是官方文档python/mxnet/rtc.py重点区分的内容。3.1 方式一extern CCUDA 7.5 及所有版本通用在 CUDA 7.5 中以及任何版本下不想用 exports 时内核定义需以extern C开头以避免名称修饰extern C __global__ void axpy(const float *x, float *y, float alpha) { ... }此时get_kernel(axpy, ...)直接使用axpy作为查找名。C 端在 src/common/rtc.cc 的GetKernel中若名字未命中 exports 集合mangled_name就保持用户传入的原名随后 src/common/rtc.cc 通过cuModuleGetFunction从已加载模块中查找该符号若找不到会输出明确提示请为内核定义加extern C或在创建CudaModule时把名字加入exports。3.2 方式二exportsCUDA 8.0支持模板从 CUDA 8.0 开始可以通过exports按名称导出函数这同时解锁了模板内核的使用例如同一份模板源码分别实例化float与double两个版本source r templatetypename DType __global__ void axpy(const DType *x, DType *y, DType alpha) { int i threadIdx.x blockIdx.x * blockDim.x; y[i] alpha * x[i]; } module mx.rtc.CudaModule(source, exports[axpyfloat, axpydouble]) func32 module.get_kernel(axpyfloat, const float *x, float *y, float alpha) x mx.nd.ones((10,), dtypefloat32, ctxmx.gpu(0)) y mx.nd.zeros((10,), dtypefloat32, ctxmx.gpu(0)) func32.launch([x, y, 3.0], mx.gpu(0), (1, 1, 1), (10, 1, 1)) print(y) func64 module.get_kernel(axpydouble, const double *x, double *y, double alpha) x mx.nd.ones((10,), dtypefloat64, ctxmx.gpu(0)) y mx.nd.zeros((10,), dtypefloat64, ctxmx.gpu(0)) func64.launch([x, y, 3.0], mx.gpu(0), (1, 1, 1), (10, 1, 1)) print(y)注意上面示例中两处launch都打印的是func32的结果这是官方 docstring 的既有写法实际使用中第二次应调用func64.launch(...)才会得到 float64 的输出。此外模板实例名如axpyfloat含特殊字符不能用extern C修饰因此这类场景必须走exports路径。底层实现上src/common/rtc.cc 在 CUDA 8.0 时对每个导出名调用nvrtcAddNameExpression登记表达式编译完成后在GetKernelsrc/common/rtc.cc中通过nvrtcGetLoweredName查询真实修饰名而在低于 CUDA 8.0 的构建中src/common/rtc.cc 会直接CHECK_EQ(exports.size(), 0)并给出错误信息导出功能仅在 CUDA 8.0 及以上可用低版本请改用extern C。4. get_kernel 与签名解析参数类型如何被解读4.1 签名语法CudaModule.get_kernel(name, signature)python/mxnet/rtc.py的signature参数描述内核的函数签名。例如内核声明为extern C __global__ void axpy(const float *x, double *y, int alpha)则签名可写作完整的const float *x, double *y, int alpha也可以省略参数名、只保留类型与指针标记const float *, double *, int其中两条规则决定了参数如何映射到 Python 侧签名中的*标记该参数为数组NDArray签名中的const标记该参数为常量只读输入数组。4.2 解析器实现与支持的类型签名在 Python 侧由 python/mxnet/rtc.py 中的正则表达式解析pattern re.compile(r^(const)?\s?([\w_])\s?(\*)?\s?([\w_])?$)每个参数被拆解为(const) 类型 (*) (参数名)四段第一组捕获const记入is_const、第二组捕获类型名、第三组捕获*记入is_ndarray。若格式不合法如组内出现const位置错误会抛出ValueError提示签名必须符合(const) type (*) (name)形式。支持的类型映射定义在模块级字典_DTYPE_CPP_TO_NPpython/mxnet/rtc.pyCUDA/C 类型对应的 NumPy 类型CUDA/C 类型对应的 NumPy 类型floatnp.float32int8_tnp.int8doublenp.float64charnp.int8__halfnp.float16int64_tnp.int64uint8_tnp.uint8intnp.int32int32_tnp.int32不在表内的类型会在get_kernel阶段直接抛出TypeError并列出全部受支持类型因此类型检查发生在取内核时而不是启动时。4.3 C 侧的一致性校验即使 Python 侧通过了类型检查C 端在启动时仍会做严格校验src/common/rtc.cc对每个被标记为 NDArray 的参数会CHECK_EQ(array.dtype(), arg_types[i].dtype)确认传入 NDArray 的 dtype 与签名声明一致不一致时给出包含期望类型与实际类型的错误信息。同时内核被提交到 MXNet 引擎时is_const标记决定了依赖关系方向只读数组进入read_vars、输出数组进入write_varssrc/common/rtc.cc从而让 MXNet 的依赖调度器dep scheduler自动保证内核与前序/后续算子之间的数据依赖这正是 rtc 能无缝融入mx.nd计算图的关键设计。5. CudaKernel.launch启动参数与调度细节CudaKernel.launch(args, ctx, grid_dims, block_dims, shared_mem0)python/mxnet/rtc.py的参数约定如下参数类型说明argsNDArray 或数值组成的 tuple指针类型float*等传 NDArray非指针类型int、float等传数值ctxmx.Context启动内核的上下文必须是 GPU 上下文grid_dims3 个整数组成的 tupleCUDA grid 维度对应gridDim.x/y/zblock_dims3 个整数组成的 tupleCUDA block 维度对应blockDim.x/y/zshared_memint可选动态共享内存大小字节默认 0Python 侧在 python/mxnet/rtc.py 做前置断言ctx.device_type必须为gpugrid_dims/block_dims必须是长度 3 的元组参数个数必须与签名类型数一致否则报 CudaKernel({name}) expects {n} arguments but got {m}。随后将 NDArray 参数直接取其handle数值参数则按声明 dtype 用np.array(arg, dtypedtype)打包并取内存地址最终调用 C APIMXRtcCudaKernelCall启动。C 端 src/common/rtc.cc 的Kernel::Launch展示了完整的调度链路按设备缓存CUfunctionfunc_[ctx.dev_id]避免重复查找收集只读/可写 NDArray 的Engine::VarHandle作为依赖变量Engine::Get()-PushSync(...)把启动动作作为 MXNet 引擎任务提交——引擎会等待所有read_vars依赖的算子完成、并保证后续算子等待本内核的write_vars任务内部对 NDArray 参数取dptr_指针、数值参数做MSHADOW_TYPE_SWITCH分派随后cuLaunchKernel在 MXNet 的 GPU 流上启动并cudaStreamSynchronize同步等待内核完成。5.1 实战测试用例中的共享内存用法仓库自带的 GPU 测试 tests/python/gpu/test_rtc.py 演示了动态共享内存与expf数学函数的组合用法import mxnet as mx import numpy as np from numpy.testing import assert_allclose x mx.nd.zeros((10,), ctxmx.gpu(0)) x[:] 1 y mx.nd.zeros((10,), ctxmx.gpu(0)) y[:] 2 rtc mx.rtc(abc, [(x, x)], [(y, y)], __shared__ float s_rec[10]; s_rec[threadIdx.x] x[threadIdx.x]; y[threadIdx.x] expf(s_rec[threadIdx.x]*5.0);) rtc.push([x], [y], (1, 1, 1), (10, 1, 1)) assert_allclose(y.asnumpy(), np.exp(x.asnumpy()*5.0))该测试在python/mxnet/rtc.py之外、通过mx.rtc模块名直接以旧式函数接口rtc(name, in_args, out_args, source)push运行并验证了结果与np.exp(x * 5.0)一致。测试文件中虽然用了__shared__静态共享内存但launch的shared_mem参数同样支持为extern __shared__数组动态分配共享内存默认 0 字节。6. 编译选项架构目标、include 路径与 SASS 直编CudaModule(source, options...)的options会原样透传给 NVRTC。常见用途包括-I/path/to/cuda/include追加 CUDA 头文件搜索路径用于在源码中使用自定义头文件--gpu-architecturesm_80或compute_80等指定编译目标架构。6.1 自动选择架构编译选项MXNet 在 python/mxnet/util.py 提供get_rtc_compile_opts(device)工具函数可针对运行设备自动生成合适的--gpu-architecture选项def get_rtc_compile_opts(device): device_cc get_cuda_compute_capability(device) # 当前设备算力 max_supported_cc get_max_supported_compute_capability() # NVRTC 支持的最大算力 can_compile_to_SASS max_supported_cc 86 # CUDA 11.1 支持 sm_86 直编 SASS should_compile_to_SASS can_compile_to_SASS and device_cc max_supported_cc device_cc_as_used min(device_cc, max_supported_cc) arch_opt --gpu-architecture{}_{}.format(sm if should_compile_to_SASS else compute, device_cc_as_used) return [arch_opt]其决策逻辑值得展开若 NVRTC 支持的最高算力 ≥ 86即 CUDA 11.1 及以后且设备算力不超过该上限则直接编译为sm_XX目标SASS否则回退为compute_XX目标PTX运行时再 JIT。官方 GPU 测试 tests/python/gpu/test_operator_gpu.py 在构建CudaModule时正是传入optionsget_rtc_compile_opts(ctx)。6.2 底层如何决定 PTX 还是 CUBINC 端 src/common/rtc.cc 会根据编译选项决定产物形式遍历options若发现包含sm_的选项即显式指定了具体 SM 架构则use_ptx false转而通过nvrtcGetCUBINSize/nvrtcGetCUBIN获取 CUBINSASS二进制——但该路径要求 CUDA 11.1否则LOG(FATAL)提示请改用 compute_XX 目标或升级到 CUDA 11.1默认情况下无sm_选项则走nvrtcGetPTXSize/nvrtcGetPTX生成 PTX。无论哪种产物最终都在 src/common/rtc.cc 通过cuModuleLoadDataEx按设备加载并对每个设备ctx.dev_id缓存一个CUmodule。7. 与 MXNet 生态的协作方式7.1 在 NDArray 计算流中使用 rtcrtc 内核的输入输出就是mx.nd.NDArray因此天然可以出现在任意 MXNet 计算序列中。由于内核通过引擎的PushSync提交并登记了read_vars/write_varssrc/common/rtc.cc它可以与前序算子生产者和后序算子消费者自动建立依赖无需手动同步——这是 rtc 相比裸cudaLaunchKernel的最大优势。7.2 C API 层面对接Python 层封装通过 include/mxnet/c_api.h 的四个接口完成MXRtcCudaModuleCreate编译源码并返回模块句柄、MXRtcCudaModuleFree释放模块、MXRtcCudaKernelCreate按名称/签名创建内核、MXRtcCudaKernelCall启动内核均通过check_call检查返回码。此外同文件还保留了一套更早期的MXRtcCreate/MXRtcPush/MXRtcFree接口include/mxnet/c_api.h对应 tests/python/gpu/test_rtc.py 中旧式mx.rtc(...)push(...)的用法。若你有更复杂的自定义算子需求也可以参考仓库中 example/extensions/lib_custom_op 的算子注册流程但 rtc 的优势在于无需任何编译期集成。8. 使用限制与注意事项综合文档与源码使用mxnet.rtc时需注意以下边界仅限 NVIDIA GPU从 Python 的assert ctx.device_type gpupython/mxnet/rtc.py到 C 的CHECK_EQ(ctx.dev_mask(), Context::kGPU)include/mxnet/rtc.h、src/common/rtc.cc全链路仅支持 GPU 上下文CPU 环境无法使用。仅限 CUDA 构建rtc.h整体包裹在#if MXNET_USE_CUDA中未启用 CUDA 的 MXNet 构建不包含该功能。名称查找规则不用extern C也不加入exports的内核将无法被get_kernel找到运行时cuModuleGetFunction返回CUDA_ERROR_NOT_FOUND并给出修正提示src/common/rtc.cc。类型限制内核参数类型仅限第 4.2 节表格中的类型数值参数按签名 dtype 打包NDArray 参数 dtype 必须与签名严格一致否则在启动时抛错。CUDA 版本相关行为exports需要 CUDA 8.0sm_XX目标直编 SASS 需要 CUDA 11.1低版本只能输出 PTX。同步语义每次launch都会cudaStreamSynchronize等待内核完成src/common/rtc.cc即当前实现是同步启动适合自定义 kernel 的调试与确定性执行。9. 小结mxnet.rtc为 MXNet 用户提供了一条零重编译的自定义 CUDA 内核路径Python 中书写源码 → NVRTC 运行时编译 → Driver API 加载 → 引擎调度启动输入输出全部复用mx.nd.NDArray并自动纳入 MXNet 的依赖调度。官方文档的核心内容——CudaModule的source/options/exports参数、extern C与 CUDA 8.0exports两种导出方式、模板内核实例化、get_kernel签名语法*表示数组、const表示只读、launch的 grid/block/shared_mem 约定、受支持的类型表——在本文中均有完整继承与展开编译产物选择PTX vs CUBIN、按设备缓存、引擎依赖登记、get_rtc_compile_opts的架构决策等实现细节则补充自 src/common/rtc.cc、include/mxnet/rtc.h、python/mxnet/util.py 与 tests/python/gpu/test_rtc.py 等仓库证据。相关 API 的正式索引可见 docs/python_docs/python/api/rtc/index.rst。赞分享深度学习人工智能机器学习分布式训练【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址https://gitcode.com/gh_mirrors/mx/mxnet点击查看免费下载相关推荐MXNet 运行时编译RTC指南在 MXNet 中编写并运行时编译 CUDA KernelMXNet 运行时编译RTC指南在 MXNet 中编写并运行时编译 CUDA Kernel MXNet 从 2.0 开始提供第三种编写与启动 CUDA K人工智能深度学习机器学习MXNet CUDA 运行时编译RTC实战用 mx.rtc 在 Python 中动态编译并启动 CUDA KernelMXNet CUDA 运行时编译RTC实战用 mx.rtc 在 Python 中动态编译并启动 CUDA Kernel mxnet.rtc 是 MXNet深度学习机器学习人工智能MXNet 2.0 运行时编译RTC在 MXNet 中动态编写与启动 CUDA Kernel 的完整指南MXNet 2.0 运行时编译RTC在 MXNet 中动态编写与启动 CUDA Kernel 的完整指南 本指南基于 docs/static_site/s深度学习人工智能机器学习分布式训练创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表