這裡介紹如何在樹莓派上使用 QPULib 這套 QPU(GPU) 平行運算函式庫,加速各種運算的執行,解決樹莓派 CPU 運算速度不足的問題。

QPULib 是一套可以運用樹莓派的 QPU 進行平行運算的 C++ 函式庫,它包含特殊的程式語言語法以及編譯器,可在執行時產生適用於 QPU 執行的程式,讓 QPU 負責較為大量的運算,大幅增加運算的速度,對於有實時(real-time)需求的應用應該很有幫助。以下是 QPULib 的使用教學與範例程式碼。


這裡我所使用的測試環境是 Raspberry Pi 3 Model B,作業系統與核心環境如下:

lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 8.0 (jessie)
Release:        8.0
Codename:       jessie
uname -a
Linux raspberrypi 4.4.43-v7+ #948 SMP Sun Jan 15 22:20:07 GMT 2017 armv7l GNU/Linux

GPU 運算基礎觀念

QPU(Quad Processing Unit)是 Broadcom 所研發的一種向量處理器,它可以一次處理 16 個 32 位元整數或浮點數的運算。

舉例來說,如果有兩個長度為 16 的數值向量:

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

QPU 的 integer-add 指令可以將這兩個向量相加,得到一個相加後的結果向量:

30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60

這個新的向量長度也是 16,其中每個元素就是由前面兩個向量中對應元素相加所得到的。

每一個長度為 16 的向量包含 4 個 quads,一個 QPU 每一個時脈週期(clock cycle)可以處理一個 quad(這也是它稱為 QPU 的原因),所以計算一個長度為 16 的向量需要 4 個時脈週期。

樹莓派中總共含有 12 個 QPU,時脈速度(clock rate)為 250MHz,也就是說每秒可以進行 250M / 4 * 12 = 750M 個長度為 16 的向量運算,等同於每秒 750M * 16 = 12G 個浮點運算,而某些特殊的 QPU 指令還可以一次執行兩個運算,所以樹莓派的 QPU 最高可達到 24GFLOPS 的運算能力。

QPU 屬於樹莓派圖形管線(pipeline)的一部分,如果您的應用會牽涉到繪圖,可以使用 OpenGL ES,而如果只是要加速非繪圖的程式運算速度,那麼就適合使用 QPULib。

安裝 QPULib

使用 git 下載 QPULib 專案的原始碼:

sudo apt-get install git
git clone https://github.com/mn416/QPULib

編譯 GCD 這個範例程式,編譯時要加上 QPU=1 參數,讓程式可以使用樹莓派實體的 GPU(QPU):

cd QPULib/Tests
make QPU=1 GCD

使用 sudo 執行 GCD 測試:

sudo ./GCD

輸出為:

gcd(183, 186) = 3
gcd(177, 115) = 1
gcd(193, 135) = 1
gcd(186, 192) = 6
gcd(149, 121) = 1
gcd(162, 127) = 1
gcd(190, 159) = 1
gcd(163, 126) = 1
gcd(140, 126) = 14
gcd(172, 136) = 4
gcd(111, 168) = 3
gcd(167, 129) = 1
gcd(182, 130) = 26
gcd(162, 123) = 3
gcd(167, 135) = 1
gcd(129, 102) = 3

若程式可以正常執行,就表示沒問題。

Hello World

在撰寫使用 GPU 運算的程式時,程式碼主要可分為兩大部份:

CPU 部份
在 CPU 上面執行的程式,使用一般 C/C++ 語言的語法來撰寫(等同於一般的 C/C++ 程式),主要處理資料的輸入與輸出,還有一些次要的少量運算。
GPU 部份
在 GPU 上面以平行化方式所執行的程式,使用 QPULib 以 C++ 類別為基礎所建立的特殊語法撰寫而成,負責最核心的平行運算,通常這部份的程式碼會包裝成一個獨立的函數,跟 CPU 的程式碼區隔開來,而這種在 GPU 上面執行的函數就稱為核心函數(kernel function)。

這是 QPULib 所提供的 Hello.cpp 範例程式碼,示範最簡單的 CPU 與 GPU 資料傳遞與平行運算的使用方式:

#include <QPULib.h>

// 定義在 QPU 上執行的核心函數
void hello(Ptr<Int> p) {
  *p = 1;
}

int main() {
  // 建立核心函數
  auto k = compile(hello);

  // 配置 CPU 與 QPU 共用的記憶體陣列
  SharedArray<int> array(16);
  for (int i = 0; i < 16; i++) {
    array[i] = 100;
  }

  // 使用 QPU 運算
  k(&array);

  // 輸出結果
  for (int i = 0; i < 16; i++) {
    printf("%i: %i\n", i, array[i]);
  }

  return 0;
}

這裡我們一開始定義一個 hello 核心函數,這個函數就是要放在 GPU 執行的部份,在程式執行時,先使用 compile 函數將這個 hello 編譯成可以被 GPU 執行的程式,接著用 SharedArray 配置一個可以同時被 CPU 與 GPU 存取的陣列,用於 GPU 運算上的資料傳遞,然後呼叫編建立好的核心函數進行平行運算,最後輸出結果。

這個 Hello 程式的 makefile 已經事先寫好了,所以我們可以直接執行 make 編譯程式,這裡同樣要加上 QPU=1 讓程式使用 GPU 運算:

make QPU=1 Hello

使用 sudo 執行:

sudo ./Hello

輸出為:

0: 1
1: 1
2: 1
3: 1
4: 1
5: 1
6: 1
7: 1
8: 1
9: 1
10: 1
11: 1
12: 1
13: 1
14: 1
15: 1

這裡我們放在 GPU 中的運算內容很簡單,只是單純把每個數值設定為 1 而已,所以輸出是 161

以上就是一個最簡單的樹莓派 GPU hello world 程式,接下來我們要繼續介紹 QPULib 的語法。