Skip to content

Commit

Permalink
[Lang] Add ti.static_assert for compile-time assertations (#1344)
Browse files Browse the repository at this point in the history
* [Lang] Add ti.static_assert for compile-time assertations

* add index

* [skip ci] Apply suggestions from code review

Co-authored-by: Danni <[email protected]>
Co-authored-by: Xudong Feng <[email protected]>

* [skip ci] enforce code format

* [skip ci] Apply suggestions from code review

Co-authored-by: Yuanming Hu <[email protected]>
Co-authored-by: Xudong Feng <[email protected]>
Co-authored-by: Danni <[email protected]>

* [skip ci] revert ti.chain_compare

* [skip ci] enforce code format

* [skip ci] update bug_report.md

* [skip ci] Apply suggestions from code review

Co-authored-by: Yuanming Hu <[email protected]>

* [skip ci] Update docs/debugging.rst

Co-authored-by: Yuanming Hu <[email protected]>

* add func desc

* Apply suggestions from code review

Co-authored-by: Danni <[email protected]>
Co-authored-by: Yuanming Hu <[email protected]>

* well @ti.pyfunc

* Apply suggestions from code review

Co-authored-by: Danni <[email protected]>

* [skip ci] Apply suggestions from code review

Co-authored-by: Yuanming Hu <[email protected]>

* Update tests/python/test_assert.py

Co-authored-by: Danni <[email protected]>
Co-authored-by: Xudong Feng <[email protected]>
Co-authored-by: Taichi Gardener <[email protected]>
Co-authored-by: Yuanming Hu <[email protected]>
  • Loading branch information
5 people authored Jul 5, 2020
1 parent 95ac7c5 commit eb40824
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 59 deletions.
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ assignees: ''
**Describe the bug**
A clear and concise description of what the bug is, ideally within 20 words.

**Log/Screenshots**
Please post the full log of the program (instead of just a few lines around the error message, unless the log is > 1000 lines). This will help us diagnose what's happening.

**To Reproduce**
Please post a sample code to reproduce the bug. The developer team will put a higher priority on bugs that can be reproduced within 20 lines of code. If you want a prompt reply, please keep the code short.

**Log/Screenshots**
Please post the full log of the program (instead of just a few lines around the error message, unless the log is > 1000 lines). This will help us diagnose what's happening.

**If you have local commits (e.g. compile fixes before you reproduce the bug), please make sure you first make a PR to fix the build errors and then report the bug.**
246 changes: 246 additions & 0 deletions docs/debugging.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
Debugging
=========

Debugging a parallel program is not easy, so Taichi provides
builtin utilities that could hopefully help you debug your Taichi program.

Run-time ``print`` in kernels
-----------------------------

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

Debug your program with ``print()`` in Taichi-scope. For example:

.. code-block:: python
@ti.kernel
def inside_taichi_scope():
x = 233
print('hello', x)
#=> hello 233
print('hello', x * 2 + 200)
#=> hello 666
print('hello', x, sep='')
#=> hello233
print('hello', x, sep='', end='')
print('world', x, sep='')
#=> hello233world233
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]
For now, Taichi-scope ``print`` supports string, scalar, vector, and matrix expressions as arguments.
``print`` in Taichi-scope may be a little different from ``print`` in Python-scope. Please see details below.

.. warning::

For the **CPU, CUDA, and Metal backend**, ``print`` will not work in Graphical Python Shells
including IDLE and Jupyter notebook. This is because these backends print the outputs to the console instead of the GUI.
Taichi developers are trying to solve this now. Use the **OpenGL backend** if you wish to
use ``print`` in IDLE / Jupyter.

.. warning::

