pybind机制

使用场景
- python里面调用C++代码
举例说明:小demo拆解
目标
- 因为跨服务器的传输,不用MPI的话就得用pytorch提供的分布式通信接口
- pytorch的分布式通信接口是python的
- 所以必须得用python调用底层编写的C++代码
- 这个项目还有一个麻烦的事情是必须要使用到nvshmem库,一些链接过程也写在这里
代码结构
1 | ├─include |
include文件夹
- 没什么特别的,include里面包含了一些头文件
src文件夹
- datacopy.cu:主要是实现了数据传输的功能,用的是普通的C++语法和CUDA语法
- pybind_data_copy.cpp:主要是实现了python和C++的接口,用的是pybind11的语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 这两个函数实现在datacopy.cu里面
std::vector<uint8_t> get_unique_id();
int init_nvshmem(const std::vector<uint8_t>& root_unique_id, int rank, int world_size);
namespace py = pybind11;
PYBIND11_MODULE(pydatacopy, m) {
py::class_<DoubleBufferManager>(m, "DoubleBufferManager")
.def(py::init<>())
.def("init", &DoubleBufferManager::init, "Initialize double buffer")
.def("cleanup", &DoubleBufferManager::cleanup, "Cleanup double buffer")
.def("test_bandwidth", &DoubleBufferManager::test_bandwidth, "Test bandwidth");
m.def("get_unique_id", &get_unique_id, "Generate a unique ID for NVSHMEM initialization");
m.def("init_nvshmem", &init_nvshmem, "Initialize NVSHMEM",
py::arg("root_unique_id"), py::arg("rank"), py::arg("world_size"));
} - 主要是用PYBIND11_MODULE宏定义了一个名为pydatacopy的模块
- 这个模块包含了一个名为DoubleBufferManager的类和两个函数get_unique_id和init_nvshmem
- 这样就可以在python中import pydatacopy,然后使用DoubleBufferManager类和这两个函数
build.sh
1 |
|
- 第一行的
#!/bin/bash
表示这个脚本是用bash解释器来执行的,这个指令必须放在脚本的第一行 mkdir -p build
:创建一个名为build的目录,-p
选项表示如果要创建的一个目录路径下任何一个目录不存在,都会自动创建出来,一次可以创建多个目录export NVSHMEM_HOME=/usr/local/nvshmem
:设置环境变量NVSHMEM_HOME为/usr/local/nvshmem,这是nvshmem库的安装路径export Python_ROOT_DIR=$(python3.12 -c "import sys; print(sys.prefix)")
:设置环境变量Python_ROOT_DIR为python3.12的安装路径python3.12 -c "import sys; print(sys.prefix)"
:这条命令会输出python3.12的安装路径$(...)
:这是命令替换的语法,会把命令的输出结果赋值给变量- 当 CMake 执行 find_package(Python COMPONENTS Interpreter Development)时:会首先检查 Python_ROOT_DIR,使用该路径查找 Python 开发文件,验证找到的 Python 版本是否符合要求
- 如果在标准安装中,返回python的安装根目录;如果是虚拟环境,返回虚拟环境的根目录
- 不设置可能导致找到错误版本的python
cmake .. \
:运行cmake命令,..
表示上一级目录,也就是项目的根目录-G Ninja \
:指定使用Ninja作为构建系统,Ninja是一种更快的替代Make的构建工具-DCMAKE_BUILD_TYPE=Release \
:指定构建类型为Release,表示生成优化过的代码-DCMAKE_CUDA_ARCHITECTURES=90 \
:指定CUDA的计算能力为9.0,H20可用-DPython_EXECUTABLE=$(which python3.12) \
:指定python解释器的路径-DTorch_DIR=/usr/local/lib/python3.12/dist-packages/torch/share/cmake/Torch \
:指定pytorch的CMake配置文件路径,这个路径下面实际上存的是一些.cmake文件-Dpybind11_DIR=$(python3.12 -c "import pybind11; print(pybind11.get_cmake_dir())")
:指定pybind11的CMake配置文件路径,编译好pybind11之后,这个目录下面存的也就是一些.cmake文件ninja -j$(nproc)
:运行ninja,编译
CMakeLists.txt
- 解释见注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127cmake_minimum_required(VERSION 3.18)
# 设置项目名称和使用的语言,这里用到C++和CUDA
project(pydatacopy LANGUAGES CXX CUDA)
# 设置编译选项,关闭冗长的编译输出
set(CMAKE_VERBOSE_MAKEFILE OFF)
# ccache加速编译,在系统中查找ccache程序,把路径赋值给CCACHE_PROGRAM变量
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CUDA_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
message(STATUS "Using ccache for compilation acceleration")
endif()
# 查找python这个外部依赖包,3.12版本,REQUIRED表示必须找到,否则报错,COMPONENTS后面的内容指定需要的部分,需要解释器和开发文件,这里会优先查找Python_ROOT_DIR变量指定的路径
find_package(Python 3.12 REQUIRED COMPONENTS Interpreter Development)
message(STATUS "Python include dirs: ${Python_INCLUDE_DIRS}")
message(STATUS "Python libraries: ${Python_LIBRARIES}")
# set():CMake 命令,用于定义变量
set(TORCH_DIR "/usr/local/lib/python3.12/dist-packages/torch/share/cmake/Torch")
message(STATUS "Using Torch at: ${TORCH_DIR}")
# CMAKE_PREFIX_PATH:CMake 的特殊变量,用于存储查找包的路径列表
# list(APPEND ...):向列表末尾添加新元素
# 作用是将 TORCH_DIR 添加到 CMAKE_PREFIX_PATH 中,这样 find_package(Torch ...) 时就会在这个路径下查找TorchConfig.cmake
list(APPEND CMAKE_PREFIX_PATH ${TORCH_DIR})
set(CMAKE_CUDA_ARCHITECTURES "90")
set(TORCH_CUDA_ARCH_LIST "9.0")
# 使用torch的cmake配置文件,用于查找外部依赖包
find_package(Torch REQUIRED CONFIG)
find_package(pybind11 REQUIRED CONFIG)
set(NVSHMEM_HOME "/usr/local/nvshmem")
message(STATUS "Using NVSHMEM at: ${NVSHMEM_HOME}")
set(NVSHMEM_LIB "${NVSHMEM_HOME}/lib/libnvshmem.a")
find_package(CUDAToolkit REQUIRED)
# 设置源文件
set(SOURCES
src/pybind_data_copy.cpp
src/data_copy.cu
)
# 包含头文件目录
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
# 7. 获取 Python 模块后缀
execute_process(
COMMAND python3.12 -c "import sysconfig; print(sysconfig.get_config_var('EXT_SUFFIX'))"
OUTPUT_VARIABLE PYTHON_MODULE_SUFFIX
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "Python module suffix: ${PYTHON_MODULE_SUFFIX}")
# 8. 创建模块
# 创建一个名为 pydatacopy的共享库(动态链接库),这将成为 Python 可导入的扩展模块
add_library(pydatacopy SHARED ${SOURCES})
# prefix:设置库文件名的前缀为空字符串,默认情况下,Linux/Mac 共享库有 lib前缀(如 libpydatacopy.so),Python 扩展模块不需要前缀,所以设为空
# suffix:设置库文件的后缀为 Python 模块的标准后缀(如 .so 或 .pyd),确保生成的文件名符合 Python 的要求
# POSITION_INDEPENDENT_CODE ON:生成位置无关代码(PIC),这对于共享库是必要的,多个程序共享同一段代码。位置无关的意思是代码可以加载到内存的任何位置,用的都是相对地址
# CUDA_SEPARABLE_COMPILATION ON:启用 CUDA 的可分离编译,这允许 CUDA 代码跨多个文件进行编译和链接,这可以支持在修改的情况下只重新编译修改的部分,还可以支持启用设备链接函数,一个CUDA设备函数可以调用其他文件里的函数
set_target_properties(pydatacopy PROPERTIES
PREFIX ""
SUFFIX "${PYTHON_MODULE_SUFFIX}"
POSITION_INDEPENDENT_CODE ON
CUDA_SEPARABLE_COMPILATION ON
)
# 9. 添加包含目录
target_include_directories(pydatacopy PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${Python_INCLUDE_DIRS}
${Torch_INCLUDE_DIRS}
${pybind11_INCLUDE_DIRS}
${NVSHMEM_HOME}/include
${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES}
)
# 10. 添加链接库
target_link_libraries(pydatacopy PRIVATE
${TORCH_LIBRARIES}
${Python_LIBRARIES}
cudart
${NVSHMEM_LIB}
dl
pybind11::pybind11
)
# 11. 添加编译选项 - 强制指定架构
target_compile_options(pydatacopy PRIVATE
$<$<COMPILE_LANGUAGE:CUDA>:
-Xcompiler=-fPIC
--expt-relaxed-constexpr
--ptxas-options=-O3 # 优化PTX汇编
--maxrregcount=64 # 限制寄存器使用以提高并行度
--use_fast_math # 使用快速数学(精度略低但更快)
--gpu-architecture=compute_90 # 强制指定计算能力
--gpu-code=sm_90 # 强制指定目标架构
>
$<$<COMPILE_LANGUAGE:CXX>:
-fPIC
-O3
-march=native # 使用本地CPU架构优化
>
)
# 12. 设置输出目录
set_target_properties(pydatacopy PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}
CUDA_RESOLVE_DEVICE_SYMBOLS ON
)
# 13. 安装规则
install(TARGETS pydatacopy
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
setup.py
1 | from setuptools import setup |
- 然后如果sources里面有东西,就执行:
1
python setup.py build
- 然后把编译好的pydatacopy.cpython-…so文件拷贝到python的site-packages目录下,以便可以直接import导入
1
python setup.py install