2024年6月15日 星期六

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

Function Multi-Versioning (FMV)

過往的 CPU 發展歷程中, x86 平台由於因應各種應用需求的提出, 而陸陸續續加入了不同的指令集, 此外也可能因為針對市場做等級區隔, 支援的數量與種類也不等. 在 Linux 平台上這些 CPU 資訊可以透過 /proc/cpuinfo 取得. 而對於 CPU 不同的指令集支援去實作軟體最佳化, 是相當繁複的工作. 而這些在過去分享過的 Function Multi-Versioning (FMV) 的出現, 而大幅地簡化.

隨著時間過去, 這十年來發展迅速的 ARM 平台, 在 ARMv8 之後不斷作小幅度的翻新, 也逐漸產生類似 x86 平台上的問題. 以 A-Profile 處理架構而言從 ARMv8.0-A 一路發展到 ARMv9.4-A. 當中有著許多新硬體特性與新增指令, 有些是 core feature 而有些則是 optional, 這些也逐漸造成了 ARM CPU 平台功能性上大小不等的碎片化. 在 ARM 官網有提供了一份 "Feature names in A-profile architecture" 列表. 而這整理僅止於 ARMv9 之前. 此外更麻煩的地方是, 各大 compiler 在先前一直沒有對應支援 FMV 的功能, 如此對於通用的應用程式, 想要能夠有效使用 ARM 這些功能特性同時顧及不支援的平台, 著實是一件不簡單的事.

而事情在 2023 年中開始有了轉機, 在當時所釋出的 Clang / LLVM 16.0 開始在 ARM 平台上支援  FMV 功能. ARM 官方甚至在其系列文的 "What's new in LLVM 16" 中有特別舉例說明 FMV 的使用. 一旦能夠使用 FMV, 這對於不確定運作的目標平台的應用程式開發很有幫助. 但在當時個人第一時間找不到具有支援的 llvm toolchain, 而之後的這段時間因工作繁忙一直沒有時間再繼續做嘗試, 也就沒辦法撰寫心得. 近日回想起心中所惦記的這麼一件事, 於是特別拿了最新的 Android NDK 26d (附的是 clang 17.0.2) 來測試.

Case Study - FP16 GEMM w/ Vector Extension, Gather Auto-Vectorization

首先請安裝 Android NDK 26d 到 /opt 目錄內, 並將 /opt/android-ndk-r26d/toolchains/llvm/prebuilt/linux-x86_64/bin/ 加入 PATH 環境變數中.

由於個人在過去所分享的教材投影片中即談論過 FMV, 本文中就不再多做詳細介紹, 有需要可以參考過去的投影片, 在這裡就透過兩個實際的例子來嘗試 ARM FMV 功能, 第一個 Case 是個人常拿來作為 Compiler Vector Extension 的 Laboratory 範例的 Matrix Multiplication, 這個範例可以顯示出使用 Compiler Vector Extension 搭配 FMV 可以共伴產生廣泛支援又高效的程式碼:

typedef __fp16 fp16_16 __attribute__((ext_vector_type(16)));
#ifdef __HAVE_FUNCTION_MULTI_VERSIONING
__attribute__((target_clones("sve2", "simd")))
#endif
void gemm_vec(
    // buffer, stride
    __bf16 *a, int sa,
    __bf16 *b, int sb,
    __bf16 *c, int sc
){
    fp16_16 vb[16];
    for(int y = 0; y < 16; y++){
        vb[y] = *((fp16_16*)(b + sb*y));
    }
    for(int y = 0; y < 16; y++){
        fp16_16 vc = *((fp16_16*)(c + sc*y));
        fp16_16 va = *((fp16_16*)(a + sa*y));
        for(int x = 0; x < 16; x++)
            vc += va[x]*vb[x];
        *((fp16_16*)(c + sc*y)) = vc;
    }
}

