Cutoff Binning 與資料規模擴充性 (Data Size Scalability)

重點總覽 (Overview)

項目 重點
核心問題 DCS 計算量 ∝ atoms × grid points ≈ O(V²)(與體積平方成長),不可擴充
解法策略 Cutoff summation:只累加 cutoff 半徑內的原子貢獻,犧牲少量精度換取 O(V) 線性複雜度
平行化分解 必須用 grid-centric (gather),每 thread 算一個 grid point;atom-centric (scatter) 無法平行化
資料結構 把原子依座標 sort 進 bins(多維陣列 [x][y][z][atom vector]
Neighborhood 一個 grid point 的 cutoff 圈會覆蓋多個 bin;用 relative-offset list 表示(範例 9 full + 12 partial = 21 bins)
記憶體選擇 不同 block 存取不同 neighborhood → constant memory 不夠用 → 改用 global memory + 協同載入 shared memory
Control divergence neighborhood bin 內的原子不一定落在每個 thread 的 cutoff 圈內 → 各 thread if 判斷不同 → divergence
負載不均 bins 原子數不一;固定 bin size 需填 dummy atoms (charge 0) → 用 overflow list + host 收尾
遠端原子 cutoff 圈外的暗原子貢獻極小,用 separate implicit method 集體處理

演算法選擇的權衡 (Algorithm Selection Tradeoffs)

同一問題通常有多種演算法,各自在四個面向上取捨,沒有單一演算法在所有面向都最佳

面向 說明
計算步數 有些演算法步數較少
平行度 有些暴露更高的平行執行度
數值穩定性 有些 numerical stability 較好
記憶體頻寬 有些消耗較少 memory bandwidth
Important

一般規則:替代演算法應得到相同的解,只能在效率/平行度上優化。
例外(更積極的策略):若問題允許最終解有輕微變動,可換取更大幅度的效率提升 —— Cutoff summation 正是這類「犧牲少量精度」的策略。

延伸閱讀見 19-Parallel-Programming-And-Computational-Thinking/02-Algorithm-Selection-and-Tradeoffs


Cutoff Summation 與擴充性 (Cutoff Summation & Scalability)

物理觀察:grid point 的能量貢獻與距離成反比(charge / r),遠處原子貢獻極小,可被集體用低複雜度的 implicit method 處理。

   Direct Summation (A)              Cutoff Summation (B)
   每個 grid point 收所有原子貢獻        只收 cutoff 半徑 r 內的原子貢獻

        o    o    o                       (dark)  o
     o     [G]     o                    o    .-----.
        o    o    o                          ( [G] )  r
     o     o    o                         o   '-----'   o (dark)
                                              ^ 圈外暗原子 → implicit method

   計算量 ∝ N_atom × N_grid            每點只看半徑內固定數量原子
          ≈ O(V²)  (quadratic)               ≈ O(V)  (linear)
Direct Coulomb Summation (DCS) Cutoff Summation
每個 grid point 收的原子 全部原子 cutoff 半徑內原子
精度 高(完全精確) 略低(捨去遠端,可 implicit 補回)
複雜度 O(V²)(atoms 與 grid points 皆 ∝ V) O(V)(線性 ∝ 體積)
平行度 極高 仍維持高平行度
大型系統適用性 不可擴充(計算過久) 可擴充
Warning

增大模擬體積時,grid points 原子數同時正比於體積成長,所以 DCS 的操作數 ≈ V × V = V² (quadratic),對真實生物系統不可行。


Grid-Centric Cutoff Binning 演算法 (Grid-Centric Binning Algorithm)

為何不能直接平行化序列 cutoff? 序列版是 atom-centric(每次處理一個原子,迭代其半徑內 grid points)—— 這是 scatter 更新模式,需 atomic operations,平行化效果差(見 18-Electrostatic-Potential-Map/01-DCS-Scatter-vs-Gather)。

解法:採 grid-centric (gather) 分解 —— 每個 thread 算一個 grid point 的能量(Rodrigues et al., 2008)。

核心步驟

  1. Binning:把輸入原子依座標 sort 進 bins。每個 bin 對應 energy grid space 的一個 box,內含所有座標落在該 box 的原子。
  2. 資料結構:bins 為多維陣列 —— x、y、z 三維 加上第四維:bin 內原子的 vector。
  3. Neighborhood:定義 grid point 的 neighborhood = 所有可能貢獻能量的 bin 集合。
   Fig 18.12 概念:grid point 的 cutoff 圈覆蓋周邊 bins
   +-----+-----+-----+
   | bin | bin | bin |   九個 bin 與 cutoff 圈重疊
   +-----+--.--+-----+   → 這 9 個 bin 內所有原子都要納入考慮
   | bin |(  [G] ) | bin|   ( ) = cutoff circle, [G] = grid point
   +-----+--'--+-----+
   | bin | bin | bin |   但 bin 內某些原子可能仍落在圈外!
   +-----+-----+-----+
Control Divergence 來源

neighborhood bin 內的原子不保證都落在每個 thread 的 cutoff 半徑內。因此處理 bin 內每顆原子時,warp 中各 thread 須各自 if (dist <= cutoff) 判斷,做出不同的 include/exclude 決定 → 造成 control divergence(見 04-Compute-Architecture-And-Scheduling/02-Warps-SIMD-and-Control-Divergence)。


Neighborhood List 與 Per-Block 設計 (Neighborhood List & Per-Block Design)

計算 neighborhood bins 是複雜的幾何問題、耗時。因此為整個 block 定義一份 neighborhood,並在 launch grid 之前先準備好(neighborhood bins 就像 energy grid space 中的一個 stencil)。

Per-block 推導(Fig 18.13/18.14 範例)

參數 數值
Grid spacing 0.5 Å
Block 維度 8 × 8 × 8
每個 block 覆蓋體積 (8×0.5)³ = 4 Å × 4 Å × 4 Å cube
Cutoff distance 典型 12 Å
Block 內 grid points 512(每個都有自己的 cutoff 圈)

保守近似 — Supercircle:以 bin 中心為圓心,半徑取

supercircle_radius = cutoff_distance + ½ × bin_diagonal

此 supercircle 可覆蓋「以 bin 四角為圓心的所有 cutoff 圈」,於是只需列出被 supercircle 完全或部分覆蓋的 bin。

   5×5 bin neighborhood(以 home bin [G] 為原點的相對 offset)
   .  = 未覆蓋(角落)    p = 部分覆蓋(partial)    F = 完全覆蓋(full)

      .    p    p    p    .
      p    F    F    F    p
      p    F   [G]   F    p      [G] = home bin, offset (0,0)
      p    F    F    F    p
      .    p    p    p    .

   full (含 [G]) = 9    partial = 12    neighborhood list = 21 bins
   9 個 full 的相對 offset:
   (-1,-1)(0,-1)(1,-1)(-1,0)(0,0)(1,0)(-1,1)(0,1)(1,1)

Neighborhood list 以 relative offset 表示,通常放在 constant memory array 供 kernel 使用。

// 預先算好、放 constant memory 的 neighborhood 相對 offset 清單
__constant__ int nbrlist[21][3];   // 每筆 {dx, dy, dz} bin offset
__constant__ int numNbrs;          // = 21 (9 full + 12 partial)

__global__ void cutoff_cenergy(float *energygrid, /* grid 維度等 */ ,
                               const float *binBase, int binSize,
                               float gridspacing, float cutoff2) {
    // 1) 由 block/thread index 算出本 thread 負責的 grid point 座標
    int i = blockIdx.x*blockDim.x + threadIdx.x;
    int j = blockIdx.y*blockDim.y + threadIdx.y;
    float gx = i*gridspacing, gy = j*gridspacing /* gz ... */;
    float energy = 0.0f;

    __shared__ float atomBin[BIN_SIZE*4];   // 協同載入的 bin 緩衝

    // 2) 所有 thread 迭代共同的 neighborhood list
    for (int b = 0; b < numNbrs; ++b) {
        // 對 block 所屬 bin 套用相對 offset → 鄰居 bin 座標
        int binIdx = blockBinIndex + nbrlist[b][0] /* + y,z 維度 */;

        // 3) 協同把該 bin 的原子 global mem -> shared mem
        for (int t = threadIdx.x; t < binSize*4; t += blockDim.x)
            atomBin[t] = binBase[binIdx*binSize*4 + t];
        __syncthreads();

        // 4) 每個 thread 各自檢查 shared mem 中每顆原子是否在 cutoff 內
        for (int a = 0; a < binSize*4; a += 4) {
            float dx = gx - atomBin[a], dy = gy - atomBin[a+1],
                  dz = /*gz*/ - atomBin[a+2];
            float r2 = dx*dx + dy*dy + dz*dz;
            if (r2 <= cutoff2)                 // <-- control divergence 點
                energy += atomBin[a+3] * rsqrtf(r2);
        }
        __syncthreads();
    }
    energygrid[/* linear index */] += energy;   // gather: 各寫各的, 免 atomic
}

執行流程

for each bin in neighborhood_list[21]:     (block 內所有 thread 一起)
   bin_coord = block_bin_coord + offset      套用相對 offset
   ── 協同載入 ──   atoms: global memory ──▶ shared memory
   __syncthreads()
   ── 各自計算 ──   每 thread 掃 shared mem 的原子:
                      if dist <= cutoff:  energy += charge / dist   ← divergence
   __syncthreads()

Shared Memory 與記憶體選擇 (Shared Memory & Memory Choice)

複雜度的改善來自:每個 thread 只檢查 neighborhood bins 定義的一小撮原子(而非全部原子)。但這帶來記憶體選擇的轉變:

DCS(前幾節) Cutoff Binning
原子放哪 constant memory(broadcast + 重用,cache 極有效) global memory
為何改變 全 thread 看同一批原子 不同 block 存取不同 neighborhood,原子總量大,constant memory 太小裝不下所有 active blocks 所需原子
頻寬對策 constant cache 廣播 block 內 thread 協同載入 共同 neighborhood bin 至 shared memory,再各自從 shared memory 讀取
Tip

Neighborhood bin 對一個 block 內所有 thread 是共同的資料,因此非常適合協同搬到 shared memory 重用 —— 與 08-Stencil/02-Shared-Memory-Tiling-for-Stencil 的 tiling 思路相同。


Bin 大小、Dummy Atoms 與 Overflow List (Bin Size, Dummy Atoms & Overflow List)

問題:原子在空間中統計分佈不均 —— 有些 bin 很多原子、有些完全沒有。

為了 memory coalescing:所有 bin 必須同樣大小且對齊 coalescing 邊界 → 需用 dummy atoms(charge = 0) 把較空的 bin 補滿。

Dummy atoms 的兩個負面效應

  1. 佔用:dummy atoms 仍佔 global / shared memory 空間,並消耗傳到 device 的傳輸頻寬。
  2. 拖慢:dummy atoms 延長那些「真實原子很少」的 thread block 的執行時間(仍要逐顆處理)。

較佳作法 — Overflow List

步驟 做法
設定 bin size 設在能容納絕大多數 bin 原子數的合理值(遠小於最大可能原子數)
溢位處理 處理原子時若其 home bin 已滿 → 改放入 overflow list
Device 完成後 grid point 能量值傳回 host
Host 收尾 host 對 overflow list 跑序列 cutoff 演算法,補上這些溢位原子的缺失貢獻
Host/Device 重疊隱藏延遲

設計成每次 kernel 只算一個 subvolume:device 跑下一個 kernel 的同時,host 處理上一個 kernel 的 overflow atoms。只要 overflow 原子佔比小(例如 < 3%),其序列處理時間通常短於 device 執行時間,可被完全隱藏。


本章總結 (Chapter Summary)

整章是一連串平行化決策與權衡的演進:

 highly-optimized 序列 C  ──平行化──▶  scatter kernel  (需大量 atomicAdd, 慢)
                                            │  改用 gather
 less-optimized 序列 C    ──平行化──▶  gather kernel   (各寫各的, 快很多)
                                            │  thread coarsening
                                       重拾優化序列的效率 (register 重用)
                                            │  coalescing-aware 配置
                                       完全 coalesced 的記憶體寫入
                                            │  資料規模擴充
 ─────────────────────────────────────────────────────────────────
 DCS = 高精度但 O(V²) 不可擴充
        │  犧牲少量精度
 Cutoff summation + binning = 大幅降低複雜度 (O(V)) 且維持高平行度

考試/面試重點 (Exam / Test Patterns)

情境 / 關鍵字 答案 / 技巧
DCS 為何不可擴充? atoms 與 grid points 都 ∝ 體積 → 操作數 ∝ V² (quadratic)
Cutoff summation 的複雜度? 每點只看固定半徑內原子 → O(V) 線性;代價是略損精度
為何 cutoff 要用 grid-centric? atom-centric 是 scatter(需 atomic)無法良好平行;grid-centric gather 各寫各的
Bins 資料結構? 多維陣列:x,y,z + 第四維為 bin 內原子 vector
Neighborhood list 怎麼表示 / 放哪? bin 的相對 offset 清單;放 constant memory;launch 前先算好(幾何計算貴)
Supercircle 半徑公式 cutoff + ½ × bin 對角線,可涵蓋以 bin 四角為心的所有 cutoff 圈
範例 neighborhood 有幾個 bin? 9 full + 12 partial = 21
Control divergence 從何而來? neighborhood bin 內原子不一定落在每 thread 的 cutoff 圈內 → 各 thread if 判斷不同
為何 cutoff 不用 constant memory 放原子? 不同 block 存取不同 neighborhood,constant memory 太小 → 改 global memory + shared memory 協同載入
Bins 大小不一怎麼辦? 固定 size 需填 dummy atoms (charge 0);但較佳是設小 size + overflow list 由 host 收尾
Dummy atoms 兩個缺點 (1) 佔記憶體 / 傳輸頻寬;(2) 拖慢原子少的 block
Overflow 隱藏延遲 subvolume 分段 → device 跑下一 kernel 時 host 處理上一批 overflow(<3% 可完全隱藏)
遠端原子貢獻怎麼處理? cutoff 圈外暗原子貢獻極小 → 另用 implicit method 集體補回