비동기 셀룰러 오토마타를위한 병렬 (GPU) 알고리즘


12

비동기 셀룰러 오토마타로 설명 할 수있는 계산 모델 모음이 있습니다. 이 모델은 Ising 모델과 비슷하지만 약간 더 복잡합니다. 마치 그러한 모델이 CPU가 아닌 GPU에서 실행되는 것이 도움이 될 것 같습니다. 불행히도 그러한 모델을 병렬화하는 것은 매우 간단하지 않으며 어떻게 진행 해야하는지 명확하지 않습니다. 나는 그 주제에 관한 문헌이 있다는 것을 알고 있지만 모든 것은 내가 구현할 수있는 것에 대한 설명을 원하는 나 같은 사람이 아니라 알고리즘 복잡성의 세부 사항에 관심이있는 하드 코어 컴퓨터 과학자를 목표로하는 것 같다. 결과적으로 나는 그것이 무적이라고 생각합니다.

명확히하기 위해 CUDA에서 신속하게 구현할 수있는 것만 큼 최적의 알고리즘을 찾고 있지는 않습니다. 이는 CPU 구현보다 속도가 크게 향상 될 수 있습니다. 프로그래머 시간은이 프로젝트에서 컴퓨터 시간보다 훨씬 제한 요소입니다.

또한 비동기 셀룰러 오토 마톤은 동기식 셀룰러 오토 마톤과는 다른 점이 있으며, Conway의 수명과 같은 동기식 CA를 병렬화하는 기술을이 문제에 쉽게 적용 할 수는 없습니다. 차이점은 동기 CA는 모든 시간 단계에서 동시에 모든 셀을 업데이트하는 반면, 비동기 CA는 아래에 설명 된대로 모든 시간 단계에서 임의로 선택된 로컬 영역을 업데이트한다는 것입니다.

병렬화하려는 모델은 ~ 100000 셀로 구성된 격자 (일반적으로 육각형 모델)에서 구현되며 (더 많이 사용하고 싶지만) 실행하기위한 비 병렬 알고리즘은 다음과 같습니다.

  1. 무작위로 인접한 셀 쌍을 선택하십시오.

  2. 이 셀을 둘러싼 지역 근처를 기반으로 "에너지"함수 계산ΔE

  3. ( 매개 변수 사용) 에 의존 할 확률로 두 셀의 상태를 바꾸거나 아무 것도 수행하지 마십시오. βeβΔEβ

  4. 위의 단계를 무기한 반복하십시오.

경계 조건과 관련하여 몇 가지 복잡한 문제가 있지만 병렬화에는 큰 어려움이 없을 것이라고 생각합니다.

평형 상태가 아닌 이러한 시스템의 과도 역학에 관심이 있으므로 동일한 평형 분포에 접근하는 것이 아니라 위와 동등한 역학이 필요한 것이 필요합니다. (그래서 chequerboard 알고리즘의 변형은 내가 찾고있는 것이 아닙니다.)

위 알고리즘을 병렬화하는 데 가장 큰 어려움은 충돌입니다. 모든 계산은 격자의 로컬 영역에만 의존하기 때문에 이웃이 겹치지 않는 한 많은 격자 사이트가 병렬로 업데이트 될 수 있습니다. 문제는 그러한 중복을 피하는 방법입니다. 몇 가지 방법을 생각할 수 있지만 구현할 수있는 최상의 방법이 무엇인지 모르겠습니다. 이들은 다음과 같습니다.

  • CPU를 사용하여 임의의 그리드 사이트 목록을 생성하고 충돌을 확인하십시오. 그리드 사이트 수가 GPU 프로세서 수와 같거나 충돌이 감지되면 각 좌표 세트를 GPU 장치로 보내 해당 그리드 사이트를 업데이트합니다. CPU에서 충돌을 검사하는 것이 CPU에서 전체 업데이트를 수행하는 것보다 그렇게 저렴하지는 않기 때문에 구현하기 쉽지만 속도가 크게 향상되지는 않을 것입니다.

  • 격자를 영역 (GPU 단위당 하나)으로 나누고, 해당 영역 내에서 그리드 셀을 임의로 선택하고 업데이트하는 하나의 GPU 장치가 있습니다. 그러나이 아이디어에는 해결 방법을 모른다는 많은 문제가 있습니다. 가장 분명한 것은 단위가 해당 지역의 가장자리와 겹치는 이웃을 선택할 때 어떻게 해야하는지입니다.

  • 다음과 같이 대략적인 시스템 : 시간을 불 연속적으로 진행하십시오. 격자를 다른 것으로 나누십시오미리 정의 된 방식에 따라 매 시간 단계마다 영역 세트를 설정하고, 각 GPU 장치가 인접 영역이 영역 경계와 겹치지 않는 그리드 셀 쌍을 무작위로 선택하고 업데이트하도록합니다. 매 단계마다 경계가 변경되므로 영역이 비교적 큰 한이 제약 조건이 역학에 크게 영향을 미치지 않을 수 있습니다. 이것은 구현하기 쉽고 빠를 것으로 보이지만, 그것이 역학에 얼마나 근접하는지 또는 각 시간 단계에서 지역 경계를 선택하는 가장 좋은 방법은 무엇인지 모르겠습니다. "블록 동기식 셀룰러 오토마타"에 대한 언급이 있는데,이 아이디어와 동일하거나 동일하지 않을 수 있습니다. (이 방법에 대한 모든 설명이 러시아어로되어 있거나 내가 액세스 할 수없는 출처에있는 것 같습니다.)

