2022年2月28日 星期一

ARM SVE 研讀筆記 Part-4 - Predicate-driven loop control and management 與 Vector partitioning & software-managed speculation

Predication-driven loop control and management

Fig. 1 - Part-3 中使用的 ARM 官方 SVE 範例

事實上在 Part-3 的 ARM 官方範例中, 就已經展示了這個特性, 在 ARM SVE 中提供了 WHILELT 與 WHILELE 指令來作為遞增 for loop 中的 > 與 >= 的比較, 並產生對應所需的 predication 結果來處理在一般 SIMD 當中較不方便處理的 loop 結尾, 這是由於 loop 的結尾所需處理的數量很可能並非是 vector-size 的倍數, 而記憶體存取的位置也很可能並非 vector-size aligned. 在範例中可以看到使用了 svwhilelt_b64, 這即為 SVE 提供的 loop control / management 功能. 在 ARM SVE  中針對處理資料寬度提供了各自對應的 intrinsic functions:

  • 8bit - svwhilelt_b8 / svwhilele_b8 / svcntb 
  • 16b -  svwhilelt_b16 / svwhilele_b16 / svcnth
  • 32b - svwhilelt_b32 / svwhilele_b32 / svcntw
  • 64b - svwhilelt_b64 / svwhilele_b64 / svcntd

而在 ARM SVE2 中針對遞減 for loop 增加了 WHILEGT 與 WHILEGE, 很直覺地這是用來處理 loop control 中的 < 與 <= 的比較.

  • 8bit - svwhilegt_b8 / svwhilege_b8 
  • 16b -  svwhilegt_b16 / svwhilege_b16
  • 32b - svwhilegt_b32 / svwhilege_b32
  • 64b - svwhilegt_b64 / svwhilege_b64

Vector partitioning & software-managed speculation

在許多時候 buffer 的長度並非 vector size 的倍數, 這造成 vector load 的行為可能會超過 buffer 邊界, 或是要處理的資料的長度未知, 可能在處理過程中造成 memory fault 的行為. 而在讀取過程中, 可能有因計算需求的其他對應的 vector load. 對於這樣的需求, ARM SVE 導入了 First-Faulting 與 Non-Faulting vector load 的設計.

Fig. 2 - ARM SVE 使用的 register set

在 ARM SVE 的 register set 中有著一個不起眼的 register - first-fault register (FFR), 其寬度與 predicate register 相同. FFR 的目的在於存放執行 first-faulting or non-faulting vector load 中每個 lane 成功與否的結果. 

Fig. 3 - first-fault register 運作機制

而 first-faulting 與 non-faulting 的差異在於 first-faulting vector load 的 VLDFF1 系列指令中, 只有第一個 active lane 會產生造成例外情形的 memory fault. 而 non-faulting vector load 的 VLDNF1 系列指令按照字義上而言就是不會產生 memory fault 的指令. 此外, 無論使用 first-faulting 或是 non-faulting vector load 的時候, 在 FFR 中每個 lane 對應的值若事先已被設為 0 / false, 那麼該 lane 的結果將會是 unknown 狀態. 

Fig. 4 - First-Faulting Vector Load 範例

在 Fig. 4 中為 ARM C Language Extension 對於 first-faulting 的範例. 一開始執行 SETFFR 之後, FFR 的所有值都會為 1/true, 接著連續執行三次 LDFF1 指令, 將指定資料讀取至 Z0 ~ Z3. 最後透過 RDFFR 指令將 FFR 的內容存放至 P3 當中. 而 P3 所存放的數值表示哪些 lanes 在 Z0 ~ Z3 當中皆為 valid 狀態. 此資訊對於一些不定數目但需要合併處理的情況相當有幫助.


3/21 - 感謝來自 FB 臉友 王北北 的指正, SVE 與 SVE2 支援的 WHILELT&WHILELE 與 WHILEGT&WHILEGE 一時不察寫反了, 已修正

2022年2月26日 星期六

ARM SVE 研讀筆記 Part-3 - Per-lane predication

在切入 ARM SVE 第二個特性 - Per-lane predication 之前, 讓我們先以 ARM 官方 SVE 範例程式為例, 說明 SVE 的  C/C++ 程式看起來會是怎樣的型態.

