Asenkron hücresel otomatalar için paralel (GPU) algoritmaları


12

Asenkron hücresel otomata olarak tanımlanabilecek bir hesaplama modelleri koleksiyonum var. Bu modeller Ising modeline benzer, ancak biraz daha karmaşıktır. Bu modeller bir CPU yerine bir GPU üzerinde çalışmaktan fayda sağlayacak gibi görünüyor. Ne yazık ki böyle bir modeli paralel hale getirmek oldukça basit değil ve bana nasıl gideceğimi net değil. Konuyla ilgili literatür olduğunu biliyorum, ancak hepsi benim gibi uygulayabileceğim bir şeyin tanımını isteyen biri yerine algoritmik karmaşıklığın ayrıntılarıyla ilgilenen hardcore bilgisayar bilimcilerini hedefliyor gibi görünüyor ve sonuç olarak ben bunu olanaksız buluyorum.

Açıklık için, CUDA'da hızlı bir şekilde uygulayabildiğim ve CPU uygulamam üzerinde önemli bir hız kazandıracak kadar uygun bir algoritma aramıyorum. Programcı zamanı, bu projede bilgisayar zamanından çok daha sınırlayıcı bir faktördür.

Ayrıca, eşzamansız bir hücresel otomasyonun eşzamanlı olandan oldukça farklı bir şey olduğunu ve eşzamanlı CA'ları (Conway'in hayatı gibi) paralel hale getirme tekniklerinin bu probleme kolayca uyarlanamayacağını açıklığa kavuşturmalıyım . Fark, eşzamanlı CA'nın her hücreyi her zaman adımında eşzamanlı olarak güncellemesi, eşzamansız hücrenin ise her zaman adımında rasgele seçilen bir yerel bölgeyi aşağıda açıklandığı gibi güncellemesi.

Paralel hale getirmek istediğim modeller ~ 100000 hücreden oluşan (genellikle daha fazla kullanmak isterim) bir kafes (genellikle altıgen bir) üzerinde uygulanır ve bunları çalıştırmak için paralel olmayan algoritma şöyle görünür:

  1. Rasgele komşu bir hücre çifti seçin

  2. Bu hücreleri çevreleyen yerel bir mahalleye göre bir "enerji" işlevi hesaplayınΔE

  3. ( bir parametre ile) bağlı bir olasılıkla , iki hücrenin durumunu değiştirin veya hiçbir şey yapmayın. βe-βΔEβ

  4. Yukarıdaki adımları süresiz olarak tekrarlayın.

Sınır koşullarıyla ilgili bazı komplikasyonlar da var, ancak bunların paralellik için çok zor olmayacağını hayal ediyorum.

Bu sistemlerin sadece denge durumundan ziyade geçici dinamikleriyle ilgilendiğimi belirtmek gerekir, bu yüzden aynı denge dağılımına yaklaşacak bir şeyden ziyade yukarıdakilere eşdeğer dinamiklere sahip bir şeye ihtiyacım var. (Yani chequerboard algoritmasının varyasyonları aradığım şey değil.)

Yukarıdaki algoritmayı paralelleştirmenin ana zorluğu çarpışmalardır. Tüm hesaplamalar yalnızca kafesin yerel bir bölgesine bağlı olduğu için, mahalleleri çakışmadığı sürece birçok kafes alanının paralel olarak güncellenmesi mümkündür. Soru, bu tür çakışmaların nasıl önleneceğidir. Birkaç yol düşünebilirim, ama hangisinin uygulanacağının en iyisi olduğunu bilmiyorum. Bunlar aşağıdaki gibidir:

  • Rastgele ızgara sitelerinin bir listesini oluşturmak ve çakışmaları kontrol etmek için CPU'yu kullanın. Izgara sitesi sayısı GPU işlemci sayısına eşit olduğunda veya bir çakışma algılandığında, ilgili koordinat alanını güncellemek için her koordinat kümesini bir GPU birimine gönderin. CPU'nun çarpışmalarını kontrol etmek muhtemelen tüm güncellemeyi CPU'da yapmaktan çok daha ucuz olmayacağından, bunun uygulanması kolay olurdu, ancak muhtemelen çok fazla hız vermeyecektir.

  • Kafesleri bölgelere bölün (GPU birimi başına bir tane) ve bölgesinde ızgara hücrelerini rastgele seçmekten ve güncellemekten sorumlu bir GPU birimine sahip olun. Ama bu fikirle nasıl çözüleceğimi bilmediğim birçok sorun var, en bariz olanı, bir birim kendi bölgesinin kenarıyla örtüşen bir mahalleyi seçtiğinde tam olarak ne olması gerektiğidir.

  • Sistemi aşağıdaki gibi yaklaşıklandırın: zamanın ayrık adımlarla ilerlemesine izin verin. Kafesleri farklı bir bölüme ayırınönceden belirlenmiş bir şemaya göre her zaman adımında bölgeler kümesi ve her GPU biriminin, komşusu bölgenin sınırını örtüşmeyen bir çift ızgara hücresini rastgele seçmesini ve güncellemesini sağlayın. Sınırlar her adımda değiştiği için, bölgeler nispeten büyük olduğu sürece bu kısıtlama dinamikleri çok fazla etkilemeyebilir. Bu, uygulanması kolay ve hızlı olması muhtemel gibi görünüyor, ancak dinamiğe ne kadar yaklaşacağını veya her zaman adımında bölge sınırlarını seçmek için en iyi şemanın ne olduğunu bilmiyorum. Bu blokla aynı fikirde olan veya olmayan "blok eşzamanlı hücresel otomata" için bazı referanslar buldum. (Biliyorum, çünkü yöntemin tüm açıklamaları Rusça veya erişimim olmayan kaynaklarda.)