這個範例中, 為了能產生較大的 code generation 的差異, 個人選擇使用 float16 的資料型別. 主要是因為 ARMv8 並非預設支援 fp16, 而是到了 ARMv8.2 以 optional extension 來提供 (而預設支援 SVE2 的 ARMv9 是預設支援的). 將上述這段程式令存為 arm_gemm.c 之後搭配下列指令編譯:

$ aarch64-linux-android34-clang -O3 --save-temps -c arm_gemm.c
如此當中可以觀察產生的 arm_gemm.s 檔案, 當中有 gemm_vec._Msve2 與 gemm_vec._Msimd 兩段程式, 以及用來做選擇的 gemm_vec.resolver, 可以觀察 sve 版本直接使用支援 fp16 的 fmul, 而 AdvSIMD 版本當中不斷使用 fcvt 指令來處理浮點數轉換, 行數落差也相當的大.


接著的第二個範例中是透過 auto-vectorization 來實際展示 ARM 官網上的 "Auto-vectorization examples" 內關於 Scatter and Gather 的部份, 而為了能確實透過 clang/llvm 的組合產生使用 gather 指令, 該段程式經過稍加修改後程式碼如下:

#include <stdint.h>
#ifdef __HAVE_FUNCTION_MULTI_VERSIONING
__attribute__((target_clones("sve2", "simd")))
#endif
float gather_reduce(float *restrict data, int *restrict indices, long c)
{
    float r = 0;
    #pragma clang loop vectorize(enable)
    for (long i = 0; i < c; i++) {
        r += data[indices[i]];
    }
    return r;
}

將上述這段程式令存為 arm_gather.c 之後搭配下列指令編譯:

$ aarch64-linux-android34-clang -O3 --save-temps -c arm_gather.c
同樣地, 透過觀察編譯過程中產生的 arm_gather.s 檔案, 當中有 gather_reduce._Msve2 與 gather_reduce._Msimd 的兩段程式碼, 從中可以看到 SVE 的版本的 gather_reduce function 確實有使用 L1DW 這個 SVE 指令集所專屬的 gather 指令.

ARM Feature List

最後談談 ARM 與 x86 平台在 FMV 在實務使用中不同的地方. 在 ARM 平台的 FMV target 僅止為 feature, 而 x86 FMV 的 target 則能夠是特定的 CPU arch, 特定的 CPU core 或是指定的 ISA. 那麼具體來說在 ARM 上 FMV 有哪些 feature 可以選擇呢? 可以參考ARM 這份 ACLE 文件, 當中有提供對應的 feature 列表與在指定 FMV target 的名稱, 另外如以往的 FMV target 的使用, 這些 feature 是可以透過 + 來合併選擇使用的, 像是 "sve2+memtag" 來合併使用 SVE2 與 Memory Tagging, 同樣地一如以往 "default" 表示的是基本的指令集, 產生完全不會使用到 feature 的 code.

2023年11月12日 星期日

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

標題為筆記, 但這篇比較屬於心得

延續上一篇的環境建立, 這次計劃藉由 Jserv 最新的課程安排來學習 Chisel, 當然個人目標是能夠按照 Jserv 的課程規劃在期限之內完成 Lab 3, 由於個人並非 digital designer (現在這年紀也算老貓學新把戲...), 因此對於 chisel 的一切也是從基本開始, 目前計劃於一週內按照 Chisel Bootcamp 的章節完成 Chap 0 ~ 3 的所有內容. (Chap 4 部份在後續會依照進階需求選擇回來看)

週末花了點時間把 Chap 0 ~ 2 的說明研讀完, code block 的操作也跑過一次流程, 另外是在 try and error 下完成當中的所有 exercise & test. 個人覺得 Chisel Bootcamp 多少是面向俱備 Verilog / HDL 有些許基礎的人. 儘管無基礎並不會造成學習的困難, 但多少難以體會 Chisel 相較之下的優點與強項.

