CMakeLists.txt
cmake
Module Directorytorch
Library今天就是最後一篇啦, 我們來欣賞一下大型 CMake 專案的設計吧
以 PyTorch 為例, 看看該專案用了哪些工具, 是否有前幾天沒有涵蓋的部分
我們會用 PyTorch v2.0.1 來介紹
由於 PyTorch 也支援其他 build system 如 bazel, 我們僅著重在 cmake 的部分
CMakeLists.txt
我們先從專案最上層的 CMakeLists.txt
開始
先來看看有多少行
wc -l CMakeLists.txt
1228 pytorch/CMakeLists.txt
可以看到總共有 1,228 行, 很大的一個 build script
我們挑重點看
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
cmake_policy()
系列
cmake_policy(CMP0010)
, 是針對 CMake2.6 以前 variable reference 的行為, 見 CMP0010
project(Torch CXX C)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(LINUX TRUE)
else()
set(LINUX FALSE)
endif()
Linux
會有不同的設定# check and set CMAKE_CXX_STANDARD
string(FIND "${CMAKE_CXX_FLAGS}" "-std=c++" env_cxx_standard)
if(env_cxx_standard GREATER -1)
message(
WARNING "C++ standard version definition detected in environment variable."
"PyTorch requires -std=c++17. Please remove -std=c++ settings in your environment.")
endif()
set(CMAKE_CXX_STANDARD 17 CACHE STRING "The C++ standard whose features are requested to build this target.")
set(CMAKE_C_STANDARD 11 CACHE STRING "The C standard whose features are requested to build this target.")
CMAKE_<LANG>_STANDARD
僅列出此專案 建議 的 standard, 即使不符合仍會向下找到最近的版本去 build, 除非搭配 CMAKE_<LANG>_STANDARD_REQUIRED
和 CMAKE_<LANG>_EXTENSION
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
include(cmake/CheckAbi.cmake)
string(APPEND CMAKE_CXX_FLAGS " -D_GLIBCXX_USE_CXX11_ABI=${GLIBCXX_USE_CXX11_ABI}")
string(APPEND CMAKE_CUDA_FLAGS " -D_GLIBCXX_USE_CXX11_ABI=${GLIBCXX_USE_CXX11_ABI}")
if(${GLIBCXX_USE_CXX11_ABI} EQUAL 1)
set(CXX_STANDARD_REQUIRED ON)
else()
# Please note this is required in order to ensure compatibility between gcc 9 and gcc 7
# This could be removed when all Linux PyTorch binary builds are compiled by the same toolchain again
include(CheckCXXCompilerFlag)
append_cxx_flag_if_supported("-fabi-version=11" CMAKE_CXX_FLAGS)
endif()
endif()
中間非 Windows 和 Linux 的設定先跳過
再來就是各種 PyTorch 提供的 build options
然後可以看到支援 ccache
if(USE_CCACHE)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}" CACHE STRING "C compiler launcher")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}" CACHE STRING "CXX compiler launcher")
set(CMAKE_CUDA_COMPILER_LAUNCHER "${CCACHE_PROGRAM}" CACHE STRING "CUDA compiler launcher")
else()
message(STATUS "Could not find ccache. Consider installing ccache to speed up compilation.")
endif()
endif()
中間針對不同 generator 的設定也跳過, 來看安裝 shared library 的部分
# Note(jiayq): when building static libraries, all PRIVATE dependencies
# will also become interface libraries, and as a result if there are any
# dependency libraries that are not exported, the following install export
# script will fail. As a result, we will only provide the targets cmake
# files for shared lib installation. For more info, read:
# https://cmake.org/pipermail/cmake/2016-May/063400.html
if(BUILD_SHARED_LIBS)
configure_file(
${PROJECT_SOURCE_DIR}/cmake/Caffe2Config.cmake.in
${PROJECT_BINARY_DIR}/Caffe2Config.cmake
@ONLY)
install(FILES
${PROJECT_BINARY_DIR}/Caffe2Config.cmake
DESTINATION share/cmake/Caffe2
COMPONENT dev)
install(FILES
${PROJECT_SOURCE_DIR}/cmake/public/cuda.cmake
${PROJECT_SOURCE_DIR}/cmake/public/glog.cmake
${PROJECT_SOURCE_DIR}/cmake/public/gflags.cmake
${PROJECT_SOURCE_DIR}/cmake/public/mkl.cmake
${PROJECT_SOURCE_DIR}/cmake/public/mkldnn.cmake
${PROJECT_SOURCE_DIR}/cmake/public/protobuf.cmake
${PROJECT_SOURCE_DIR}/cmake/public/utils.cmake
${PROJECT_SOURCE_DIR}/cmake/public/LoadHIP.cmake
DESTINATION share/cmake/Caffe2/public
COMPONENT dev)
install(DIRECTORY
${PROJECT_SOURCE_DIR}/cmake/Modules_CUDA_fix
DESTINATION share/cmake/Caffe2/
COMPONENT dev)
install(FILES
${PROJECT_SOURCE_DIR}/cmake/Modules/FindCUDAToolkit.cmake
DESTINATION share/cmake/Caffe2/
COMPONENT dev)
install(FILES
${PROJECT_SOURCE_DIR}/cmake/Modules/FindCUSPARSELT.cmake
DESTINATION share/cmake/Caffe2/
COMPONENT dev)
install(EXPORT Caffe2Targets DESTINATION share/cmake/Caffe2
FILE Caffe2Targets.cmake
COMPONENT dev)
else()
message(WARNING
"Generated cmake files are only available when building "
"shared libs.")
endif()
caffe2
和 cuda
的 modules 裝到 share/cmake/Caffe2
底下, 根據 FHS, share
底下是放一些共用的 dataCaffe2Targets.cmake
讓我們可以透過 Caffe2Config.cmake
引進 caffe2
的 targetsCaffe2Config.cmake
是由 Caffe2Config.cmake.in
產生的, 確實在 113 行可以看到 include ("${CMAKE_CURRENT_LIST_DIR}/Caffe2Targets.cmake")
引進這些 targets最後來看看 Summary
include(cmake/Summary.cmake)
caffe2_print_configuration_summary()
因為 PyTorch 的 dependencies 相當多, 尤其很多 caffe2
的設定, 蠻奇特的, 好奇去查了一下, 發現 caffe2 在 2018 年就被整合進 PyTorch 了, 見 Caffe2 and PyTorch join forces to create a Research + Production platform PyTorch 1.0, 看起來是 coffe2 也提供了 PyTorch 很核心的功能
總而言之, 頂端的 CMakeLists.txt
會像是 project 的導覽
也就是 developer 會接觸到的部分, 像是各種 options 和設定
其餘的 libraries 和 executables 等等實作都會在各自的路徑底下, 這也符合 CMake3.0 的 target-centric 的風格
cmake
Module Directory再來看看同層的 cmake
.
├── External
├── Modules
├── Modules_CUDA_fix
├── public
├── Allowlist.cmake
├── BuildVariables.cmake
├── Caffe2Config.cmake.in
├── CheckAbi.cmake
├── cmake_uninstall.cmake.in
├── Codegen.cmake
├── DebugHelper.cmake
├── Dependencies.cmake
├── FlatBuffers.cmake
├── GoogleTestPatch.cmake
├── IncludeSource.cpp.in
├── iOS.cmake
├── Metal.cmake
├── MiscCheck.cmake
├── ProtoBuf.cmake
├── ProtoBufPatch.cmake
├── Summary.cmake
├── TorchConfig.cmake.in
├── TorchConfigVersion.cmake.in
├── VulkanCodegen.cmake
└── VulkanDependencies.cmake
先排除 directory, 我們來看看這裡有哪些檔案吧
*.cmake
.cmake
結尾的都是 CMake module*Config.cmake.in
, *ConfigVersion.cmake.in
*Config.cmake
和 *ConfigVersion.cmake
是 Day 21 介紹的, 由 configre_package_config_file()
和 write_basic_package_version_file()
產生*.cmake.in
則是提供了我們 (developer) 一些 configure 時的選項, 讓我們能控制 pytorch 的 build processDependencies.cmake
include()
External
或 Modules
裡對應的 modules 來檢查套件是否能用torch
Library接著來看 torch 的 build script
花了一些時間找到底是誰把他加 build tree
和預想的由頂端 CMakeLists.txt
引入不同, torch
是由 caffe2
的 build script 引入的
add_subdirectory(../torch torch)
set(TORCH_PYTHON_COMPILE_OPTIONS ${TORCH_PYTHON_COMPILE_OPTIONS} PARENT_SCOPE)
set(TORCH_PYTHON_LINK_FLAGS ${TORCH_PYTHON_LINK_FLAGS} PARENT_SCOPE)
不過畢竟 torch 會用到 caffe2 的 libraries 和 headers, 先 build caffe2 也算合理
該 script 會產生兩個 shared libraries: torch_python
和 nnapi_backend
Target torch_python
會 link 以下 libraries
set(TORCH_PYTHON_LINK_LIBRARIES
python::python
pybind::pybind11
shm
fmt::fmt-header-only
ATEN_CPU_FILES_GEN_LIB)
if(USE_ASAN AND TARGET Sanitizer::address)
list(APPEND TORCH_PYTHON_LINK_LIBRARIES Sanitizer::address)
endif()
if(USE_ASAN AND TARGET Sanitizer::undefined)
list(APPEND TORCH_PYTHON_LINK_LIBRARIES Sanitizer::undefined)
endif()
if(USE_TSAN AND TARGET Sanitizer::thread)
list(APPEND TORCH_PYTHON_LINK_LIBRARIES Sanitizer::thread)
endif()
還有其他 libraries 像是 cuda, mpi, nccl 等
不過 ucc 沒有包含在 TORCH_PYTHON_LINK_LIBRARIES
, 卻是在 target_link_libraries(torch_python PRIVATE torch_library ${TORCH_PYTHON_LINK_LIBRARIES})
之後才單獨 link, 有點奇怪
torch_python
包含
set(TORCH_PYTHON_SRCS
${GENERATED_THNN_CXX}
${GENERATED_CXX_PYTHON}
)
GENERATED_CXX_PYTHON
caffe2/CMakeLists.txt
set(GENERATED_CXX_PYTHON
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_functions_0.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_functions_1.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_functions_2.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_functions_3.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_functions_4.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_variable_methods.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_torch_functions_0.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_torch_functions_1.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_torch_functions_2.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_nn_functions.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_fft_functions.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_linalg_functions.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_nested_functions.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_sparse_functions.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_special_functions.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_return_types.cpp"
"${TORCH_SRC_DIR}/csrc/autograd/generated/python_enum_tag.cpp"
)
GENERATED_THNN_CXX_CUDA
可以看到 PyTorch 很好心的提供了很~多 build scripts 的範例, 甚至包含 build_ios.sh
和 build_andriod.sh
讓我們能cross compile iOS 和 Android 版本, 雖然 Apple 系列平台不在這次介紹的範圍, 不過還是可以大概看一下
# Use ios-cmake to build iOS project from CMake.
# This projects sets CMAKE_C_COMPILER to /usr/bin/gcc and
# CMAKE_CXX_COMPILER to /usr/bin/g++. In order to use ccache (if it is available) we
# must override these variables via CMake arguments.
CMAKE_ARGS+=("-DCMAKE_TOOLCHAIN_FILE=$CAFFE2_ROOT/cmake/iOS.cmake")
if [ -n "${CCACHE_WRAPPER_PATH:-}"]; then
CCACHE_WRAPPER_PATH=/usr/local/opt/ccache/libexec
fi
if [ -d "$CCACHE_WRAPPER_PATH" ]; then
CMAKE_ARGS+=("-DCMAKE_C_COMPILER=$CCACHE_WRAPPER_PATH/gcc")
CMAKE_ARGS+=("-DCMAKE_CXX_COMPILER=$CCACHE_WRAPPER_PATH/g++")
fi
可以看到 pytorch 提供了 iOS.cmake
toolchain file
並且如果 developer 機器有 ccache 的話, 就改用 ccache 下的 compiler 來加速
30 天終於結束了🎉🎉🎉
第一次參加鐵人賽, 原本想說要先來存個稿, 避免因為上班或平時太忙而沒有時間寫, 結果人算不如天算, 大綱在開賽前的一個星期才開始想, 直到開賽前一天才堪堪寫出來...
對我來說, 這次參加鐵人賽的目的主要是分享學習 CMake 時遇到的一些困難, 和其他相關的知識, 畢竟說到底, CMake 只是一個 build tool (or meta build system), 他僅僅是讓我們免去手刻 build scripts 和呼叫 compiler 的工具, 但對於開發來說, 還是需要理解每個步驟所代表的意義
最開始的計畫其實是想要寫出一個厲害的專案的, 但是思來想去總是想不到要做什麼比較好, 刪刪改改後時間也不多了, 就只能以 "本系列不會做出厲害的專案" 為開頭QQ
開賽後第一週真的是惡夢, 早上 6 點就要起床開始寫, 寫到 7 點去公司
中午午休再繼續寫, 下班 7 點再繼續寫...現在回憶起來都想哭了嗚嗚
幸好後來想到一個解法: 每週的假日花兩天把下一週工作天的稿先擬好, 這大概幫我每天省下了一個小時 (感謝女朋友的建議QQ)
儘管常常到了當天大綱又都改掉...
真的沒時間的時候, 還是逼著自己水一些東西出來, 果然網路小說的作者會水也是有原因的
總之, 雖然過程不盡人意, 但是想表達的東西沒有八成也有六成了, 第一次的鐵人賽就到這了, 希望未來有時間的時候, 還能夠回來改改這些文章, 把偷懶的那幾天補好補滿
最後, 感謝 It 邦幫忙提供這個平台, 讓大家能有一個督促自己的動力, 逼自己寫出一點東西來, 儘管不一定有用, 總是個自我反省的過程, 希望能有越來越多好文章出現~
下台一鞠躬<(_ _)>