Stencil 基礎與基本平行核心 (Background & Basic Kernel)
重點總覽 (Overview)
| 概念 | 重點 | 數值 / 公式 |
|---|---|---|
| Discretization | 連續函數 → structured grid 上的格點值,存成多維陣列 | 格距 h,愈小愈精確但耗儲存與運算 |
| Structured grid | 等距 parallelotope 覆蓋空間 → finite-difference methods | unstructured grid 留給 finite-element/-volume |
| Finite difference | 用鄰點值的差近似導數 | f'(x) ≈ (f(x+h)−f(x−h))/(2h) + O(h²) |
| Stencil order | 中心點「每一側」的格點數 = 近似導數的階數 | 1D: 2·order+1 點 (3/5/7-point = order 1/2/3) |
| Stencil vs Convolution | 都用鄰域算新值,但 stencil 係數來自微分方程、可迭代、常用高精度浮點 | 7-point 3D stencil 只用 7 個輸入 |
| Basic kernel | 1 thread ↔ 1 output 格點;邊界格點不更新 | 3D 7-point sweep |
| OP/B (基本核心) | 13 FLOP / 7 次 4-byte load | 0.46 OP/B → 嚴重 memory-bound |
Stencil sweep 與 convolution 在「程式形態」上幾乎相同(中心點 + 鄰域加權求和),但 stencil 的權重由微分方程決定、用於迭代求解 PDE、且常用 3D 高精度資料 — 這些差異會引出與 convolution 不同的最佳化(見 08-Stencil/02-Shared-Memory-Tiling-for-Stencil 與 08-Stencil/03-Thread-Coarsening-and-Register-Tiling)。
離散化與結構化網格 (Discretization & Structured Grids)
把連續、可微的函數轉成電腦可處理的離散表示 (discrete representation):
(A) 連續函數 (B) 結構化網格 (h = π/6) (C) 離散陣列 F
y = sin(x) x = i·h, 等距格點 F[0..6]
.-''-. o o o o o o o F[2] = sin(2·π/6) = 0.87
/ \ | | | | | | | x 值「隱含」= i·h
0 ........ π 0 π/6 2π/6 ... π (不另存 x)
- 格距
h愈小 → 表示愈精確,但需要更多儲存與更多運算(解 PDE 時)。 - 非格點處的值用 interpolation(linear / splines)近似。
- 精度也取決於浮點位寬:
| 精度 | 位寬 | 取捨 |
|---|---|---|
| double | 64-bit | 精度/fidelity 最高;但頻寬、容量、tiling 壓力最大 |
| single | 32-bit | 算術吞吐量遠高於 double |
| half | 16-bit | 吞吐量最高、佔用最小 |
| 網格類型 | 形狀 | 數值方法 |
|---|---|---|
| Structured / regular grid | 等同的 parallelotope(線段/矩形/磚塊) | finite-difference (本章採用) |
| Unstructured grid | 不規則 | finite-element / finite-volume |
結構化網格的好處:導數可以方便地寫成 finite differences,所以 finite-difference 法只在 regular grid 上使用。
有限差分與 Stencil 的階數 (Finite Difference & Stencil Order)
Stencil 定義:在 structured grid 每個點上套用的一組幾何權重模式 (geometric pattern of weights),規定如何由中心點與鄰點的值,用數值近似求出該點的輸出。
一階導數的經典有限差分近似:
- 誤差
O(h²)→ 與h的平方成正比,h愈小近似愈好(圖 8.1 中h = π/6 ≈ 0.52)。 - 因為
x = i·h,可寫成對陣列的運算:
→ 係數 [-1/2h, 0, 1/2h] 定義了一個 1D three-point stencil。
Order(階數)= 中心點「每一側」的格點數 = 近似導數的階數:
1D stencils (● = 中心, ○ = 鄰點)
order 1 (3-point): ○──●──○ (含至一階導數)
order 2 (5-point): ○──○──●──○──○ (含至二階導數)
order 3 (7-point): ○──○──○──●──○──○──○ (含至三階導數)
通則: 1D 點數 = 2·order + 1;含至 n 階導數 → 每側 n 點
| 維度 / 形狀 | 點數 | order |
|---|---|---|
| 2D 十字 (five-point) | 5 | 1 |
| 2D 九點 (nine-point) | 9 | 2 |
| 3D 七點 (seven-point) | 7 | 1 |
| 3D 十三點 (13-point) | 13 | 2 |
Order 不是總點數,也不是維度。它是「每一側的格點數」。3D 7-point stencil 是 order 1(每軸每側 1 點 × 3 軸 = 6 鄰點 + 1 中心)。
若 PDE 只含對單一變數的偏導(如 ∂f/∂x、∂f/∂y,而不含混合偏導 ∂²f/∂x∂y),則 2D/3D stencil 的格點全部落在各座標軸上 → 形成「十字 / 星形」而非含角點的方塊。這個「不含角點」的特性,稍後讓 register tiling 變得可行。
基本平行 Stencil Sweep 核心 (Basic Parallel Kernel)
Stencil sweep:對所有相關輸入格點套用 stencil,一次產生所有輸出格點。基本核心的簡化假設:
- 一次 sweep 內,輸出格點之間無相依(可全平行)。
- 邊界格點存 boundary condition,輸出 = 輸入,不更新(只算內部陰影區)。
Output grid (2D 示意,每個 block 算 4×4 output tile)
┌─────────────────────────┐
│ □ □ □ □ □ □ □ □ □ □ □ □ │ □ = boundary cell:輸出 = 輸入(thread 關閉)
│ □ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ □ │ ▓ = 內部格點:由 stencil 計算
│ □ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ □ │
│ □ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ □ │ 邊界形成 grid 表面的一層
│ □ □ □ □ □ □ □ □ □ □ □ □ │
└─────────────────────────┘
3D seven-point stencil(中心 + x/y/z 各 2 鄰點):
in[i+1][j][k] (z+)
│
in[i][j-1][k] │ in[i][j+1][k]
(y-) \ │ / (y+)
in[i][j][k-1]──[center]──in[i][j][k+1] center = in[i][j][k]
(x-) / │ \ (x+)
│
in[i-1][j][k] (z-)
7 loads → c0·center + c1..c6·neighbors
Thread → data 映射(3D,row-major 線性化,見 03-Multidimensional-Grids-And-Data/02-Mapping-Threads-to-Multidimensional-Data):
__global__ void stencil_kernel(float* in, float* out, unsigned int N) {
// i = z(最外/慢), j = y, k = x(最內/快)
unsigned int i = blockIdx.z*blockDim.z + threadIdx.z;
unsigned int j = blockIdx.y*blockDim.y + threadIdx.y;
unsigned int k = blockIdx.x*blockDim.x + threadIdx.x;
// 只算內部格點;邊界 (index 0 或 N-1) 保持不變
if (i >= 1 && i < N-1 && j >= 1 && j < N-1 && k >= 1 && k < N-1) {
out[i*N*N + j*N + k] = c0 * in[ i *N*N + j *N + k ] // center
+ c1 * in[ i *N*N + j *N + (k-1)] // x-
+ c2 * in[ i *N*N + j *N + (k+1)] // x+
+ c3 * in[ i *N*N + (j-1)*N + k ] // y-
+ c4 * in[ i *N*N + (j+1)*N + k ] // y+
+ c5 * in[(i-1)*N*N + j *N + k ] // z-
+ c6 * in[(i+1)*N*N + j *N + k ]; // z+
}
}
// c0..c6 由所解的微分方程決定;線性索引 = i*N*N + j*N + k
係數 c0..c6 與 convolution 的 filter 不同:它們由所解的微分方程決定,而非影像濾鏡。不同 PDE → 不同係數。
算術強度:0.46 OP/B (Arithmetic Intensity)
對基本核心計算 floating-point 對 global memory access 比值:
| 項目 | 數量 |
|---|---|
| 每 thread FLOP | 7 乘 + 6 加 = 13 OP |
| 每 thread global load | 7 個輸入 × 4 bytes = 28 B |
Roofline 直覺:
compute-bound ┤ ╱────── 峰值算力
│ ╱
│ ╱
│ ╱ ← kernel 落在這條斜線上(memory-bound)
memory-bound │ ╱ ▲ 0.46 OP/B (遠在左側)
└──────┴──────────────► OP/B
0.46
0.46 OP/B 太低 → kernel 被 global memory 頻寬綁死 (memory-bound),遠未用到算力資源(見 05-Memory-Architecture-And-Data-Locality/01-Memory-Access-Efficiency-and-CUDA-Memory-Types)。解法:套用 shared memory tiling(如 Ch.7 convolution),把比值拉高 → 08-Stencil/02-Shared-Memory-Tiling-for-Stencil。
考試/面試重點 (Exam / Test Patterns)
| 情境 / 關鍵字 | 答案 / 技巧 |
|---|---|
| 「stencil order = ?」 | 中心點每一側的格點數 = 近似導數的階數;1D 總點數 = 2·order+1 |
| 「3D seven-point stencil 幾階?」 | order 1(每軸每側 1 點);6 鄰點 + 1 中心 |
| 「為何 stencil 用 structured grid?」 | 導數可寫成 finite differences;finite-difference 法只用 regular grid |
| 「stencil vs convolution 差異」 | 係數來自 PDE、用於迭代求解、常用高精度 3D 資料 → 引出 thread coarsening / register tiling |
| 「基本 7-point kernel 的 OP/B?」 | 13/(7×4) = 0.46 OP/B,memory-bound |
| 「為何要 tiling?」 | 0.46 OP/B 太低,頻寬綁死;tiling 提升資料重用與算術強度 |
| 「邊界格點如何處理?」 | 存 boundary condition,輸出=輸入不更新;index 0 與 N-1 的 thread 關閉 |
| 「為何 double 精度對 tiling 不利?」 | 64-bit 佔更多頻寬與 on-chip 記憶體,壓縮可放入 shared memory/register 的格點數 |
| 「finite difference 一階近似誤差?」 | O(h²),與格距平方成正比;h 愈小愈準 |
| 「線性化 3D 索引」 | row-major: i*N*N + j*N + k(i=z 最慢、k=x 最快) |
Related Notes
- 08-Stencil/02-Shared-Memory-Tiling-for-Stencil
- 08-Stencil/03-Thread-Coarsening-and-Register-Tiling
- 07-Convolution/01-Convolution-Fundamentals-and-Basic-Kernel
- 05-Memory-Architecture-And-Data-Locality/01-Memory-Access-Efficiency-and-CUDA-Memory-Types
- 03-Multidimensional-Grids-And-Data/02-Mapping-Threads-to-Multidimensional-Data
- 24-Numerical-Considerations/01-Floating-Point-Data-Representation