使用 SIMD Intrinsics 依然是性價比相當高的方式, 除了可以直接與 C/C++ 程式碼混合撰寫外, 也避免了直接使用 assembly 撰寫的曠日費時與難以除錯. 當然使用 SIMD intrinsics 的缺點是可攜性與跨平台問題. 這點就在先前文章多少提及, 在此就不綴述. 這裡讓我們以第一個範例來切入 SVE intrinsics 的使用

Fig. 1 - SVE  intrinsics 使用範例程式 double dot

Fig. 1 中的程式來自 ARM 官方 SVE intrinsics 的範例, 這當中有幾個重點:

  • 首先是第一行藍色標示的部份, "arm_sve.h" 的使用對 SVE intrinsics 是必要的
  • 在上一篇有談到 SVE vector type 型別的使用來宣告變數, 上圖中以草綠色標示
  • 如同一般的 ARM NEON 程式, 呼叫所需要的 intrinsic function, 這裡以暗紅色標示. 值得一提的是, 雖然相較於 NEON intrinsics,  SVE 增加了 postfix 來做特定用途. 但多數基本功能, 都可以將 ARM NEON intrinsics 原有的名稱在開頭加上一個 "s" 並且移除用來判別處理 64/128-bit 寬度的 "q", 就可以索引到所需的功能. 例如, 原本做 float 型別的vector element 總和會使用 vaddvq_f32, 這裡就可以去搜尋到 Fig. 2 中所列出的 svaddv_f32
  • 最後是紫色標示的部份, 這也是本文要說明的 Per-lane predication, 在 ARM SVE 中新增了在 DSP SIMD 中常見的 Per-lane predication, 這能用來做更為細緻的控制, 來減少在程式撰寫中因 SIMD 限制而不得不以 scalar code 來處理的部份. 這個範例精簡地展示了 predicate 用在 SVE 中的 3 個 predication 特性 - 本文的 per-lane predication 與後續的Predicate-driven loop control and management 與 Vector partitioning & software-managed speculation.
Fig. 2 - 透過 NEON vaddvq_f32 能找到的 SVE intrinsics 系列

所謂的 Per-lane predication 功能是指在 SIMD 中在套用的操作上, 對於每個 lane 提供一個 bit 來做 active or inactive 的控制. 而 active lane 即表示需要直接套用所使用的操作. 對於 inactive lane 部份則有幾個模式, 這點會在稍候解釋.

Fig. 3 - predication 圖示

在 ARM SVE 的設計上對於多數 intrinsic function 都加入了 svbool_t 型別的參數 pg, 這即是用來作為 lane control 的 predication. 其基本概念如 Fig. 3 所示, 一旦該 lane predication 值為 1/true 的即表示該 lane 狀態是 active; 而若該 lane 的 predication 值為 0/false 的, 該 lane 狀態即為 inactive. 若 intrinsic function 沒有特別的 postfix, 那麼 inactive lane 則是維持原本的數值.

Fig. 4 - Predication register 對應 Vector register 的方式.

若對 Part-1 的內容還有印象的話, ARM SVE 的 vector register 為 128b 的倍數, 可為 128~2048b 的任何選擇. 而 predication register 為 16b 的倍數, 可為 16~256b 的任何選擇. 但 predication register 與 vector register 長度的關係是連動的. 一旦決定了 ARM SVE 中關於 vector length 的 LEN 參數, 就固定了兩者的長度. 這也表示 vector / predication 是以 8:1 的比例對應的. 對於 8b 以上的型別會有額外的 bit. 這時的選擇方式如 Fig. 4 所圖解的, 對於 16/32/64b 的 data types, 是以對應的多個 bits 之中最低位的 bit 來控制該 lane.
Fig. 5 - Predication 產生與操作的方式

到此可以輕易地理解, Predication 通常是用來處理 SIMD 中通常難以處理的 if/else 或是 switch case 等等的流程. 因此 predication 的產生最直觀的就是對應 C++ boolean type 中的許多 logical operations - 舉凡數值間各種比較運算 !=, ==, <, <= , >, >= 與 boolean type 間各類 !, &&, || 等等.

Fig. 6 - Inactive Lane 的模式

