平行演算法考量與數值穩定性 (Algorithm Considerations and Numerical Stability)

重點總覽 (Overview)

主題 核心訊息 對平行化的影響 對策
加總順序 (summation order) 浮點加法不滿足結合律 (not associative) 平行 reduction tree 與循序加總結果可能不同 presort 升冪後分組;Kahan 補償加總
小數消失 (swamping) 大數 + 極小數時,小數對齊右移後整個消失 把大小相近的數放同組可減少誤差 同 magnitude 分組 / 排序
數值穩定性 (numerical stability) 演算法能對任意可解輸入找到解 → stable 線性解算器對運算順序更敏感 pivoting 交換 row
Gaussian elimination 每 thread 負責一個 row,逐欄消元 + barrier 簡單可平行,但 pivoting 需跨 thread 檢查 shared memory 內做 parallel max reduction
Pivoting 成本 找 lead 變數係數絕對值最大的 row 來交換 跨 block/node 的全域檢查極昂貴 partial pivoting / randomization (communication-avoiding)
Important

Precision (精度) 由 mantissa 位數決定(見 24-Numerical-Considerations/01-Floating-Point-Data-Representation);Accuracy (準確度)運算與其順序決定。本筆記聚焦後者:相同數值、不同順序,平行與循序可能得到不同答案。


加總順序與平行 Reduction 的準確度 (Summation Order in Parallel Reduction)

矩陣乘法的 dot product、reduction 等都需要把大量數值加總。理想上順序無關(加法符合結合律),但在有限精度下,順序會改變最終準確度。

範例:5-bit 格式 (1-bit S, 2-bit E, 2-bit M)

加總 1.00B×2⁰ + 1.00B×2⁰ + 1.00B×2⁻² + 1.00B×2⁻²(即 1 + 1 + 0.25 + 0.25,精確值 = 2.5 = 1.01B×2¹)。

循序 (sequential, 左到右):

1.00×2⁰ + 1.00×2⁰              = 1.00×2¹            (= 2)
1.00×2¹ + 1.00×2⁻²            → 1.00×2¹            (0.25 對齊右移後消失)
1.00×2¹ + 1.00×2⁻²            → 1.00×2¹  = 2       (再次消失,誤差 0.5)

平行 (parallel reduction tree, 見第 10 章):

1.00×2⁰ + 1.00×2⁰  = 1.00×2¹           (= 2)
1.00×2⁻² + 1.00×2⁻² = 1.00×2⁻¹         (= 0.5)
1.00×2¹ + 1.00×2⁻¹ = 1.01×2¹  = 2.5    (精確!)

ASCII:兩種加總路徑

循序 (left-to-right)                     平行 (reduction tree)
  1   1  0.25 0.25                          1   1   0.25 0.25
   \ /                                       \ /     \ /
    2  +0.25 → 2  (0.25 消失)                  2       0.5
         \                                      \     /
          2 +0.25 → 2  (0.25 消失)               2 + 0.5
              = 2   (誤差 0.5)                     = 2.5  (精確)
Warning

此例平行更準,但只是巧合。結合律對浮點不成立,所以也能構造平行更不準的情境。不要假設「平行 = 更準」或「循序 = 更準」。第二、三步的小運算元 (0.25) 因 alignment shifting 右移到超出 mantissa 範圍而整個被吞掉 (swamping)

提升準確度的技巧

技巧 做法 為何有效
Presort (升冪排序) reduction 前依數值升冪排序 (含正負號) 分組時大小相近的數同組,且循序加總由小到大累積誤差最小
分組對齊 magnitude 把絕對值接近的數放同一 group / thread 避免大數吞掉小數
Kahan / compensated summation 額外追蹤捨入餘量並補回 更穩健的高準確度加總 (Kahan, 1965)
Tip

這是為何排序在大規模平行數值演算法中如此常見的原因之一:排序不只為了輸出,也能讓後續 reduction 更準確。若每個 thread 循序 reduce 自己 group 內的值,升冪排列能讓單 thread 的循序加總獲得更高準確度。

Warning

反例(課本習題 4):若陣列已由小到大排序,而你用「相鄰配對」的 reduction 把最小最大放在同一棵子樹底端配對,反而會讓小值被大值吞掉、降低準確度。排序方向要配合分組策略。


線性解算器與數值穩定性 (Linear Solvers and Numerical Stability)

Important

數值穩定 (numerically stable):只要解存在,演算法對任意輸入都能找到合適的運算順序並求得解。
數值不穩定 (numerically unstable):對某些輸入會找不到解,即使解存在

線性方程組解算器中,不同的輸入數值可能需要不同的運算順序才能求解 — 比 reduction 的順序問題更嚴重。

Gaussian Elimination 流程

以下系統(解為 X=1, Y=2, Z=3):

3X + 5Y + 2Z = 19
2X + 3Y +  Z = 11
 X + 2Y + 2Z = 11

矩陣 (augmented) 視角的消元 + 回代:

