作业1
作业描述
(写码小白斗胆发上来)很简单的更新了一下Julia Set的数学公式为Mandelbrot Set,加上了迭代次数变化的梯度颜色。在作业区发现大家都好会玩!向大家学习。
效果展示
不懂就问
(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 个赞
mzhang
2021 年9 月 28 日 02:29
#2
(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也会造成不同的行为)
2 个赞
g1n0st
2021 年9 月 28 日 02:39
#3
猜测是不是循环语句写的还是 for i, j in pixels:
如果不想让一个循环 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 个赞
g1n0st
2021 年9 月 28 日 02:51
#4
mzhang给的这个例子是会做type promotion的 ,但是
这个例子里不会,而是会报一个 [$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 是强类型的语言,还是建议在定义变量的时候把类型写清楚。
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 个赞
g1n0st
2021 年9 月 28 日 09:27
#7
哈~ 这里需要一些编译技术的知识。简单来说不能这么判断,因为Taichi编译器会自动做很多优化,有时候会合并你写的多个load,或者做一些dead code elimination。但是如果你的变量定义在一个循环以外,并在循环里 load/store,大概率就是一个local variable了。
具体来说,如果你在 ti.init()
里写 print_ir = True
,可以看到编译器输出的中间代码表示(IR),一个 local variable 会被一个 LocalAllocaStmt
所定义,对它的 load/store 会以 LocalLoadStmt
和 LocalStoreStmt
所表示,而 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还相对挺难的,这样在做作业尝试中也可以做到。