非同期セルオートマトン用の並列(GPU)アルゴリズム


12

非同期セルラーオートマトンと呼ばれる計算モデルのコレクションがあります。これらのモデルはイジングモデルに似ていますが、少し複雑です。このようなモデルは、CPUではなくGPUで実行することでメリットが得られるようです。残念ながら、そのようなモデルを並列化することは非常に簡単ではなく、どのように実行するかはまったくわかりません。私は主題に関する文献があることを知っていますが、それはすべて、私が実装できるものの説明を望んでいる私のような人ではなく、アルゴリズムの複雑さの詳細に興味がある筋金入りのコンピュータ科学者を対象にしているようです、その結果、私はそれをかなり浸透していません。

明確にするために、私はCPU実装を大幅に高速化する可能性があるCUDAで迅速に実装できるものほど最適なアルゴリズムを探していません。このプロジェクトでは、プログラマーの時間はコンピューターの時間よりもはるかに制限要因です。

また、非同期セルオートマトンは同期セルオートマトンとはかなり異なるものであり、同期CA(Conwayの生活など)を並列化する技術はこの問題に簡単に適応できないことを明確にする必要があります。違いは、同期CAはすべてのセルをタイムステップごとに同時に更新するのに対して、非同期CAはランダムに選択されたローカルリージョンをタイムステップごとに更新することです。

並列化したいモデルは、〜100000セルからなる格子(通常は六角形)に実装されています(さらに使用したいのですが)、それらを実行するための非並列化アルゴリズムは次のようになります。

  1. 隣接するセルのペアをランダムに選択します

  2. 「エネルギー」機能計算これらの細胞の周囲の局所近傍に基づきますE

  3. 依存確率で(とβ Aパラメータ)、いずれか2つのセルの状態を入れ替えるか、何もしません。eβEβ

  4. 上記の手順を無期限に繰り返します。

境界条件にはいくつかの複雑な問題もありますが、並列化にそれほど困難はないと思います。

ちょうど平衡状態ではなく、これらのシステムの過渡的なダイナミクスに興味があることに言及する価値があるため、同じ平衡分布に近づくだけではなく、上記と同等のダイナミクスを持つものが必要です。(したがって、チェッカーボードアルゴリズムのバリエーションは、私が探しているものではありません。)

上記のアルゴリズムを並列化する際の主な困難は衝突です。すべての計算はラティスのローカル領域のみに依存するため、近傍が重複していない限り、多くのラティスサイトを並行して更新できます。問題は、このような重複を回避する方法です。いくつかの方法を考えることができますが、実装するのに最適な方法があるかどうかはわかりません。これらは次のとおりです。

  • CPUを使用して、ランダムグリッドサイトのリストを生成し、衝突をチェックします。グリッドサイトの数がGPUプロセッサの数と等しい場合、または衝突が検出された場合、座標の各セットをGPUユニットに送信して、対応するグリッドサイトを更新します。これは簡単に実装できますが、おそらくCPUでの衝突のチェックはCPUで更新全体を行うよりもそれほど安くないので、おそらく速度をあまり上げません。

  • ラティスをリージョン(GPUユニットごとに1つ)に分割し、そのリージョン内のグリッドセルをランダムに選択および更新するGPUユニットを1つ用意します。しかし、このアイデアには解決方法がわからない多くの問題があります。最も明白なのは、ユニットがその領域の端に重なる近傍を選択したときに正確に何が起こるかです。

  • システムを次のように近似します。時間を個別のステップで進めます。格子を上に分割します別のものに事前に定義されたスキームに従ってタイムステップごとに領域のセットを作成し、各GPUユニットが、領域の境界と重なり合わないグリッドセルのペアをランダムに選択および更新します。境界はタイムステップごとに変化するため、領域が比較的大きい限り、この制約はダイナミクスにあまり影響しません。これは簡単に実装でき、高速であるように見えますが、ダイナミクスをどれだけ近似するか、または各タイムステップで領域境界を選択するための最適なスキームはどれかわかりません。「ブロック同期セルオートマトン」への参照をいくつか見つけましたが、これはこの考えと同じである場合とそうでない場合があります。(メソッドの説明はすべてロシア語であるか、アクセスできないソースにあるようですので、わかりません。)