[ 3 5 2 | 19 ]   step1: 各 row 除以 lead 係數,使 X 係數一致
[ 2 3 1 | 11 ]   step2: 減去 top row → 消去 X
[ 1 2 2 | 11 ]   重複對 Y ... → upper-triangular(上三角)
                 → 對角線化為 identity matrix
                 → backward substitution: 由最後一式往回解 Z→Y→X

平行化策略 (CUDA)

每個 thread 負責矩陣的一個 row;若系統可放進 shared memory,用一個 thread block 處理。所有 thread 同步迭代各 step:

// 一 thread 一 row;augmented matrix A 為 n×(n+1),放在 shared memory
__global__ void gaussianElim(float* A, int n) {
    int row = threadIdx.x;              // 每個 thread 擁有一個 row
    for (int k = 0; k < n; ++k) {       // 逐欄 k 消元
        // ── (pivoting 步驟會插在這裡:找 |A[i][k]| 最大者並 swap row) ──
        if (row == k) {                 // pivot row 正規化:除以 lead 係數
            float piv = A[k*(n+1) + k];
            for (int j = k; j <= n; ++j) A[k*(n+1)+j] /= piv;
        }
        __syncthreads();                // barrier:pivot row 已就緒
        if (row > k) {                  // 下方各 row 減去 pivot row → 消去變數
            float f = A[row*(n+1) + k];
            for (int j = k; j <= n; ++j)
                A[row*(n+1)+j] -= f * A[k*(n+1)+j];
        }
        __syncthreads();                // barrier:更新完成才進下一欄
    }
}

數值不穩定:lead 係數為 0

把第一式換成 5Y + 2Z = 16(X 係數 = 0):

0X + 5Y + 2Z = 16      ← step1 需除以 X 係數 0 → 失敗!
2X + 3Y +  Z = 11
 X + 2Y + 2Z = 11

此系統仍可解 (X=1, Y=2, Z=3),但 naive Gaussian elimination 除以零而失敗numerically unstable

Pivoting:交換 row 求穩定

Tip

Pivoting:在剩餘方程式中找出 lead 變數係數不為 0(實務上選絕對值最大)的一式,與目前最上方的式子交換 (swap row),同時對應交換變數 (column)。

[ 0 5 2 | 16 ]   lead 係數 = 0 → 無法 divide
[ 2 3 1 | 11 ]   找 |lead| 最大的 row → swap row0 ↔ row1
[ 1 2 2 | 11 ]
        ↓
[ 2 3 1 | 11 ]   lead 係數 = 2 ≠ 0 → 正常進行 Gaussian elimination
[ 0 5 2 | 16 ]
[ 1 2 2 | 11 ]
Warning

額外洞見:有些 row 本來就不含當前要消去的變數(如 swap 後的 0X+5Y+2Z),負責該 row 的 thread 不需做 division。kernel 中要對「係數已為 0」做判斷。

Pivoting 的平行成本

範圍 成本 對策
單一 block / shared memory 係數都在 shared memory,可跑 parallel max reduction 控制 warp 內 control divergence 即可
多 block / 多 cluster node 係數分散,全域檢查極昂貴 communication-avoiding algorithms (Ballard et al., 2011)

兩種降低全域檢查成本的途徑:

方法 概念 代價
Partial pivoting 候選 swap 限制在局部方程式集合 可能略降數值準確度
Randomization 隨機化處理 研究顯示仍能維持高準確度

附錄總結 (Appendix Summary)

概念 重點
Floating-point format / representable numbers precision 的基礎(見 sibling 筆記 01 / 02)
Denormalized numbers 對許多數值應用很重要;早期 CUDA 不支援,近代硬體已支援
Arithmetic accuracy 快速算術(special function units)可能較低準確度,需留意
平行演算法準確度 平行常改變計算準確度;排序等技巧可改善

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

情境 / 關鍵字 答案 / 技巧
「平行 reduction 與循序加總結果不同」 浮點加法不符結合律;(a+b)+c ≠ a+(b+c),屬正常現象
「大數加極小數,小數不見了」 alignment shifting / swamping:小運算元右移超出 mantissa 範圍被吞掉
「如何提升 reduction 準確度」 presort 升冪 + 大小相近分組;進階用 Kahan compensated summation
「已排序陣列用相鄰配對 reduction 反而變差」 分組把最小最大配對 → 小值被吞;排序方向要配合分組(課本習題 4)
「numerically stable 定義」 解存在時對任意輸入都能找到運算順序求解
「Gaussian elimination 為何失敗 / 不穩定」 lead 係數為 0 無法 divide,即使解存在仍失敗
「如何修正 → pivoting」 找 lead 係數絕對值最大的 row,swap 到最上方
「pivoting 在多 block/cluster 為何慢」 全域檢查分散的係數 → 用 partial pivoting / randomization (communication-avoiding)
「CUDA Gaussian elimination 結構」 一 thread 一 row;division → barrier → subtraction → barrier;消完的 thread 退出
「max ULP error of round-to-zero add」(習題 3) round-to-zero(truncation)最大誤差約 1 ULP(非 0.5 ULP)