Skip to content

Commit 8d4aa5d

Browse files
committed
add cuda code
1 parent 1a8e72a commit 8d4aa5d

19 files changed

+4524
-7
lines changed

.gitignore

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
build/
22
site/
33
__pycache__/
4-
*.pdf
54
node_modules/
65
package.json
76
package-lock.json
8-
/a.cpp
9-
/a.bc
10-
/a.ll
7+
*.pdf
8+
*.o

CMakeLists.txt

+39-2
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,50 @@ else()
1414
endif()
1515

1616

17-
find_package(fmt REQUIRED)
18-
find_package(Boost REQUIRED COMPONENTS locale)
17+
set(libraries Boost::locale fmt)
18+
19+
20+
# find_package(fmt REQUIRED)
21+
# find_package(Boost REQUIRED COMPONENTS locale)
22+
foreach(lib_name IN LISTS libraries)
23+
string(FIND "${lib_name}" "::" found)
24+
if (found EQUAL -1)
25+
find_package(${lib_name} REQUIRED)
26+
else()
27+
# split by ::
28+
string(REGEX REPLACE "::" ";" lib_name_list "${lib_name}")
29+
list(GET lib_name_list 0 lib_name)
30+
list(GET lib_name_list 1 lib_name_component)
31+
find_package(${lib_name} REQUIRED COMPONENTS ${lib_name_component})
32+
endif()
33+
endforeach()
1934

2035
file(GLOB sources CONFIGURE_DEPENDS "examples/*.cpp")
2136
foreach(source IN LISTS sources)
2237
get_filename_component(name ${source} NAME_WE)
2338
add_executable(${name} ${source})
39+
# read file content and check if it really required "boost/*" include
40+
foreach(lib_name IN LISTS libraries)
41+
string(TOLOWER "${lib_name}" lib_expr)
42+
string(FIND "${lib_expr}" "::" found)
43+
if (found EQUAL -1)
44+
string(APPEND lib_name "::${lib_name}")
45+
string(APPEND lib_expr "/")
46+
else()
47+
string(REPLACE "::" "/" lib_expr "${lib_expr}")
48+
endif()
49+
file(STRINGS ${source} lines REGEX "#include[ \t]+[\"<]${lib_expr}.*[\">]")
50+
if (lines)
51+
target_link_libraries(${name} PRIVATE ${lib_name})
52+
endif()
53+
endforeach()
2454
target_link_libraries(${name} PRIVATE fmt::fmt Boost::locale)
2555
target_include_directories(${name} PRIVATE include)
2656
endforeach()
57+
58+
file(GLOB subdirs CONFIGURE_DEPENDS "examples/*/CMakeLists.txt")
59+
foreach(subdir IN LISTS subdirs)
60+
get_filename_component(name ${subdir} DIRECTORY)
61+
get_filename_component(name ${name} NAME)
62+
add_subdirectory(${name})
63+
endforeach()

docs/cpp_tricks.md