Özel sorularım aşağıdaki gibidir:

  • Yukarıdaki algoritmalardan herhangi biri, eşzamansız bir CA modelinin GPU paralelleştirmesine yaklaşmanın mantıklı bir yolu var mı?

  • Daha iyi bir yol var mı?

  • Bu tür bir sorun için mevcut kütüphane kodu var mı?

  • "Blok eşzamanlı" yönteminin İngilizce açıklamasını nerede bulabilirim?

İlerleme

Uygun olabilecek asenkron bir CA'yı paralel hale getirmenin bir yolunu bulduğuma inanıyorum. Aşağıda özetlenen algoritma, benimki gibi komşu bir hücre çiftinden ziyade bir seferde sadece bir hücreyi güncelleyen normal bir eşzamansız CA içindir. Özel durumuma genelleme ile ilgili bazı sorunlar var, ancak bunları nasıl çözeceğime dair bir fikrim var. Ancak, aşağıda tartışılan nedenlerden dolayı, ne kadar bir hız avantajı sağlayacağından emin değilim.

Fikir, asenkron CA'yı (bundan sonra ACA) eşdeğer davranan stokastik bir senkron CA (SCA) ile değiştirmektir. Bunu yapmak için öncelikle ACA'nın bir Poisson süreci olduğunu hayal ediyoruz. Yani, zaman sürekli olarak ilerler ve her hücre, diğer hücrelerden bağımsız olarak güncelleme işlevini yerine getirmek için birim zaman başına sabit bir olasılık olarak.

Bu yazıda, hücreler her bir mağaza iki şey SCA inşa: durum hücrenin (yani, sıralı uygulama, her bir hücre içinde saklanacaktır verileri) ve bir kayan nokta sayısı (sürekli temsil ) bir sonraki güncelleme zamanı . Bu sürekli süre, SCA'nın güncelleme adımlarına karşılık gelmez. İkincisini "mantıksal zaman" olarak anlatacağım. Zaman değerleri üstel bir dağılıma göre rastgele başlatılır: . (Burada , değeri keyfi olarak seçilebilen bir parametredir.) t i j t i j ( 0 ) Uzm ( λ ) λXbenjtbenjtbenj(0)~Tecrübe(λ)λ

Her mantıksal zaman adımında, SCA hücreleri aşağıdaki gibi güncellenir:

  • Eğer herhangi yakınında , zaman , hiçbir şey.i , j t k l < t i jk,lben,jtkl<tbenj

  • XbenjXklΔt~Tecrübe(λ)tbenjtbenj+Δt

Bunun, çarpışmalardan kaçınarak ve bazı hücrelerin paralel olarak güncellenmesine izin verirken, hücrelerin orijinal ACA'ya karşılık gelen "kodu çözülebilecek" bir sırada güncelleneceğini garanti ettiğine inanıyorum. Bununla birlikte, yukarıdaki ilk kurşun noktası nedeniyle, GPU işlemcilerinin çoğunun, SCA'nın her zaman adımında ideal olandan daha az olacağı anlamına gelir.

Bu algoritmanın performansının iyileştirilip iyileştirilemeyeceği ve ACA'da birden çok hücrenin aynı anda güncellendiği durumla başa çıkmak için bu algoritmayı nasıl genişleteceğimi biraz daha düşünmem gerekiyor. Ancak, umut verici görünüyor, bu yüzden (a) literatürde benzer bir şey bilen veya (b) bu ​​kalan meseleler hakkında herhangi bir fikir verebileceği durumlarda burada tarif edeceğimi düşündüm.


Belki de probleminizi şablon tabanlı bir yaklaşımla formüle edebilirsiniz. Şablon tabanlı sorunlar için çok fazla yazılım mevcuttur. Şuna bir göz atabilirsiniz : libgeodecomp.org/gallery.html , Conway'in Hayat Oyunu. Bunun bazı benzerlikleri olabilir.
vanCompute

