Ich habe eine Sammlung von Rechenmodellen, die als asynchrone zellulare Automaten beschrieben werden könnten. Diese Modelle ähneln dem Ising-Modell, sind jedoch etwas komplizierter. Es scheint, als würden solche Modelle davon profitieren, auf einer GPU anstatt auf einer CPU betrieben zu werden. Leider ist es nicht ganz einfach, ein solches Modell zu parallelisieren, und mir ist überhaupt nicht klar, wie ich damit umgehen soll. Mir ist bewusst, dass es Literatur zu diesem Thema gibt, aber alles scheint sich an Hardcore-Informatiker zu richten, die sich für Details der algorithmischen Komplexität interessieren, und nicht an jemanden wie mich, der nur eine Beschreibung von etwas möchte, das ich implementieren kann, und folglich finde ich es eher undurchdringlich.
Der Klarheit halber suche ich nicht so sehr nach einem optimalen Algorithmus, als nach etwas, das ich schnell in CUDA implementieren kann und das wahrscheinlich zu einer deutlichen Beschleunigung meiner CPU-Implementierung führt. Die Programmierzeit ist in diesem Projekt ein wesentlich begrenzenderer Faktor als die Computerzeit.
Ich sollte auch klarstellen, dass ein asynchroner zellularer Automat etwas anderes ist als ein synchroner, und dass Techniken zur Parallelisierung synchroner CAs (wie Conways Leben) nicht einfach an dieses Problem angepasst werden können. Der Unterschied besteht darin, dass eine synchrone Zertifizierungsstelle jede Zelle zu jedem Zeitschritt gleichzeitig aktualisiert, während eine asynchrone Zertifizierungsstelle zu jedem Zeitschritt eine zufällig ausgewählte lokale Region aktualisiert, wie unten dargestellt.
Die Modelle, die ich parallelisieren möchte, sind auf einem Gitter (normalerweise einem hexagonalen) implementiert, das aus ~ 100000 Zellen besteht (obwohl ich gerne mehr verwenden würde), und der nicht parallelisierte Algorithmus für deren Ausführung sieht folgendermaßen aus:
Wählen Sie ein benachbartes Zellenpaar nach dem Zufallsprinzip
Berechnen Sie eine "Energie" -Funktion basierend auf einer diese Zellen umgebenden lokalen Nachbarschaft
Vertauschen Sie mit einer Wahrscheinlichkeit, die von abhängt (mit a parameter), entweder die Zustände der beiden Zellen oder tun Sie nichts.
Wiederholen Sie die obigen Schritte auf unbestimmte Zeit.
Es gibt auch einige Komplikationen im Zusammenhang mit den Randbedingungen, aber ich kann mir vorstellen, dass diese für die Parallelisierung keine großen Schwierigkeiten bereiten.
Es ist erwähnenswert, dass ich mich eher für die transiente Dynamik dieser Systeme als nur für den Gleichgewichtszustand interessiere. Deshalb brauche ich etwas, das der obigen Dynamik entspricht, und nicht nur etwas, das sich der gleichen Gleichgewichtsverteilung annähert. (Daher sind Variationen des Schachbrett-Algorithmus nicht das, wonach ich suche.)
Die Hauptschwierigkeit bei der Parallelisierung des obigen Algorithmus sind Kollisionen. Da alle Berechnungen nur von einer lokalen Region des Gitters abhängen, können viele Gitterstandorte parallel aktualisiert werden, sofern sich ihre Nachbarschaften nicht überlappen. Die Frage ist, wie solche Überschneidungen vermieden werden können. Ich kann mir verschiedene Möglichkeiten vorstellen, aber ich weiß nicht, welche, wenn überhaupt, die beste ist, die ich implementieren kann. Dies sind wie folgt:
Verwenden Sie die CPU, um eine Liste von zufälligen Gitterstandorten zu erstellen und auf Kollisionen zu prüfen. Wenn die Anzahl der Grid-Standorte der Anzahl der GPU-Prozessoren entspricht oder wenn eine Kollision festgestellt wird, senden Sie jeden Koordinatensatz an eine GPU-Einheit, um den entsprechenden Grid-Standort zu aktualisieren. Dies wäre einfach zu implementieren, würde aber wahrscheinlich keine große Beschleunigung bringen, da das Überprüfen auf Kollisionen auf der CPU wahrscheinlich nicht viel billiger wäre, als das gesamte Update auf der CPU durchzuführen.
Teilen Sie das Gitter in Regionen auf (eine pro GPU-Einheit) und lassen Sie eine GPU-Einheit für die zufällige Auswahl und Aktualisierung von Gitterzellen in ihrer Region verantwortlich sein. Aber es gibt viele Probleme mit dieser Idee, die ich nicht lösen kann. Das offensichtlichste ist, was genau passieren sollte, wenn eine Einheit eine Nachbarschaft auswählt, die den Rand ihrer Region überlappt.
Annähern Sie das System wie folgt: Lassen Sie die Zeit in diskreten Schritten ablaufen. Teilen Sie das Gitter in ein anderes aufSatz von Regionen in jedem Zeitschritt gemäß einem vordefinierten Schema, und jede GPU-Einheit wählt zufällig ein Paar von Gitterzellen aus und aktualisiert sie, deren Nachbarschaft die Grenzen der Region nicht überlappt. Da sich die Grenzen bei jedem Schritt ändern, wirkt sich diese Einschränkung möglicherweise nicht zu stark auf die Dynamik aus, solange die Bereiche relativ groß sind. Dies scheint einfach zu implementieren und wahrscheinlich schnell zu sein, aber ich weiß nicht, wie gut es die Dynamik annähert, oder was das beste Schema für die Auswahl der Regionsgrenzen bei jedem Zeitschritt ist. Ich habe einige Verweise auf "block-synchrone zellulare Automaten" gefunden, die möglicherweise mit dieser Idee identisch sind oder nicht. (Ich weiß es nicht, weil es den Anschein hat, dass alle Beschreibungen der Methode entweder in Russisch sind oder sich in Quellen befinden, auf die ich keinen Zugriff habe.)
Meine spezifischen Fragen lauten wie folgt:
Sind die oben genannten Algorithmen eine sinnvolle Methode, um die GPU-Parallelisierung eines asynchronen CA-Modells zu erreichen?
Gibt es einen besseren Weg?
Gibt es einen Bibliothekscode für diese Art von Problem?
Wo finde ich eine eindeutige Beschreibung der block-synchronen Methode in englischer Sprache?
Fortschritt
Ich glaube, ich habe eine Möglichkeit gefunden, eine möglicherweise geeignete asynchrone Zertifizierungsstelle zu parallelisieren. Der im Folgenden beschriebene Algorithmus gilt für eine normale asynchrone Zertifizierungsstelle, die jeweils nur eine Zelle aktualisiert, und nicht für ein benachbartes Zellenpaar wie meins. Es gibt einige Probleme bei der Verallgemeinerung auf meinen speziellen Fall, aber ich denke, ich habe eine Idee, wie ich sie lösen kann. Ich bin mir jedoch nicht sicher, wie viel Geschwindigkeitsvorteil sich daraus ergibt, und zwar aus den nachfolgend erläuterten Gründen.
Die Idee ist, die asynchrone Zertifizierungsstelle (fortan ACA) durch eine stochastische synchrone Zertifizierungsstelle (SCA) zu ersetzen, die sich gleichwertig verhält. Dazu stellen wir uns zunächst vor, dass der ACA ein Poisson-Prozess ist. Das heißt, die Zeit läuft kontinuierlich ab, und jede Zelle hat eine konstante Wahrscheinlichkeit pro Zeiteinheit, ihre Aktualisierungsfunktion unabhängig von den anderen Zellen auszuführen.
ist ein Parameter, dessen Wert beliebig gewählt werden kann.)
Bei jedem logischen Zeitschritt werden die Zellen der SCA wie folgt aktualisiert:
Ich glaube, dies garantiert, dass die Zellen in einer Reihenfolge aktualisiert werden, die "decodiert" werden kann, um der ursprünglichen ACA zu entsprechen, während Kollisionen vermieden werden und ermöglicht wird, dass einige Zellen parallel aktualisiert werden. Aufgrund des ersten Aufzählungspunkts oben bedeutet dies jedoch, dass die meisten GPU-Prozessoren in jedem Zeitschritt des SCA größtenteils im Leerlauf sind, was weniger als ideal ist.
Ich muss etwas mehr darüber nachdenken, ob die Leistung dieses Algorithmus verbessert werden kann und wie dieser Algorithmus erweitert werden kann, um mit dem Fall umzugehen, dass mehrere Zellen gleichzeitig im ACA aktualisiert werden. Da es jedoch vielversprechend aussieht, dachte ich, ich würde es hier beschreiben, falls jemand (a) etwas Ähnliches in der Literatur kennt oder (b) einen Einblick in diese verbleibenden Probleme geben kann.
quelle
exp()
), daher hätte ich nicht gedacht, dass es viel Sinn macht, es auf mehrere Threads zu verteilen. Ich denke, es ist besser (und für mich einfacher), mehrere Paare parallel zu aktualisieren, mit einem Paar pro Thread.Antworten:
Ich würde die erste Option verwenden und würde einen synchronen AC-Lauf verwenden, bevor (unter Verwendung der GPU), um Kollisionen zu erkennen, einen Schritt eines hexagonalen AC ausführen, dessen Regel der Wert der mittleren Zelle ist = Summe (Nachbarn), die diese CA haben muss Sieben Status sollten mit einer zufällig ausgewählten Zelle initiiert und ihr Status überprüft werden, bevor die Aktualisierungsregel für jede GPU ausgeführt wird.
Beispiel 1. Der Wert einer benachbarten Zelle wird geteilt
0 0 0 0 0 0 0 0
0 0 1 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
ein Schritt einer CA, deren Regel hexagonale zentrale Zelle ist = Summe (Nachbarn)
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
Beispiel 2. Der Wert einer zu aktualisierenden Zelle wird als Nachbarn auf der anderen Seite berücksichtigt
0 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
Nach der Iteration
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 0
Probe 3. Es besteht keine Beziehung
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 0 1 0 0
0 0 0 0 0 0 0 0
Nach der Iteration
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
quelle
Nach Ihren Antworten auf meine Fragen in den obigen Kommentaren würde ich vorschlagen, dass Sie einen sperrenbasierten Ansatz verwenden, bei dem jeder Thread versucht, die zu aktualisierende Umgebung zu sperren, bevor das tatsächliche Update berechnet wird.
Sie können dies mit den in CUDA vorgesehenen atomaren Operationen und einem Array
int
mit den Sperren für jede Zelle tun , zlock
. Jeder Thread führt dann Folgendes aus:Beachten Sie, dass dieser Ansatz wahrscheinlich nicht der optimalste ist, aber einen interessanten Ausgangspunkt bieten könnte. Wenn es viele Kollisionen zwischen Threads gibt, dh eine oder mehrere pro 32 Threads (wie bei einer Kollision pro Warp), wird es ein gutes Stück Verzweigungsumleitung geben. Atomare Operationen können auch etwas langsam sein, aber da Sie nur Compare-and-Swap-Operationen ausführen, sollte die Skalierung in Ordnung sein.
Der Aufwand für das Sperren mag einschüchternd aussehen, aber es sind wirklich nur ein paar Aufgaben und Zweige, nicht viel mehr.
Beachten Sie auch, dass ich schnell und locker mit Notation in den Schleifen
i
der Nachbarn bin .Nachtrag: Ich war unbekümmert genug anzunehmen, dass man sich einfach zurückziehen könnte, wenn Paare kollidieren. Ist dies nicht der Fall, können Sie ab der zweiten Zeile alles in eine
while
-Loop einschließen undbreak
am Ende der finalenif
-Anweisung ein hinzufügen.Alle Threads müssen dann warten, bis der letzte erledigt ist, aber wenn Kollisionen selten sind, sollten Sie in der Lage sein, damit durchzukommen.
Anhang 2: Versuchen Sie nicht ,
__syncthreads()
irgendwo in diesem Code Anrufe hinzuzufügen , insbesondere in der im vorherigen Anhang beschriebenen Loop-Version! Eine Asynchronität ist wesentlich, um im letzteren Fall wiederholte Kollisionen zu vermeiden.quelle
Ich bin der Hauptentwickler von LibGeoDecomp. Ich bin mit vanCompute einverstanden, dass Sie Ihren ACA mit einer Zertifizierungsstelle emulieren können, aber Sie haben Recht, dass dies nicht sehr effizient ist, da nur wenige Zellen in einem bestimmten Schritt aktualisiert werden sollen. Dies ist in der Tat eine sehr interessante Anwendung - und es macht Spaß, daran zu basteln!
Ich empfehle Ihnen, die von jlopez1967 und Pedro vorgeschlagenen Lösungen zu kombinieren: Der Algorithmus von Pedro erfasst die Parallelität gut, aber diese atomaren Verriegelungen sind schrecklich langsam. Die Lösung von jlopez1967 ist elegant, wenn es darum geht, Kollisionen zu erkennen, aber alle
n
Zellen zu überprüfen , wenn nur eine kleinere Teilmenge (von nun an gehe ich davon aus, dass es einen Parameter gibt,k
der die Anzahl der gleichzeitig zu aktualisierenden Zellen angibt) aktiv ist. ist eindeutig unerschwinglich.In Ermangelung einer guten globalen Synchronisation auf der GPU müssen Sie mehrere Kernel für die verschiedenen Phasen aufrufen. Auf Nvidias Kepler können Sie sogar die Hauptschleife zur GPU verschieben, aber ich erwarte, dass das nicht viel bringt.
Die Algorithmen erreichen einen (konfigurierbaren) Parallelitätsgrad. Ich denke, die interessante Frage ist, ob die Kollisionen Ihre zufällige Verteilung beeinflussen, wenn Sie zunehmen
k
.quelle
Ich schlage Ihnen vor, dass Sie diesen Link http://www.wolfram.com/training/courses/hpc021.html ungefähr 14:15 Minuten in das Video natürlich Mathematica-Training sehen, wo sie eine Implementierung eines zellularen Automaten mit CUDA durchführen , von dort und Sie können es ändern.
quelle