基本上Chap 0 ~ 2 的重點其實並不多

  • Chisel 的特性
  • Scala 的基本語法
  • 建構於 Scala 之上的 Chisel DSL
  • 電路的基本: module, combinational, control & sequential logic
  • 除錯與測試方式

由於並非第一次學習建構在另一語言的 Domain Specific Language. 過往學習 Halide 的過程就是這樣的一個經驗. 然而與先前 Halide 不一樣的地方是 Halide 所依附的 C++ 個人有所基礎, 但是對於 Scala / Chisel 這樣的組合卻是完全從頭學起. 而這大概有幾個面向是需要反覆思考與刻意練習的.

第一個主要的點在於 Value 與 Typing System, 類似於 C++ 與 Halide 有著各自的 value & typing system, C++ 主要用在撰寫與控成 Halide code 的流程, Halide 體系著重在產生 target code 上. Scala 與 Chisel 也有各自的 value & typing system. Scala 也是用在撰寫 Chisel 主體與流程上, Chisel 是用以定義 hardware 使用到的 module 與產生 instance. Chap 1 ~ 2.1 強調分清楚 Scala 與 Chisel 的 value & typing system 確實是很重要的一件事.

第二個是 Chisel 的實作撰寫必需了解的 3 個階段 - circuit generation, circuit simulation 與 testing. (Halide 也有著類似的階段, 甚至都使用 Generator 這樣的名稱) Scala 與 Chisel 在使用上容易搞混的是 generation, Scala 是讓 code generation 能透過參數化來產生對應不同的 circuit (按照教學說法, 這是 Chisel 強大的地方). 因此這些都是在 compile time 決定的, 而 Chisel 的撰寫則是決定了 circuit generation 的結果. 對於一些 if / else 的思考必須區隔希望影響的是 compile time 的行為抑或者是 runtime 時的所需. (習題上來說, 觀察前後輸入的 data type 就可以快速反應)

儘管這些並不是什麼艱深難懂的心得, 然而困難的點是在熟悉與快速應用 Scala / Chisel. 重要的還是需要刻意練習來讓思維方式有著正確的對應. 若儘只是研讀一番並不足以深刻體會, 或是習得實務上的撰寫能力. Chisel Bootcamp 的作業與測試非常值得一一花時間下去練習. 藉由這些能夠一次次找到認知錯誤的地方或是盲點. 個人認為能嘗試完成 2.2 Arbiter, 2.3 Finite State Machine 與 2.5 FIR Filter Generator 這三個 exercise 應符合了基本要求. ( 2.2 中提供的 Cheatsheet 第一頁對於閱讀內容與作業相當有幫助)


2023年11月10日 星期五

Chisel 學習筆記 - 環境建立

近日 Jserv 在期 2023 年度課程中仿效 UC Berkeley 的課程, 計劃透過課程 Lab 引導學生學習 Chisel 來實作 RISC-V 核心

為此他高度簡化了原本 UC Berkeley 的教程, 建立一個新的 Lab, 目標是帶領學生實作 5-stage pipeline RISC-V processor

Lab 3 的 Hackmd 頁面上可以看到幾個章節:

  • Install sbt
  • Chisel Tutorial
  • Chisel Bootcamp & Chisel 介紹
  • Single-cycle RISC-V CPU

這篇文章的重點是放在建立可攜式的 Scala Build Tool 環境, 由於並非每個人都想在日常使用環境與這類開發環境混在一起, 希望儘可能保持系統的單純與乾淨程度. 所以還是選擇透過 Apptainer 這個 rootless 的 container 技術來建立環境. Apptainer 的安裝相當簡單, 在官網的 Installation 頁面可以找到個別 Linux Distro 的安裝方式. 至於 Apptainer 的使用方式則可以參考個人先前的介紹文.

對於 SBT 於 Linux  上的安裝, 在官網提供了兩個方式, 分別是透過 SDKMAN 與 debian-based repository 的方裝方式, 由於使用 apptainer, 所以我們可以選擇透過 debian-based repository 的方式.

