再現性と決定的並列リダクション

同じ入力・同じコードなのに実行のたびに答えが変わる。原因は並列リダクションの加算順序にあります。原理を押さえればビット単位で再現する結果を設計できます。

応用HPC浮動小数点再現性並列計算リダクション数値解析最終更新: 2026-07-29
3つの要点
TL;DR
  1. 浮動小数点加算は結合則を満たさないため、並列リダクションのスレッド数や集約順序が変わると総和の最下位ビットが変化する。
  2. ビット再現性(bitwise reproducibility)は決定的な集約順序を固定して初めて得られ、アトミック加算やatomicの到着順依存は非決定性の主因になる。
  3. Kahanの補償加算は誤差を小さくするが順序非依存にはしない。真の再現には固定順序リダクションや長蓄積器・整数化などの決定的手法が要る。

なぜ「同じ計算」の答えがブレるのか

同一のバイナリを同一のノードで動かしても、スレッド数を変えたりGPUで実行したりすると、総和やノルムの最下位ビットが実行ごとに変わることがあります。バグではありません。原因は浮動小数点加算が結合則(associativity)を満たさない、という数学的事実にあります。

実数の加算では (a + b) + ca + (b + c) は等しいですが、IEEE 754浮動小数点では各加算のたびに結果が最近接の表現可能値へ丸められるため、途中結果の丸め位置が違えば最終値も違い得ます。典型例として、大きな値 A に対して非常に小さな値 e を足すと、A + e が丸めで A に戻ってしまう「吸収(absorption)」が起きます。

結合則が崩れる最小例(10進で仮数3桁の丸めを仮定)

  A = 1.00e+3,  b = 1.00e+0,  c = -1.00e+3

  (A + b) + c = (1001 → 丸め 1.00e+3) + (-1.00e+3) = 0.00
  A + (b + c) = 1.00e+3 + (1.00e+0 - 1.00e+3)      = 1.00e+0

  同じ3つの数でも、足す順序で答えが 0 と 1 に割れる

つまり総和の値は集合 {A,b,c} だけでは決まらず、加算の順序に依存します。逐次実行では順序が一意なので毎回同じ答えになりますが、並列化すると順序が実装・スケジューリング任せになり、ここに非再現性が入り込みます。この丸め自体の性質は「数値安定性と浮動小数点誤差」で扱った蓄積誤差と同根ですが、本稿の主題は誤差の大小ではなく結果が一意に定まるかという別軸です。

精度(accuracy)と再現性(reproducibility)は別物

より正確な答えを出すこと(accuracy)と、毎回同じ答えを出すこと(reproducibility)は独立した目標です。ツリー型リダクションは逐次加算より真値に近い高精度な総和を返すことが多い一方、木の形がスレッド数で変わると再現性は失われます。逆に、多少不正確でも順序を固定すれば完全に再現します。どちらが要件かを取り違えないことが設計の出発点です。

並列リダクションで順序が変わる仕組み

n 要素の総和を並列化する典型は、部分和を各スレッドが計算し、それらを木状に集約するツリー型リダクションです。逐次加算が深さ n の直鎖であるのに対し、ツリー型は深さ log2(n) で集約するため、加算のグルーピング(結合の入れ方)が根本的に異なります。

横にスクロール

再現性と決定的並列リダクションについて、問題分割から計算、通信、集約、性能限界までを示す図
並列処理の実行経路と、性能・精度・通信のトレードオフを整理します。

問題は、そのグルーピングが実行環境で変わる点です。スレッド数を変えれば1スレッドが担当する要素範囲が変わり、部分和の切れ目がずれます。GPUではブロック数・ワープ数・占有率で分割が決まります。さらに、集約の最終段でしばしば使われるアトミック加算(atomicAdd)は、各スレッドの部分和が到着した順にグローバル変数へ足し込むため、到着順そのものがハードウェアのスケジューリングに左右され、実行ごとに変わります。

非決定性が混入する主なポイント

  1. スレッド/ブロック数の変化 → 部分和の区切りが変わる(結合の入れ方が変わる)
  2. atomicAdd による集約     → 部分和の到着順で加算順が変わる(実行ごとに非決定)
  3. 動的スケジューリング       → どのスレッドがどの範囲を取るかが可変
  4. FMA(融合積和)の有無       → a*b+c を1回で丸めるか2回で丸めるかで結果が変わる

このうちアトミック加算は再現性にとって最も厄介です。atomicAdd 自体は競合を排他して値の破壊は防ぎますが、順序は保証しません。同じ数の集合をランダムな順で足すのだから、結果は毎回変わり得ます。この種の集約は分散学習の勾配同期やモンテカルロ積分の集計でも現れ、「モンテカルロ法(HPC・科学計算)」で扱うサンプル総和や、CG法の内積計算(「反復法による連立一次方程式の求解」)でも同じ非決定性が問題になります。

atomicAddは高速だが再現しない

GPUの atomicAdd は実装が容易で速いため多用されますが、浮動小数点に対しては加算順が非決定なので、ビット再現性を要件とするコードでは原則使えません。再現性が必要なら、順序が定まる決定的リダクションへ置き換える必要があります。

ビット再現性を得るための決定的手法

ビット再現性(bitwise reproducibility)とは、入力が同じなら実行環境(スレッド数・ハードウェア)が変わっても結果がビット単位で一致することを指します。これを満たす基本方針は「加算のグルーピングを実行環境から切り離して固定する」ことです。代表的な手法を整理します。

