Eu tenho uma coleção de modelos computacionais que podem ser descritos como autômatos celulares assíncronos. Esses modelos se assemelham ao modelo de Ising, mas são um pouco mais complicados. Parece que esses modelos se beneficiariam de serem executados em uma GPU, e não em uma CPU. Infelizmente, não é muito simples paralelizar esse modelo, e não está claro para mim como fazê-lo. Estou ciente de que há literatura sobre o assunto, mas tudo parece ser direcionado a cientistas da computação que estão interessados nos detalhes da complexidade algorítmica, em vez de alguém como eu, que quer apenas uma descrição de algo que eu possa implementar, e consequentemente, acho bastante inpenetrável.
Para maior clareza, não estou procurando um algoritmo ideal, mas algo que eu possa implementar rapidamente no CUDA que provavelmente dará uma aceleração significativa na implementação da minha CPU. O tempo do programador é muito mais um fator limitante do que o tempo do computador neste projeto.
Devo também esclarecer que um autômato celular assíncrono é algo bem diferente de um síncrono, e as técnicas para paralelizar CAs síncronas (como a vida de Conway) não podem ser facilmente adaptadas a esse problema. A diferença é que uma CA síncrona atualiza todas as células simultaneamente a cada etapa, enquanto uma assíncrona atualiza uma região local escolhida aleatoriamente a cada etapa, conforme descrito abaixo.
Os modelos que eu desejo paralelizar são implementados em uma rede (geralmente hexagonal) que consiste em ~ 100000 células (embora eu gostaria de usar mais), e o algoritmo não paralelo para executá-las se parece com o seguinte:
Escolha um par de células vizinho aleatoriamente
Calcular uma função "energia" base em uma vizinhança local ao redor dessas células
Com uma probabilidade que depende de (com a parâmetro), troque os estados das duas células ou não faça nada. β
Repita as etapas acima indefinidamente.
Também existem algumas complicações relacionadas às condições de contorno, mas imagino que elas não representem muita dificuldade para a paralelização.
Vale ressaltar que estou interessado na dinâmica transitória desses sistemas, e não apenas no estado de equilíbrio, por isso preciso de algo que tenha uma dinâmica equivalente à acima, em vez de apenas algo que se aproxime da mesma distribuição de equilíbrio. (Portanto, variações do algoritmo do tabuleiro de damas não são o que estou procurando.)
A principal dificuldade em paralelizar o algoritmo acima é a colisão. Como todos os cálculos dependem apenas de uma região local da rede, é possível que muitos sites da rede sejam atualizados em paralelo, desde que suas vizinhanças não estejam sobrepostas. A questão é como evitar essas sobreposições. Posso pensar em várias maneiras, mas não sei qual é a melhor para implementar. Estes são os seguintes:
Use a CPU para gerar uma lista de sites de grade aleatórios e verificar colisões. Quando o número de sites da grade for igual ao número de processadores GPU, ou se uma colisão for detectada, envie cada conjunto de coordenadas para uma unidade GPU para atualizar o site da grade correspondente. Isso seria fácil de implementar, mas provavelmente não daria muita velocidade, pois verificar colisões na CPU provavelmente não seria muito mais barato do que fazer toda a atualização na CPU.
Divida a estrutura em regiões (uma por unidade de GPU) e tenha uma unidade de GPU responsável por selecionar e atualizar aleatoriamente as células da grade em sua região. Mas há muitos problemas com essa ideia que não sei resolver, sendo o mais óbvio o que exatamente deveria acontecer quando uma unidade escolhe um bairro que se sobrepõe à extremidade de sua região.
Aproxime o sistema da seguinte maneira: deixe o tempo prosseguir em etapas discretas. Divida a estrutura em um diferenteconjunto de regiões em todas as etapas, de acordo com algum esquema predefinido, e cada unidade da GPU seleciona e atualiza aleatoriamente um par de células da grade cuja vizinhança não se sobrepõe ao limite da região. Como os limites mudam a cada passo, essa restrição pode não afetar muito a dinâmica, desde que as regiões sejam relativamente grandes. Parece fácil de implementar e provavelmente rápido, mas não sei até que ponto isso aproximará a dinâmica ou qual é o melhor esquema para escolher os limites da região a cada etapa do tempo. Encontrei algumas referências a "autômatos celulares síncronos em bloco", que podem ou não ser iguais a essa idéia. (Não sei porque parece que todas as descrições do método estão em russo ou em fontes às quais não tenho acesso.)
Minhas perguntas específicas são as seguintes:
Algum dos algoritmos acima é uma maneira sensata de abordar a paralelização por GPU de um modelo de CA assíncrono?
Existe uma maneira melhor?
Existe código de biblioteca existente para esse tipo de problema?
Onde posso encontrar uma descrição clara em inglês do método "síncrono em bloco"?
Progresso
Acredito que criei uma maneira de paralelizar uma autoridade de certificação assíncrona que possa ser adequada. O algoritmo descrito abaixo é para uma CA assíncrona normal que atualiza apenas uma célula por vez, em vez de um par de células vizinho como o meu. Existem alguns problemas com a generalização no meu caso específico, mas acho que tenho uma idéia de como resolvê-los. No entanto, não tenho certeza de quanto benefício de velocidade ele oferecerá, pelos motivos discutidos abaixo.
A idéia é substituir a CA assíncrona (doravante ACA) por uma CA síncrona estocástica (SCA) que se comporte de maneira equivalente. Para fazer isso, primeiro imaginamos que o ACA é um processo de Poisson. Ou seja, o tempo continua continuamente e cada célula tem uma probabilidade constante por unidade de tempo de executar sua função de atualização, independentemente das outras células.
Construímos um SCA cujas células armazenam duas coisas: o estado da célula (ou seja, os dados que seriam armazenados em cada célula na implementação seqüencial) e um número de ponto flutuante representando o (contínuo ) hora em que será atualizada na próxima. Esse tempo contínuo não corresponde às etapas de atualização do SCA. Vou me referir a este último como "tempo lógico". Os valores de tempo são inicializados aleatoriamente de acordo com uma distribuição exponencial: . (Onde é um parâmetro cujo valor pode ser escolhido arbitrariamente.)
Em cada etapa do tempo lógico, as células do SCA são atualizadas da seguinte maneira:
Se, para qualquer na vizinhança de , o tempo , não faça nada.
Acredito que isso garanta que as células sejam atualizadas em uma ordem que possa ser "decodificada" para corresponder à ACA original, evitando colisões e permitindo que algumas células sejam atualizadas em paralelo. No entanto, devido ao primeiro ponto acima, isso significa que a maioria dos processadores GPU ficará ociosa a cada etapa do SCA, o que é menos do que o ideal.
Eu preciso pensar um pouco mais sobre se o desempenho desse algoritmo pode ser aprimorado e como estender esse algoritmo para lidar com o caso em que várias células são atualizadas simultaneamente no ACA. No entanto, parece promissor, então pensei em descrevê-lo aqui, caso alguém (a) saiba de algo semelhante na literatura ou (b) possa oferecer qualquer insight sobre esses problemas restantes.
fonte
exp()
), então eu não pensaria que faz muito sentido espalhá-lo por vários segmentos. Eu acho que é melhor (e mais fácil para mim) tentar atualizar vários pares em paralelo, com um par por thread.Respostas:
Eu usaria a primeira opção e usaria uma execução CA síncrona antes (usando a GPU), para detectar colisões, executar uma etapa de uma CA hexagonal cuja regra é o valor da célula central = Sum (vizinhos), esta CA deve ter sete estados devem ser iniciados com célula selecionada aleatoriamente e seu status verificado antes de executar a regra de atualização para cada gpu.
Amostra 1. O valor de uma célula vizinha é compartilhado
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
uma etapa de uma CA cuja regra é célula central hexagonal = Sum (vizinhos)
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
Amostra 2. O valor de uma célula a ser atualizada é levado em consideração como vizinho do outro
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
Após a iteração
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
Amostra 3. Não há relacionamento
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
Após a iteração
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
fonte
Seguindo suas respostas às minhas perguntas nos comentários acima, sugiro que você tente uma abordagem baseada em bloqueio na qual cada thread tente bloquear o bairro que será atualizado antes de calcular a atualização real.
Você pode fazer isso usando as operações atômicas previstas no CUDA e uma matriz
int
contendo os bloqueios para cada célula, por exemplolock
. Cada thread faz o seguinte:Observe que essa abordagem provavelmente não é a mais ideal, mas poderia fornecer um ponto de partida interessante. Se houver muitas colisões entre threads, ou seja, uma ou mais por 32 threads (como em uma colisão por warp), haverá um pouco de desvio de ramificação. Além disso, as operações atômicas podem ser um pouco lentas, mas como você está executando apenas operações de comparação e troca, ela deve ser dimensionada ok.
A sobrecarga de bloqueio pode parecer intimidadora, mas na verdade são apenas algumas atribuições e ramificações, não muito mais.
Note também que estou sendo rápido e solto com notação nos loops de
i
sobre os vizinhos.Adendo: Eu era suficientemente corajoso para assumir que você poderia simplesmente recuar quando os pares colidirem. Se esse não for o caso, você pode agrupar tudo a partir da segunda linha em um
while
loop e adicionar abreak
no final daif
declaração final .Todos os encadeamentos terão que esperar até que o último seja concluído, mas se as colisões forem raras, você poderá se safar.
Adendo 2: Do não ser tentado a acrescentar chamadas para
__syncthreads()
qualquer lugar este código, especialmente, a versão looping descrito na adenda anterior! A assincronicidade é essencial para evitar colisões repetidas no último caso.fonte
Sou o principal desenvolvedor do LibGeoDecomp. Embora eu concorde com o vanCompute de que você pode emular seu ACA com uma CA, você está certo de que isso não seria muito eficiente, pois apenas poucas células em uma determinada etapa devem ser atualizadas. Esta é realmente uma aplicação muito interessante - e divertida de mexer!
Eu sugiro que você combine as soluções propostas por jlopez1967 e Pedro: o algoritmo de Pedro captura bem o paralelismo, mas esses bloqueios atômicos são terrivelmente lentos. A solução de jlopez1967 é elegante quando se trata de detectar colisões, mas verificar todas as
n
células, quando apenas um subconjunto menor (assumirei a partir de agora que existe algum parâmetrok
que indica o número de células a serem atualizadas simultaneamente), é claramente proibitivo.Na ausência de boa sincronização global na GPU, você precisa chamar vários kernels para as diferentes fases. No Kepler da Nvidia, você pode mover até o loop principal para a GPU, mas não espero que isso ganhe muito.
Os algoritmos atingem um grau (configurável) de paralelismo. Acho que a questão interessante é se as colisões afetarão sua distribuição aleatória quando você aumentar
k
.fonte
Sugiro que você veja este link http://www.wolfram.com/training/courses/hpc021.html aproximadamente 14:15 minutos no vídeo, é claro, treinamento mathematica onde eles implementam um autômato celular usando CUDA , a partir daí e você pode modificá-lo.
fonte