2018年3月15日 星期四

Halide - 實務心得 1

近日著手開始以 Halide 撰寫 AOT 程式 發現有一些特別的東西沒有寫在教學文件上

 

 1. buffer pointer of Halide::Runtime::Buffer

宣告一個 Buffer object 並不困難像是:
Halide::Runtime::Buffer buf(640, 480);
而在教學文件中很常看到 Buffer 的使用 
但是範例上常常是直接載入 jpg/png 圖檔, 
或是透過下列的方式來讀取或寫入 (x, y) 位置的數值: (見 Lesson 10 part 2)
buf(x, y) = val

而對於 C/C++ 而言是可以取得 buffer pointer 的
取得方式也很簡單, 透過 Buffer::data() 這個成員函式的呼叫
以上述例子就是:
unsigned char* buf_ptr = (unsigned char*)(buf.data());
使用由 data() 取得的 pointer 就必須注意 layout, 預設的是 Planar
若需要使用 interleaving data 則要透過 set_stride  來設定 (見 Lesson 16)

2. Buffer allocation

儘管一般開發 Halide 程式使用的裝置離不開 CPU/GPU
但是 Halide 俱備介面, 只要實作 code generation 與 runtime interface 就可以接上
以 Qualcomm HVX 來說, DSP backend 的 Buffer 使用與 CPU 有相當不同
透過的是 Buffer::device_malloc() 來配置特定計算裝置的記憶體
並以此避免 memcpy 的呼叫, 來達到 zero-copy 的目的
而 device_malloc 需要帶入 Device API 的 interface
像是在 Qualcomm 官方 Halide 文件中提到 Qualcomm Hexagon DSP buffer 配置的呼叫為:
buf.device_malloc(halide_hexagon_device_interface());
如此即可配置針對 Hexagon DSP 所使用的記憶體空間, 並且無需 data copy

3. Prefetch schedule directive

計算單元與記憶體間資料的傳輸方式一直是改善效能的重點
個人經驗是撰寫 SIMD code 透過 prefetch 能改善不少效能
其他像是 GPU 俱備 local/shared memory,
有些 DSP 也有著自己的 scratchpad memory
透過重疊(overlapping) 執行時間與傳輸時間
這部分都牽涉到 2D 的 Prefetch 或 DMA 的呼叫使用

在 2015 年剛接觸 Halide 時, 與當時熟悉 Halide 同事討論
當時給的答覆是並無法對應 prefetch 這樣的行為
而日前第一時間在 Qualcomm Halide SDK 中的範例看到 prefetch 排程指令時:
(像是 Halide SDK 中  HALIDE_Tools/2.0/Halide/hexagon_benchmarks/gaussian5x5_generator.cpp )
output.hexagon().prefetch(input, y, 2) ... ;
個人一度以為這只是 Qualcomm 為了 Hexagon DSP 而客製的 API
然而近日發現 Halide 中的 Prefetch.h說明文件
又挖了 History 後才發現在 2016 年中 Halide 已加入了對於 prefetch 行為的支援
這也讓 Halide 能夠更彈性的支援不同處理器的運作特性
而類似於 data 在 Boundary 的處理 Prefetch 也有 PrefetchBoundStrategy

4. AOT target

在 Lesson 15 並沒有提及 Generator Enhancements
在後續 Halide 對於 Generator 分為 generate() 與 schedule()
顧名思義, generate() 是用來定義 pipeline 的, 而 schedule() 是處理排程的
若希望有效地支援不同平台, 就需要知道 code generation target 的特性
這裡主要透過二個 API 與二個 public member
* Halide::GeneratorContext::get_target() - 這可以得到 Halide::Target 的物件, 透過這物件可以獲得相關資訊, 最直接的就是 Target::to_string() 可以得到指令傳入的 target 字串
* Halide::Target::has_feature() - 可以確認是否支援某些特性像是 SSE/AVX, ARMv7s or HVX
* Halide::Target.arch - X86, ARM, Hexagon ...
* Halide::Target.os - Linux, Windows ...
如此可以透過條件判別來針對平台特性呼叫在 schedule() 中呼叫對應的排程指令


簡短列出這些日子看到的有趣東西, 一方面也做個紀錄


沒有留言:

在 ARM 平台上使用 Function Multi-Versioning (FMV) - 以使用 Android NDK 為例

Function Multi-Versioning (FMV) 過往的 CPU 發展歷程中, x86 平台由於因應各種應用需求的提出, 而陸陸續續加入了不同的指令集, 此外也可能因為針對市場做等級區隔, 支援的數量與種類也不等. 在 Linux 平台上這些 CPU 資訊可以透過...