구체적인 질문은 다음과 같습니다.

  • 위 알고리즘 중 비동기 CA 모델의 GPU 병렬화에 접근 할 수있는 합리적인 방법이 있습니까?

  • 더 좋은 방법이 있습니까?

  • 이 유형의 문제에 대한 기존 라이브러리 코드가 있습니까?

  • "블록 동기식"방법에 대한 명확한 영어 설명은 어디에서 찾을 수 있습니까?

진행

적절한 비동기 CA를 병렬화하는 방법을 생각해 냈습니다. 아래에 설명 된 알고리즘은 일반적인 비동기 CA를위한 것으로, 한 번에 하나의 셀만 업데이트하며 주변 셀 쌍이 아닌 한 셀만 업데이트합니다. 특정 사례로 일반화하는 데 몇 가지 문제가 있지만 해결 방법이 있다고 생각합니다. 그러나 아래에서 논의 할 이유로 속도 이점이 얼마나 큰지 잘 모르겠습니다.

아이디어는 비동기 CA (이하 ACA)를 동등하게 작동하는 확률 적 동기 CA (SCA)로 바꾸는 것입니다. 이를 위해 먼저 ACA가 포아송 프로세스라고 생각합니다. 즉, 시간은 연속적으로 진행되며, 각 셀은 다른 셀과 독립적으로 업데이트 기능을 수행하는 단위 시간당 일정한 확률로 사용된다.

우리는 그 세포를 각 점포 두가지는 SCA 구성 다음 상태 셀 (즉 순차적 실행의 각 셀에 저장 될 데이터), 및 부동 소수점 숫자 제 (연속을 나타내는 ) 다음에 업데이트 될 시간 입니다. 이 연속 시간은 SCA의 업데이트 단계와 일치하지 않습니다. 나는 후자를 "논리적 시간"으로 언급 할 것이다. 시간 값은 지수 분포 에 따라 무작위로 초기화됩니다 . 여기서 는 임의로 값을 선택할 수있는 매개 변수입니다. t i j t i j ( 0 ) ~ Exp ( λ ) λXijtijtij(0)Exp(λ)λ

각 논리적 시간 단계에서 SCA의 셀은 다음과 같이 업데이트됩니다.

  • 만약 어떤 대한 의 근방의 , 시간 , 아무것도하지 않는다.I , J의 t의 유전율 L < t I Jk,li,jtkl<tij

  • 그렇지 않으면, (1) 원래 ACA와 동일한 규칙을 사용하여 인접 셀 의 상태 에 따라 상태 업데이트합니다 . 그리고 (2) 임의의 값 하고 를 . X k l Δ t Exp ( λ ) t i j t i j + Δ tXijXklΔtExp(λ)tijtij+Δt

이것이 충돌을 피하고 일부 셀을 병렬로 업데이트하면서 원래 ACA에 대응하여 "디코딩"될 수있는 순서로 셀이 업데이트 될 것이라고 보장합니다. 그러나 위의 첫 번째 글 머리 기호 때문에 대부분의 GPU 프로세서는 SCA의 각 단계에서 대부분 유휴 상태가되므로 이상적이지 않습니다.

이 알고리즘의 성능을 향상시킬 수 있는지 여부와 ACA에서 여러 셀이 동시에 업데이트되는 경우를 처리하기 위해이 알고리즘을 확장하는 방법에 대해 좀 더 생각할 필요가 있습니다. 그러나 유망한 것처럼 보이므로 누구나 (a) 문헌에서 비슷한 것을 알고 있거나 (b) 나머지 문제에 대한 통찰력을 제공 할 수있는 경우 여기에 설명하겠다고 생각했습니다.


