在進入 PyTorch C++ front-end API 以及其他的 C++ use case 之前,會先做一個簡單的 CMake 教學,因為 PyTorch 的官方網站似乎偏好使用 CMake 做編譯(筆者也是)。至於關於 PyTorch C++ front-end API USING THE PYTORCH C++ FRONTEND 更詳細的介紹,將會緊接在後。
誠如在前文所提及的,CMake 是一個自動編譯,測試和 packaging 的系統。一個尋常的 CMake workflow 如下:
cmake <CMakeLists.txt 所在的資料夾>
。這一步驟 cmake 會走訪整個專案資料夾,並讀取在子資料夾的 CMakeLists.txt。完成後會自動產生幾個重要的資料夾和檔案,部分列舉如下:
<project name>
的資料夾make <Makefile >
進行編譯。cmake -D<variable>="<value>" <dir_to_the_top_most_cmake_file>
:<dir_to_the_top_most_cmake_file>
為專案檔案結構最上層的 CMakeLists.txt 檔案。而 -D 選項可用來指定 configuration 或編譯時需要的變數 。例子:
cmake -DCMAKE_CXX_COMPILER="clang++” -DCMAKE_CXX_FLAGS="-std=c++11 -stdlib=libc++" ../source
。
在上面的例子中:
source/
# top most CMakeLists.txt
CMakeLists.txt
build/
# the file level where cmake is invoked (cmake 被呼叫的檔案層級)
cmake 的命令有一個必要的引數,該引數為專案最上層的 CMakeLists.txt 的所在地。如果我們在 build 的位置呼叫 cmake,為了讀入專案最上層資料夾下的 CMakeLists.txt,就需要以 cmake ../source
來呼叫。
2. 呼叫 ccmake:是 CMake's unix 命令列指令,如果考慮互動式的命令列介面,可以呼叫 cmake -i 或 CMakeSetup 指令。
3. 呼叫 ctest: 如果曾在專案內的每一 CMakeLists.txt 增加了 add_test
就可以呼叫 ctest 來執行 unittest
set(CMAKE_VERBOSE_MAKEFILE TRUE)
,可以在 CMakeLists.txt 中將 CMAKE_VERBOSE_MAKEFILE
屬性設為 TRUE 。一旦 CMAKE_VERBOSE_MAKEFILE
為 True,可以讓 make 在執行時是 verbose output。--trace / --trace-expand
,這兩個參數會鉅細靡遺的紀錄 cmake 如何被呼叫,和處理 CMakeLists.txt 的設定在撰寫 CMakeLists.txt 時,必須要使用 CMake 自己的語言,這些語言可以依功能分為以下兩類:Project command 和 Scripting command。
這類的命令都是屬於 project-scope 的命令,主要提供建立與專案編譯相關的指令。常見的指令列舉如下:
add_sub_directories(<source code folder>)
增加一個檔案夾到專案內。要記住加入成為專案的子資料夾,其資料夾內需要包含 CMakeList.txtadd_executable(TARGET_NAME SOURCES)
:這個命令會生成專案目標執行檔(如 *.exe
for Windows 或 MacOSX bundle)。這個命令有兩個主要引數,目標執行檔的名稱(TARGET_NAME)和可編譯成目標執行檔的來源檔(SOURCES)。SOURCES 的部分可以是單一檔案,也可以是需多檔案。例子如:add_executable(example main.cpp main.h)
,在這個命令 SOURCES 是一個包含兩個檔案名“main.cpp main.h" 的 list。add_library(TARGET_NAME [STATIC | SHARED | MODULE] SOURCES)
:這個命令會生成可供多個執行檔使用的 library。目標型態包括了:
add_library(TARGET_NAME [STATIC | SHARED | MODULE | OBJECT| UNKNOWN] IMPORTED [GLOBAL])
:有另一個方法可以增加 library 目標是利用 IMPORTED 關鍵字。這個 IMPORTED 會假設另外一個專案或 framework 編譯了一個 library,可以供此專案引用。引用 TorchConfig.cmake 增加名為 torch 的 target 的例子:add_library(torch UNKNOWN IMPORTED)
在 CMake 裏需要先用上面三種方法建立 target 後,再對如何編譯該 target 進行 configure。此類常見的指令如下:
target_compile_definitions(TARGET_NAME INTERFACE|PUBLIC|PRIVATE DEFINITION)
:這個指令會產生定義,如編譯時使用 -D
的選項產生定義。一個常用的例子應該是:target_compile_definitions(foo PUBLIC DEBUG)
,這個命令會定義 DEBUG 符號。target_compile_options(TARGET_NAME INTERFACE|PUBLIC|PRIVATE OPTION)
:藉著設定任何編譯選項來改變編譯行為。如從 onnxruntime/onnxruntime_util.cmake 摘錄下來的例子:target_compile_options(onnxruntime_util PUBLIC "-Wno-error=comment")
會增加編譯選項 "-Wno-error=comment" 當要編譯 onnxruntime_util 的時候。target_link_libraries(TARGET_NAME PRIVATE|PUBLIC|INTERFACE LIBRARY)
:指示 linker 對名為 TARGET_NAME 的執行檔建立 LIBRARY 的連結參考。LIBRARY 可以是一個或多個 library 的名字,絕對路徑,library 名稱(也就是 lib<library_name>.so
中的 <library_name
)或 -l<library_name>
這樣的形式。例子:target_link_libraries(example "${TORCH_LIBRARIES}")
。TORCH_LIBRARIES 這個變數會在執行 TorchConfig.cmake 時給予值。target_include_directories(TARGET_NAME INTERFACE|PUBLIC|PRIVATE DIR)
:編譯名為 TARGET_NAME 的 target 時,會增加 DIR 目錄到標頭檔的搜尋路徑中.在上列 target_*
的指令中,我們都可以看到有三個選項可以選擇,分別是 INTERFACE,PUBLIC,PRIVATE。這三個選項分別對應著三種模式,每一種模式都對應設定檔的 property scope。讀者可以從下表得到更清楚的對應:
Command name \ keywords | PRIVATE | INTERFACE | PUBLIC |
---|---|---|---|
target_include_directories() |
INCLUDE_DIRECTORIES | INTERFACE_INCLUDE_DIRECTORIES | INCLUDE_DIRECTORIES and INTERFACE_INCLUDE_DIRECTORIES |
target_link_libraries() |
LINK_LIBRARIES | INTERFACE_LINK_LIBRARIES | LINK_LIBRARIES and INTERFACE_LINK_LIBRARIES |
target_compile_definitions() |
COMPILE_DEFINITIONS | INTERFACE_COMPILE_DEFINITION | COMPILE_DEFINITIONS and INTERFACE_COMPILE_DEFINITIONS |
target_compile_options() |
COMPILE_OPTIONS | INTERFACE_COMPILE_OPTIONS | COMPILE_OPTIONS and INTERFACE_COMPILE_OPTIONS |
要注意的是:INTERFACE_* 的 properties 是給 "Imported" 的編譯目標所用。 |
CMake 語言中處理 flow control 等等相關的語法,如 if-else, for-loop, while-break, continue 等。下面是從 pytorch/examples/cpp/dcgan/CMakeLists.txt 摘錄下來的例子,說明如何利用 CMake 語言完成下載資料集
option(DOWNLOAD_MNIST "Download the MNIST dataset from the internet" ON)
if (DOWNLOAD_MNIST)
message(STATUS "Downloading MNIST dataset")
execute_process(
COMMAND python ${CMAKE_CURRENT_LIST_DIR}/../tools/download_mnist.py
-d ${CMAKE_BINARY_DIR}/data
ERROR_VARIABLE DOWNLOAD_ERROR)
if (DOWNLOAD_ERROR) # 如果執行發生錯誤會為 true
message(FATAL_ERROR "Error downloading MNIST dataset: ${DOWNLOAD_ERROR}")
endif()
endif()
在上面的程式碼用到了下列幾個命令
OPTION(OPTION_NAME HELP_MESSAGE DEFAULT_VALUE)
:建立使用者選項參數,在例子中會建立一個使用者參數,名為 DOWNLOAD_MNIST,預設為 ON。message([MODE] "message to display")
:message 可以看做是 CMake 的 print。MODE 必須填入 CMake 提供的常數選項,比較常用的包括了 none(未給定), STATUS, WARNING 和 FATAL_ERROR。模式的不同會決定輸出的目的地,若是 STATUS 則會輸出於 stdout
,其他則會輸出於 stderr
。execute_process(COMMAND CMD_NAME [ARGUMENTS...] [ERROR_VARIABLE VARIABLE_NAME])
:execute_process 會在子程序內執行所給定的程式名稱。CMD_NAME 是執行程式的名稱,在例子中為 python。在 CMD_NAME 後的 ARGUMENTS 是字串陣列,作為執行程式的引數。最後 VARIABLE_NAME 是當程式運行失敗時,將會設為 true 的變數。另外一個重要的 CMake 指令為 find_package()
: 因為 CMake 語言和一般的 scripting 語言一般,可以模組化。所以可以把常用且性質相近的 CMake scripts 放在同一個 cmake 檔案夾內,待要執行的時候在專案中 top-level 的 CMakeLists.txt,可以使用 find_package
載入在 cmake 模組檔案上所定義的 function 或執行結果。
因為 PyTorch 有自己的 cmake module, 所以使用 find_package
來執行 torch libraries 之間的相依關係是很重要的。而 cmake modules 的位置則在 <where the site-packages live>/torch/share/cmake
底下就可以看到三個資料夾,而關於 Torch 函式庫的 configuration 則在 <where the site-packages live>/share/cmake/Torch/TorchConfig.cmake