【作业1】生命游戏

作业1

作业描述

实现了一个简单的生命游戏。使用几种不同的模式进行初始化。
按照像素画出的细胞太小了,按比例放大显示每个细胞。

效果展示

Random

gol

Oscillators

blinker beacon

Glider

glider

遇到的问题

使用gpu时,可以得到以上图形,但使用cpu时得到的图形并不一致,且不符合预期,不清楚哪里可能导致了这样的问题。对比Random初始化后分别使用gpu和cpu得到的图形。
gol video

代码链接

Code

4 个赞

Taichi 里不同 backend 实现 ti.random() 的方法确实不同,但是 cpu 的运行效果应该是由bug导致的。

在 Python-scope 里如果想把一个 field 的数据拷贝给另一个 field,应该使用 next_gen.copy_from(cells)。如果单纯写 next_gen = cells,实际上是使得 next_gencells 指向了同一个 field。
把你代码里的 85 行和 89 行改成 copy_from,cpu 和 gpu 上的结果应该就一致了。

4 个赞

如果把evolve函数换成下面这个(把inner loop变成串行),也会得到和cpu一样类似的错误结果:

@ti.kernel
def evolve():
    neighbors = 0
    for i in range(cells.shape[0]):
        for j in range(cells.shape[1]):
            neighbors = int(cells[(i-1) % N, (j-1) % N]+cells[i, (j-1) % N] +
                            cells[(i+1) % N, (j-1) % N] + cells[(i-1) % N, j] +
                            cells[(i+1) % N, j] + cells[(i-1) % N, (j+1) % N] +
                            cells[i, (j+1) % N]+cells[(i+1) % N, (j+1) % N])
            if cells[i, j] == 1:
                if (neighbors < 2) or (neighbors > 3):
                    next_gen[i, j] = 0
            else:
                if neighbors == 3:
                    next_gen[i, j] = 1

所以猜测,目前GPU结果看起来正确,大概率是因为线程够多,避免了data race

3 个赞

这个解释是正确的,原因是Python本身是弱类型的,当你把两个field在Python Scope中赋值了之后,他们两个实际指向了同一个field,并没有进行数据拷贝。

所以你的代码实际上是在做这件事:

@ti.kernel
def evolve():
    neighbors = 0
    for i, j in cells:
        neighbors = int(cells[(i-1) % N, (j-1) % N]+cells[i, (j-1) % N] +
                        cells[(i+1) % N, (j-1) % N] + cells[(i-1) % N, j] +
                        cells[(i+1) % N, j] + cells[(i-1) % N, (j+1) % N] +
                        cells[i, (j+1) % N]+cells[(i+1) % N, (j+1) % N])
        if cells[i, j] == 1:
            if (neighbors < 2) or (neighbors > 3):
                cells[i, j] = 0
        else:
            if neighbors == 3:
                cells[i, j] = 1

同一个kernel中同时在读写cells这个field。当你运气好并行度高的时候不会发生数据争用,运行结果是正确的。当你需要串行执行,或者当你并发数少于cells的大小的时候,就会出问题。

给余畅同学点赞!

2 个赞

感谢几位老师帮忙找bug :wink: :wink:

同时也解决了glider图形移动一段时间丢失的问题 : )

改写了一下,把临时field的拷贝写在kernel函数里了。

@ti.kernel
def evolve():
    for i, j in cells:
        new_cells[i, j] = cells[i, j]

    neighbors = 0
    for i, j in cells:
        neighbors = (cells[(i-1) % N, (j-1) % N]+cells[i, (j-1) % N]
                     + cells[(i+1) % N, (j-1) % N] + cells[(i-1) % N, j]
                     + cells[(i+1) % N, j] + cells[(i-1) % N, (j+1) % N]
                     + cells[i, (j+1) % N]+cells[(i+1) % N, (j+1) % N])
        if cells[i, j] == 1:
            if (neighbors < 2) or (neighbors > 3):
                new_cells[i, j] = 0
        else:
            if neighbors == 3:
                new_cells[i, j] = 1

    for i, j in cells:
        cells[i, j] = new_cells[i, j]
1 个赞