+2
Original file line numberDiff line numberDiff line change
@@ -1675,6 +1675,8 @@ struct Class {
16751675
16761676
## map + any 外挂属性
16771677
1678+
TODO
1679+
16781680
## 自定义 shared_ptr 的 deleter
16791681
16801682
## CHECK_CUDA 类错误检测宏

docs/cuda_intro.md

+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
# 现代 C++ 的 CUDA 编程
2+
3+
[TOC]
4+
5+
参考资料:
6+
7+
- https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html
8+
- https://www.cs.sfu.ca/~ashriram/Courses/CS431/assets/lectures/Part8/GPU1.pdf
9+
10+
## 配置 CUDA 开发环境
11+
12+
硬件方面建议使用至少 GTX 1060 以上显卡,但是更老的显卡也可以运行。
13+
14+
软件方面则可以尽可能最新,以获得 CUDA C++20 支持,我安装的版本是 CUDA 12.5。
15+
16+
以下仅演示 Arch Linux 中安装 CUDA 的方法,因为 Arch Linux 官方源中就自带 `nvidia` 驱动和 `cuda` 包,而且开箱即用,其他发行版请自行如法炮制。
17+
18+
Wendous 用户可能在安装完后遇到“找不到 cuxxx.dll”报错,说明你需要拷贝 CUDA 安装目录下的所有 DLL 到 `C:\\Windows\\System32`
19+
20+
WSL 用户要注意,WSL 环境和真正的 Linux 相差甚远。很多 Linux 下的教程,你会发现在 WSL 里复刻不出来。这是 WSL 的 bug,应该汇报去让微软统一修复,而不是让教程的作者零零散散一个个代它擦屁股。建议直接在 Wendous 本地安装 CUDA 反而比伺候 WSL 随机拉的 bug 省力。
21+
22+
Ubuntu 用户可能考虑卸载 Ubuntu,因为 Ubuntu 源中的版本永不更新。想要安装新出的软件都非常困难,基本只能安装到五六年前的古董软件,要么只能从网上下 deb 包,和 Wendous 一个软耸样。所有官方 apt 源中包的版本从 Ubuntu 发布那一天就定死了,永远不会更新了。这是为了起夜级服务器安全稳定的需要,对于个人电脑而言却只是白白阻碍我们学习,Arch Linux 这样的滚动更新的发行版才更适合个人桌面用户。
23+
24+
### 安装 NVIDIA 驱动
25+
26+
首先确保你安装了 NVIDIA 最新驱动:
27+
28+
```bash
29+
pacman -S nvidia
30+
```
31+
32+
运行以下命令,确认显卡驱动正常工作:
33+
34+
```bash
35+
nvidia-smi
36+
```
37+
38+
应该能得到:
39+
40+
```
41+
Mon Aug 26 14:09:15 2024
42+
+-----------------------------------------------------------------------------------------+
43+
| NVIDIA-SMI 555.58.02 Driver Version: 555.58.02 CUDA Version: 12.5 |
44+
|-----------------------------------------+------------------------+----------------------+
45+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
46+
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
47+
| | | MIG M. |
48+
|=========================================+========================+======================|
49+
| 0 NVIDIA GeForce RTX 4070 ... Off | 00000000:01:00.0 On | N/A |
50+
| 0% 30C P8 17W / 285W | 576MiB / 16376MiB | 41% Default |
51+
| | | N/A |
52+
+-----------------------------------------+------------------------+----------------------+
53+
54+
+-----------------------------------------------------------------------------------------+
55+
| Processes: |
56+
| GPU GI CI PID Type Process name GPU Memory |
57+
| ID ID Usage |
58+
|=========================================================================================|
59+
| 0 N/A N/A 583 G /usr/lib/Xorg 370MiB |
60+
| 0 N/A N/A 740 G xfwm4 4MiB |
61+
| 0 N/A N/A 783 G /usr/lib/firefox/firefox 133MiB |
62+
| 0 N/A N/A 4435 G obs 37MiB |
63+
+-----------------------------------------------------------------------------------------+
64+
```
65+
66+
如果不行,那就重启。
67+
68+
### 安装 CUDA
69+
70+
然后安装 CUDA Toolkit(即 nvcc 编译器):
71+
72+
```bash
73+
pacman -S cuda
74+
```
75+
76+
打开 `.bashrc`(如果你是 zsh 用户就打开 `.zshrc`),在末尾添加两行:
77+
78+
```bash
79+
export PATH="/opt/cuda/bin:$PATH" # 这是默认的 cuda 安装位置
80+
export NVCC_CCBIN="/usr/bin/g++-13" # Arch Linux 用户才需要这一行
81+
```
82+
83+
然后重启 `bash`,或者执行以下命令重载环境变量:
84+
85+
```bash
86+
source .bashrc
87+
```
88+
89+
运行以下命令测试 CUDA 编译器是否可用:
90+
91+
```bash
92+
nvcc --version
93+
```
94+
95+
应该能得到:
96+
97+
```
98+
nvcc: NVIDIA (R) Cuda compiler driver
99+
Copyright (c) 2005-2024 NVIDIA Corporation
100+
Built on Thu_Jun__6_02:18:23_PDT_2024
101+
Cuda compilation tools, release 12.5, V12.5.82
102+
Build cuda_12.5.r12.5/compiler.34385749_0
103+
```
104+
105+
### 常见问题解答
106+
107+
CMake 报错找不到 CUDA?添加环境变量:
108+
109+
```bash
110+
export PATH="/opt/cuda/bin:$PATH" # 这里换成你的 cuda 安装位置
111+
export NVCC_CCBIN="/usr/bin/g++-13" # 只有 Arch Linux 需要这一行
112+
```
113+
114+
IDE 使用了 Clangd 静态检查插件,报错不认识 `-forward-unknown-to-host-compiler` 选项?
115+
116+
创建文件 `~/.config/clangd/config.yaml`
117+
118+
```yaml
119+
CompileFlags:
120+
Add: # 要额外添加到 Clang 的 NVCC 没有的参数
121+
- --no-cuda-version-check
122+
Remove: # 移除 Clang 不认识的 NVCC 参数
123+
- -forward-unknown-to-host-compiler
124+
- --expt-*
125+
- --generate-code=*
126+
- -arch=*
127+
- -rdc=*
128+
```
129+
130+
### 建议开启的 CMake 选项
131+
132+
#### CUDA 编译器路径
133+
134+
如果你无法搞定环境变量,也可以通过 `CMAKE_CUDA_COMPILER` 直接设置 `nvcc` 编译器的路径:
135+
136+
```cmake
137+
set(CMAKE_CUDA_COMPILER "/opt/cuda/bin/nvcc") # 这里换成你的 cuda 安装位置
138+
```
139+
140+
不建议这样写,因为会让使用你项目的人也被迫把 CUDA 安装到这个路径去。
141+
142+
建议是把你的 `nvcc` 安装好后,通过 `PATH` 环境变量,`cmake` 就能找到了,不需要设置这个变量。
143+
144+
#### CUDA C++ 版本
145+
146+
CUDA 是一种基于 C++ 的领域特定语言,CUDA C++ 的版本和正规 C++ 一一对应。
147+
148+
目前最新的是 CUDA C++20,可以完全使用 C++20 特性的同时书写 CUDA 代码。
149+
150+
- 在 `__host__` 函数(未经特殊修饰的函数默认就是此类,在 CPU 端执行)中,CUDA 和普通 C++ 没有区别,任何普通 C++ 代码,都可以用 CUDA 编译器编译。
151+
- 在 `__device__` 函数(CUDA kernel,在 GPU 端执行)中,能使用的函数和类就有一定限制了:
152+
- 例如你不能在 `__device__` 函数里使用仅限 `__host__` 用的 `std::cout`(但 `printf` 可以,因为 CUDA 团队为了方便用户调试,为你做了 `printf` 的 `__device__` 版特化)。
153+
- `__device__` 中不能使用绝大多数非 `constexpr` 的 STL 容器,例如 `std::map` 等,但是在 `__host__` 侧还是可以用的!
154+
- 所有的 `constexpr` 函数也是可以使用的,例如各种 C++ 风格的数学函数如 `std::max`,`std::sin`,这些函数都是 `constexpr` 的,在 `__host__` 和 `__device__` 都能用。
155+
- 如果一个容器的成员全是 `constexpr` 的,那么他可以在 `__device__` 函数中使用。例如 `std::tuple`、`std::array` 等等,因为不涉及 I/O 和内存分配,都是可以在 `__device__` 中使用的。
156+
- 例如 C++20 增加了 constexpr-new 的支持,让 `std::vector` 和 `std::string` 变成了 `constexpr` 的容器,因此可以在 `__device__` 中使用 `std::vector`(会用到 `__device__` 版本的 `malloc` 函数,这是 CUDA 的一大特色:你可以在 kernel 内部用 `malloc` 动态分配设备内存,并且从 CUDA C++20 开始 `new` 也可以了)。
157+
- `std::variant` 现在也是 `constexpr` 的容器,也可以在 `__device__` 函数中使用了。
158+
- 异常目前还不是 `constexpr` 的,因此无法在 `__device__` 函数中使用 `try/catch/throw` 系列关键字。
159+
- 总之,随着,我们可以期待越来越多纯计算的函数和容器能在 CUDA kernel(`__device__` 环境)中使用。
160+
161+
正如 `CMAKE_CXX_STANDARD` 设置了 `.cpp` 文件所用的 C++ 版本,也可以用 `CMAKE_CUDA_STANDARD` 设置 `.cu` 文件所用的 CUDA C++ 版本。
162+
163+
```cmake
164+
set(CMAKE_CXX_STANDARD 20) # .cpp 文件采用的 C++ 版本是 C++20
165+
set(CMAKE_CUDA_STANDARD 20) # .cu 文件采用的 CUDA C++ 版本是 C++20
166+
```
167+
168+
### 赋能现代 C++ 语法糖
169+
170+
```cmake
171+
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --expt-extended-lambda")
172+
```
173+
174+
* `--expt-relaxed-constexpr`: 让所有 `constexpr` 函数默认自动带有 `__host__ __device__`
175+
* `--expt-extended-lambda`: 允许为 lambda 表达式指定 `__host__` 或 `__device__`
176+
177+
#### 显卡架构版本号
178+
179+
不同的显卡有不同的“架构版本号”,架构版本号必须与你的硬件匹配才能最佳状态运行,可以略低,但将不能发挥完整性能。
180+
181+
```cmake
182+
set(CMAKE_CUDA_ARCHITECTURES 86) # 表示针对 RTX 30xx 系列(Ampere 架构)生成
183+
set(CMAKE_CUDA_ARCHITECTURES native) # 如果 CMake 版本高于 3.24,该变量可以设为 "native",让 CMake 自动检测当前显卡的架构版本号
184+
```
185+
186+
架构版本号:例如 75 表示 RTX 20xx 系列(Turing 架构);86 表示 RTX 30xx 系列(Ampere 架构);89 表示 RTX 40xx 系列(Ada 架构)等。
187+
188+
完整的架构版本号列表可以在 [CUDA 文档](https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#virtual-architecture-feature-list) 中找到。
189+
190+
也可以运行如下命令(如果有的话)查询当前显卡的架构版本号:
191+
192+
```bash
193+
__nvcc_device_query
194+
```
195+
196+
#### 设备函数分离定义
197+
198+
默认只有 `__host__` 函数可分离声明和定义。如果你需要分离 `__device__` 函数的声明和定义,就要开启这个选项:
199+
200+
```cmake
201+
set(CMAKE_CUDA_SEPARABLE_COMPILATION ON) # 可选
202+
```
203+
204+
#### 创建 CUDA 项目
205+
206+
完成以上选项的设定后,使用 `project` 命令正式创建 CUDA C++ 项目。
207+
208+
```cmake
209+
project(这里填你的项目名 LANGUAGES CXX CUDA)
210+
```
211+
212+
> {{ icon.fun }} 我见过有人照抄代码把“这里填你的项目名”抄进去的。
213+
214+
如需在特定条件下才开启 CUDA,可以用 `enable_language()` 命令延迟 CUDA 环境在 CMake 中的初始化:
215+
216+
```cmake
217+
project(这里填你的项目名 LANGUAGES CXX)
218+
219+
...
220+
221+
option(ENABLE_CUDA "Enable CUDA" ON)
222+
223+
if (ENABLE_CUDA)
224+
enable_language(CUDA)
225+
endif()
226+
```
227+
228+
#### CMake 配置总结
229+
230+
注意!以上这些选项设定都必须在 `project()` 命令之前!否则设定了也无效。
231+
232+
因为实际上是 `project()` 命令会检测这些选项,用这些选项来找到编译器和 CUDA 版本等信息。
233+
234+
总之,我的选项是:
235+
236+
```cmake
237+
cmake_minimum_required(VERSION 3.12)
238+
239+
set(CMAKE_CXX_STANDARD 20)
240+
set(CMAKE_CUDA_STANDARD 20)
241+
set(CMAKE_CUDA_SEPARABLE_COMPILATION OFF)
242+
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --expt-extended-lambda")
243+
if (NOT DEFINED CMAKE_CUDA_ARCHITECTURES AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
244+
set(CMAKE_CUDA_ARCHITECTURES native)
245+
endif()
246+
247+
project(你的项目名 LANGUAGES CXX CUDA)
248+
249+
file(GLOB sources "*.cpp" "*.cu")
250+
add_executable(${PROJECT_NAME} ${sources})
251+
target_link_libraries(${PROJECT_NAME} PRIVATE cusparse cublas)
252+
```
253+
254+
## 开始编写 CUDA
255+
256+
CUDA 有两套 API:
257+
258+
- [CUDA runtime API](https://docs.nvidia.com/cuda/cuda-runtime-api/index.html):更加简单,兼顾性能,无需手动编译 kernel,都替你包办好了,但不够灵活。
259+
- [CUDA driver API](https://docs.nvidia.com/cuda/cuda-driver-api/index.html):更加灵活多变,但操作繁琐,需要手动编译 kernel,适合有特殊需求的用户。
260+
261+
他们都提供了大量用于管理 CUDA 资源和内存的函数。
262+
263+
我们要学习的是比较易懂、用的也最多的 CUDA runtime API。
264+
265+
使用 `<cuda_runtime.h>` 头文件即可导入所有 CUDA runtime API 的函数和类型:
266+
267+
```cuda
268+
#include <cuda_runtime.h>
269+
```
270+
271+
> {{ icon.tip }} 虽然 CUDA 基于 C++(而不是 C 语言),支持所有 C++ 语言特性。但其 CUDA runtime API 依然是仿 C 风格的接口,可能是照顾了部分从 C 语言转过来的土木老哥,也可能是为了方便被第三方二次封装。

docs/design_virtual.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ struct PoostInputer {
580580
他们要求的用法是先判断 hasNext(),然后才能调用 getNext 读取出真正的值。小彭老师设计了一个 Poost 适配器,把 PoostInputer 翻译成我们的 Inputer:
581581

582582
```cpp
583-
struct PoostInputerAdapter {
583+
struct PoostInputerAdapter : Inputer {
584584
PoostInputer *poostIn;
585585

586586
PoostInputerAdapter(PoostInputer *poostIn)

examples/llvm/a.bc

2.16 KB
Binary file not shown.

examples/llvm/a.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
inline int i;
2+
3+
int main() {
4+
int a = 1;
5+
int b = 2;
6+
return a + b + i;
7+
}

0 commit comments

Comments
 (0)