异步蜂窝自动机的并行(GPU)算法


12

我有一组计算模型,这些模型可以描述为异步蜂窝自动机。这些模型类似于Ising模型,但稍微复杂一些。这样的模型似乎可以从在GPU而非CPU上运行中受益。不幸的是,并行化这样一个模型不是很简单,而且我也不清楚如何去实现它。我知道有关于该主题的文献,但似乎所有这些文献都是针对对算法复杂性细节感兴趣的铁杆计算机科学家,而不是像我这样只想要描述我可以实现的东西的人,以及因此,我觉得它是不可渗透的。

为了清楚起见,我并不是在寻找最佳算法,而是希望可以在CUDA中快速实现的最佳算法比我的CPU实现有明显的提高。在这个项目中,程序员的时间比计算机的时间更多地是一个限制因素。

我还应该澄清,异步蜂窝自动机与同步自动机是完全不同的事情,并且并行化同步CA的技术(例如Conway的生活)不能轻易地适应此问题。区别在于,同步CA在每个时间步同时更新每个小区,而异步CA在每个时间步更新随机选择的本地区域,如下所述。

我希望并行化的模型是在由约100000个单元组成的网格(通常是六角形)上实现的(尽管我想使用更多),用于运行它们的非并行算法如下所示:

  1. 随机选择一对相邻的单元格

  2. 根据围绕这些单元格的局部邻域计算“能量”函数ΔË

  3. 以取决于的概率(使用为参数),要么交换两个单元的状态,要么不执行任何操作。Ë-βΔËβ

  4. 无限重复上述步骤。

边界条件也有一些复杂性,但是我想这些对于并行化不会造成太大困难。

值得一提的是,我对这些系统的瞬态动力学感兴趣,而不仅仅是平衡状态,因此我需要具有与上述等效的动力学特性的东西,而不是仅仅具有相同平衡分布的东西。(因此,chequerboard算法的变化不是我想要的。)

并行化上述算法的主要困难是冲突。由于所有计算仅取决于晶格的局部区域,因此许多晶格位点可以并行更新,只要它们的邻域不重叠即可。问题是如何避免这种重叠。我可以想到几种方法,但是我不知道哪种方法最适合实施。这些如下:

  • 使用CPU生成随机网格站点的列表并检查冲突。当网格站点的数量等于GPU处理器的数量时,或者如果检测到冲突,请将每组坐标发送到GPU单元以更新相应的网格站点。这将很容易实现,但可能不会大大提高速度,因为检查CPU上的冲突可能不会比对CPU进行整个更新便宜得多。

  • 将网格划分为区域(每个GPU单元一个),并拥有一个GPU单元负责随机选择和更新其区域内的网格单元。但是这个想法有很多我不知道如何解决的问题,最明显的是当一个单位选择一个与其区域边缘重叠的邻域时应该发生什么。

  • 大致如下所示:让时间分步进行。将晶格分成不同的根据某个预定义方案在每个时间步上设置一组区域,并让每个GPU单元随机选择和更新一对邻域不与区域边界重叠的网格单元。由于边界每时每刻都在变化,因此只要区域相对较大,此约束就不​​会对动力学产生太大影响。这似乎易于实现并且可能很快,但是我不知道它对动态的近似程度,或者在每个时间步长上选择区域边界的最佳方案是什么。我发现了一些对“块同步细胞自动机”的引用,它们可能与这个想法相同或不同。(我不知道,因为似乎该方法的所有描述要么都是俄语的,要么是我无法访问的资源。)

我的具体问题如下:

  • 以上任何算法是否是处理异步CA模型的GPU并行化的明智方法?

  • 有没有更好的办法?

  • 是否存在针对此类问题的现有库代码?

  • 在哪里可以找到“块同步”方法的清晰英语描述?

进展

我相信我已经想出了一种可能合适的并行化异步CA的方法。下面概述的算法适用于一次仅更新一个单元的普通异步CA,而不是像我的那样更新相邻的一对单元。将其推广到我的具体情况有一些问题,但是我认为我有一个解决方案的想法。但是,由于下面讨论的原因,我不确定它将带来多少速度优势。

这个想法是用等效的随机同步CA(SCA)代替异步CA(以下称为ACA)。为此,我们首先想到ACA是一个泊松过程。即,时间连续进行,并且每个单元以每单位时间执行其更新功能的恒定概率独立于其他单元。

我们构造了一个SCA,该SCA的每个单元均存储两件事:该单元的状态 (即,在顺序实现中将存储在每个单元中的数据),以及一个浮点数代表(连续),在其将在下一次更新。此连续时间不对应于SCA的更新步骤。我将后者称为“逻辑时间”。时间值根据指数分布随机初始化:。(其中是一个可以任意选择其值的参数。)X一世ĴŤ一世ĴŤ一世Ĵ0经验值λλ