스텐실 기반 접근 방식으로 문제를 공식화 할 수 있습니다. 스텐실 기반 문제에 대한 많은 소프트웨어가 있습니다. Conway의 인생 게임 인 libgeodecomp.org/gallery.html을 살펴보십시오 . 이것은 몇 가지 유사점이 있습니다.
vanCompute

@vanCompute는 환상적인 도구처럼 보이지만 초기 (커피가 아닌) 조사에서 스텐실 코드 패러다임이 본질적으로 동기적인 것처럼 보이므로 아마도 내가하려는 일에 적합하지 않을 것입니다. 그러나 더 자세히 살펴볼 것입니다.
Nathaniel

SIMT를 사용하여이를 병렬화하는 방법에 대한 추가 정보를 제공 할 수 있습니까? 쌍당 하나의 스레드를 사용 하시겠습니까? 아니면 단일 쌍 업데이트와 관련된 작업을 32 개 이상의 스레드로 분산시킬 수 있습니까?
Pedro

@Pedro 단일 쌍 업데이트와 관련된 작업은 상당히 작습니다 (기본적으로 이웃에 대한 합산, 난수 생성기 반복 및 하나의 반복 exp()). 따라서 여러 스레드에 분산시키는 것이 의미가 없다고 생각합니다. 스레드 당 한 쌍으로 여러 쌍을 병렬로 업데이트하고 업데이트하는 것이 더 낫고 더 쉽다고 생각합니다.
Nathaniel

좋아, 그리고 업데이트를 페어하기 위해 겹치는 부분을 어떻게 정의합니까? 쌍 자체가 겹치거나 이웃이 겹치는 경우?
페드로

답변:


4

첫 번째 옵션을 사용하고 이전에 GPU를 사용하여 동기식 AC를 사용하여 충돌을 감지하고 규칙이 중심 셀 값 = 합 (이웃) 인 6 각형 AC 단계를 실행합니다.이 CA는 임의로 선택된 셀로 7 개의 상태를 시작하고 각 GPU에 대한 업데이트 규칙을 실행하기 전에 상태를 확인해야합니다.

샘플 1. 인접 셀의 값이 공유됩니다

0 0 0 0 0

  0 0 1 0 0 0

0 0 0 0 0

  0 0 0 1 0 0

0 0 0 0 0

규칙이 6 각형 중앙 셀인 CA의 단계 = 합 (이웃)

0 1 1 0 0 0

  0 1 1 0 0

012 2 0 0

  0 0 1 1 1 0

0 0 1 1 0 0

샘플 2. 업데이트 할 셀의 값이 다른 셀의 이웃으로 고려됩니다.

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 1 1 0 0 0

  0 1 2 2 0 0

0 2 2 1 0 0

  0 1 1 0 0

0 0 0 0 0

샘플 3. 관계가 없습니다

  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 1 0 0

0 0 0 0 0

반복 후

  0 1 0 0 0

0 1 1 0 0 0

  0 1 0 0 0

0 0 1 1 0 0

  0 0 1 1 1 0

0 0 1 1 0 0


이것은 흥미로운 아이디어이지만 잘 작동한다고 확신하지는 않습니다. 문제는 충돌 감지 단계를 완료하기 위해 그리드에 2가 포함되어 있는지 여부를 해결하는 문제에 직면한다는 것입니다. 여기에는 계산 이 필요합니다 . 여기서 은 그리드 셀의 수입니다. 영리한 속임수를 사용하면 GPU 측에서 많은 것들을 병렬로 수행 할 수 있지만 가장 확실한 방법으로 그리드의 충돌 위치를 알 수있는 방법이 없으므로 시작해야 할 경우 다시. 그것은 흥미로운 아이디어이지만, 그것에 대해 약간의 생각을하겠습니다. nO(n)n
Nathaniel

병렬화 할 수있는 것이 많이 있다고 생각합니다. 충돌 처리는 GPU에 전적으로 영향을 미치며 위에 게시 된 링크에 표시된 것처럼 동기 AC의 단계입니다. 확인을 위해 Sum (이웃) = 8 충돌 없음, Sum (이웃)> 8 충돌 인 경우 로컬 규칙을 사용합니다. 충돌 셀 상태가없는 경우 업데이트 규칙 변경을 실행하기 전에 두 규칙이 근처에 있어야하기 때문에 로컬 규칙을 사용합니다. 그들이 가까이 있지 않은 경우 평가해야 할 점은 다른 세포에 속하는 것입니다.
jlopez1967

이해하지만 문제는 충돌을 감지하면 어떻게합니까? 위에서 설명한 것처럼 CA 알고리즘은 충돌을 감지하는 첫 번째 단계 일뿐입니다. 두 번째 단계는 상태가 2보다 큰 셀을 그리드에서 검색하는 것입니다.
Nathaniel