經嘗試後, 下列為建立 apptainer sandbox 的 script

#!/bin/sh export SANDBOX=chisel_env
rm -rf $SANDBOX
rm -rf $SANDBOX-overlay
apptainer build --fakeroot --sandbox $SANDBOX docker://ubuntu:latest
apptainer exec --fakeroot --writable $SANDBOX apt update
apptainer exec --fakeroot --writable $SANDBOX apt dist-upgrade -yqq
apptainer exec --fakeroot --writable $SANDBOX apt install openssh-client openjdk-11-jre-headless openjdk-11-jdk-headless ca-certificates-java apt-transport-https curl gnupg -yqq
apptainer exec --fakeroot --writable $SANDBOX sh -c "echo 'deb https://repo.scala-sbt.org/scalasbt/debian all main' | tee /etc/apt/sources.list.d/sbt.list"
apptainer exec --fakeroot --writable $SANDBOX sh -c "echo 'deb https://repo.scala-sbt.org/scalasbt/debian /' | tee /etc/apt/sources.list.d/sbt_old.list"
apptainer exec --fakeroot --writable $SANDBOX sh -c "curl -sL 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823' | gpg --no-default-keyring --keyring gnupg-ring:/etc/apt/trusted.gpg.d/scalasbt-release.gpg --import"
apptainer exec --fakeroot --writable $SANDBOX chmod 644 /etc/apt/trusted.gpg.d/scalasbt-release.gpg apptainer exec --fakeroot --writable $SANDBOX apt update
apptainer exec --fakeroot --writable $SANDBOX apt install sbt graphviz -yqq mkdir $SANDBOX-overlay
# testing sbt
apptainer exec --overlay $SANDBOX-overlay $SANDBOX sbt --version

完成後可以看到如下的字串

sbt version in this project: 1.9.7
sbt script version: 1.9.7

後續使用方式並不難, 只要提供 overlay 與 sandbox 的路徑即可, 所以可以宣告絕對路徑來使用, 假設 sandbox 與 overlay 的目錄為 ~/workspace/ca2023/ 下, 那麼可以如下先建立環境變數:

$ export SANDBOX_PATH=~/workspace/ca2023/chisel_env
$ export SANDBOX_OVERLAY=~/workspace/ca2023/chisel_env-overlay

如此可以透過下列指令在 chisel-tutorial 來執行 sbt run

$ apptainer exec --overlay $SANDBOX_OVERLAY $SANDBOX_PATH sbt run

當然也可以先執行下列指令, 得到建立環境的 shell 後切到 chisel-tutorial 目錄執行 sbt run

$ apptainer shell --overlay $SANDBOX_OVERLAY $SANDBOX_PATH
Apptainer> cd chisel-tutorial/
Apptainer> sbt run

[info] Loading project definition from /home/champ/workspace/ca2023/chisel-tutorial/project
[info] Loading settings for project chisel-tutorial from build.sbt ...
[info] Set current project to chisel-tutorial (in build file:/home/champ/workspace/ca2023/chisel-tutorial/)
[info] running hello.Hello
[info] [0.001] Elaborating design...
[info] [0.054] Done elaborating.
Computed transform order in: 208.3 ms
Total FIRRTL Compile Time: 261.5 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1699679987261
test Hello Success: 1 tests passed in 6 cycles taking 0.008084 seconds
[info] [0.002] RAN 1 CYCLES PASSED
[success] Total time: 2 s, completed Nov 11, 2023, 5:19:48 AM
Apptainer>

如此就建立好可以使用的 container, 需要清除也很簡單, 刪除 chisel_env 與 chisel_env-overlay 即可


最後對於 Chisel 語言的學習提供的材料是 Chisel Bootcamp, Lab 3 中提供的是 GitHub 上面的兩個方式 - Docker 與 Local Install. 