私の具体的な質問は次のとおりです。

  • 上記のアルゴリズムのいずれかは、非同期CAモデルのGPU並列化にアプローチする賢明な方法ですか?

  • もっと良い方法はありますか?

  • このタイプの問題に対応する既存のライブラリコードはありますか?

  • 「ブロック同期」メソッドの明確な英語の説明はどこにありますか?

進捗

適切な非同期CAを並列化する方法を考え出したと思います。以下に概説するアルゴリズムは、私のように隣接するセルのペアではなく、一度に1つのセルのみを更新する通常の非同期CA用です。私の特定のケースにそれを一般化することにはいくつかの問題がありますが、私はそれらを解決する方法を考えていると思います。ただし、以下で説明する理由により、どれだけの速度のメリットが得られるかはわかりません。

考え方は、非同期CA(以降ACA)を、同等に動作する確率的同期CA(SCA)に置き換えることです。これを行うには、まずACAがポアソンプロセスであると考えます。つまり、時間は継続的に進み、各セルは、他のセルとは無関係に、その更新機能を実行する単位時間あたりの一定の確率として進行します。

バツjtjtj0Expλλ 値を任意に選択できるパラメーターです。)

各論理タイムステップで、SCAのセルは次のように更新されます。

  • kljtkl<tj

  • バツjバツkltExpλtjtj+t

これにより、元のACAに対応するように「デコード」可能な順序でセルが更新され、衝突を回避して一部のセルを並行して更新できるようになると確信しています。ただし、上記の最初の箇条書きのため、ほとんどのGPUプロセッサはSCAの各タイムステップでほとんどアイドル状態になり、理想的ではありません。

このアルゴリズムのパフォーマンスを改善できるかどうか、およびこのアルゴリズムを拡張して、ACAで複数のセルが同時に更新される場合に対処する方法について、さらに検討する必要があります。しかし、有望に見えるので、誰かが(a)文献で似たようなことを知っている、または(b)これらの残りの問題についての洞察を提供できる場合に、ここで説明しようと思いました。


おそらく、ステンシルベースのアプローチで問題を定式化できます。ステンシルベースの問題には多くのソフトウェアが存在します。libgeodecomp.org/gallery.html、ConwayのGame of Lifeをご覧ください。これにはいくつかの類似点があります。
vanCompute

@vanComputeは素晴らしいツールのように見えますが、最初の(やや大雑把な)調査から、ステンシルコードのパラダイムは本質的に同期しているように見えるため、おそらく私がやろうとしていることにはあまり適していません。ただし、さらに詳しく調べます。
ナサニエル

SIMTを使用してこれをどのように並列化するかについて、さらに詳しく説明していただけますか?ペアごとに1つのスレッドを使用しますか?または、単一のペアの更新に関連する作業を32以上のスレッドに分散できますか?
ペドロ

@Pedroは、単一のペアの更新に関連する作業がかなり小さい(基本的には近隣で合計するだけでなく、乱数ジェネレーターの1回の反復と1回の反復exp())ので、複数のスレッドに分散することはあまり意味がないと思います。私は、スレッドごとに1つのペアを使用して、複数のペアを並行して更新しようとする方が良いと思います(そして私にとっては簡単です)。
ナサニエル

さて、更新をペアリングするためのオーバーラップをどのように定義しますか?ペア自体が重複する場合、またはそれらの近傍が重複する場合は?
ペドロ

回答:


4

最初のオプションを使用し、前に(GPUを使用して)同期AC実行を使用して、衝突を検出し、ルールが中央セルの値= Sum(隣接)である六角形ACのステップを実行します。ランダムに選択したセルで7つの状態を開始し、各GPUの更新ルールを実行する前に状態を確認する必要があります。

サンプル1.隣接セルの値は共有されています

0 0 0 0 0 0 0

  0 0 1 0 0 0

0 0 0 0 0 0 0

  0 0 0 1 0 0

0 0 0 0 0 0 0

規則が六角形の中心セル= Sum(隣接)であるCAのステップ

0 0 1 1 0 0 0

  0 1 1 1 0 0

0 0 1 2 1 0 0

  0 0 1 1 1 0

0 0 0 1 1 0 0

サンプル2.更新するセルの値は、他方の隣接セルとして考慮されます

0 0 0 0 0 0 0

  0 0 1 0 0 0

0 0 0 1 0 0 0

  0 0 0 0 0 0

0 0 0 0 0 0 0

反復後

0 0 1 1 0 0 0

  0 1 2 2 0 0

0 0 2 2 1 0 0

  0 0 1 1 0 0

