# OpenCL 學習筆記 (二): OpenCL 模型架構
###### tags: `OpenCL`
在第一篇裡實做的第一個 OpenCL 程式讓你有初步認識,這個章節則會描述 OpenCL 的模型設計,會以下列幾點討論。
* 程式架構模型
* 平行計算模型
* 記憶體模型
## 程式設計模型
### API
如果你已經寫慣 C++ 或 Python 之類的程式語言的話,在初次接觸 OpenCL 時一定會覺得很奇怪,很難想透為甚麼程式要這樣寫,這裡要先建立一個慨念,OpenCL 是一套界面,而不是單純的程式語言,它的運作模式是這樣的,首先由 Khronos Group 設計它的標準界面和規格書,爾後其它廠商要根據它規格書實做 API,只要廠商實做的 API 執行行為完全符合規格書的描述,就可以稱廠商實做的 API 為 OpenCL,所以如果你願意的話,完完全全可以只用 C 語言撰寫 API,只要實做正確,也能稱作 OpenCL,像是這個[開源專案](https://github.com/pocl/pocl)。
### Context

<br>
第一篇裡實裡,我們有提過 OpenCL 選擇使用 Context 作為溝通的橋樑,任何對 GPU 的實際操作都會牽涉到 Context,如上圖所示,編譯、執行和傳送資料都是通過它,這樣好處是使用者不需要理會硬體實做細節。比如 Context 可以收集多個 Device,這個平台可能是 NUMA ,像是平常我們用的電腦 GPU、CPU,不共用記憶體,也可能是 UMA,像是蘋果 M1 ,GPU 和 CPU 記憶體共用。如果你還有印象的話,Buffer 連接的目標是 Context 而非個別的 Device,這樣廠商可以根據它們自己硬體的實做資源調度方式。
<br>
## 平行計算模型
OpenCL 是典型的 SIMD 架構,理想狀況會希望所有裝置裡的所有核心同時執行相同程式,但現實中硬體不是這樣設計的,GPU 會有一個最小平行計算單位,我們稱為計算單元(compute unit, CU),一個計算單元一般包含 20 到 40 個核心,同一個計算單元內的核心才保證同時執行相同程式,而不同計算單元可能會執行不同程式。
| 名稱 | 對應的 GPU 硬體 | 層級 |
| ------- | ------- | -------- |
| device | GPU | 大|
| work group | 計算單元 |中|
| work item | GPU 核心 |小|
<br>
同樣的 OpenCL 有相應的虛擬的計算單位對應不同 GPU 硬體,work group 對應 GPU 計算單元,work item 對應單個 GPU 核心。對應的硬體並不完全相等,我們可以設定大於實際硬體的計算任務,例如可以設定 10000 個 work group 計算,這明顯超過 GPU 實際的計算單元,所以這個模型只保證每個 work group 會被送入相對應的實際硬體,至於怎麼分配,work group 的執行順序如何,由實做決定。同理 work item 也是相似的概念。

<br>
前一篇提過,最後在執行前必須要決定執行緒的數目,在 OpenCL 稱為 work size , work size 分為兩個組,一個是決定整個裝置要執行的 work size 數量,稱為 global work size ,一個是每個 work group 要執行的 work size 數量,稱為 local work size 。因此 global work size 必須是 local work size 的整數倍,上圖為 work size 分配的示意圖,可以看到 global work size 被數個 work group 分隔。
```cpp=
queue.enqueueNDRangeKernel(
cl_kernel,
cl::NullRange,
cl::NDRange(120, 120, 20), // global work size
cl::NDRange(10, 30, 20)); // local work size
```
一個 global work size 最多可以分成 X、Y、Z 三個維度,對應的 local work size 也必須是相同維度,且對應的維度必須是整數被,如上方的範例程式,代表總共有 120 x 120 x 20 個執行緒,每個 work group 會執行 10 x 30 x 20。
```cpp=
queue.enqueueNDRangeKernel(
cl_kernel,
cl::NullRange,
cl::NDRange(120, 120, 20), // global work size
cl::NullRange); // 自動分配 local work size
```
如果沒有指定 local work size ,則 OpenCL 會自動分配一個適合的 local work size。這裡可能會有一個疑問,既然 OpenCL 會自動分配 local work size,未啥要還需要手動分配?原因跟等等講的記憶體模型有關。
<br>
## 記憶體模型
在 OpenCL 框架內,共分為四個記憶體三個等級,越高級的記憶體其記憶容量通常會比較多,global memory 是整個裝置的記憶體,被所有 work group 共享,constant memory 是對於低級記憶體唯獨的記憶體,同樣位於裝置,local memory 是 work group 的記憶體,被自己本身的 work item 共享,private memory 是 work item 的記憶體。
| 記憶體名稱 | 對應位置 | 大小 |
| ------- | ------- | -------- |
| global memory | device |最大|
| constant memory | device | 次之|
| local memory | work group |中等|
| private memory | work item |最小|
<br>
在整個記憶體模型中,層級越低的記憶體,距離實際的 GPU 核心越接近,讀取寫入的速度越快速。假設有一個任務需要大量讀寫記憶體,直接讀取 global memory 並不是好方法,而且同步也會是一個大問題,最好的方法為先將資料寫在 local memory,需要時才將整個或部份記憶體寫回 global memory,一來減少讀取的延遲,一來 work group 內同步也是比較簡單快速的是。這個也是我們需要手動分配 local work size 的原因,需要指定多少虛擬核心共用同一塊記憶體。

<br>
## Linking
[上一篇](https://hackmd.io/@yrHb-fKBRoyrKDEKdPSDWg/HJVX-WH2v)
[下一篇](https://hackmd.io/@yrHb-fKBRoyrKDEKdPSDWg/HyKeNkZIh)