最後是 inactive lane 的動作, 依照所使用的 intrinsic function 共有 3 種, 也就是在一些 intrinsic functions 的名稱中在 data type 之後還可以看到 "_z", "_m" 或是 "_z" 的 postfix. 這是對於 inactive lane 模式的選擇. 使用了 "_z" 結尾的 intrinsic function 表示了希望 inactive lane 的數值填為 0; 而使用了 "_m" 結尾的 function 則是代表維持原本輸入的第一個 vector 參數並不做任何的更動; 最後是 "_x", 這表示不在意 inactive lane 的數值, 讓 compiler 去針對 compilation 參數(performance or code size)去作較佳的選擇( _z, _m 或是甚至不使用 predication ), 一旦使用 "_x" 的 intrinsic function 必須清楚地知道, inactive lane 當中並沒有預設或保證當中為何值.

下一篇將延續 Per-lane predication 來同時講述 Predicate-driven loop control and management 與 Vector partitioning & software-managed speculation.



ARM SVE 研讀筆記 Part-2 - Vector Length Agnostic (VLA)

個人認為 Vector Length Agnostic (VLA)  是討論 SVE 時必須先討論的特性. 談到這特性必須先了解多數的 SIMD 指令集都有固定使用的 vector width/length, 像是 intel 對於 x86 平台的 SIMD 發展過程, 從 MMX 的 64-bit 開始, 一路發展 SSE / 128-bit , AVX & AVX2 / 256-bit 到最近的 AVX-512 / 512-bit 每一組都有固定的 vector length, 但是即便使用相同的運算, 對於使用了不同的 SIMD instruction set, 這也必須對應到 CPU 內不同的 opcode. 因此使用舊的 SIMD instruction 的程式並無法受益於新一代 SIMD ISA 的好處.

而 ARM SVE 在制定時一開始就允許 IC 商能夠依照應用的需求的考量, 去配置硬體內的 SIMD vector length. 另外制定 SVE 專屬的 VLA vector type 與 intrinsics, 讓開發者能夠達到撰寫所撰寫的 ARM SVE 程式無需特別針對 vector length 改寫或重新編譯, 即可執行運作. 而 IC vendor 也能更為安心地藉由增加 vector length 達到效能增加而無需擔心程式的重新客製問題.

Fig. 1 - Vector Length Agnostic 的圖解與程式特性

類似於其他各 SIMD 指令集的 C intrinsics, 需要對應的 vector type 與 intrinsics function. ARM 對於 SVE,  也提供了對應的 vector type 以及 function 介面. 如 Fig. 2 中所列出的, 其 vector type 涵蓋了基本的 8/16/32/64-bit signed / unsigned int 與 float / double 等等, 然而還增加了 boolean, float16 與 bfloat16 等等新的型別.
Fig. 2 - ARM SVE intrinsics提供的 vector types

然而這些 Vector types 與其他 SIMD intrinsics 所提供的最大不同是這些是 "sizeless", 也就是對於 compiler 而言,  這些 vector types 並沒有俱備任何 size 資訊. 於第一時間或許覺得沒大問題, 但是事實上 VLA 帶來了許多在語言特性以及工具使用上的限制: 

Fig. 3 - Sizeless 帶來的實務限制

如 Fig. 3 所列出的, VLA 帶來的限制主要都是因為缺乏 size 資訊造成需要此資訊的語言特性使用的限制. 這些包含了無法去使用 sizeof / alignment, pointer arithmetic. 比較不直覺的像是無法作為 union / struct / class 的 member field 與宣告用來存放的 STL container , 另外無法成為 exception handling 中 throw / catch 機制的 object. 最後是 lambda 中 capature 機制無法 capture by value. (但是能夠使用 capture by reference )

 

2022年2月25日 星期五

Halide 實務心得 5

在實作影像處理演算的過程或是實務的功能整合中, 都圍繞著 Halide::Runtime:: Buffer 的使用. 有時難以避免需要初始化預設值或是從其他 Buffer 物件複製資料. 這些可以透過 Buffer::fill() 或是 Buffer::copy_from 來做基本的填值處理.

對於一些相當 routine 但是卻又需要計算的部份,  通常會透過 loop 來走過每個座標, 甚至每個一個 value. 像是做簡單的 gamma correction 可能就會做這樣的動作:

Buffer<uint8_t, 2> src = Buffer<uint8_t>(w, h);
...
Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);
for(int y = 0; y < dst.height(); y++){
    for(int x = 0; x < dst.width(); x++){
        for(int c = 0; c < dst.channel(); c++){
            dst(x, y, c) = gamma(src(x, y, c));
        }
    }
}

