CppCon 2024 学习:Bridging the Gap: Writing Portable Programs for CPU and GPU
不要直接在函数里用或 host-only 类型。禁用警告可以用或编译器选项,但只是表面解决。根本解决方案是:用模板区分 Host / Device / Both根据类型实例化对应版本用 wrapper 函数统一调用这样既安全又可维护。// Host/Device 类型 trait// 判断类型是否 host-only// fill_ones 函数// CPU 版本// 如果需要 GPU 版本,可以
3. CUDA 快速入门(Motivation: CUDA Crash Course)
3.1 GPU 与 CPU 的函数标识符
| 位置 | 函数修饰符 | 说明 |
|---|---|---|
| GPU | __global__ |
全局核函数(kernel),可由 CPU 调用执行在 GPU 上) |
| GPU | __device__ |
设备函数,只能在 GPU 上被 GPU 调用 |
| CPU | __host__ |
主机函数(默认是 CPU) |
3.2 CUDA “Hello World” 示例(没有输出世界)
#include <cstdio>
// GPU 设备函数
__device__ int print() { return 0; }
// GPU 全局核函数
__global__ void kernel() { printf("%i", print()); }
// CPU 主机函数
__host__ void start() { kernel<<<2, 3>>>(); }
int main() { // 默认 __host__
start();
return cudaDeviceSynchronize();
}
理解:
__device__ int print()- 定义在 GPU 上的设备函数,返回 0。
__global__ void kernel()- GPU 核函数,可由 CPU 调用。
- 内部调用 GPU 设备函数
print()并通过printf打印返回值。
kernel<<<2, 3>>>();- CUDA 的执行配置:
- 2 个块 (blocks)
- 每个块 3 个线程 (threads per block)
- CUDA 中核函数执行是并行的,线程编号可用
threadIdx、blockIdx等标识。
- CUDA 的执行配置:
cudaDeviceSynchronize()- 等待 GPU 所有核函数完成。
- 返回 0 表示成功。
- 编译器:
nvcc+ 主机编译器(clang 或 gcc)可同时编译 CPU/GPU 代码。- 其他编译器如 HIP/nvc/gpucc 等也支持类似 CUDA 代码。
输出结果
stdout: 000000
return code: 0
解释:
- 由于 kernel 被 2 块 × 3 线程调用,每个线程都会打印
0,所以总共输出 6 个0。 return code: 0表示程序正常退出,无错误。
3.3 CUDA 核函数的数学模型
假设有 B B B 个块,每块 T T T 个线程,总线程数为 N = B × T N = B \times T N=B×T。
- 每个线程独立执行 kernel:
Thread i , j executes f ( x ) i ∈ [ 0 , B − 1 ] , j ∈ [ 0 , T − 1 ] \text{Thread}_{i,j} \text{ executes } f(x) \quad i \in [0, B-1],\ j \in [0, T-1] Threadi,j executes f(x)i∈[0,B−1], j∈[0,T−1] - 在本例中:
B = 2 , T = 3 ⟹ N = 6 B = 2, \quad T = 3 \implies N = 6 B=2,T=3⟹N=6
每个线程调用print()输出0,所以总输出为0× 6。
3.4 关键注意点
- CPU 代码通过
__host__调用 GPU 核函数__global__。 - GPU 内部函数只能在 GPU 上调用。
- 并行执行会产生多个输出,需要注意
printf的顺序可能不是固定的。 cudaDeviceSynchronize()是 CPU 等待 GPU 完成的重要函数。
WSL(Windows Subsystem for Linux)下配置 CUDA 环境 的方法,并解释关键原理和注意事项。
1. WSL 支持的 CUDA 环境
- 要求:
- Windows 10/11 的 WSL 2。
- 安装 NVIDIA GPU 驱动 支持 WSL(通常是 515 及以上)。
- WSL 内部 Linux 发行版(Ubuntu 推荐)通过 apt 安装 CUDA 工具包。
- CUDA 支持情况:
- CUDA 在 WSL 下可以访问 GPU(类似 Linux 原生环境)。
- 支持
nvcc编译器、cuda-runtime、cuda-driver等完整 CUDA 生态。 - 可直接运行 GPU 核函数。
2. 安装步骤(以 Ubuntu 为例)
2.1 安装 WSL 2
wsl --install -d ubuntu
wsl --set-version ubuntu 2
wsl --list --verbose可以查看 WSL 版本。- WSL 2 必须,因为 WSL 1 不支持 GPU。
2.2 安装 NVIDIA 驱动(Windows 端)
- 下载 支持 WSL 的 NVIDIA 驱动:
- 安装完成后,在 Windows CMD 中:
nvidia-smi
- 输出 GPU 信息表示驱动安装成功。
2.3 安装 CUDA 工具包(WSL 内 Ubuntu)
2.3.1 添加 NVIDIA 软件源
sudo apt update
sudo apt install -y wget gnupg
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update
2.3.2 安装 CUDA
sudo apt install -y cuda
会安装半天很多库下载
- 安装完成后,CUDA 默认在
/usr/local/cuda。 - 可用
nvcc --version检查版本。
xiaqiu@xz:~$ whereis cuda
cuda: /usr/local/cuda
xiaqiu@xz:~$
xiaqiu@xz:~$ ls /usr/local/cuda/bin/nvcc
/usr/local/cuda/bin/nvcc
xiaqiu@xz:~$ nvcc
Command 'nvcc' not found, but can be installed with:
sudo apt install nvidia-cuda-toolkit
添加到环境变量
xiaqiu@xz:~$ echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
xiaqiu@xz:~$ echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
xiaqiu@xz:~$ source ~/.bashrc
xiaqiu@xz:~$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2025 NVIDIA Corporation
Built on Wed_Aug_20_01:58:59_PM_PDT_2025
Cuda compilation tools, release 13.0, V13.0.88
Build cuda_13.0.r13.0/compiler.36424714_0
xiaqiu@xz:~$
xiaqiu@xz:~$ ls /usr/local/cuda-13.0/bin/
__nvcc_device_query cuda-gdb-minimal cuda-gdbserver nsight-sys nvdisasm
bin2c cuda-gdb-python3.10-tui cudafe++ nsight_ee_plugins_manage.sh nvlink
compute-sanitizer cuda-gdb-python3.11-tui cuobjdump nsys nvprune
crt cuda-gdb-python3.12-tui fatbinary nsys-ui ptxas
cu++filt cuda-gdb-python3.8-tui ncu nvcc
cuda-gdb cuda-gdb-python3.9-tui ncu-ui nvcc.profile
xiaqiu@xz:~$
在实际 CUDA 开发中,常用的工具其实集中在几个核心类别:编译、调试、性能分析和小工具。按用途划分如下:
1⃣ 编译相关(必用)
| 工具 | 用途 |
|---|---|
nvcc |
核心编译器,把 .cu 文件编译成 GPU 可执行文件或库。 |
ptxas |
PTX 汇编器,nvcc 内部使用,必要时可以单独查看 PTX 汇编生成 machine code。 |
nvlink |
链接器,用于 device code 链接生成最终可执行文件或库。 |
开发中一般只直接用
nvcc,其他工具 nvcc 会自动调用。
2⃣ 调试相关(重要)
| 工具 | 用途 |
|---|---|
cuda-gdb |
GPU kernel 调试器,单步调试 GPU 代码。 |
compute-sanitizer |
检测内存越界、未初始化访问、race condition 等。 |
__nvcc_device_query |
测试 GPU 是否可用,获取设备信息。 |
开发阶段必备:先用 device query 确认 GPU,逻辑复杂或报错用 compute-sanitizer,定位 bug 用 cuda-gdb。
3⃣ 性能分析(常用)
| 工具 | 用途 |
|---|---|
ncu |
Nsight Compute 命令行版本,采集 GPU kernel 性能数据。 |
ncu-ui |
Nsight Compute GUI,可视化分析 GPU kernel 性能。 |
nsys / nsys-ui |
Nsight Systems(命令行/GUI),系统级性能分析,包括 CPU/GPU/IO。 |
性能优化阶段使用,普通开发可先用
ncu或ncu-ui。
4⃣ 二进制查看/小工具(偶尔用)
| 工具 | 用途 |
|---|---|
nvdisasm |
查看 SASS / PTX 汇编,调试 GPU 汇编级问题。 |
cuobjdump |
查看 GPU 对象文件,定位 PTX/SASS 信息。 |
cu++filt |
还原 C++ mangled 名称,调试输出可读性。 |
一般普通开发很少用,除非调汇编或内核低层问题。
总结:常用工具 TOP 列表
- 编译:
nvcc - 调试:
cuda-gdb,compute-sanitizer,__nvcc_device_query - 性能分析:
ncu,ncu-ui - 偶尔:
nvdisasm,cuobjdump
简单记忆:编译 → 调试 → 性能 → 汇编。
2.4 测试 CUDA
在 WSL 终端中创建 test.cu:
#include <cstdio>
__global__ void kernel() {
printf("Hello CUDA from WSL!\n");
}
int main() {
kernel<<<1,1>>>();
cudaDeviceSynchronize();
return 0;
}
编译并运行:
nvcc test.cu -o test
./test
# CMakeLists.txt — 现代方式(推荐)
cmake_minimum_required(VERSION 3.18) # 3.18+ 更好,3.10 也可
# CUDA 架构:可设置为 AUTO 或指定 compute capability 列表,例如 70;75;80
if(NOT DEFINED CUDA_ARCHS)
set(CUDA_ARCHS native)
endif()
set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCHS})
project(MyCudaProject LANGUAGES CXX CUDA)
# C/C++ 标准
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_STANDARD 14)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
# 编译选项(示例)
add_compile_options("$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-fPIC>")
# 查找源码(修改成你自己的源文件)
set(SRC
main.cu
)
# 可选:把 CUDA separable compilation 打开(如果有 device 函数跨文件调用)
set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)
# 可选:如果要生成位置无关代码(对于 shared lib 有用)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# 创建可执行文件 / 库
add_executable(my_app ${SRC})
# 如果你的 CUDA 运行时需要链接(通常 CMake 会自动处理)
find_package(CUDAToolkit REQUIRED) # modern CMake module
target_link_libraries(my_app PRIVATE CUDA::cudart)
# 包含目录
target_include_directories(my_app PRIVATE ${CMAKE_SOURCE_DIR}/include)
# 目标属性(示例)
target_compile_features(my_app PRIVATE cxx_std_20)
# 如果你想让 nvcc 使用特定编译选项:
target_compile_options(my_app PRIVATE
"$<$<COMPILE_LANGUAGE:CUDA>:--expt-relaxed-constexpr>"
)
# 安装规则(可选)
install(TARGETS my_app RUNTIME DESTINATION bin)
- 如果输出
Hello CUDA from WSL!,说明环境配置成功。
3. WSL CUDA 的注意事项
- 驱动版本匹配:
- Windows 端 GPU 驱动必须支持 WSL。
- WSL 内的 CUDA 版本需要和驱动兼容。
- 文件系统性能:
- WSL 读取 Windows 文件 (
/mnt/c/...) 比较慢,建议代码在 WSL 内/home/...下。
- WSL 读取 Windows 文件 (
- GUI/OpenGL:
- CUDA 本身可以用,但图形渲染或 GUI 可能需要 WSLg 或额外配置。
- 多 GPU 支持:
- 可用
nvidia-smi或cudaGetDeviceCount()检查 GPU 数量。
- 可用
4. 数学/原理理解
WSL 下 CUDA 原理与 Linux 类似:
- CPU(主机)在 WSL 内发起核函数调用:
kernel < < < B , T > > > ( ) \text{kernel}<<<B, T>>>() kernel<<<B,T>>>() - 通过 Windows 驱动与 GPU 通信,GPU 执行线程:
N = B × T 线程并行计算 N = B \times T \quad \text{线程并行计算} N=B×T线程并行计算 - 数据在 WSL 内存空间 和 GPU 显存之间传输:
Host memory ↔ cudaMemcpy Device memory \text{Host memory} \xleftrightarrow{\text{cudaMemcpy}} \text{Device memory} Host memorycudaMemcpy Device memory
发现代码没有打印输出"Hello CUDA from WSL!"
#include <cstdio>
#include <cuda_runtime.h>
__global__ void kernel() { //
std::printf("Hello CUDA from WSL!\n");
}
int main() {
kernel<<<1, 1>>>();
cudaError_t err = cudaDeviceSynchronize();
if (err != cudaSuccess) {
printf("CUDA Error: %s\n", cudaGetErrorString(err));
}
return 0;
}
CUDA driver version 和 CUDA runtime version 不匹配
CUDA Error: CUDA driver version is insufficient for CUDA runtime version
xiaqiu@xz:~/test$ nvidia-smi
Wed Dec 3 14:32:30 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.105.08 Driver Version: 566.07 CUDA Version: 12.7 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce RTX 4060 ... On | 00000000:01:00.0 Off | N/A |
| N/A 41C P8 3W / 95W | 725MiB / 8188MiB | 2% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+
xiaqiu@xz:~/test$
xiaqiu@xz:~/test$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2025 NVIDIA Corporation
Built on Wed_Aug_20_01:58:59_PM_PDT_2025
Cuda compilation tools, release 13.0, V13.0.88
Build cuda_13.0.r13.0/compiler.36424714_0
xiaqiu@xz:~/test$
CUDA Version: 12.7
nvcc --version release 13.0, V13.0.88
如何更新 Windows 驱动(步骤详解)
问题找到了!CUDA 运行时版本(13.0)比驱动支持的版本(12.7)要新。
驱动支持:CUDA 12.7
运行时版本:CUDA 13.0
这就是不匹配的原因。
解决方案 :更新 Windows 驱动(如果可行)
在 Windows 中更新到支持 CUDA 13.0 的最新驱动:
最好方法windows的nvidia app更新最新的显卡驱动
- 重启 WSL:
wsl --shutdown
然后重新打开 WSL
临时解决方案:使用已有的旧版本 CUDA(如果存在)
检查是否有其他 CUDA 版本:
ls /usr/local/ | grep cuda
如果有 cuda-12.x,可以临时切换:
export PATH=/usr/local/cuda-12.6/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-12.6/lib64:$LD_LIBRARY_PATH
nvcc --version # 验证版本
我的方法是用nvidia app 更新最新的显卡驱动
C:\Users\16956>nvidia-smi
Wed Dec 3 15:27:29 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 581.57 Driver Version: 581.57 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Driver-Model | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce RTX 4060 ... WDDM | 00000000:01:00.0 Off | N/A |
| N/A 45C P0 18W / 95W | 0MiB / 8188MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+
C:\Users\16956>
再次运行程序输出
Hello CUDA from WSL!
windows 下的Nsight Compute可以下载安装
1⃣ 下载 Nsight Compute
- 打开 NVIDIA 官方下载页:NVIDIA Nsight Compute
- 选择 Nsight Compute 版本
- Windows 版:下载
.exe安装包 - Linux 版(如果在 WSL 或 Linux 使用):下载
.run或.deb文件
- Windows 版:下载
注意:Nsight Compute 版本要与你的 CUDA Toolkit 驱动兼容,最好使用最新版本或与 CUDA 12.x/13.x 对应的版本。
2⃣ Windows 安装步骤
- 双击下载的
NsightCompute-win-x.y.z.exe安装包 - 选择安装目录(默认即可)
- 安装完成后,会生成以下主要内容:
- GUI 应用:用于性能分析和查看报告
- 命令行工具
ncu:可在命令行使用 - 集成 Visual Studio 插件(可选)
- 配置环境变量(可选):
- 将 Nsight Compute 的
bin目录加入 PATH,例如:C:\Program Files\NVIDIA Corporation\Nsight Compute 2025.1\bin - 这样就可以在 CMD / PowerShell 里直接运行
ncu.exe
- 将 Nsight Compute 的
**3⃣ Linux / WSL **
xiaqiu@xz:~$ whereis ncu-ui
ncu-ui: /usr/local/cuda-13.0/bin/ncu-ui
xiaqiu@xz:~$
xiaqiu@xz:~$ ncu-ui
xiaqiu@xz:~$
在 WSLg(Windows Subsystem for Linux GUI) 下,ncu-ui 的界面缩小或者字体很小,是常见问题,因为 WSLg 的 DPI 缩放和 Qt 应用的默认缩放有关。你可以通过以下方法放大界面或字体:
1⃣ 设置环境变量 QT_SCALE_FACTOR
ncu-ui 是 Qt 应用,可以通过 Qt 的缩放变量放大:
export QT_SCALE_FACTOR=1.5 # 1.5 倍缩放,可以改成 2
ncu-ui
1.0→ 默认大小1.5→ 放大 1.5 倍2.0→ 放大 2 倍
可以在
~/.bashrc或~/.zshrc添加这一行,让每次打开自动生效。
2⃣ 使用 QT_AUTO_SCREEN_SCALE_FACTOR
Qt 可以自动根据 DPI 缩放:
export QT_AUTO_SCREEN_SCALE_FACTOR=1
ncu-ui
- 会根据 WSLg 提供的屏幕 DPI 自动放大
- 有时比
QT_SCALE_FACTOR更智能
3⃣ 改变 Windows 缩放设置
如果你在 4K 显示器下,Windows 的显示缩放会影响 WSLg 窗口:
- 右键桌面 → 显示设置 → 缩放与布局
- 将缩放比例调整到 125%~200%,然后重新打开
ncu-ui
4⃣ 临时命令一行启动
结合以上方法,可以直接在终端运行:
QT_SCALE_FACTOR=1.8 ncu-ui
建议:
- 如果你经常用
ncu-ui,最好把QT_SCALE_FACTOR=1.5加到~/.bashrc,这样每次打开都会按你设定的缩放显示。 - 如果界面仍然很小,配合 WSLg DPI 设置 + QT_SCALE_FACTOR 一般就能正常。
1⃣ 代码分析
#include <cstdio> // 1
__device__ int print() { return 0; } // 3
__global__ void kernel() { // 4
std::printf("%i", print());
}
__host__ void start() { // 5
kernel<<<2, 3>>>(); // 启动 kernel,grid=2, block=3
}
int main() { // 7
start(); // 8
return cudaDeviceSynchronize(); // 9-10
}
关键点解释
__device__函数
__device__ int print() { return 0; }
- 这是 设备端函数,只能在 GPU kernel 或其他 device 函数里调用。
- 返回值是 0。
- 数学理解:
- 每个线程执行
print()得到的值是:
f device ( t h r e a d I d x , b l o c k I d x ) = 0 f_\text{device}(threadIdx, blockIdx) = 0 fdevice(threadIdx,blockIdx)=0
- 每个线程执行
__global__kernel
__global__ void kernel() { printf("%i", print()); }
__global__表示这是 GPU kernel,可以被 host 调用。- 内部调用
print()并打印结果。 - 注意:
printf在 GPU 内是异步输出的,实际输出需要cudaDeviceSynchronize()才会刷新。
- 启动 kernel
kernel<<<2, 3>>>();
<<<gridDim, blockDim>>>是 CUDA 启动 kernel 的语法。- gridDim=2 → 两个 block
- blockDim=3 → 每个 block 三个线程
- 总线程数:
N threads = gridDim × blockDim = 2 × 3 = 6 N_\text{threads} = \text{gridDim} \times \text{blockDim} = 2 \times 3 = 6 Nthreads=gridDim×blockDim=2×3=6 - 每个线程都会调用一次
print()并打印 0,因此输出:
000000
__host__函数 start
__host__ void start() { kernel<<<2,3>>>(); }
- host 端函数,用来启动 kernel。
- CUDA kernel 是 异步执行,调用 kernel 后会立即返回,kernel 在 GPU 上运行。
- 同步
return cudaDeviceSynchronize();
cudaDeviceSynchronize()等待 GPU 上所有 kernel 执行完成,并收集错误码。- 这里保证所有线程的
printf输出在 host 端可见。
2⃣ 程序执行流程(理解)
- 主函数
main()调用start() start()启动 kernel:- grid=2,block=3 → 总 6 个线程
- 每个线程调用
print()→ 输出 0
- GPU kernel 异步执行
- 主函数调用
cudaDeviceSynchronize()→ 等待 kernel 执行完成 - 输出:
000000
- 每个 0 对应一个线程输出
- 返回值 0 表示程序成功结束
3⃣ 数学/线程视角理解
- 总线程数 N threads N_\text{threads} Nthreads:
N threads = gridDim × blockDim = 2 × 3 = 6 N_\text{threads} = \text{gridDim} \times \text{blockDim} = 2 \times 3 = 6 Nthreads=gridDim×blockDim=2×3=6 - 每个线程的执行过程:
thread t : x t = f device ( t ) = 0 \text{thread } t : x_t = f_\text{device}(t) = 0 thread t:xt=fdevice(t)=0 - 最终输出可以看作:
Output = ∏ t = 1 6 x t \text{Output} = \prod_{t=1}^{6} x_t Output=t=1∏6xt
在这里,x_t = 0,打印显示为000000。
4⃣ 关于输出和调试
printf在 kernel 内异步输出- 如果没有
cudaDeviceSynchronize(),可能看不到输出 - 调试 kernel 时:
- 不能直接在
__device__函数打断点 - 可以在
kernel内设置断点
- 不能直接在
- 输出顺序可能不是严格按照线程索引,因为 GPU 线程是并行执行的
5⃣ 总结
- CUDA kernel 是 GPU 并行执行单元,每个线程独立运行
__device__函数只能在 GPU 上调用kernel<<<grid, block>>>启动 kernel,grid 和 block 决定总线程数cudaDeviceSynchronize()保证 kernel 执行完成和printf输出可见- 本例输出:
000000
1⃣ 代码回顾
// 定义一个只在 Host(CPU)上可调用的结构体 H
struct H {
__host__ int func() {
return 42; // Host 函数 func,返回整数 42
}
}; // 1
// 定义一个只在 Device(GPU)上可调用的结构体 D
struct D {
__device__ int func() {
return 666; // Device 函数 func,返回整数 666
}
}; // 3
// 定义一个模板 wrap,可以同时在 Host 和 Device 上调用
template <typename T>
__host__ __device__ int wrap() {
// 调用类型 T 的 func 方法
// 注意:
// 1. 如果 T=H → host 版本 ok,device 版本非法(警告)
// 2. 如果 T=D → host 版本非法(编译错误或 UB),device 版本 ok
return T{}.func();
} // 5-6
int main() {
// Host 调用 H 的 func → 合法
// return H{}.func(); // OK
// Host 调用 D 的 func → 非法
// return D{}.func(); // 编译错误:host 不能调用 device 函数
// Host 调用 wrap<H>() → 会生成 device 版本 wrap<H>(),对 device 来说 H.func() 非法 → 编译警告
// return wrap<H>(); // 警告
// Host 调用 wrap<D>() → host 版本 wrap<D>() 内调用 D.func() 非法 → UB / 运行时未定义行为
// return wrap<D>(); // UB / 运行时未定义行为
}
2⃣ CUDA 的 host/device 函数规则
__host__函数- 只能在 CPU 端 调用
- 对应主机执行环境
__device__函数- 只能在 GPU 端 调用
- 对应 device 执行环境
- 不能直接在 CPU 端调用
__host__ __device__函数- 可以同时在 CPU 和 GPU 调用
- 编译器会生成两份函数版本:
- host 版本(CPU 调用)
- device 版本(GPU 调用)
- 注意:模板实例化时,调用的成员函数必须符合目标环境的规则
3⃣ wrap 模板函数解析
template<typename T> __host__ __device__
int wrap() { return T{}.func(); }
- 当
wrap<H>()在 host 端 被调用:- 生成 host 版本:
int wrap<H>() { return H{}.func(); } // OK - 生成 device 版本:
__device__ int wrap<H>() { return H{}.func(); } // 错误,device 不能调用 host - 所以会报 警告 #20014-D,因为 device 版本不能调用 host 函数
- 生成 host 版本:
- 当
wrap<D>()在 host 端 被调用:- host 版本:
int wrap<D>() { return D{}.func(); } // 错误,host 不能调用 device - device 版本:
__device__ int wrap<D>() { return D{}.func(); } // OK - 所以如果只调用 host 版本,会编译报错或 UB
- host 版本:
4⃣ main 函数中不同调用行为
| 调用 | 说明 | 结果 |
|---|---|---|
H{}.func() |
host 调用 host 函数 | OK |
D{}.func() |
host 调用 device 函数 | 编译错误 |
wrap<H>() |
host 调用 wrap() | 警告,device 版本非法 |
wrap<D>() |
host 调用 wrap() | UB/运行时未定义行为 |
5⃣ 数学公式角度理解
- 函数调用限制可以看作集合关系:
- 定义集合:
- H host H_\text{host} Hhost = { host 函数 }
- D device D_\text{device} Ddevice = { device 函数 }
- 允许调用规则:
host 环境只能调用 H host device 环境只能调用 D device \text{host 环境只能调用 } H_\text{host} \ \text{device 环境只能调用 } D_\text{device} host 环境只能调用 Hhost device 环境只能调用 Ddevice
- 模板 wrap 生成两份版本:
- Host 版本:
w r a p H host = H host (合法) w r a p D host = D device (非法) wrap_H^\text{host} = H_\text{host} \text{ (合法) } \ wrap_D^\text{host} = D_\text{device} \text{ (非法) } wrapHhost=Hhost (合法) wrapDhost=Ddevice (非法) - Device 版本:
w r a p H device = H host (非法) w r a p D device = D device (合法) wrap_H^\text{device} = H_\text{host} \text{ (非法) } \ wrap_D^\text{device} = D_\text{device} \text{ (合法) } wrapHdevice=Hhost (非法) wrapDdevice=Ddevice (合法)
这就是为什么
wrap<H>()会产生警告,wrap<D>()会在 host 调用时报错或 UB。
6⃣ 总结
- Host/Device 函数不能跨环境调用
- host 不能直接调用 device
- device 不能直接调用 host
__host__ __device__模板- 编译器会生成两份函数版本
- 调用模板时,必须确保调用环境和目标函数匹配
- 常见错误模式:
D{}.func() // host 端调用 device → 编译错误
wrap<D>() // host 端实例化 device 函数 → UB
- 编译器提示:
- 警告 #20014-D:调用 host 函数在 device 版本
- 错误 #20014-D:调用 device 函数在 host 版本
- 编译器和平台差异:
- nvcc 12.6 遵守严格规则
- Clang / HIP 编译器对 host/device 检测略有不同,有些可能允许,但运行时行为未定义
1⃣ “Oldschool” 方法——旧版本 CUDA
在早期 CUDA 中,host/device 函数的使用有很多限制:
- 只能用很老的 CUDA 版本才能实现跨 host/device 的模板调用。
- host/device 关键字在普通 C++ 编译器(如 gcc)下会报错,因为 gcc 不认识
__host__和__device__。
2⃣ host / device / all the things 模式
这个模式的核心是:
- host → CPU 可调用函数
- device → GPU 可调用函数
- all the things → 同时在 CPU 和 GPU 可调用函数(
__host__ __device__)
示例:
__host__ __device__ inline constexpr
float MHzToWavelength(int frequency_in_MHz) {
return 299792458.f / (frequency_in_MHz * 1000.f * 1000.f);
}
- 将函数标记为
__host__ __device__:- CPU 上可直接调用
- GPU 上可在 kernel 内调用
constexpr可以在编译期计算,适合常量计算
数学上:
λ = c f = 299792458 f Hz 其中 f Hz = f MHz ⋅ 1 0 6 \lambda = \frac{c}{f} = \frac{299792458}{f_\text{Hz}} \quad \text{其中 } f_\text{Hz} = f_\text{MHz} \cdot 10^6 λ=fc=fHz299792458其中 fHz=fMHz⋅106
3⃣ 跨平台编译问题
如果直接用 gcc 编译上面的函数:
gcc mhz.cpp
- 会报错:
error: ‘__host__’ does not name a type
- 原因:gcc 不认识
__host__和__device__,这些是 CUDA nvcc 扩展关键字。
解决方法:条件宏定义
#ifndef CUDATAGS
#define CUDATAGS
#ifndef __CUDACC__ // 如果不是 nvcc 编译
#define __host__
#define __device__
#endif
#endif
- 这样在非 CUDA 编译器下,
__host__和__device__被定义为空,保证普通 C++ 编译器能编译。 - 使用示例:
__host__ __device__
void func() {}
- 在 GPU 上 nvcc 编译生成 device/host 两份版本
- 在 CPU 上普通编译器可直接调用
4⃣ Host / Device / Everywhere 示例
struct H {
__host__ int func() { return 42; }
};
struct D {
__device__ int func() { return 666; }
};
template<typename T>
int wrap() { return T{}.func(); }
int main() {
H{}.func(); // OK
D{}.func(); // GPU 端 OK, CPU 端不可
wrap<H>(); // OK
wrap<D>(); // Device 内 OK, Host 内 UB
}
核心概念:
- Host 调用 device 函数 → 编译错误
- Device 调用 host 函数 → 编译错误
wrap<T>模板必须确保类型 T 对应的函数在目标环境合法
5⃣ 条件函数体 / 条件编译
- 可以使用宏定义简化:
#ifndef __CUDACC__
#define HST
#define DEV
#else
#define HST __host__
#define DEV __device__
#endif
HST DEV void func() {}
HST DEV在 CPU 上为空,在 CUDA 上是__host__ __device__- 避免在非 CUDA 编译器报错
6⃣ 数学 / 物理公式示例
以 MHz 转波长函数为例:
- 波长公式:
λ = c f \lambda = \frac{c}{f} λ=fc - 将 MHz 转为 Hz:
f Hz = f MHz ⋅ 1 0 6 f_\text{Hz} = f_\text{MHz} \cdot 10^6 fHz=fMHz⋅106 - 综合公式:
λ = 299792458 f MHz ⋅ 1 0 6 单位:米 (m) \lambda = \frac{299792458}{f_\text{MHz} \cdot 10^6} \quad \text{单位:米 (m)} λ=fMHz⋅106299792458单位:米 (m)
- 使用
constexpr可以在编译期计算波长,提高性能
7⃣ 总结
- Host / Device / Everywhere 模式
__host__ __device__→ 生成 CPU/GPU 两份版本- 适合模板函数、物理计算函数
- 条件编译
- 使用宏判断是否 nvcc
- 在非 CUDA 编译器下定义为空,保证可移植性
- 注意事项
- Host 不能调用 Device 函数
- Device 不能调用 Host 函数
- 模板 wrap() 要确保每个实例化版本合法,否则可能 UB
- 数学公式
- 将物理公式(如波长)直接写成
constexpr函数,可同时在 CPU/GPU 上计算 - λ = c f Hz = 299792458 f MHz ⋅ 1 0 6 \lambda = \frac{c}{f_\text{Hz}} = \frac{299792458}{f_\text{MHz} \cdot 10^6} λ=fHzc=fMHz⋅106299792458
- 将物理公式(如波长)直接写成
1⃣ 条件函数体的概念
在 CUDA 中,我们经常希望 同一函数在 Host(CPU)和 Device(GPU)端使用不同实现。
例如:计算向量的 2-范数:
struct Vec3f { float x, y, z; float operator[](int i) const; };
- Host 实现(在 CPU 上):手动累加平方和再开方
float norm(const Vec3f &v) {
double sum = 0;
for (int i = 0; i < 3; ++i)
sum += v[i]*v[i];
return sqrtf((float)sum);
}
- Device 实现(在 GPU 上):调用 CUDA 内置函数
norm3df
float norm(const Vec3f &v) {
return norm3df(v[0], v[1], v[2]);
}
2⃣ 条件编译实现
为了写同一个函数名 norm(),可以使用宏判断 __CUDA_ARCH__:
__host__ __device__
float norm(const Vec3f &v) {
#ifndef __CUDA_ARCH__ // CPU 端
double sum = 0;
for (int i = 0; i < 3; ++i)
sum += v[i]*v[i];
return sqrtf((float)sum);
#else // GPU 端
return norm3df(v[0], v[1], v[2]);
#endif
}
- 宏
__CUDA_ARCH__在 device 编译时定义 - 宏判断保证 Host / Device 使用不同函数体
- 公式解释:
∣ ∣ v ∣ ∣ 2 = v 0 2 + v 1 2 + v 2 2 ||\mathbf{v}||_2 = \sqrt{v_0^2 + v_1^2 + v_2^2} ∣∣v∣∣2=v02+v12+v22
在 CPU 上手算,GPU 上调用norm3df函数(硬件加速)
3⃣ 条件函数体的问题
- Host / Device 不同版本函数签名限制:
- 函数签名、模板实例化参数 不能依赖
__CUDA_ARCH__ - 如果用
if constexpr(__CUDA_ARCH__)或宏修改签名,nvcc 会报错或 UB - 仅能修改函数体内容,不要修改返回类型或参数类型
- 函数签名、模板实例化参数 不能依赖
- 示例错误用法
struct H { __host__ int func() { return 42; } };
template<typename T>
__host__ __device__
void wrap() {
#ifndef __CUDA_ARCH__
T{}.func(); // 在 CPU 编译时 OK
#else
T{}.func(); // Device 编译时可能 UB
#endif
}
- 在 wrap 模板实例化时,如果 T 类型在 Device 上不可调用 host 函数 → UB
- 所以不推荐在模板中使用宏条件改变函数体调用
4⃣ 条件返回类型的安全做法
有时候希望 Host / Device 函数返回不同类型:
- 错误示例
__host__ H func() { return H{}; }
__device__ D func() { return D{}; }
- 在 nvcc 编译时,如果同时在模板或宏中调用可能产生 UB
- 推荐做法
#ifndef __CUDA_ARCH__
__host__ H func() { return H{}; }
#else
__device__ D func() { return D{}; }
#endif
- Host 端只看到 H 类型函数
- Device 端只看到 D 类型函数
- 保证类型安全和调用合法性
5⃣ 总结
- Host / Device 条件函数体:
- 使用
#ifndef __CUDA_ARCH__/#else判断函数体内容 - 不要改变函数签名或模板参数
- CPU 上手动实现,GPU 上调用内置函数(如
norm3df)
- 使用
- 模板函数安全使用:
- 模板 wrap() 调用必须确保 T 的 func() 在目标环境合法
- 宏条件只能影响函数体,不影响模板类型或返回类型
- 条件返回类型:
- 不要在同一函数名下返回不同类型
- 用宏区分 host/device 函数定义即可
1⃣ 背景
在 C++ 中,constexpr 函数表示 可以在编译期求值,通常要求函数体内只调用 constexpr 函数或可编译期求值的操作。
在 CUDA 中,如果你想在 Device(GPU)端也使用 constexpr,就会遇到 nvcc 的限制:
constexpr int func() {
std::array<int,5> a;
// ...
return std::accumulate(a.begin(), a.end(), 0);
}
- 编译时 nvcc 报错:
error: calling a host function("std::accumulate") from a device function("func") is not allowed
- 原因:
std::accumulate是 host 函数,Device 端不允许直接调用
2⃣ 解决方案:–expt-relaxed-constexpr
CUDA 提供了一个实验性编译选项:
--expt-relaxed-constexpr
作用:
- 允许更多 host-side constexpr 函数在 device 上求值
- 让一些原本不允许的
constexpr调用在 nvcc 编译时通过 - 不需要修改源代码,但属于实验性功能,可能有潜在风险
例如:
nvcc --expt-relaxed-constexpr test.cu
这会允许像 std::array、std::accumulate 等在 device 上的 constexpr 调用。
3⃣ 已知使用案例
- RAPIDS1(NVIDIA 数据科学和 AI 库)
- 讨论是否使用
--expt-relaxed-constexpr,最终决定不使用
- 讨论是否使用
- MatX2(NVIDIA 矩阵库)
- 使用了
--expt-relaxed-constexpr
- 使用了
- Dimetor
- 仍在考虑使用
4⃣ 失败示例
- 在 nvcc 12.2 之前,如果在
constexpr函数里调用非constexpr函数或抛异常:
constexpr int foo(int j) {
if (j < 0) throw; // 或 return bar(j);
}
- nvcc 会直接忽略原函数内容:
__device__ constexpr int foo(int j) { return 42; }
- 也就是说,函数行为被重写,可能产生 未定义行为(UB)
- 使用 malloc/free 也会失败:
constexpr int set() {
auto i = (int*) malloc(sizeof(int));
*i = 42;
int y = *i;
free(i);
return y;
}
- 是否能工作取决于 编译器/操作系统/驱动/GPU
- 并不是编译期求值真正意义上的 constexpr
5⃣ 后果与注意事项
优点:
- 易用:几乎不用改动代码
- 支持第三方 constexpr 函数:
std::array、std::optional、std::upper_bound等
缺点:
- 只适用于
constexpr函数 - 如果在库中使用可能导致不兼容或隐藏 bug
- 属于实验性功能(<= 2016),未来 C++ 版本可能不支持
- 可能引发 subtle bugs(微妙的运行时错误)
6⃣ 总结
- 问题:Device 端无法调用 host-side constexpr 函数
- 实验性解决方案:使用 nvcc
--expt-relaxed-constexpr - 使用场景:
- 数值库、矩阵库、科学计算库中常见
- 需要 minimal 代码修改,但要注意兼容性
- 风险:
- 实验性特性 → 未来可能不稳定
- 可能隐藏运行时问题
1⃣ 问题背景
你最开始写了一个模板函数:
template< typename Container >
__host__ __device__ constexpr
void fill_ones(Container & ct) {
for (auto & x : ct) x = 1;
}
然后在 main 里用:
std::vector<int> v{1,2,3,4,5};
fill_ones(v);
结果:
warning #20014-D: calling a __host__ function from a __host__ __device__ function is not allowed
原因:
__host__ __device__表示这个函数既可以在 CPU 上调用,也可以在 GPU 上调用。std::vector的操作(如begin()、operator[]等)是 host-only 的。- 因此,
nvcc会在__device__上生成代码时报错(或至少警告)。
换句话说,你不能在 device 函数里调用 host-only 函数。
2⃣ 禁用警告的几种方法
(a) #pragma 方法
#pragma nv_diag_suppress 20014
或者针对多个警告:
#pragma nv_diag_suppress 20011,20014
- 作用:告诉 nvcc 编译器不要显示某些警告。
- 位置:函数或文件开头。
- 缺点:
- 这是 undocumented 的 pragma,未来可能会失效。
- 不能解决根本问题,只是隐藏警告。
(b) nv_exec_check_disable(Thrust、Eigen 等常用)
#pragma nv_exec_check_disable
- 用于 Thrust 或 Eigen 的 host/device 函数模板中,忽略某些 exec-check。
- 也有类似
--expt-relaxed-constexpr选项用于 Eigen,使constexpr与 device 函数兼容。
© 编译器全局选项
--diag-suppress 20011,20014
- 在 nvcc 命令行加上,可以禁掉指定警告。
3⃣ 根本解决方案:模板分离 Host/Device
警告的根源是 函数被错误实例化,调用了 host-only 的代码。
正确做法是生成不同版本的函数。
(a) 定义 Host/Device 兼容性类型
enum class HDC { Hst, Dev, HstDev }; // Host / Device / Both
(b) 为不同目标写不同版本函数
template<typename T, HDC x = hdc<T>>
requires(x == HDC::Hst)
__host__ void func(T t) { /* Host-only body */ }
template<typename T, HDC x = hdc<T>>
requires(x == HDC::Dev)
__device__ void func(T t) { /* Device-only body */ }
template<typename T, HDC x = hdc<T>>
requires(x == HDC::HstDev)
__host__ __device__ void func(T t) { /* Host+Device */ }
- hdc 是一个 trait,用来判断类型 T 的目标:
- 例如
std::vector<int>只能用于Hst float[N]或自定义数组可用于Dev或HstDev
- 例如
© 包装调用函数
template<typename T>
__host__ __device__ void wrap(T t) {
func(t); // 根据 T 的类型实例化正确版本
}
这样:
- CPU 调用
wrap(v)→ 调用 host 版本。 - GPU 调用
wrap(arr)→ 调用 device 版本。 - 避免了
host-only调用被实例化到 device 的情况。
4⃣ 数学/类型理解(用公式表示)
如果把类型目标映射抽象为数学公式,可以写为:
func target : T ↦ { host-only , T ∈ H device-only , T ∈ D host+device , T ∈ H ∩ D \text{func}_{\text{target}} : T \mapsto \begin{cases} \text{host-only}, & T \in H \\ \text{device-only}, & T \in D \\ \text{host+device}, & T \in H \cap D \end{cases} functarget:T↦⎩
⎨
⎧host-only,device-only,host+device,T∈HT∈DT∈H∩D
- H H H 表示 host-only 类型集合
- D D D 表示 device-compatible 类型集合
- H ∩ D H \cap D H∩D 表示两边都可用的类型
wrap 函数相当于:
wrap ( T ) = func hdc ( T ) ( T ) \text{wrap}(T) = \text{func}_{\text{hdc}(T)}(T) wrap(T)=funchdc(T)(T)
5⃣ 使用示例
#include <vector>
std::vector<int> v{1,2,3,4,5};
// Host 调用
wrap(v);
// Device 调用
float arr[5] = {0,0,0,0,0};
wrap(arr); // 生成 __device__ 版本
- 注意:在 kernel 中不能传
std::vector:
__global__ void kernel() {
// fill_ones(H{}); // 错误:H 是 host-only
fill_ones(D{}); // 正确:D 是 device-compatible
}
总结
- 不要直接在
__host__ __device__函数里用std::vector或 host-only 类型。 - 禁用警告可以用
#pragma nv_diag_suppress或编译器选项,但只是表面解决。 - 根本解决方案是:
- 用模板区分 Host / Device / Both
- 根据类型实例化对应版本
- 用 wrapper 函数统一调用
这样既 安全 又 可维护。
#include <iostream>
#include <vector>
#include <type_traits>
// ==========================
// Host/Device 类型 trait
// ==========================
enum class HDC { Hst, Dev, HstDev };
// 判断类型是否 host-only
template <typename T>
struct hdc {
static constexpr HDC value = std::is_array_v<T> ? HDC::HstDev : HDC::Hst;
};
// ==========================
// fill_ones 函数
// ==========================
template <HDC tag, typename Container>
__host__ __device__ void fill_ones_impl(Container& ct) {
if constexpr (tag == HDC::Hst || tag == HDC::HstDev) {
// CPU 版本
for (auto& x : ct) x = 1;
}
// 如果需要 GPU 版本,可以在这里添加
// if constexpr (tag == HDC::Dev || tag == HDC::HstDev) {
// // GPU 版本实现
// }
}
template <typename T, HDC tag = hdc<T>::value>
__host__ __device__ void fill_ones(T& ct) {
fill_ones_impl<tag>(ct);
}
// ==========================
// CUDA kernel 示例
// ==========================
__global__ void kernel_fill(float* arr, int n) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < n) arr[idx] = 1.0f;
}
// ==========================
// 主程序示例
// ==========================
int main() {
// ===== CPU host-only 容器 =====
std::vector<int> v{1, 2, 3, 4, 5};
std::cout << "Before fill_ones (vector): ";
for (auto x : v) std::cout << x << " ";
std::cout << "\n";
fill_ones(v); // 安全调用 host-only vector
std::cout << "After fill_ones (vector): ";
for (auto x : v) std::cout << x << " ";
std::cout << "\n";
// ===== CPU/GPU 兼容容器 =====
float arr[5] = {0, 0, 0, 0, 0};
fill_ones(arr); // 生成 host+device 版本
std::cout << "After fill_ones (array): ";
for (auto x : arr) std::cout << x << " ";
std::cout << "\n";
// ===== GPU kernel 调用 =====
float* d_arr;
cudaMalloc(&d_arr, 5 * sizeof(float));
kernel_fill<<<1, 5>>>(d_arr, 5);
cudaMemcpy(arr, d_arr, 5 * sizeof(float), cudaMemcpyDeviceToHost);
std::cout << "After kernel_fill: ";
for (auto x : arr) std::cout << x << " ";
std::cout << "\n";
cudaFree(d_arr);
return 0;
}
# CMakeLists.txt — 现代方式(推荐)
cmake_minimum_required(VERSION 3.18) # 3.18+ 更好,3.10 也可
# CUDA 架构:可设置为 AUTO 或指定 compute capability 列表,例如 70;75;80
if(NOT DEFINED CUDA_ARCHS)
set(CUDA_ARCHS native)
endif()
set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCHS})
project(MyCudaProject LANGUAGES CXX CUDA)
# C/C++ 标准
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_STANDARD 20)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
# 编译选项(示例)
add_compile_options("$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-fPIC>")
add_compile_options("$<$<COMPILE_LANGUAGE:CUDA>:--expt-relaxed-constexpr>")
# 查找源码(修改成你自己的源文件)
set(SRC
main.cu
)
# 可选:把 CUDA separable compilation 打开(如果有 device 函数跨文件调用)
set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)
# 可选:如果要生成位置无关代码(对于 shared lib 有用)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# 创建可执行文件 / 库
add_executable(my_app ${SRC})
# 如果你的 CUDA 运行时需要链接(通常 CMake 会自动处理)
find_package(CUDAToolkit REQUIRED) # modern CMake module
target_link_libraries(my_app PRIVATE CUDA::cudart)
# 包含目录
target_include_directories(my_app PRIVATE ${CMAKE_SOURCE_DIR}/include)
# 目标属性(示例)
target_compile_features(my_app PRIVATE cxx_std_20)
# 如果你想让 nvcc 使用特定编译选项:
target_compile_options(my_app PRIVATE
"$<$<COMPILE_LANGUAGE:CUDA>:--expt-relaxed-constexpr>"
)
# 安装规则(可选)
install(TARGETS my_app RUNTIME DESTINATION bin)
Before fill_ones (vector): 1 2 3 4 5
After fill_ones (vector): 1 1 1 1 1
After fill_ones (array): 1 1 1 1 1
After kernel_fill: 1 1 1 1 1
1⃣ 背景问题
你最开始的函数是:
template<typename Container>
__host__ __device__ constexpr
void fill_ones(Container & ct) {
for (auto & x : ct) x = 1;
}
在调用 std::vector<int> 时:
std::vector<int> v{1,2,3,4,5};
fill_ones(v);
会出现 CUDA 编译器警告:
warning #20014-D: calling a __host__ function from a __host__ __device__ function is not allowed
原因:
__host__ __device__表示函数可以在 CPU 和 GPU 调用。std::vector是 host-only 类型,不能在 GPU device 函数里调用。
数学上可以抽象成类型约束映射:
func target : T ↦ { host-only , T ∈ H device-only , T ∈ D host+device , T ∈ H ∩ D \text{func}_{\text{target}} : T \mapsto \begin{cases} \text{host-only}, & T \in H \\ \text{device-only}, & T \in D \\ \text{host+device}, & T \in H \cap D \end{cases} functarget:T↦⎩ ⎨ ⎧host-only,device-only,host+device,T∈HT∈DT∈H∩D
这里 H H H 是 host-only 类型集合, D D D 是 device-compatible 类型集合。
2⃣ Host/Device 模板宏解决方案
(a) 三重模板派发宏
#define MACRO( targ_, hdc_, func_ ) \
template< targ_, HDC x = hdc_ > \
requires( x == HDC::Hst ) __host__ func_ \
template< targ_, HDC x = hdc_ > \
requires( x == HDC::Dev ) __device__ func_ \
template< targ_, HDC x = hdc_ > \
requires( x == HDC::HstDev ) __host__ __device__ func_
用法示例:
MACRO( typename Container,
hdc<Container>,
void fill_ones(Container & ct) { for(auto & x : ct) x = 1; } )
说明:
- 宏生成 三个版本的函数模板:
- Host-only:
__host__ - Device-only:
__device__ - Host+Device:
__host__ __device__
- Host-only:
hdc<T>是类型 trait,根据类型选择派发目标。
(b) Dispatcher 包装函数
#pragma nv_exec_check_disable
template<typename T>
__host__ __device__ void wrap_impl(T&& t) {
t.func();
}
function_dispatch_macro((typename T), (hdc<T>),
(void wrap(T&& t)),
(return wrap_impl(std::forward<T>(t));))
- Dispatcher 自动根据类型实例化对应版本的 host/device 函数。
- 宏展开可避免重复写模板。
#pragma nv_exec_check_disable可以抑制一些 CUDA host/device 警告。
数学表示:
wrap ( T ) = func hdc ( T ) ( T ) \text{wrap}(T) = \text{func}_{\text{hdc}(T)}(T) wrap(T)=funchdc(T)(T)- CPU 调用 → 调用 host 版本
- GPU 调用 → 调用 device 版本
3⃣ 例子:兼容 Host/Device 的数组类型
template<typename T, int N>
struct hstdev_array {
static constexpr HDC hdc = HDC::HstDev;
__host__ __device__ T& operator[](int idx) { return data_[idx]; }
T data_[N];
};
__global__ void kernel() {
hstdev_array<int, 5> hd;
fill_ones(hd); // GPU 上安全
}
int main() {
std::array<int,5> a{1,2,3,4,5};
fill_ones(a); // CPU 上安全
}
说明:
hstdev_array是 host+device 兼容数组。std::array默认HDC::Hst,只能在 CPU 调用。- Dispatcher + 宏派发保证 类型安全,避免 host-only 调用进入 device 代码。
4⃣ CUDA Proposal(未来扩展)
- Conditional host/device annotation:让
__host__和__device__接受布尔参数,例如:
template<typename T>
__host__(hdc<T>==HDC::Hst)
__device__(hdc<T>==HDC::Dev)
void wrap() {
T{}.func();
}
- Forbid bad cross function calls:编译器保证 host-only 函数绝不在 device 上被调用,避免手动宏和模板技巧。
5⃣ 总结
- 直接在
__host__ __device__函数里调用 host-only 类型(如std::vector)会报错。 - 使用 三重模板派发宏 + trait 可以安全生成 host/device 版本函数。
- Dispatcher + wrapper 保证了 CPU/GPU 统一接口:
∀ T , wrap ( T ) = func hdc ( T ) ( T ) \forall T, \quad \text{wrap}(T) = \text{func}_{\text{hdc}(T)}(T) ∀T,wrap(T)=funchdc(T)(T) - 将来 CUDA 扩展可能直接允许 条件 host/device 注解,无需宏。
更多推荐



所有评论(0)