0 0 0 0 0 0 0

サンプル3.関係はありません

  0 0 0 0 0 0

0 0 1 0 0 0 0

  0 0 0 0 0 0

0 0 0 0 0 0 0

  0 0 0 1 0 0

0 0 0 0 0 0 0

反復後

  0 1 1 0 0 0

0 1 1 1 0 0 0

  0 1 1 0 0 0

0 0 0 1 1 0 0

  0 0 1 1 1 0

0 0 0 1 1 0 0


Onn

並列化できることはたくさんあると思います。衝突処理は、上記のリンクに示されているように、GPU上で完全に行われ、同期ACのステップです。Sum(neighbors)= 8 NO collision、Sum(neighbors)> 8 Collisionの場合、検証のためにローカルルールが使用されます。近くにない場合に評価されるポイントは、他のセルに属するポイントです。
jlopez1967

私はそれを理解していますが、問題は、衝突を検出したらどうしますか?上で説明したように、CAアルゴリズムは衝突を検出する最初のステップにすぎません。2番目のステップは、2以上の状態のセルをグリッドで検索することです。これは簡単なことではありません。
ナサニエル

たとえば、セルオートマトンと実行合計(セルの隣人(5,7))で衝突セル(5.7)を検出し、値が8で衝突が8より大きい場合は衝突なしを検出することを想像してください。非同期セルオートマトンでセルの次の状態を定義するために各セルを評価する関数内にある必要があります。各セルの衝突の検出は、その隣接セルのみを含むローカルルールです
-jlopez1967

はい。ただし、非同期CAを並列化するために答える必要がある質問は、「セル(5,7)で衝突があった」ではなく、「グリッドのどこかに衝突があり、そうであればどこにあったか」です。それ?" それは、グリッド上で反復することなく答えることはできません。
ナサニエル

1

上記のコメントの私の質問への回答に続いて、実際の更新を計算する前に、各スレッドが更新する近傍をロックダウンしようとするロックベースのアプローチを試すことをお勧めします。

これを行うには、CUDAで提供されているアトミック操作と、int各セルのロックを含む配列などを使用しlockます。その後、各スレッドは次のことを行います。

ci, cj = choose a pair at random.

int locked = 0;

/* Try to lock the cell ci. */
if ( atomicCAS( &lock[ci] , 0 , 1 ) == 0 ) {

    /* Try to lock the cell cj. */
    if ( atomicCAS( &lock[cj] , 0 , 1 ) == 0 ) {

        /* Now try to lock all the neigbourhood cells. */
        for ( cn = indices of all neighbours )
            if ( atomicCAS( &lock[cn] , 0 , 1 ) != 0 )
                break;

        /* If we hit a break above, we have to unroll all the locks. */
        if ( cn < number of neighbours ) {
            lock[ci] = 0;
            lock[cj] = 0;
            for ( int i = 0 ; i < cn ; i++ )
                lock[i] = 0;
            }

        /* Otherwise, we've successfully locked-down the neighbourhood. */
        else
            locked = 1;

        }

    /* Otherwise, back off. */
    else
        lock[ci] = 0;
    }

/* If we got everything locked-down... */
if ( locked ) {

    do whatever needs to be done...

    /* Release all the locks. */
    lock[ci] = 0;
    lock[cj] = 0;
    for ( int i = 0 ; i < cn ; i++ )
        lock[i] = 0;

    }

このアプローチはおそらく最適ではありませんが、興味深い出発点を提供できることに注意してください。スレッド間に多くの衝突がある場合、つまり32スレッドごとに1つ以上の衝突がある場合(ワープごとに1つの衝突のように)、分岐分岐がかなり発生します。また、アトミック操作は少し遅くなる可能性がありますが、比較とスワップ操作のみを実行しているため、スケールは問題ありません。

ロックのオーバーヘッドは恐ろしく見えるかもしれませんが、実際にはほんの数個の割り当てとブランチであり、それ以上ではありません。

また、私はi隣人以上のループの表記法で速くてゆるんでいることに注意してください。

補遺:ペアが衝突するタイミングを単純に引き下げることができると仮定するのに十分無頓着でした。そうでない場合は、2行目のすべてを-loopでラップし、最後の最後にwhilea breakを追加できます。if -loopでラップし -statementのます。

その場合、すべてのスレッドは最後のスレッドが完了するまで待機する必要がありますが、衝突がまれであれば、それを回避できるはずです。

