Skip to content

Commit

Permalink
[Doc] [lang] Cherrypick commits related to dynamic SNode (#6764)
Browse files Browse the repository at this point in the history
Issue: #

### Brief Summary

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Zhao Liang <[email protected]>
Co-authored-by: Olinaaaloompa <[email protected]>
  • Loading branch information
4 people authored Nov 29, 2022
1 parent 9bcd216 commit 16244cc
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 25 deletions.
53 changes: 40 additions & 13 deletions docs/lang/articles/basic/sparse.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ In Taichi, programmers can compose data structures similar to VDB and SPGrid wit


:::note
**Backend compatibility**: The LLVM backends (CPU/CUDA) and the Metal backend offer the full functionality of computation on spatially sparse data structures.
**Backend compatibility**: The LLVM-based backends (CPU/CUDA) offer the full functionality for performing computations on spatially sparse data structures.
Using sparse data structures on the Metal backend is now deprecated. The support for Dynamic SNode has been removed in v1.3.0,
and the support for Pointer/Bitmasked SNode will be removed in v1.4.0.
:::


Expand Down Expand Up @@ -170,31 +172,56 @@ The bitmasked SNodes are like dense SNodes with auxiliary activity values.

### Dynamic SNode

To support variable-length fields, Taichi provides dynamic SNodes. The code snippet below first creates a 5x1 dense block (line 2). Then each cell of the dense block contains a variable-length dynamic container (line 3). The maximum length of the dynamic container is 5. In the `make_lists()` function, you can use `ti.append()` to add a value to the end of a dynamic SNode. `x.parent()` is the same as `pixel`. The dense field `l` stores the length of each dynamic SNode.
To support variable-length fields, Taichi provides dynamic SNodes.

```python {3} title=dynamic.py
x = ti.field(ti.i32)
block = ti.root.dense(ti.i, 5)
pixel = block.dynamic(ti.j, 5)
pixel.place(x)
The first argument of `dynamic` is the axis, the second argument is the maximum length,
and the third argument (optional) is the `chunk_size`.

The `chunk_size` specifies how much space the dynamic SNode allocates when the previously-allocated space runs out.
For example, with `chunk_size=4`, the dynamic SNode allocates the space for four elements when the first element is appended, and
allocates space for another four when the 5th (, 9th, 13th...) element is appended.

You can use `x[i].append(...)` to append an element,
use `x[i].length()` to get the length, and use `x[i].deactivate()` to clear the list.

The code snippet below creates a struct field that stores pairs of `(i16, i64)`.
The `i` axis is a dense SNode, and the `j` axis is a dynamic SNode.

```python {5,13,15,20} title=dynamic.py
pair = ti.types.struct(a=ti.i16, b=ti.i64)
pair_field = pair.field()

block = ti.root.dense(ti.i, 4)
pixel = block.dynamic(ti.j, 100, chunk_size=4)
pixel.place(pair_field)
l = ti.field(ti.i32)
ti.root.dense(ti.i, 5).place(l)

@ti.kernel
def make_lists():
for i in range(5):
for j in range(i):
ti.append(x.parent(), i, j * j) # ti.append(pixel, i, j * j)
l[i] = ti.length(x.parent(), i) # [0, 1, 2, 3, 4]
def dynamic_pair():
for i in range(4):
pair_field[i].deactivate()
for j in range(i * i):
pair_field[i].append(pair(i, j + 1))
# pair_field = [[],
# [(1, 1)],
# [(2, 1), (2, 2), (2, 3), (2, 4)],
# [(3, 1), (3, 2), ... , (3, 8), (3, 9)]]
l[i] = pair_field[i].length() # l = [0, 1, 4, 9]
```


<center>

![Dynamic](https://raw.githubusercontent.com/taichi-dev/public_files/master/taichi/doc/dynamic.png)

</center>

:::note
A dynamic SNode must have one axis only, and the axis must be the last axis.
No other SNodes can be placed under a dynamic SNode. In other words, a dynamic SNode must be directly placed with a field.
Along the path from a dynamic SNode to the root of the SNode tree, other SNodes *must not* have the same axis as the dynamic SNode.
:::

## Computation on spatially sparse data structures

### Sparse struct-fors
Expand Down
13 changes: 13 additions & 0 deletions python/taichi/_snode/fields_builder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from typing import Any, Optional, Sequence, Union

from taichi._lib import core as _ti_core
Expand Down Expand Up @@ -74,6 +75,10 @@ def dense(self, indices: Union[Sequence[_Axis], _Axis],
def pointer(self, indices: Union[Sequence[_Axis], _Axis],
dimensions: Union[Sequence[int], int]):
"""Same as :func:`taichi.lang.snode.SNode.pointer`"""
if impl.current_cfg().arch == _ti_core.metal:
warnings.warn(
"Pointer SNode on metal backend is deprecated, and it will be removed in v1.4.0.",
DeprecationWarning)
self._check_not_finalized()
self.empty = False
return self.root.pointer(indices, dimensions)
Expand All @@ -87,13 +92,21 @@ def dynamic(self,
dimension: Union[Sequence[int], int],
chunk_size: Optional[int] = None):
"""Same as :func:`taichi.lang.snode.SNode.dynamic`"""
if impl.current_cfg().arch == _ti_core.metal:
raise TaichiRuntimeError(
"Dynamic SNode on metal backend is deprecated and removed in this release."
)
self._check_not_finalized()
self.empty = False
return self.root.dynamic(index, dimension, chunk_size)

def bitmasked(self, indices: Union[Sequence[_Axis], _Axis],
dimensions: Union[Sequence[int], int]):
"""Same as :func:`taichi.lang.snode.SNode.bitmasked`"""
if impl.current_cfg().arch == _ti_core.metal:
warnings.warn(
"Bitmasked SNode on metal backend is deprecated, and it will be removed in v1.4.0.",
DeprecationWarning)
self._check_not_finalized()
self.empty = False
return self.root.bitmasked(indices, dimensions)
Expand Down
8 changes: 5 additions & 3 deletions python/taichi/lang/ast/ast_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from taichi.lang.field import Field
from taichi.lang.impl import current_cfg
from taichi.lang.matrix import Matrix, MatrixType, Vector, is_vector
from taichi.lang.snode import append, deactivate
from taichi.lang.snode import append, deactivate, length
from taichi.lang.struct import Struct, StructType
from taichi.lang.util import is_taichi_class, to_taichi_type
from taichi.types import (annotations, ndarray_type, primitive_types,
Expand Down Expand Up @@ -781,7 +781,7 @@ def build_Module(ctx, node):
@staticmethod
def build_attribute_if_is_dynamic_snode_method(ctx, node):
is_subscript = isinstance(node.value, ast.Subscript)
names = ("append", "deactivate")
names = ("append", "deactivate", "length")
if node.attr not in names:
return False
if is_subscript:
Expand All @@ -801,8 +801,10 @@ def build_attribute_if_is_dynamic_snode_method(ctx, node):
return False
if node.attr == "append":
node.ptr = lambda val: append(x.parent(), indices, val)
else:
elif node.attr == "deactivate":
node.ptr = lambda: deactivate(x.parent(), indices)
else:
node.ptr = lambda: length(x.parent(), indices)
return True

@staticmethod
Expand Down
13 changes: 13 additions & 0 deletions python/taichi/lang/snode.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import numbers
import warnings

from taichi._lib import core as _ti_core
from taichi.lang import expr, impl, matrix
Expand Down Expand Up @@ -47,6 +48,10 @@ def pointer(self, axes, dimensions):
Returns:
The added :class:`~taichi.lang.SNode` instance.
"""
if impl.current_cfg().arch == _ti_core.metal:
warnings.warn(
"Pointer SNode on metal backend is deprecated, and it will be removed in v1.4.0.",
DeprecationWarning)
if isinstance(dimensions, numbers.Number):
dimensions = [dimensions] * len(axes)
return SNode(
Expand Down Expand Up @@ -74,6 +79,10 @@ def dynamic(self, axis, dimension, chunk_size=None):
Returns:
The added :class:`~taichi.lang.SNode` instance.
"""
if impl.current_cfg().arch == _ti_core.metal:
raise TaichiCompilationError(
"Dynamic SNode on metal backend is deprecated and removed in this release."
)
assert len(axis) == 1
if chunk_size is None:
chunk_size = dimension
Expand All @@ -91,6 +100,10 @@ def bitmasked(self, axes, dimensions):
Returns:
The added :class:`~taichi.lang.SNode` instance.
"""
if impl.current_cfg().arch == _ti_core.metal:
warnings.warn(
"Bitmasked SNode on metal backend is deprecated, and it will be removed in v1.4.0.",
DeprecationWarning)
if isinstance(dimensions, numbers.Number):
dimensions = [dimensions] * len(axes)
return SNode(
Expand Down
6 changes: 3 additions & 3 deletions tests/python/test_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from tests import test_utils


@test_utils.test(require=ti.extension.sparse)
@test_utils.test(require=ti.extension.sparse, exclude=ti.metal)
def test_compare_basics():
a = ti.field(ti.i32)
ti.root.dynamic(ti.i, 256).place(a)
Expand Down Expand Up @@ -44,7 +44,7 @@ def func():
assert a[11]


@test_utils.test(require=ti.extension.sparse)
@test_utils.test(require=ti.extension.sparse, exclude=ti.metal)
def test_compare_equality():
a = ti.field(ti.i32)
ti.root.dynamic(ti.i, 256).place(a)
Expand Down Expand Up @@ -120,7 +120,7 @@ def func():
assert b[None] == 2


@test_utils.test(require=ti.extension.sparse)
@test_utils.test(require=ti.extension.sparse, exclude=ti.metal)
def test_chain_compare():
a = ti.field(ti.i32)
ti.root.dynamic(ti.i, 256).place(a)
Expand Down
10 changes: 8 additions & 2 deletions tests/python/test_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ def func():
func()


@test_utils.test(require=ti.extension.assertion, debug=True, gdb_trigger=False)
@test_utils.test(require=ti.extension.assertion,
debug=True,
gdb_trigger=False,
exclude=ti.metal)
def test_out_of_bound_dynamic():
x = ti.field(ti.i32)

Expand All @@ -80,7 +83,10 @@ def func():
func()


@test_utils.test(require=ti.extension.assertion, debug=True, gdb_trigger=False)
@test_utils.test(require=ti.extension.assertion,
debug=True,
gdb_trigger=False,
exclude=ti.metal)
def test_not_out_of_bound_dynamic():
x = ti.field(ti.i32)

Expand Down
23 changes: 23 additions & 0 deletions tests/python/test_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,26 @@ def test_deprecate_field_dim_ndarray_annotation():
@ti.kernel
def func(x: ti.types.ndarray(field_dim=(16, 16))):
pass


@test_utils.test(arch=ti.metal)
def test_deprecate_metal_sparse():
with pytest.warns(
DeprecationWarning,
match=
"Pointer SNode on metal backend is deprecated, and it will be removed in v1.4.0."
):
a = ti.root.pointer(ti.i, 10)
with pytest.warns(
DeprecationWarning,
match=
"Bitmasked SNode on metal backend is deprecated, and it will be removed in v1.4.0."
):
b = a.bitmasked(ti.j, 10)

with pytest.raises(
ti.TaichiRuntimeError,
match=
"Dynamic SNode on metal backend is deprecated and removed in this release."
):
ti.root.dynamic(ti.i, 10)
2 changes: 1 addition & 1 deletion tests/python/test_dynamic_append_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test():
for j in range(i):
x[i].append(j)
for i in range(10):
assert (ti.length(x.parent(), i) == i)
assert (x[i].length() == i)
for j in range(i):
assert (x[i, j] == j)

Expand Down
2 changes: 1 addition & 1 deletion tests/python/test_dynamic_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test():
x[i].append(j)

for i in range(n):
assert (ti.length(x.parent(), i) == i)
assert (x[i].length() == i)
for j in range(i):
assert (x[i, j] == j)

Expand Down
2 changes: 1 addition & 1 deletion tests/python/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def test_block():
_test_block_gc()


@test_utils.test(require=ti.extension.sparse)
@test_utils.test(require=ti.extension.sparse, exclude=ti.metal)
def test_dynamic_gc():
x = ti.field(dtype=ti.i32)

Expand Down
2 changes: 1 addition & 1 deletion tests/python/test_no_activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from tests import test_utils


@test_utils.test(require=ti.extension.sparse)
@test_utils.test(require=ti.extension.sparse, exclude=ti.metal)
def test_no_activate():
x = ti.field(ti.f32)

Expand Down

0 comments on commit 16244cc

Please sign in to comment.