@vanCompute, harika bir araç gibi görünüyor, ancak ilk (oldukça cursory) araştırmamdan, şablon kodu paradigması doğası gereği senkronize gibi görünüyor, bu yüzden muhtemelen yapmaya çalıştığım şey için uygun değil. Bununla birlikte, daha ayrıntılı olarak ele alacağım.
Nathaniel

SIMT kullanarak bunu nasıl paralelleştireceğinize ilişkin birkaç ayrıntı verebilir misiniz? Çift başına bir iplik kullanır mısınız? Yoksa tek bir çiftin güncellenmesi ile ilgili çalışma 32 veya daha fazla konuya yayılabilir mi?
Pedro

@Pedro, tek bir çiftin güncellenmesi ile ilgili çalışma oldukça küçüktür (temelde sadece mahalle, artı bir rastgele sayı üretecinin bir tekrarı ve bir tane exp()), bu yüzden birden fazla iş parçacığına yaymanın çok mantıklı olduğunu düşünmezdim. Bence iplik başına bir çift ile, birden fazla çift paralel ve güncellemek denemek daha iyi (ve benim için daha kolay).
Nathaniel

Tamam, güncellemeleri eşleştirmek için bir çakışma nasıl tanımlarsınız? Çiftlerin kendileri çakışıyorsa veya mahalleleri çakışıyorsa?
Pedro

Yanıtlar:


4

Ben ilk seçeneği kullanmak ve daha önce (GPU kullanarak), çarpışmaları tespit etmek, kural merkezi hücrenin değeri olan altıgen bir AC adımını yürütmek için eşzamanlı bir AC çalıştırmak kullanacak = Sum (komşular), Bu CA olmalıdır rastgele seçilen hücre ile yedi durum başlatılmalı ve her gpu için güncelleme kuralı çalıştırılmadan önce durumları doğrulanmalıdır.

Örnek 1. Komşu bir hücrenin değeri paylaşılıyor

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

kuralı altıgen merkezi hücre olan bir CA adımı = Sum (komşular)

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

Örnek 2. Güncellenecek hücrenin değeri, diğerinde komşu olarak dikkate alınır

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

Yinelemeden sonra

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

Örnek 3. İlişki yoktur

  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

Yinelemeden sonra

  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


Ö(n)n

Bence paralelleştirilebilecek çok şey var. Çarpışma işlemi tamamen GPU üzerinde gerçekleştirilir, yukarıda yayınlanan bağlantıda gösterildiği gibi senkron bir AC'de bir adımdır. Doğrulama için Sum (komşular) = 8 NO çarpışması, Sum (komşular)> 8 Çarpışma durumunda yerel bir kural kullanılır, eğer çarpışma hücresi durumu yoksa güncelleme kuralı değişikliğiniz çalıştırılmadan önce doğrulanır. yakın olmadıklarında değerlendirilecek noktalar diğer hücrelere aittir.
jlopez1967

Bunu anlıyorum, ama sorun şu ki, bir çarpışma tespit ettiğinizde ne yaparsınız? Yukarıda açıkladığım gibi, CA algoritmanız bir çarpışmayı tespit etmenin sadece ilk adımıdır. İkinci adım, ızgarayı durumu> = 2 olan hücreleri aramaktır ve bu önemsiz değildir.
Nathaniel

örneğin, çarpışma hücresini (5.7), hücresel otomata ve yürütülen toplamda (hücrenin komşuları (5,7)) tespit etmek istediğimizi ve değer 8 ise ve çarpışma yoksa 8'den büyükse bu çarpışma olmaz eşzamansız hücresel otomatadaki hücrenin bir sonraki durumunu tanımlamak için her hücreyi değerlendiren işlevde olmalıdır. Her hücre için çarpışma tespiti, yalnızca komşu hücrelerini içeren yerel bir kuraldır
jlopez1967

Evet, ancak eşzamansız bir CA'yı paralel hale getirmek için cevaplayabilmemiz gereken soru "hücrede (5,7) bir çarpışma oldu" değil "" ızgarada bir yerde bir çarpışma oldu mu? o?" Şebeke üzerinden tekrarlamadan bu cevaplanamaz.
Nathaniel

1

Yukarıdaki yorumlarda yer alan sorularıma verdiğiniz cevapların ardından, her bir iş parçacığının güncellemeyi hesaplamadan önce güncelleyeceği mahalleyi kilitlemeye çalıştığı kilit tabanlı bir yaklaşım denemenizi öneririz.

