【作业1】Mandelbrot Set & Related Questions

作业1

作业描述

(写码小白斗胆发上来)很简单的更新了一下Julia Set的数学公式为Mandelbrot Set,加上了迭代次数变化的梯度颜色。在作业区发现大家都好会玩!向大家学习。

效果展示

ezgif.com-gif-maker

不懂就问

(1) 代码中有一行

    z = complex_sqr(z) + c

根据Mandelbrot Set,我设置的z初始作为0向量,在Taichi这里做向量加法的时候我发现如果用[0,0]与一个[float,float]相加并不会自动进行type promotion,记得课上说是可以的?

(2)在尝试进行添加颜色的时候,成功用了

ti.Vector.field(3, shape=(x, y)) (r, g, b)

但尝试

ti.field(shape=(x, y, 3))

总是失败,比如以下就会报错。想问有没有应用的例子!

pixels = ti.field(dtype=float, shape=(n * 2, n, 3))
pixels[i, j, 0] = 0.2
pixels[i, j, 1] = 1 - iterations * 0.02
pixels[i, j, 2] = 0.9

(3)关于函数调用的问题
想问被ti.kernal调用的函数可以是非taichi的函数(即没有被@ti.func修饰)么?还是因为是在对taichi的datatype进行运算,所以只能选取taichi自己的函数

代码链接

Code

3 个赞

(1) 测试了下以下例子,应该是会自动做type promotion的?

import taichi as ti
ti.init(arch = ti.cpu)

@ti.kernel
def promotion():
	a = ti.Vector([0, 0])
	b = ti.Vector([1.0, 1.0])
	c = a + b
	print(c)
promotion()

(2) 下面这个例子是可以正常运行的;可以提供一下你那边的报错信息?

import taichi as ti
ti.init(arch = ti.cpu)

n = 1
pixels = ti.field(dtype=float, shape=(n * 2, n, 3))

@ti.kernel
def fill():
	for i, j, k in pixels:
		pixels[i, j, k] = 0.2
		pixels[i, j, k] = 1 - 1 * 0.02
		pixels[i, j, k] = 0.9
fill()

(3) taichi kernel里目前建议只调用taichi自己的函数,不建议使用非taichi的函数,调用非taichi函数会出现一些user unexpected behaviors (比如只在编译前被执行了一次,或者在有template时生成不同kernels时被执行多次,编译方式不同如jit与aot也会造成不同的行为) :slight_smile:

2 个赞

猜测是不是循环语句写的还是 for i, j in pixels: :thinking:
如果不想让一个循环 loop 所有 field 元素的话,要手动展开成 range loop:

for i in range(n * 2):
   for j in range (n):
        # ...

或者:

for i, j in ti.ndrange(n * 2, n):
    # ...
2 个赞

mzhang给的这个例子是会做type promotion的 :joy:,但是

这个例子里不会,而是会报一个 [$141] Local store may lose precision 的 warning。

因为这两个变量的性质不同。mzhang例子里的 a 只会被load一次,是一个 临时变量(tmp variable),而Mandelbrot Set里的 z 会被下面的 while 循环 load/store 多次,是一个 局部变量(local variable),会自动做 type cast,但是会报warning。同理,如果对 全局变量(global variable),也就是 field 的元素赋值,也会报同样的 warning。

综上所述,虽然 Taichi 有些时候会自动帮你做类型转化,但考虑到 Taichi 是强类型的语言,还是建议在定义变量的时候把类型写清楚。 :grinning:

3 个赞

(1) 如g1n0st所说,如果仅仅把我的代码里面的

z = ti.Vector([0.0, 0.0]) 改为 z = ti.Vector([0, 0])

就会有 [$141] Local store may lose precision 的问题
图像就会变成


这个样子。应该是自动cast为int了。

感谢临时变量和局部变量的解释!以后会注意定义变量类型。
一个新的小问题:是否当一个变量只load一次的时候就会被自动识别为临时变量呢?

(2)已经被下面的贴子解决啦~~

(3)好,了解!感谢

2 个赞

哭了,exactly!!! 感谢解释

哈~ 这里需要一些编译技术的知识。简单来说不能这么判断,因为Taichi编译器会自动做很多优化,有时候会合并你写的多个load,或者做一些dead code elimination。但是如果你的变量定义在一个循环以外,并在循环里 load/store,大概率就是一个local variable了。

具体来说,如果你在 ti.init() 里写 print_ir = True,可以看到编译器输出的中间代码表示(IR),一个 local variable 会被一个 LocalAllocaStmt 所定义,对它的 load/store 会以 LocalLoadStmtLocalStoreStmt 所表示,而 tmp variable 都是 SSA form,它的 load/store 会被编译器所消除。

2 个赞

感谢详细的解释!感觉print_ir = True打开了新的世界!我看了一下好像initial IR没有太大的区别

做了一个小测试

import taichi as ti
ti.init(arch = ti.cpu)
#ti.init(arch = ti.cpu, print_ir = True)
n=2
@ti.kernel
def promotion1():
a = ti.Vector([1.0, 1.0])
b = ti.Vector([0, 0])
for i in range(n):
a = a+b
print(a)
def promotion2():
a = ti.Vector([0, 0])
b = ti.Vector([1.0, 1.0])
for i in range(n):
a = a+b
print(a)
promotion1()
promotion2()

和要把a + b 的结果重新assign给a有关系?我的猜想这里不是type promotion而是cast了

(题外话。。你们怎么做到把code的format也保持进来的呀,比如颜色字体?我直接从VSCODE copy paste就不带任何format)

啊哈哈 打开新世界好顶赞:)

关于code format,论坛用的是MD格式,你可以在代码第一行之前和最后一行之后使用 ```把你的代码块包起来:

def foo():
1 个赞

另外照着你的代码写了一个示例:

n=2
@ti.kernel
def promotion1():
    a = ti.Vector([0, 0])
    b = ti.Vector([1.1, 1.2])
    for i in range(n):
        a = a+b
        print(a)

def promotion2():
    a = ti.Vector([0, 0])
    b = ti.Vector([1.1, 1.2])
    for i in range(n):
        a = a+b
        print(a)

不管是在Python scope还是Taichi scope中,a+b 都是会被生成一个临时变量的,并且这个生成临时变量的expr中隐含了一次type promotion。

然后赋值的时候,如果是在Taichi scope中,赋值的target:a是一个整型向量,Taichi会额外cast一次把临时变量变成整型。如果在Python scope中,因为python变量弱类型的缘故,a会被赋值为新的临时变量的type,也就是浮点数向量。

另外这段代码中还藏着一个小危险:在promotion1()函数里的range_for是一个并行for,在这个for里面的a=a+b没有被原子操作保护。

1 个赞

谢谢老师改的例子!设计的很巧妙,明白为什么了!

想问老师可以把学习debug taichi的工具、方法提前么? 因为并行运算的原因感觉进行手动debug还相对挺难的,这样在做作业尝试中也可以做到。

还没有备好课就很惨 (逃走)