예를 들어, 셀룰러 오토마타 및 실행 합 (셀 이웃 (5,7))에서 충돌 셀 (5.7)을 감지하고 값이 8이고 충돌이 8보다 크지 않으면 충돌이 없다고 가정합니다. 비동기 셀룰러 오토마타에서 셀의 다음 상태를 정의하기 위해 각 셀을 평가하는 기능에 있어야합니다. 각 셀의 충돌 감지는 인접 셀만 포함하는 로컬 규칙입니다.
jlopez1967

예, 그러나 비동기 CA를 병렬화하기 위해 응답 할 수 있어야하는 질문은 "셀 (5,7)에 충돌이 있었는지"가 아니라 "그리드에 어딘가에 충돌이 있었습니까?" 그것?" 그리드를 반복하지 않으면 대답 할 수 없습니다.
Nathaniel

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 개의 스레드 당 하나 이상 (워프 당 하나의 충돌에서와 같이)), 분기에 약간의 전환이 발생합니다. 또한 원자 연산은 약간 느릴 수 있지만 비교 및 ​​스왑 연산 만 수행하므로 확장이 가능합니다.

잠금 오버 헤드는 위협적인 것처럼 보일 수 있지만 실제로는 몇 가지 과제와 분기에 지나지 않습니다.

또한 i이웃 사람들 의 고리에 표기되어있어 느리고 느슨 합니다.

부록 : 나는 당신이 단순히 짝짓기를 할 때 단순히 물러날 수 있다고 생각할 정도로 무심했습니다. 그렇지 않은 경우 두 번째 줄부터 모든 것을 while루프로 감싸고 break마지막 if문장 의 끝에 a 를 추가 할 수 있습니다.

그런 다음 모든 스레드는 마지막 스레드가 완료 될 때까지 기다려야하지만 충돌이 드물다면이를 피할 수 있습니다.

부록 2 : 이 코드의 어디에나, 특히 이전 부록에 설명 된 루핑 버전에 대한 호출을 추가하려는 유혹을 받지 마십시오 __syncthreads()! 후자의 경우 반복되는 충돌을 피하려면 비동기 성이 필수적입니다.


고마워, 이것은 꽤 좋아 보인다. 아마도 내가 생각했던 복잡한 아이디어보다 훨씬 낫고 구현하기가 훨씬 쉽습니다. 충분히 큰 그리드를 사용하여 충돌을 드물게 만들 수 있습니다. Just-Back-Off 방법이 훨씬 더 빠른 것으로 판명되면 매개 변수를 비공식적으로 조사하는 데 사용할 수 있으며 공식 결과를 생성해야 할 때 모두를 기다리는 다른 방법으로 전환 할 수 있습니다. 나는 이것을 잠시 후에 시도 할 것이다.
Nathaniel

1

저는 LibGeoDecomp의 수석 개발자입니다. 나는 당신이 CA를 사용하여 ACA를 에뮬레이트 할 수 있다는 vanCompute에 동의하지만, 주어진 단계에서 셀이 거의 업데이트되지 않기 때문에 이것이 효율적이지 않을 것입니다. 이것은 실제로 매우 흥미로운 응용 프로그램입니다.

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에서 우수한 글로벌 동기화가 없으면 여러 단계에 대해 여러 커널을 호출해야합니다. 엔비디아의 케플러에서는 메인 루프조차 GPU로 옮길 수 있지만 많은 것을 기대하지는 않습니다.

알고리즘은 (구성 가능한) 병렬 처리 수준을 달성합니다. 흥미로운 질문은 증가 할 때 충돌이 무작위 분포에 영향을 미치는지 여부 k입니다.


0

CUDA를 사용하여 셀룰러 오토마타를 구현하는 수학 교육 과정에 대한 약 14:15 분의 비디오 링크에 http://www.wolfram.com/training/courses/hpc021.html 링크가 표시되는 것이 좋습니다. 거기에서 수정하면됩니다.


불행히도 그것은 동기 CA이며, 내가 다루고있는 비동기와는 다른 유형의 짐승입니다. 동기식 CA에서는 모든 셀이 동시에 업데이트되며 GPU에서 병렬화가 쉽지만 비동기식 CA에서는 무작위로 선택된 단일 셀이 매 단계마다 업데이트됩니다 (실제로는 두 개의 인접 셀임). 병렬화가 훨씬 더 어렵다. 내 질문에 요약 된 문제는 비동기 업데이트 기능이 필요한 것입니다.
Nathaniel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.