然而 Chisel Bootcamp 的安裝設定, 並不是 Lab 的主要重點, 個人最建議的方式是直接使用在其 GitHub 主頁面上的 Get Started 一段中提供的 "Try it out HERE! No local installation required!", 如此可以省下安裝設置 Docker 或是 local 安裝 jupyter 碰到各種問題的時間.

2023年11月5日 星期日

OpenCL 1.2 之後(個人觀點中)重要的 feature 與 extension

最近因工作緣故, 而看到了使用 subgroup 特性的實作, 在第一時間個人難以解讀程式碼的目的與原意,  後來看了原始實作與擴充功能的說明文件才得知 subgroup 的功能特性為何. 主要因為個人對於 OpenCL 2.x 印象僅停留在 Shared Virtual Memory. 然而事實上 OpenCL 2.0 到 OpenCL 3.0 這段期間依然有數個實用的功能特性與擴充 對於 OpenCL 3.0 而言, 這些 2.0 以後加入的新 features 與 extensions 都屬於 extension. 因此可以到 The OpenCL™ Extension Specification 頁面一一查看. 以下列出個人覺得實用的 extensions:

Creating a 2D Image From A Buffer

許多時候人們很希望能透過 vload / vstore 來存取 buffer, 又希望能夠使用 GPU 的 texturing unit. 在 OpenCL 1.x 的時代, 通常是透過 ION buffer 來重複 mapping 到 Buffer 與 Image, 並且傳入對應的 kernel 中使用. 基本上這需要 GPU vendor 確認 cache coherency 的問題, 甚至提供對應的 driver 支援. 在 OpenCL 2.0 中將類似功能成為 core feature, 也就是 OpenCL 2.0 中是必要的存在. 透過 cl_khr_image2d_from_buffer 這個 extension, 可以先配置 Buffer 後再以此 Buffer 來建立 Image. 一開始提到相同目的.

Subgroups

儘管 OpenCL 中提供了 Global 與 Local work size 來對整體工作做切割, 而 Local work size 另一個重要的意義是對應的 workgroup size 是能作同步的最小單位. 在 OpenCL 2.1 中新增了這個 core feature. 在 Workgroup 中能夠再切分一個 1D 的 subgroup, 除了 subgroup 層及的 barrier 做同步外, 也提供了 3 個種類的 kernel function: reduce_op, scan_exclusive_op 與 scan_inclusive_op. 這些 operation 對於 reduction 與 integral 這兩類原本 OpenCL 不適合應付的計算提供了有效的介面, 讓 device 能夠使用硬體支援來處理這類的計算.

Mipmaps

在 OpenGL 中支援以硬體產生 Mipmaps 來做 texturing. 可以避免 aliasing. 這裡應該是同等的結構. 但除了做原本 antialiasing 用途外, 不知是否有機會利用這個 pyramid 結構來做 CV 應用.

Integer Dot Product

OpenCL 中原本只支援 float / double 型別的內積 (dot product), 然而現今 NN 應用中 8-bit integer dot product 的需求相當大. 這個 extension 有效的提供了對於 8-bit dot product 硬體加速的介面. 理論上透過 multiplication 搭配 subgroup 就可以得到相同結果, 但若支援此 extension 且俱備硬體加速, 可以得到更高的 computation throughput.


當然 OpenCL 2.x 系列還是有其他像是 SVM, device-side enqueue 與 Pipe 的功能, 有時間再來寫程式並撰文說明這些功能的實際的應用.


2023年10月14日 星期六

OpenCL 在 2023 相關的研讀資料 / 工具

儘管近年已經沒有密集使用 OpenCL

但最近同仁因工作而起的 OpenCL 相關實務討論還是令我相當感興趣與熱衷

雖然 OpenCL 在現今已不是各家密集發展的重點技術標準

然而在有實際應用場景需求, 但沒有更好的新標準, 儘管各家計算硬體開發商缺乏合作默契的情況之下(特別是 Mobile 平台), 繼續良好支援 OpenCL 似乎是實務上的一個共識了