Bunu, CUDA'da sağlanan atomik işlemleri ve inther hücre için kilitleri içeren bir dizi kullanarak yapabilirsiniz , örn lock. Daha sonra her bir iş parçacığı aşağıdakileri yapar:

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;

    }

Bu yaklaşımın muhtemelen en uygun olmadığını unutmayın, ancak ilginç bir başlangıç ​​noktası sağlayabilir. Eğer dişler arasında çok fazla çarpışma varsa, yani 32 iş parçacığı için bir veya daha fazla (çözgü başına bir çarpışmada olduğu gibi), o zaman biraz dal saptırma olacaktır. Ayrıca, atomik işlemler biraz yavaş olabilir, ancak sadece karşılaştırma ve takas işlemleri yaptığınız için, tamam ölçeklendirilmelidir.

Kilitleme yükü korkutucu görünebilir, ancak gerçekten sadece birkaç ödev ve dal, daha fazlası değil.

Ayrıca i, komşuların döngülerinde notasyon ile hızlı ve gevşek olduğumu da unutmayın .

Zeyilname: Çiftler çarpıştığında geri çekilebileceğini düşünecek kadar süvaridaydım. Eğer durum böyle değilse, o zaman ikinci satırdan itibaren her şeyi bir- whiledöngüsünde sarabilir ve breakson- ifdurumun sonuna a ekleyebilirsiniz .

Daha sonra tüm iş parçacıkları sonuncusu yapılana kadar beklemek zorunda kalacak, ancak çarpışmalar nadirse, ondan kurtulabilmelisiniz.

Ek 2: Do not çağrıları eklemek için cazip __syncthreads()özellikle döngü sürümü önceki ekinde tanımlanmıştır, bu kodda her yerde! Asenkroniklik, ikinci durumda tekrarlanan çarpışmalardan kaçınmak için gereklidir.


Teşekkürler, bu oldukça iyi görünüyor. Muhtemelen düşündüğüm karmaşık fikirden daha iyi ve uygulanması çok daha kolay. Muhtemelen iyi olan büyük bir ızgara kullanarak çarpışmaları nadir yapabilirim. Just-back-off yöntemi önemli ölçüde daha hızlı çıkıyorsa, parametreleri gayri resmi olarak araştırmak için kullanabilir ve resmi sonuçlar üretmem gerektiğinde herkesi tamamlamayı bekleyen yönteme geçebilirim. Bunu bir süre sonra deneyeceğim.
Nathaniel

1

LibGeoDecomp'ın baş geliştiricisiyim. VanCompute ile ACA'nızı bir CA ile taklit edebileceğinizi kabul etsem de, herhangi bir adımdaki sadece birkaç hücrenin güncellenmesi gerektiği için bunun çok verimli olmayacağını haklıyorsunuz. Bu gerçekten çok ilginç bir uygulama - ve tamircilik eğlenceli!

Jlopez1967 ve Pedro'nun önerdiği çözümleri birleştirmenizi öneririm: Pedro'nun algoritması paralelliği iyi yakalar, ancak bu atomik kilitler son derece yavaştır. Jlopez1967'nin çözümü, çarpışmaları tespit etme konusunda zariftir, ancak tüm nhücreleri kontrol etmek , sadece daha küçük bir alt küme ( kbundan sonra aynı anda güncellenecek hücre sayısını gösteren bir parametre olduğunu varsayalım ) aktif olduğunda, açıkça yasaklayıcı.

__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'da iyi bir küresel senkronizasyon olmadığında, farklı fazlar için birden çok çekirdeği çağırmanız gerekir. Nvidia'nın Kepler'ında ana döngüyü bile GPU'ya taşıyabilirsiniz, ancak bunun çok fazla kazanmasını beklemiyorum.

Algoritmalar (yapılandırılabilir) bir paralellik derecesine ulaşır. Sanırım ilginç soru, çarpışmaları artırdığınızda rastgele dağılımınızı etkileyip etkilemeyeceğidir k.


0

Bu bağlantıyı görmenizi öneririm http://www.wolfram.com/training/courses/hpc021.html CUDA kullanarak bir hücresel otomata uygulaması yaptıkları video dersine tabii ki videoda yaklaşık 14:15 dakika , oradan ve değiştirebilirsiniz.


Ne yazık ki bu eşzamanlı CA, uğraştığım eşzamansız olanlardan oldukça farklı bir canavar türü. Eşzamanlı bir CA'da her hücre aynı anda güncellenir ve bunun bir GPU'da paralel olması kolaydır, ancak eşzamansız bir CA'da her adımda rastgele seçilen tek bir hücre güncellenir (aslında benim durumumda iki komşu hücre) ve bu da Paralellik çok daha zor. Sorumda özetlenen sorunlar, zaman uyumsuz bir güncelleme işlevine ihtiyaç duyuyor.
Nathaniel
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.