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
Important

Stencil sweep 與 convolution 在「程式形態」上幾乎相同(中心點 + 鄰域加權求和),但 stencil 的權重由微分方程決定、用於迭代求解 PDE、且常用 3D 高精度資料 — 這些差異會引出與 convolution 不同的最佳化(見 08-Stencil/02-Shared-Memory-Tiling-for-Stencil08-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)
精度 位寬 取捨
double 64-bit 精度/fidelity 最高;但頻寬、容量、tiling 壓力最大
single 32-bit 算術吞吐量遠高於 double
half 16-bit 吞吐量最高、佔用最小
網格類型 形狀 數值方法
Structured / regular grid 等同的 parallelotope(線段/矩形/磚塊) finite-difference (本章採用)
Unstructured grid 不規則 finite-element / finite-volume
Tip

結構化網格的好處:導數可以方便地寫成 finite differences,所以 finite-difference 法只在 regular grid 上使用。


有限差分與 Stencil 的階數 (Finite Difference & Stencil Order)

Stencil 定義:在 structured grid 每個點上套用的一組幾何權重模式 (geometric pattern of weights),規定如何由中心點與鄰點的值,用數值近似求出該點的輸出。

一階導數的經典有限差分近似:

f(x)f(x+h)f(xh)2h+O(h2) FD[i]=F[i+1]F[i1]2h=12hcoefF[i1]+0F[i]+12hcoefF[i+1]

→ 係數 [-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 的常見誤解

Order 不是總點數,也不是維度。它是「每一側的格點數」。3D 7-point stencil 是 order 1(每軸每側 1 點 × 3 軸 = 6 鄰點 + 1 中心)。

Note

若 PDE 只含對單一變數的偏導(如 ∂f/∂x、∂f/∂y,而不含混合偏導 ∂²f/∂x∂y),則 2D/3D stencil 的格點全部落在各座標軸上 → 形成「十字 / 星形」而非含角點的方塊。這個「不含角點」的特性,稍後讓 register tiling 變得可行。


基本平行 Stencil Sweep 核心 (Basic Parallel Kernel)

Stencil sweep:對所有相關輸入格點套用 stencil,一次產生所有輸出格點。基本核心的簡化假設:

  1. 一次 sweep 內,輸出格點之間無相依(可全平行)。
  2. 邊界格點存 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
Warning

係數 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
OP/B=137×4=13280.46 OP/B
Roofline 直覺:
 compute-bound  ┤            ╱──────  峰值算力
                │          ╱
                │        ╱
                │      ╱   ← kernel 落在這條斜線上(memory-bound)
 memory-bound   │    ╱ ▲ 0.46 OP/B (遠在左側)
                └──────┴──────────────► OP/B
                      0.46
Important

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 最快)