iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 11
1
AI & Data

深度學習裡的冰與火之歌 : Tensorflow vs PyTorch系列 第 11

Day 11: 中場休息 CMake 教學

在進入 PyTorch C++ front-end API 以及其他的 C++ use case 之前,會先做一個簡單的 CMake 教學,因為 PyTorch 的官方網站似乎偏好使用 CMake 做編譯(筆者也是)。至於關於 PyTorch C++ front-end API USING THE PYTORCH C++ FRONTEND 更詳細的介紹,將會緊接在後。

夠用就好的 CMake 教學

誠如在前文所提及的,CMake 是一個自動編譯,測試和 packaging 的系統。一個尋常的 CMake workflow 如下:

  1. 在專案資料夾下建立 CMakeLists.txt 檔案
  2. 呼叫命令 cmake <CMakeLists.txt 所在的資料夾>。這一步驟 cmake 會走訪整個專案資料夾,並讀取在子資料夾的 CMakeLists.txt。完成後會自動產生幾個重要的資料夾和檔案,部分列舉如下:
    1. CMakeCache.txt: 這會紀錄 configuration 的結果
    2. CMakeFile 資料夾
    3. cmake_install.cmake: 編譯完成的安裝檔
    4. Makefile
    5. 最後還有一個 <project name>的資料夾
  3. 呼叫命令 make <Makefile > 進行編譯。

命令列的使用方法:

  1. cmake 的命令列使用方式為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

在上面的例子中:

  1. 覆寫了 CXX 的內容,改以 clang++ 作為 c++ 的編譯器(指定 CMAKE_CXX_COMPILER property)
  2. 覆寫了 CXX_FLAGS 的內容,改以 "-std=c++11 -stdlib=libc++" 來編譯(指定CMAKE_CXX_FLAGS
  3. 最後,為了能讓在 cmake configure 時產生的檔案都能集中在一起,所以通常會在專案資料夾最頂端建立 build 資料夾,並在 build 資料夾呼叫 cmake 命令。下方是用於這個例子的專案檔案結構:
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

CMake 如何除錯:

  1. 透過使用 SET 語法, set(CMAKE_VERBOSE_MAKEFILE TRUE),可以在 CMakeLists.txt 中將 CMAKE_VERBOSE_MAKEFILE 屬性設為 TRUE 。一旦 CMAKE_VERBOSE_MAKEFILE 為 True,可以讓 make 在執行時是 verbose output。
  2. 在呼叫 cmake 命令時,一併使用 --trace / --trace-expand,這兩個參數會鉅細靡遺的紀錄 cmake 如何被呼叫,和處理 CMakeLists.txt 的設定

CMake 語言

在撰寫 CMakeLists.txt 時,必須要使用 CMake 自己的語言,這些語言可以依功能分為以下兩類:Project commandScripting command

Project command:

這類的命令都是屬於 project-scope 的命令,主要提供建立與專案編譯相關的指令。常見的指令列舉如下:

包括處理檔案相關,如增加原始檔,專案的執行樹和安裝時的架構。常見的指令為:

  1. add_sub_directories(<source code folder>) 增加一個檔案夾到專案內。要記住加入成為專案的子資料夾,其資料夾內需要包含 CMakeList.txt

增加編譯目標,包括:

  1. add_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。
  2. add_library(TARGET_NAME [STATIC | SHARED | MODULE] SOURCES):這個命令會生成可供多個執行檔使用的 library。目標型態包括了:
    • 在執行檔編譯時寫入參考的靜態(STATIC) 函式庫
    • 執行檔執行時參考的動態或共享(SHARED) 函式庫
    • 執行檔使用 dlopen 的方式進行參考的模組 (MODULE) 函式庫。
  3. 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。此類常見的指令如下:

  1. target_compile_definitions(TARGET_NAME INTERFACE|PUBLIC|PRIVATE DEFINITION):這個指令會產生定義,如編譯時使用 -D 的選項產生定義。一個常用的例子應該是:target_compile_definitions(foo PUBLIC DEBUG),這個命令會定義 DEBUG 符號。
  2. 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 的時候。
  3. 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 時給予值。
  4. target_include_directories(TARGET_NAME INTERFACE|PUBLIC|PRIVATE DIR):編譯名為 TARGET_NAME 的 target 時,會增加 DIR 目錄到標頭檔的搜尋路徑中.

在上列 target_* 的指令中,我們都可以看到有三個選項可以選擇,分別是 INTERFACE,PUBLIC,PRIVATE。這三個選項分別對應著三種模式,每一種模式都對應設定檔的 property scope。讀者可以從下表得到更清楚的對應:

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" 的編譯目標所用。

Scripting command:

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()

在上面的程式碼用到了下列幾個命令

  1. OPTION(OPTION_NAME HELP_MESSAGE DEFAULT_VALUE):建立使用者選項參數,在例子中會建立一個使用者參數,名為 DOWNLOAD_MNIST,預設為 ON。
  2. message([MODE] "message to display"):message 可以看做是 CMake 的 print。MODE 必須填入 CMake 提供的常數選項,比較常用的包括了 none(未給定), STATUS, WARNING 和 FATAL_ERROR。模式的不同會決定輸出的目的地,若是 STATUS 則會輸出於 stdout,其他則會輸出於 stderr
  3. 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


上一篇
Day 10: 利用 numpy 和 scipy 客製化 PyTorch 模型
下一篇
Day 12: PyTorch C++ front-end API
系列文
深度學習裡的冰與火之歌 : Tensorflow vs PyTorch31

尚未有邦友留言

立即登入留言