或是可能是格式轉換 (像是 RGB <-> YUV 或是這裡的 Demosaic):

Buffer<uint8_t, 2> src = Buffer<uint8_t>(w, h);
...
Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);
for(int y = 0; y < dst.height(); y++){
    for(int x = 0; x < dst.width(); x++){
        uint8_t r, g, b;
        demosaic(src(x, y), r, g, b);
        dst(x, y, 0) = r;
        dst(x, y, 1) = g;
        dst(x, y, 2) = b;
    }
}

一旦數量很多, 或是 dimension 很大一一撰寫 loop 除了費時費力外, 也讓 code 不易維護與可讀性不佳. 對於這種問題, Halide 透過了 C++ lambda 的方式能夠簡短地處理, Halide Buffer 提供了兩個在其官方教學沒有提到的兩個實用的方式: for_each_valuefor_each_element :

對於 Gamma 的例子可以簡短地改寫為:

Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);
dst.for_each_value([&](uint8_t &dval, uint8_t &sval){
    dval = gamma(sval);
}, src);

而對於像 demosaic 的例子則可以改寫為:

Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);
dst.for_each_element([&](int x, int y){
        uint8_t r, g, b;
        demosaic(src(x, y), r, g, b);
        dst(x, y, 0) = r;
        dst(x, y, 1) = g;
        dst(x, y, 2) = b;
});

 這樣的作法也適合套用在一些特殊的初始化.

2022年2月21日 星期一

How to make "Dark mode" listed in Facebook APP on Android Tablet

I've been annoyed by Facebook's dark mode on my Android tablet for a long time. 
After one of the update several months ago, my Facebook app doesn't list "dark mode" on menu any more. All articles on the internet said you can find it under "Setting & Privacy" of burger menu. But I just can't find it for a long time


Today I accidentally found the root cause. Therefore I write this to share my finding in English to let more people know about the tricky thing. It's all about display setting. If you have the same issue, please check your "Display size" in the "Display" menu of Settings. 

For some Android tablet users, they want to show more on the screen, so it would be changed to "default", "small", "smaller", or "smallest". There is no identical setting that will make dark mode be shown on menu. Just try "default", "large", "larger" or "largest" one bye one. After eche changing of the setting,  please remember to kill Facebook app and the relaunch it. Then you will find a setting that works. (According to my experience, the "middle" one will be the first working setting.)


Hope this article would be helpful for some people.

2022年2月16日 星期三

ARM SVE 研讀筆記 Part-1 - ARM SVE 基本概述

ARM 於 ARMv8.2 時期開發了新一代 SIMD 指令集, 以選擇性擴充的方式來接替已使用多年的 NEON, 並在 ARMv9 中預設涵蓋, 該 SIMD ISA 全名為 Scalable Vector Extension 簡寫為 SVE. 這中中最著名的使用案例是與富士合作的 ARM 超級電腦平台中使用了配置為 512b vector length 的 SVE 實作. 相較 NEON 而言, SVE 除了新增功能特性的設計上, 除了增進便利性, 也同時一併考慮了 SIMD 效能擴充 / 延伸性的問題. 

Fig.1 - ARM SVE vector register 與 NEON register 關係圖.

ARM SVE 的設計最特別的地方在於它採用了不同於現今主流 CPU / DSP 皆以 fixed vector length 來制定出一組 SIMD 指令集的方式. 在設計上採用了硬體實作中可對於內部 vector length 作可調整配置的方式, 這也是名稱上使用了 "Scalable" 一詞的原因. ARM SVE 允許硬體商因應用效能上需求的考量而去決定 vector length, 越長的 vector 因為需要更多的 ALU 來提供 ILP, 也會直接地反映在 die area 上. 而 ARM SVE 的 vector length 選擇上必須是介於 128~2048b 而且為 128b 的倍數.