Note that ``print`` in Taichi-scope can only receive **comma-separated parameter**. Neither f-string nor formatted string should be used. For example:
.. code-block:: python
import taichi as ti
ti.init(arch=ti.cpu)
a = ti.var(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()
.. note::

For the **OpenGL and CUDA backend**, the printed result will not show up until ``ti.sync()`` is called:

.. code-block:: python
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')
obtains:

.. code-block:: none
before kernel
after kernel
inside kernel
after
Please note that host access or program end will also implicitly invoke ``ti.sync()``.


Compile-time ``ti.static_print``
--------------------------------

Sometimes it is useful to print Python-scope objects and constants like data types or SNodes in Taichi-scope.
So, similar to ``ti.static`` we provide ``ti.static_print`` to print compile-time constants.
It is similar to Python-scope ``print``.

.. code-block:: python
x = ti.var(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
Unlike ``print``, ``ti.static_print`` will only print the expression once at compile-time, and
therefore it has no runtime cost.


Runtime ``assert`` in kernel
----------------------------

Programmers may use ``assert`` statements in Taichi-scope. When the assertion condition failed, a
``RuntimeError`` will be raised to indicate the error.

To make ``assert`` work, first make sure you are using the **CPU backend**.
For performance reason, ``assert`` only works when ``debug`` mode is on, For example:

.. code-block:: python
ti.init(arch=ti.cpu, debug=True)
x = ti.var(ti.f32, 128)
@ti.kernel
def do_sqrt_all():
for i in x:
assert x[i] >= 0
x[i] = ti.sqrt(x)
When you are done with debugging, simply set ``debug=False``. Now ``assert`` will be ignored
and there will be no runtime overhead.


Compile-time ``ti.static_assert``
---------------------------------

.. function:: ti.static_assert(cond, msg=None)

Like ``ti.static_print``, we also provide a static version of ``assert``:
``ti.static_assert``. It can be useful to make assertions on data types, dimensionality, and shapes.
It works whether ``debug=True`` is specified or not. When an assertion fails, it will
raise an ``AssertionError``, just like a Python-scope ``assert``.

For example:

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

Debugging a Taichi program can be hard even with the builtin tools above.
Here we showcase some common bugs that one may encounter in a Taichi program.

Static type system
++++++++++++++++++

Python code in Taichi-scope is translated into a statically typed language for high performance. This means code in Taichi-scope can have a different behavior compared with that in Python-scope, especially when it comes to types.

The type of a variable is simply **determined at its initialization and never changes later**.

Although Taichi's static type system provides better performance, it may lead to bugs if
programmers carelessly used the wrong types. For example,

.. code-block:: python
@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()
The code above shows a common bug due to Taichi's static type system.
The Taichi compiler should show a warning like:

.. code-block:: none
[W 06/27/20 21:43:51.853] [type_check.cpp:visit@66] [$19] Atomic add (float32 to int32) may lose precision.
This means that Taichi cannot store a ``float32`` result precisely to ``int32``.
The solution is to initialize ``ret`` as a float-point value:

.. code-block:: python
@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()
Advanced Optimization
+++++++++++++++++++++

Taichi has an advanced optimization engine to make your Taichi kernel to be as fast as it could.
But like what ``gcc -O3`` does, advanced optimization may occasionally lead to bugs as it tries
too hard. This includes runtime errors such as:

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

You may use ``ti.core.toggle_advance_optimization(False)`` to turn off advanced
optimization and see if the issue still exists:

.. code-block:: python
import taichi as ti
ti.init()
ti.core.toggle_advance_optimization(False)
...
If turning off optimization fixes the issue, please report this bug on `GitHub <https://github.com/taichi-dev/taichi/issues/new?labels=potential+bug&template=bug_report.md>`_. Thank you!
5 changes: 3 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ The Taichi Programming Language
:caption: Miscellaneous
:maxdepth: 3

extension_libraries
gui
debugging
extension_libraries
export_results
cli_utilities
global_settings
faq
acknowledgments


Expand All @@ -76,3 +76,4 @@ The Taichi Programming Language
:maxdepth: 3

legacy_installation
faq
54 changes: 0 additions & 54 deletions docs/syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,57 +204,3 @@ Supported scalar functions:
for i in ti.static(range(2)):
for j in ti.static(range(3)):
A[i, j] += B[i, j]
Debugging
---------

Debug your program with ``print()`` in Taichi-scope. For example:

.. code-block:: python
@ti.kernel
def inside_taichi_scope():
x = 233
print('hello', x)
#=> hello 233
m = ti.Matrix([[2, 3, 4], [5, 6, 7]])
print('m is', m)
#=> m is [[2, 3, 4], [5, 6, 7]]
v = ti.Vector([3, 4])
print('v is', v)
#=> v is [3, 4]
.. warning::

General speaking, the printed result won't shows up until ``ti.sync()``:

.. code-block:: python
import taichi as ti
ti.init(arch=ti.gpu)
@ti.kernel
def kern():
print('inside kernel')
print('before kernel')
kern()
print('after kernel')
ti.sync()
print('after sync')
obtains:

.. code-block:: none
before kernel
after kernel
inside kernel
after sync
This is because GPU memory is only copied when necessary, for performance considerations.

Also note that host access or program end will also implicitly invoke for
``ti.sync()``.
2 changes: 2 additions & 0 deletions python/taichi/lang/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,12 @@ def reset():
taichi_lang_core.reset_default_compile_config()


@taichi_scope
def static_print(*args, __p=print, **kwargs):
__p(*args, **kwargs)


# we don't add @taichi_scope decorator for @ti.pyfunc
def static_assert(cond, msg=None):
if msg is not None:
assert cond, msg
Expand Down
44 changes: 44 additions & 0 deletions tests/python/test_assert.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,47 @@ def func():
assert 10 <= x <= 20

func()


@ti.host_arch_only
def test_static_assert_is_static():
@ti.kernel
def func():
x = 0
ti.static_assert(x) # Expr is not None

func()


@ti.host_arch_only
@ti.must_throw(AssertionError)
def test_static_assert_message():
x = 3

@ti.kernel
def func():
ti.static_assert(x == 4, "Oh, no!")

func()


@ti.host_arch_only
def test_static_assert_vector_n_ok():
x = ti.Vector(4, ti.f32, ())

@ti.kernel
def func():
ti.static_assert(x.n == 4)

func()


@ti.host_arch_only
def test_static_assert_data_type_ok():
x = ti.var(ti.f32, ())

@ti.kernel
def func():
ti.static_assert(x.dtype == ti.f32)

func()

0 comments on commit eb40824

Please sign in to comment.