在每个逻辑时间步,SCA的单元将更新如下:

  • 如果对于附近的任何,时间,则不执行任何操作。ķ一世ĴŤķ<Ť一世Ĵ

  • 否则,(1)使用与原始ACA相同的规则,根据相邻小区的状态更新状态;(2)生成一个随机值并将更新为。X一世ĴXķΔŤ经验值λŤ一世ĴŤ一世Ĵ+ΔŤ

我相信这保证了将以可以被“解码”的顺序更新单元以对应于原始ACA,同时避免冲突并允许并行更新某些单元。但是,由于上面的第一个要点,这意味着大多数GPU处理器在SCA的每个时间步上都会大部分处于空闲状态,这不理想。

我需要进一步考虑是否可以提高该算法的性能,以及如何扩展该算法以处理ACA中同时更新多个单元的情况。但是,它看起来很有希望,所以我想我将在这里进行描述,以防万一(a)知道文献中类似的内容,或者(b)可以提供对这些剩余问题的任何见解。


也许您可以使用基于模板的方法来表达您的问题。存在许多用于基于模板的问题的软件。您可以查看一下:libgeodecomp.org/gallery.html,康威的人生游戏。这可能有一些相似之处。
vanCompute

@vanCompute看起来像一个很棒的工具,但是从我的初步(而不是粗略的)调查来看,它看起来像模板代码范例固有地是同步的,因此它可能不太适合我要尝试的工作。但是,我将进一步研究它。
纳撒尼尔(Nathaniel)

您能否提供更多有关如何使用SIMT并行化的详细信息?您会每对使用一个线程吗?还是可以将更新单个对的工作分散到32个或更多线程上?
Pedro

@Pedro更新一对对的工作量很小(基本上只是在邻居中求和,再加上一个随机数生成器的迭代,再加上一个exp()),所以我认为将其分布在多个线程上没有多大意义。我认为最好尝试并行更新多对,每个线程一对,这样对我来说更容易。
纳撒尼尔(Nathaniel)2013年

好的,您如何定义配对更新之间的重叠?如果这些对本身重叠,或者它们的邻域重叠?
Pedro

Answers:


4

我将使用第一个选项,并在(使用GPU)之前使用同步AC运行,以检测碰撞,执行六角AC的步骤,其规则是中心单元格的值= Sum(邻居),该CA必须具有应使用随机选择的单元格初始化七个状态,并在为每个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

CA的规则,其规则是六边形中央单元= Sum(邻居)

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


Øññ

我认为有很多可以并行化的地方。冲突处理完全在GPU上进行,这是同步AC中的一个步骤,如上面发布的链接所示。如果Sum(neighbors)= 8 NO冲突,Sum(neighbors)> 8 Collision,则将使用本地规则进行验证,如果没有冲突单元格状态,则会在运行更新规则更改之前对其进行验证,因为两者应放置在附近如果它们不接近,则要评估的点属于其他单元格。
jlopez1967

我理解这一点,但是问题是,当您检测到碰撞时该怎么办?如前所述,您的CA算法只是检测冲突的第一步。第二步是在网格中搜索状态> = 2的单元,这并不容易。
纳撒尼尔(Nathaniel)2013年

例如,假设我们要在元胞自动机和已执行的和(像元邻域(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-loop中,并break在最终if-statement 的末尾添加a 。

然后所有线程将不得不等待最后一个线程完成,但是如果冲突很少发生,那么您应该能够摆脱它。

附录2:待办事项被诱惑呼吁增加__syncthreads()在这段代码的任何地方,尤其是它的循环版本在之前的附录说明!在后一种情况下,异步性对于避免重复冲突至关重要。


谢谢,这看起来不错。可能比我正在考虑的复杂想法要好,并且容易实现。我可以通过使用足够大的网格来减少碰撞,这可能很好。如果证明后退方法要快得多,那么我可以用它来非正式地研究参数,当我需要生成正式结果时,可以改用等待所有人完成的方法。我将在一段时间内尝试一下。
纳撒尼尔(Nathaniel)

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当然是在mathematica培训视频中大约14:15分钟,他们在其中使用CUDA实现了细胞自动机的实现,然后您可以对其进行修改。


不幸的是,这是一个同步CA,它与我正在处理的异步CA完全不同。在同步CA中,每个单元都同时更新,这很容易在GPU上并行化,但是在异步CA中,每个时间步都会更新一个随机选择的单元(实际上,在我的情况下,它是两个相邻的单元),这使得并行化要困难得多。我的问题中概述的问题特定于需要异步更新功能。
纳撒尼尔(Nathaniel)2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.