Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lang] Add ti.static_assert for compile-time assertations #1344

Merged
merged 26 commits into from
Jul 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
685fe0c
[Lang] Add ti.static_assert for compile-time assertations
archibate Jun 27, 2020
809a7d2
add index
archibate Jun 27, 2020
6590d0f
well doc merge
archibate Jun 27, 2020
38b6e26
[skip ci] Apply suggestions from code review
archibate Jun 28, 2020
30496dd
[skip ci] enforce code format
taichi-gardener Jun 28, 2020
1a5c832
[skip ci] Apply suggestions from code review
archibate Jun 28, 2020
ca33871
[skip ci] Merge branch 'master' into static-assert
archibate Jun 29, 2020
d1a356e
[skip ci] revert ti.chain_compare
archibate Jun 29, 2020
a769781
Merge branch 'static-assert' of https://github.com/archibate/taichi i…
archibate Jun 29, 2020
2acd2dc
[skip ci] enforce code format
taichi-gardener Jun 29, 2020
dd6160b
[skip ci] update bug_report.md
archibate Jun 30, 2020
378a5cb
[skip ci] Apply suggestions from code review
archibate Jul 1, 2020
946cc02
[skip ci] Update docs/debugging.rst
archibate Jul 1, 2020
472ef77
merge
archibate Jul 1, 2020
e4dc0f3
Merge branch 'static-assert' of https://github.com/archibate/taichi i…
archibate Jul 1, 2020
41dd9eb
add func desc
archibate Jul 1, 2020
a26670b
Apply suggestions from code review
archibate Jul 2, 2020
7129673
Merge branch 'master' into static-assert
archibate Jul 2, 2020
d882bc4
Merge branch 'master' into static-assert
archibate Jul 2, 2020
76d33e3
Merge branch 'static-assert' of https://github.com/archibate/taichi i…
archibate Jul 3, 2020
4ead322
Merge branch 'master' into static-assert
archibate Jul 3, 2020
d819cf0
Merge branch 'master' into static-assert
archibate Jul 3, 2020
bcc594a
well @ti.pyfunc
archibate Jul 3, 2020
7991cea
Apply suggestions from code review
archibate Jul 4, 2020
df7027c
[skip ci] Apply suggestions from code review
archibate Jul 4, 2020
bd2e4a3
Update tests/python/test_assert.py
yuanming-hu Jul 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

archibate marked this conversation as resolved.
Show resolved Hide resolved
.. 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may give ti.static_assert a function demonstration here? so that people can realize it receive 2 parameters, the first one is assert expresstion and the second is a statement.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought all Python users who know assert knows this failure message feature?


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 @@ -245,10 +245,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()