跳转至主要内容
Version: v1.1.3

程序调试

调试并行程序并非易事,因此 Taichi 提供了内置的小工具,希望能帮助你更快捷地调试 Taichi 程序。

Taichi 作用域的 assert 运行时。

print(arg1, ..., sep='', end='\n')

在 Taichi 作用域内用 print() 调试程序。 For example:

@ti.kernel
def inside_taichi_scope():
x = 256
print('hello', x)
#=> hello 256

print('hello', x * 2 + 200)
#=> hello 712

print('hello', x, sep='')
#=> hello256

print('hello', x, sep='', end='')
print('world', x, sep='')
#=> hello256world256

m = ti.Matrix([[2, 3, 4], [5, 6, 7]])
print('m =', m)
#=> m = [[2, 3, 4], [5, 6, 7]]

v = ti.Vector([3, 4])
print('v =', v)
#=> v = [3, 4]

ray = ti.Struct({
"ori": ti.Vector([0.0, 0.0, 0.0]),
"dir": ti.Vector([0.0, 0.0, 1.0]),
"len": 1.0
})
# print(ray)
# Print a struct directly in Taichi-scope has not been supported yet
# Instead, use:
print('ray.ori =', ray.ori, ', ray.dir =', ray.dir, ', ray.len =', ray.len)
#=> ray.ori = [0.0, 0.0, 0.0], ray.dir = [0.0, 0.0, 1.0], ray.len = 1.0

目前,Taichi 作用域的 print 支持字符串、标量、向量和矩阵表达式作为参数。 Taichi 作用域中的 print 可能与 Python 作用域中的 print 略有不同。 详情如下:

note

print in Taichi-scope is currently supported on the CPU, CUDA, OpenGL, and Metal backends.

caution

For the CPU and CUDA backends, print will not work in Graphical Python Shells including IDLE and Jupyter notebook. 这是因为这些后端将输出打印到控制台而非 GUI。 如果你希望在 IDLE/ Jupyter 中使用 print,请使用 OpenGL 或 Metal 后端

warning

对于 CUDA 后端,打印的结果直到 ti.sync() 被调用后才会显示。“

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

@ti.kernel
def kern():
print('inside kernel')

print('before kernel')
kern()
print('after kernel')
ti.sync()
print('after sync')

得到:

before kernel
after kernel
inside kernel
after sync

请注意,主机访问或程序终止也将隐性调用 ti.sync()

note

Note that print in Taichi-scope can only receive comma-separated parameters. f-字符串或格式化的字符串都不接受。 例如:

import taichi as ti
ti.init(arch=ti.cpu)
a = ti.field(ti.f32, 4)


@ti.kernel
def foo():
a[0] = 1.0
print('a[0] = ', a[0]) # right
print(f'a[0] = {a[0]}') # wrong, f-string is not supported
print("a[0] = %f" % a[0]) # wrong, formatted string is not supported

foo()

编译时 ti.static_print

有时,打印 Python 作用域的对象和常量(如数据类型或 SNodes)或者 Taichi 作用域的 Snode 会非常有用。 因此,类似于 ti.static, Taichi 提供了 ti. static_print 打印编译时常量,类似于 Python 作用域的 print

x = ti.field(ti.f32, (2, 3))
y = 1

@ti.kernel
def inside_taichi_scope():
ti.static_print(y)
# => 1
ti.static_print(x.shape)
# => (2, 3)
ti.static_print(x.dtype)
# => DataType.float32
for i in range(4):
ti.static_print(i.dtype)
# => DataType.int32
# will only print once

print 不同,ti.static_print 在编译时只打印一次表达式,因此没有运行时的成本。

串行执行

Taichi 的自动并行化特性有时会导致不确定的行为。 为了方便调试,可能需要串行化程序的执行来获得可重复的结果并诊断数据竞争问题。 当在 CPU 上运行你的 Taichi 程序时,你可以在初始化 Taichi 时通过设置 cpu_max_num_threads=1 来使用单线程,这样整个程序就变为串行的和确定的了。 例如,

ti.init(arch=ti.cpu, cpu_max_num_threads=1)

如果你的程序在串行时表现良好但并行时出现了问题,请检查并行相关的问题,比如数据竞争。

Taichi 作用域的 assert 运行时。

程序员可能在 Taichi 作用域内使用 assert 语句。 当 assert 条件失败时,一个 RuntimeError 报错信息会被抛出。

note

assert is currently supported on the CPU, CUDA, and Metal backends.

其次出于性能方面的考量,assert 仅在 debug 模式开启时有效。 For example:

ti.init(arch=ti.cpu, debug=True)

x = ti.field(ti.f32, 128)

@ti.kernel
def do_sqrt_all():
for i in x:
assert x[i] >= 0
x[i] = ti.sqrt(x)

完成调试后,只需设置 debug=False。 那么 assert 将被忽略,并且不会产生运行时开销。

编译时 ti.static_assert

ti.static_assert(cond, msg=None)