Fig. 2 - ARM SVE 新增的 predicate register.
設計上與 ARM NEON 的另一個重大差異, 在於 ARM SVE 導入 16 個 predicate register.  這是用在於 DSP SIMD 中相當常見的 Per-lane predication 用途上. 在此必須特別說明的是於 Fig. 2 中的 LEN 參數與 Fig. 1 的是連動的相同數值, 這表示 IC vendor 選擇了 LEN 就同時固定了 vector 與 predicate register 的長度, 因此兩者長度的比例固定為 8:1. 在 ARM SVE 中透過使用  predicate, 能夠精確的控制個別資料的計算與否, 如此處理一些 if/else 等等的狀況會更為便利. 由於 SVE 的進階功能都圍繞著 predication 的使用, 因而 SVE 也有著對 predication 運算操作的指令.
Fig. 3 - 總結 ARM SVE 的重大特性

個人統整認為 SVE 的主要特性有 1(個人認為應該加入) + 5, 分別是:

  • Vector Length Agnostic (VLA)
  • Gather-load and Scatter-Store
  • Per-lane predication
  • Predicate-driven loop control and managment
  • Vector partitioning for software-managed speculation
  • Extended floating-point and bitwise horizontal reductions.

後續陸續將這些心得一一分享. 下一篇將介紹說明 Vector Length Agnostic (VLA)

2022年2月9日 星期三

Halide 實務心得 4

這陣子因為同事在弄 Hexagon DSP 且需要實作 Laplacian Pyramid 來完成所需功能, 由於這是 Halide 當初推出時 paper 內說明的範例, 所以搜尋了一下檔名, 建議他參考 Halide git repo 內的 Laplacian 範例.

今日與演算開發者開會討論時, 這位同事建議演算開發者考慮去使用 Halide language, 並且以當中的 downsample 為範例解說給演算開發的同事聽. 這時我注意到了 downsample 中有著先前沒看過, 另外也沒在任何 Halide 文件 / 教學文件提到的東西:

using Halide::_;

最有趣的是透過使用 "_" 所實作的 downsample Funciton:

Func downsample(Func f)
{
    using Halide::_;
    Func downx, downy;
    downx(x, y, _) = (
        f(2 * x - 1, y, _) +
        3.0f * (f(2 * x, y, _) + f(2 * x + 1, y, _)) +
        f(2 * x + 2, y, _)
    ) / 8.0f;
    downy(x, y, _) = (
        downx(x, 2 * y - 1, _) +
        3.0f * (downx(x, 2 * y, _) + downx(x, 2 * y + 1, _)) +
        downx(x, 2 * y + 2, _)
    ) / 8.0f;
    return downy;
}

很明顯地, "Halide::_" 是用來處理 function 內 argument / dimension 不定數量的問題. 然而詳細的使用應該有正式的解說, 經過搜尋後發現在 Halide 文件中的 Var 當中, 這個功能稱為 Implicit Variable Constructor. 文件中的解釋為: "Implicit variables are injected automatically into a function call if the number of arguments to the function are fewer than its dimensionality and a placeholder ("_") appears in its argument list. Defining a function to equal an expression containing implicit variables similarly appends those implicit variables, in the same order, to the left-hand-side of the definition where the placeholder ('_') appears." 這也就是說在 function 呼叫若使用了 "_" 在參數 list 中 , 一旦參數的數目少於函數所需參數, 將會自動對應遞補足. 透過使用 implicit variable "_" 來定義一個函數, 這表示函數有著以 "_" 出現所代表的參數 list. 

透過 implicit variable constructor 如此可以無論傳進的 Func f 有幾個參數, 像是對於影像處理的函數而言, 最前面兩個參數能固定被使用作為座標的 (x, y), 而對應的維度在處理時將直接補足對應. 因此無論純 2D 黑白影像可以套用外, 3D (有3個參數, 分別為 Width, Height, Channels) 的 RGB / RGBA / YUV444 等等影像都可以直接套用這個 downsample 來處理, 更重要的是參數更多的更高維度資料都可以因此而透過單一實作來處理. 另外應可以搭配 RDom / RVar 來使用, 像是上述程式碼中的 downx / downy 的範圍, 搭配 RDom 來使用, 應該能實作出更為簡潔的 Func 表示方式.


Chisel 學習筆記 - Scala 與 Chisel 基礎語法

標題為筆記, 但這篇比較屬於心得 延續 上一篇 的環境建立, 這次計劃藉由 Jserv 最新的 課程安排 來學習 Chisel, 當然個人目標是能夠按照 Jserv 的課程規劃在 期限之內 完成 Lab 3, 由於個人並非 digital designer (現在這年紀也算老貓學...