補遺2:このコードのどこにでも、特に前の補遺で説明したループバージョンに呼び出しを追加しようとしないでください__syncthreads()!後者の場合に繰り返される衝突を回避するには、非同期性が不可欠です。


ありがとう、これはかなりよさそうだ。おそらく私が考えていた複雑なアイデアよりも優れており、実装がはるかに簡単です。おそらく十分な大きさのグリッドを使用することで、衝突をまれにすることができます。ジャストバックオフ方式の方が大幅に高速であることが判明した場合、それを使用して非公式にパラメーターを調査し、公式結果を生成する必要がある場合は、「すべての人が他の人を待つ」方法に切り替えます。しばらくしてこれを試してみます。
ナサニエル

1

私はLibGeoDecompの主任開発者です。vanComputeには、CAを使用してACAをエミュレートできることは同意しますが、特定のステップで更新されるセルはごくわずかであるため、これはあまり効率的ではないことは間違いありません。これは確かに非常に興味深いアプリケーションであり、いじくり回すのも楽しいです!

jlopez1967とPedroによって提案されたソリューションを組み合わせることをお勧めします。Pedroのアルゴリズムは並列処理をうまくキャプチャしますが、これらのアトミックロックは非常に遅いです。jlopez1967の解決策は、衝突の検出に関してはエレガントですがn、より小さいサブセット(k同時に更新されるセルの数を示すパラメーターがあると仮定します)のみがアクティブな場合、すべてのセルをチェックします。明らかに禁止です。

__global__ void markPoints(Cell *grid, int gridWidth, int *posX, int *posY)
{
    int id = blockIdx.x * blockDim.x + threadIdx.x;
    int x, y;
    generateRandomCoord(&x, &y);
    posX[id] = x;
    posY[id] = y;
    grid[y * gridWidth + x].flag = 1;
}

__global__ void checkPoints(Cell *grid, int gridWidth, int *posX, int *posY, bool *active)
{
    int id = blockIdx.x * blockDim.x + threadIdx.x;
    int x = posX[id];
    int y = posY[id];
    int markedNeighbors = 
        grid[(y - 1) * gridWidth + x + 0].flag +
        grid[(y - 1) * gridWidth + x + 1].flag +
        grid[(y + 0) * gridWidth + x - 1].flag +
        grid[(y + 0) * gridWidth + x + 1].flag +
        grid[(y + 1) * gridWidth + x + 0].flag +
        grid[(y + 1) * gridWidth + x + 1].flag;
    active[id] = (markedNeighbors > 0);
}


__global__ void update(Cell *grid, int gridWidth, int *posX, int *posY, bool *active)
{
    int id = blockIdx.x * blockDim.x + threadIdx.x;
    int x = posX[id];
    int y = posY[id];
    grid[y * gridWidth + x].flag = 0;
    if (active[id]) {
        // do your fancy stuff here
    }
}

int main() 
{
  // alloc grid here, update up to k cells simultaneously
  int n = 1024 * 1024;
  int k = 1234;
  for (;;) {
      markPoints<<<gridDim,blockDim>>>(grid, gridWidth, posX, posY);
      checkPoints<<<gridDim,blockDim>>>(grid, gridWidth, posX, posY, active);
      update<<<gridDim,blockDim>>>(grid, gridWidth, posX, posY, active);
  }
}

GPUに適切なグローバル同期が存在しない場合、さまざまなフェーズで複数のカーネルを呼び出す必要があります。NvidiaのKeplerでは、メインループをGPUに移動することもできますが、それほど大きな効果は期待できません。

アルゴリズムは(構成可能な)並列度を実現します。興味深い質問は、を増やしkたときに衝突がランダム分布に影響するかどうかです。


0

このリンクをご覧になることをお勧めしますhttp://www.wolfram.com/training/courses/hpc021.htmlビデオのおよそ14:15分、CUDAを使用してセルオートマトンの実装を行う数学トレーニング、そこから変更できます。


残念なことに、これは同期CAであり、私が扱っている非同期CAとはかなり異なる種類の獣です。同期CAでは、すべてのセルが同時に更新され、これはGPUで簡単に並列化できますが、非同期CAでは、ランダムに選択された単一のセルがタイムステップごとに更新されます(実際には私の場合は2つの隣接セルです)。並列化ははるかに困難です。私の質問で概説されている問題は、非同期更新機能が必要な場合に固有のものです。
ナサニエル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.