ti.static_print 类似,Taichi 还提供了静态版本的 assertti.static_assert。 对数据类型、维度和形状进行声明应该是有用的。 无论是否指定 debug=True,它都有效。 就像 Python 作用域中的 assert 类似,声明失败时 Taichi 也会抛出一个 AssertionError 错误。

For example:

@ti.func
def copy(dst: ti.template(), src: ti.template()):
ti.static_assert(dst.shape == src.shape, "copy() needs src and dst fields to be same shape")
for I in ti.grouped(src):
dst[I] = src[I]
return x % 2 == 1

优雅的 Taichi 作用域的回溯

如果在 Taichi 作用域 中遇到错误,Taichi 将报告回溯消息。 For example:

import taichi as ti
ti.init()

@ti.func
def func3():
ti.static_assert(1 + 1 == 3)

@ti.func
def func2():
func3()

@ti.func
def func1():
func2()

@ti.kernel
def func0():
func1()

func0()

以上代码片段会触发一个很长的 AssertionError

Traceback (most recent call last):
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 23, in __call__
return method(ctx, node)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer.py", line 342, in build_Call
node.ptr = node.func.ptr(*args, **keywords)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/impl.py", line 471, in static_assert
assert cond
AssertionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 23, in __call__
return method(ctx, node)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer.py", line 360, in build_Call
node.ptr = node.func.ptr(*args, **keywords)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/kernel_impl.py", line 59, in decorated
return fun.__call__(*args)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/kernel_impl.py", line 178, in __call__
ret = transform_tree(tree, ctx)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/transform.py", line 8, in transform_tree
ASTTransformer()(ctx, tree)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 26, in __call__
raise e
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 23, in __call__
return method(ctx, node)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer.py", line 488, in build_Module
build_stmt(ctx, stmt)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 26, in __call__
raise e
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 23, in __call__
return method(ctx, node)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer.py", line 451, in build_FunctionDef
build_stmts(ctx, node.body)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer.py", line 1086, in build_stmts
build_stmt(ctx, stmt)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 26, in __call__
raise e
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 23, in __call__
return method(ctx, node)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer.py", line 964, in build_Expr
build_stmt(ctx, node.value)
File "/Users/lanhaidong/taichi/taichi/python/taichi/lang/ast/ast_transformer_utils.py", line 32, in __call__
raise TaichiCompilationError(msg)
taichi.lang.exception.TaichiCompilationError: File "misc/demo_traceback.py", line 10:
ti.static_assert(1 + 1 == 3)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError:

...

错误信息可能非常冗长。 不过,许多堆栈帧都会显示 Taichi 编译器实现细节,这些细节噪声往往会影响调试。 在当前版本中,您可以选择通过设置 sys.tracebacklimit 降低回溯消息的颗粒度,让从 Taichi 作用域返回的堆栈回溯更加直观:

import taichi as ti
import sys
sys.tracebacklimit=0
...

结果就像这样:

AssertionError

During handling of the above exception, another exception occurred:

taichi.lang.exception.TaichiCompilationError: File "misc/demo_traceback.py", line 10:
ti.static_assert(1 + 1 == 3)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError:

...

此外,在报告 issue 时,请总是取消 sys.tracebacklimit 的设置值并粘贴完整的回溯消息。

调试技巧

即使有上面的内置工具,调试 Taichi 程序很可能会很难。 在这里,我们展示了一些 Taichi 程序中可能会遇到的常见错误。

静态类型系统

Taichi 作用域中的 Python 代码被翻译成静态类型语言以实现高性能。 这意味着 Taichi 作用域中的代码与 Python 作用域中的代码可以有不同的行为,尤其是在数据类型方面。

变量的类型只在初始化时确定,并且之后不会更改

虽然 Taichi 的静态类型系统性能更优,但如果程序员不小心使用了错误的类型,还是会导致错误。 例如:

@ti.kernel
def buggy():
ret = 0 # 0 is an integer, so `ret` is typed as int32
for i in range(3):
ret += 0.1 * i # i32 += f32, the result is still stored in int32!
print(ret) # will show 0

buggy()

上面的代码显示了由于错误使用了 Taichi 的静态类型系统而导致的常见错误。 Taichi 编译器会显示以下警告:

[W 06/27/20 21:43:51.853] [type_check.cpp:visit@66] [$19] Atomic add (float32 to int32) may lose precision.

这意味着 Taichi 不能将 float32 结果精确存储到 int32 当中。 解决方案是在初始化 ret 时把它设成浮点数:

@ti.kernel
def not_buggy():
ret = 0.0 # 0 is a floating point number, so `ret` is typed as float32
for i in range(3):
ret += 0.1 * i # f32 += f32. OK!
print(ret) # will show 0.6

not_buggy()

进阶优化

默认情况下,Taichi 会进行少量高级的 IR 优化,以使你的 Taichi kernel 尽可能表现最优。 不过,高级优化偶尔会导致编译错误,例如:

RuntimeError: [verify.cpp:basic_verify@40] stmt 8 cannot have operand 7.

你可以用 ti.init(advanced_optimization=False) 关闭高级优化再查看是否有变化。 如果问题仍然存在,请随时在 GitHub 上报告这一问题。