這篇主要的目的是統整一下在 2023 的今日要著手 OpenCL 有哪些資料可以研讀

書籍

由於 OpenCL 3.0 標準是以 OpenCL 1.2 為基底, 因此過往的 OpenCL 圖書內容都還是相當實用. 至於談到 OpenCL 2.0 標準後新增的部份 (個人覺得現今 OpenCL runtime 逐漸完備下 device-side enqueue, subgroup 以及 pipe 都可以把玩一番), 而目前僅有一本參考書目可以研讀 - "Heterogeneous Computing with OpenCL 2.0", 這本書被標為 3rd edition 的原因是前面有著兩版談論 1.1 與 1.2 的兩個版本

標準與相關資訊

OpenCL 的制定組織統整了 OpenCL 相關的資源連結 - OpenCL Resource Guide

若要查找標準相關的資訊, 另外是相關的程式資源, 這裡算是參考資料翻找的入口

各 GPU vendor 提供的 OpenCL Programming Guide

由於各家還是有自己的 OpenCL runtime 與 compiler 實作, 因此在使用與實作 OpenCL 程式還是有各家平台在 Host 與 Device 中需要注意的一些細節. 這裡整理了各家目前提供的文件.

Intel

OpenCL Developer Guide for Intel® Processor Graphics

AMD 

ROCm OpenCL Programming Guide (這是 archive.org 在去年底的備份, 其實不清楚為何 AMD 再最新的文件中將 OpenCL 的部份移除)

Nvidia

NVIDIA OpenCL Best Practices Guide Version 1.0

NVIDIA OpenCL SDK Code Samples

Qualcomm

Qualcomm Snapdragon  Mobile Platform OpenCL General Programming and
Optimization
(原名 Adreno OpenCL Programming Guide)

ARM

Mali GPU Bifrost Valhall OpenCL Developer Guide

Mali Midgard OpenCL Developer Guide

Imagination

A quick guide to writing OpenCL kernels for PowerVR Rogue GPUs (Imagination 事實上是有 OpenCL performance optimization 相關文件, 只是比較可惜的是僅會對客戶提供)

工具

與 Optimization Guide 類似, 由於 GPU hardware counter 並沒有一致性的標準與介面, 所以這部份比較可惜的是必須仰賴各家的 Offline Compiler 與 Profiler

Intel

Intel® Graphics Performance Analyzers

Ahead-of-Time Compilation for GPU

AMD

Radeon GPU Profiler

Radeon GPU Analyzer

Nvidia

NVIDIA Visual Profiler

NVIDIA CUDA Compiler (NVCC)

Qualcomm

Snapdragon Profiler

Adreno GPU SDK

ARM

Mali Graphics Analyzer

Mali Offline Compiler

Imagination

PVRTune

PVRSHADEREDITOR




2023年5月25日 星期四

SIMD Programming Guide 簡報上線

今年定下的年度目標之一是建立比較完整的 SIMD 教學資源, 原本預計撰寫三個部份, 最後完成是有四個部份:

  • Part 1 - 透過 compiler 跨硬體架構平台的 compiler vector extension 來撰寫 (architecture-independent) SIMD 程式
  • Part 2 -講述一般而言 SIMD 指令有哪些類型, 開始導入 Arch-dependent Intrinsics, 並提供 NEON 與 SSE 的具體使用例子, 與 Lab2 的說明
  • Part 3 - SIMD optimization 的一些基本步驟, 套用 SIMD ILP 的兩種基本面向, 並各自以 Gaussian 5x5 與 4x4 SAD 為舉例, 最後是 fuse / tiling / sliding-window / prefetch 等初步的進階技巧
  • Part 4 - 講述 SIMD 的弱點也就是 SIMD 不擅長或是缺點的部份


 

  

 

2022年8月7日 星期日

Halide Introduction 2022 簡報上線

近日應公司同仁邀請分享對 Halide 做介紹
於是基於 2019 的投影片做一點整理並增加一點常被詢問的資訊

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

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