Skip to content

Latest commit

 

History

History
274 lines (172 loc) · 6.06 KB

syntax.rst

File metadata and controls

274 lines (172 loc) · 6.06 KB

Kernels and functions

Taichi-scope vs Python-scope

Code decorated by @ti.kernel or @ti.func is in the Taichi-scope.

They are to be compiled and executed on CPU or GPU devices with high parallelization performance, on the cost of less flexibility.

Note

For people from CUDA, Taichi-scope = device side.

Code outside @ti.kernel or @ti.func is in the Python-scope.

They are not compiled by the Taichi compiler and have lower performance but with a richer type system and better flexibility.

Note

For people from CUDA, Python-scope = host side.

Kernels

A Python function decorated by @ti.kernel is a Taichi kernel:

@ti.kernel
def my_kernel():
    ...

my_kernel()

Kernels should be called from Python-scope.

Note

For people from CUDA, Taichi kernels = __global__ functions.

Arguments

Kernels can have at most 8 parameters so that you can pass values from Python-scope to Taichi-scope easily.

Kernel arguments must be type-hinted:

@ti.kernel
def my_kernel(x: ti.i32, y: ti.f32):
    print(x + y)

my_kernel(2, 3.3)  # prints: 5.3

Note

For now, we only support scalars as arguments. Specifying ti.Matrix or ti.Vector as argument is not supported. For example:

@ti.kernel
def bad_kernel(v: ti.Vector):
    ...

@ti.kernel
def good_kernel(vx: ti.f32, vy: ti.f32):
    v = ti.Vector([vx, vy])
    ...

Return value

A kernel may or may not have a scalar return value. If it does, the type of return value must be hinted:

@ti.kernel
def my_kernel() -> ti.f32:
    return 233.33

print(my_kernel())  # 233.33

The return value will be automatically cast into the hinted type. e.g.,

@ti.kernel
def add_xy() -> ti.i32:  # int32
    return 233.33

print(my_kernel())  # 233, since return type is ti.i32

Note

For now, a kernel can only have one scalar return value. Returning ti.Matrix or ti.Vector is not supported. Python-style tuple return is not supported either. For example:

@ti.kernel
def bad_kernel() -> ti.Matrix:
    return ti.Matrix([[1, 0], [0, 1]])  # Error

@ti.kernel
def bad_kernel() -> (ti.i32, ti.f32):
    x = 1
    y = 0.5
    return x, y  # Error

Advanced arguments

We also support template arguments (see :ref:`template_metaprogramming`) and external array arguments (see :ref:`external`) in Taichi kernels. Use ti.template() or ti.ext_arr() as their type-hints respectively.

Note

When using differentiable programming, there are a few more constraints on kernel structures. See the Kernel Simplicity Rule in :ref:`differentiable`.

Also, please do not use kernel return values in differentiable programming, since the return value will not be tracked by automatic differentiation. Instead, store the result into a global variable (e.g. loss[None]).

Functions

A Python function decorated by @ti.func is a Taichi function:

@ti.func
def my_func():
    ...

@ti.kernel
def my_kernel():
    ...
    my_func()  # call functions from Taichi-scope
    ...

my_kernel()    # call kernels from Python-scope

Taichi functions should be called from Taichi-scope.

Note

For people from CUDA, Taichi functions = __device__ functions.

Note

Taichi functions can be nested.

Warning

Currently, all functions are force-inlined. Therefore, no recursion is allowed.

Arguments and return values

Functions can have multiple arguments and return values. Unlike kernels, arguments in functions don't need to be type-hinted:

@ti.func
def my_add(x, y):
    return x + y


@ti.kernel
def my_kernel():
    ...
    ret = my_add(2, 3.3)
    print(ret)  # 5.3
    ...

Function arguments are passed by value. So changes made inside function scope won't affect the outside value in the caller:

@ti.func
def my_func(x):
    x = x + 1  # won't change the original value of x


@ti.kernel
def my_kernel():
    ...
    x = 233
    my_func(x)
    print(x)  # 233
    ...

Advanced arguments

You may use ti.template() as type-hint to force arguments to be passed by reference:

@ti.func
def my_func(x: ti.template()):
    x = x + 1  # will change the original value of x


@ti.kernel
def my_kernel():
    ...
    x = 233
    my_func(x)
    print(x)  # 234
    ...

Note

Unlike kernels, functions do support vectors or matrices as arguments and return values:

@ti.func
def sdf(u):  # functions support matrices and vectors as arguments. No type-hints needed.
    return u.norm() - 1

@ti.kernel
def render(d_x: ti.f32, d_y: ti.f32):  # kernels do not support vector/matrix arguments yet. We have to use a workaround.
    d = ti.Vector([d_x, d_y])
    p = ti.Vector([0.0, 0.0])
    t = sdf(p)
    p += d * t
    ...

Warning

Functions with multiple return statements are not supported for now. Use a local variable to store the results, so that you end up with only one return statement:

# Bad function - two return statements
@ti.func
def safe_sqrt(x):
  if x >= 0:
    return ti.sqrt(x)
  else:
    return 0.0

# Good function - single return statement
@ti.func
def safe_sqrt(x):
  ret = 0.0
  if x >= 0:
    ret = ti.sqrt(x)
  else:
    ret = 0.0
  return ret