手法考え方コスト・制約
固定順序リダクションスレッド数に依らず常に同じ木形状・同じ区切りで集約する(例: 固定ブロックサイズの決定的ツリー)並列度を落とすことがある。実装を環境非依存に固定する必要
長蓄積器(long accumulator)総和を超広ビットの固定小数点で保持し丸めなしで足す。順序に完全非依存メモリ・実装が重い。Kulisch型は数百〜数千ビット
再現可能BLAS(ReproBLAS等)ビン分割(binning)で指数域ごとに部分和を分け、順序非依存に合算専用ライブラリ依存、素朴な総和より数倍遅い
整数化(fixed-point)既知のスケールで整数へ写し整数加算する。整数加算は結合則を満たす値域とスケールの事前設計が必須。桁あふれ管理が要る

鍵となる洞察は、整数加算は結合則を満たすという点です。浮動小数点の非再現性は「途中で丸めること」に由来するので、丸めが起きないほど広いビット幅で足し込めば順序に依存しなくなります。長蓄積器(Kulischアキュムレータ)は倍精度の全指数域を丸めなしに収容できる固定小数点レジスタで、どの順で足しても同じビット列になります。再現可能BLASはこの発想を軽量化し、指数の範囲をいくつかのビンに分け、各ビン内では丸めが起きないように部分和を積んでから最後に合算することで、順序非依存かつ現実的なコストを実現します。

固定順序リダクションが最も実装しやすい

特別なライブラリを使わずに再現性を確保する最短路は、集約の木形状をコード側で固定してしまうことです。ブロックサイズ・削減ステップ・ランクの集約順序を実行環境に依らず定数で決め打ちすれば、スレッド数を変えても同じ加算順になります。MPIレベルでも、集団通信のReduce/Allreduceは実装がメッセージサイズやプロセス数でアルゴリズム(木・リング・バタフライ)を切り替えるため順序が変わり得ます。詳細は「MPIと集団通信」を参照してください。再現性が要るなら固定アルゴリズムを明示指定します。

Kahan補償加算は「再現性」ではなく「精度」の道具

総和の誤差対策として知られるKahanの補償加算(compensated summation)は、各加算で切り捨てられた下位ビットを補正項 c に退避し次の加算で足し戻すことで、総和の誤差を O(n・ε) から O(ε) 程度へ抑えます。ここで注意すべきは、Kahan補償加算は精度を上げるが順序非依存にはしないという点です。

Kahan補償加算(擬似コード)── 精度は上がるが順序は依然として意味を持つ

  sum = 0.0
  c   = 0.0                 // 切り捨てられた誤差を保持
  for x in 配列:            // ← この for の巡回順が変われば結果も変わり得る
      y = x - c
      t = sum + y
      c = (t - sum) - y     // 今回失われた下位ビットを回収
      sum = t
  戻り値: sum

補償加算はあくまで逐次的な誤差回収の仕組みで、要素を足す順序が変われば補正項 c の履歴も変わり、最終ビットは一致しません。したがって並列環境で各スレッドがKahan補償加算した部分和を、さらにアトミックに集約すれば、部分和の到着順で答えは揺れます。補償加算と再現性を混同すると「精度は上げたのに実行ごとに答えが違う」という状態に陥ります。

実務では両者を組み合わせるのが定石です。すなわち(1)集約の順序を固定して再現性を確保し、その固定順序の各段で(2)Kahan補償加算や長蓄積器を使って精度を確保する、という二段構えです。順序の固定が再現性を、補償・広ビット化が精度を担うと役割を分けて考えると設計が明快になります。

試験・面接での要点

「並列総和が実行ごとに違う値になる根本原因は何か」と問われたら、答えは浮動小数点加算が結合則を満たさず、並列化で加算順序(グルーピング)が非決定になるためです。対策としてKahan補償加算を挙げるのは不十分で、補償加算は精度対策であり順序非依存ではない点まで述べられると上級者の理解を示せます。ビット再現性には固定順序リダクション・長蓄積器・再現可能BLAS・整数化といった決定的手法が要ります。

まとめ

  • 浮動小数点加算は丸めのため結合則を満たさず、並列リダクションのスレッド数・集約順序が変わると総和の最下位ビットが変化する。
  • 非決定性の主因は、部分和の区切りの変化・アトミック加算の到着順依存・動的スケジューリング・FMAの有無であり、なかでも atomicAdd は高速だが再現しない。
  • ビット再現性は加算のグルーピングを環境から切り離して固定することで得られ、固定順序リダクション・長蓄積器・再現可能BLAS・整数化が決定的手法となる。
  • Kahan補償加算は誤差を O(ε) に抑える精度の道具であって順序非依存ではないため、順序固定(再現性)と補償・広ビット化(精度)は役割を分けて二段構えで設計する。

HPC・科学技術計算の記事ガイド

再現性と決定的並列リダクションを実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

HPC

比較で見る軸

難易度: advanced / カテゴリ: HPC・科学技術計算 / タグ数: 6

導入後に効く点

ビット再現性(bitwise reproducibility)は決定的な集約順序を固定して初めて得られ、アトミック加算やatomicの到着順依存は非決定性の主因になる。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
HPC・科学技術計算
タグ数
6

判断チェックリスト

  • 自社の用途が「HPC / 浮動小数点」に近いか確認する。
  • 強みである「浮動小数点加算は結合則を満たさないため、並列リダクションのスレッド数や集約順序が変わると総和の最下位ビットが変化する。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

HPC浮動小数点再現性並列計算リダクション