生产力(写图形学算法)、可移植性、面向对象的自动并行的、megakernel、Decouple 稀疏数据结构html
指定在什么地方上运行,cpu gpu前端
Taichi 是一种嵌入在 Python 中的领域特定语言(Domain-Specific Language, DSL )。为了使 Taichi 能像 Python 包同样易于使用,基于这个目标咱们作了大量的工程工做——使得每一个 Python 程序员可以以最低的学习成本编写 Taichi 程序。你甚至能够选择你最喜欢的 Python 包管理系统、Python IDE 以及其余 Python 包和 Taichi 一块儿结合使用。python
Taichi 既能在 CPU,也能在 GPU 上运行。你只需根据你的硬件平台初始化 Taichi:程序员
# 在 GPU 上运行,自动选择后端 ti.init(arch=ti.gpu) # 在 GPU 上运行, 使用 NVIDIA CUDA 后端 ti.init(arch=ti.cuda) # 在 GPU 上运行, 使用 OpenGL 后端 ti.init(arch=ti.opengl) # 在 GPU 上运行, 使用苹果 Metal 后端(仅对 OS X)有效 ti.init(arch=ti.metal) # 在 CPU 上运行 (默认) ti.init(arch=ti.cpu)
注解算法
不一样操做系统所支持的后端:编程
平台 | CPU | CUDA | OpenGL | Metal |
---|---|---|---|---|
Windows | 可用 | 可用 | 可用 | 不可用 |
Linux | 可用 | 可用 | 可用 | 不可用 |
Mac OS X | 可用 | 不可用 | 不可用 | 可用 |
(可用: 该系统上有最完整的支持;不可用: 因为平台限制,咱们没法实现该后端)后端
在参数 arch=ti.gpu
下,Taichi 将首先尝试在 CUDA 上运行。若是你的设备不支持 CUDA,那么 Taichi 将会转到 Metal 或 OpenGL。若是所在平台不支持 GPU 后端(CUDA、Metal 或 OpenGL),Taichi 将默认在 CPU 运行。数组
注解数据结构
当在 Windows 平台 或者 ARM 设备(如 NVIDIA Jetson)上使用 CUDA 后端时, Taichi 会默认分配 1 GB 显存用于张量存储。如需重载显存分配,你能够在初始化的时候经过 ti.init(arch=ti.cuda, device_memory_GB=3.4)
来分配 3.4
GB 显存,或者使用 ti.init(arch=ti.cuda, device_memory_fraction=0.3)
来分配全部可用显存的 30%
.app
在其余平台上, Taichi 将会使用它的自适应内存分配器来动态分配内存。
不一样的后端支持不一样的数据类型。具体须要查看文档
Taichi 支持常见的数据类型。每种类型都由一个字符表示,指明它的 类别 和 精度位数,例如 i32
和 f64
。
数据的 类别 能够是如下其中之一:
i
用于有符号整数,例如233,-666u
用于无符号整数,例如233,666f
用于浮点数,例如2.33, 1e-4数据的 精度位数 能够是如下其中之一:
8
16
32
64
它表示存储数据时使用了多少 位。位数越多,精度越高。
例如,下列是两种最经常使用的数据类型:
i32
表示一个32位有符号整数。f32
表示一个32位浮点数。目前,Taichi支持的基本类型有
ti.i8
ti.i16
ti.i32
ti.i64
ti.u8
ti.u16
ti.u32
ti.u64
ti.f32
ti.f64
注解
每种后端支持的类型分别有:
类型 | CPU/CUDA | OpenGL | Metal |
---|---|---|---|
i8 | OK | N/A | OK |
i16 | OK | N/A | OK |
i32 | OK | OK | OK |
i64 | OK | EXT | N/A |
u8 | OK | N/A | OK |
u16 | OK | N/A | OK |
u32 | OK | N/A | OK |
u64 | OK | N/A | N/A |
f32 | OK | OK | OK |
f64 | OK | OK | N/A |
(OK:已支持,EXT:须要扩展支持,N/A:目前不支持)
注解
布尔类型使用 ti.i32
表示。
不一样类型间的二元运算将会发生数据类型提高,提高遵循 C 语言下的转换规则,例如:
i32 + f32 = f32
(integer + float = float)i32 + i64 = i64
(less-bits + more-bits = more-bits)简单地说,在发生数据提高时会尝试选择更精确的数据类型来包含结果值。
默认状况下,全部的数值都具备32位精度。 例如,42
的类型为 ti.i32
, 3.14
的类型为 ti.f32
。
能够在 Taichi 初始化时,指定默认的整数和浮点精度( 分别经过 default_ip
和 default_fp
):
ti.init(default_fp=ti.f32) ti.init(default_fp=ti.f64) ti.init(default_ip=ti.i32) ti.init(default_ip=ti.i64)
另外须要注意的是,你能够在类型定义时使用 float
或 int
做为默认精度的别名,例如:
ti.init(default_ip=ti.i64, default_fp=ti.f32) x = ti.var(float, 5) y = ti.var(int, 5) # 至关于: x = ti.var(ti.f32, 5) y = ti.var(ti.i64, 5) def func(a: float) -> int: … # 至关于: def func(a: ti.f32) -> ti.i64: …
警告
变量的类型在它 初始化时决定。
当一个 低精度 变量被赋值给 高精度 变量时,它将被隐式提高为 高精度 类型,而且不会发出警告:
a = 1.7 a = 1 print(a) # 1.0
当一个 高精度 变量被赋值给 低精度 类型时,它会被隐式向下转换为 低精度 类型,此时 Taichi 会发出警告:
a = 1 a = 1.7 print(a) # 1
你可使用 ti.cast
在不一样类型之间显式地强制转换标量值:
a = 1.7 b = ti.cast(a, ti.i32) # 1 c = ti.cast(b, ti.f32) # 1.0
一样,可使用 int()
和 float()
将标量值转换为默认精度的浮点或整数类型:
a = 1.7 b = int(a) # 1 c = float(a) # 1.0
应用于向量/矩阵中的类型转换是逐元素的:
u = ti.Vector([2.3, 4.7]) v = int(u) # ti.Vector([2, 4]) # 若是你使用的是 ti.i32 做为默认整型精度, 那么这至关于: v = ti.cast(u, ti.i32) # ti.Vector([2, 4])
使用 ti.bit_cast
将一个值按位转换为另外一种数据类型。 基础位将在此转换中保留。 新类型的宽度必须与旧类型的宽度相同。 例如,不容许将 i32
转换成 f64
。 请谨慎使用此操做。
张量,多维向量,张量里的每个元素均可以是别的张量
在以上代码中,pixels = ti.var(dt=ti.f32, shape=(n * 2, n))
分配了一个叫作 pixels
的二维张量,大小是 (640, 320)
,数据类型是 ti.f32
(即,C语言中的 float
).
在Taichi中,张量是全局变量。张量分为稀疏张量和密集张量。张量的元素能够是标量,也能够是矩阵。
每一个全局变量都是个N维张量。
标量
被视为标量的0-D张量。老是使用索引访问张量
- 例如,若是
x
是标量3D张量,则x[i, j, k]
。- 即便访问0-D张量
x
,也应使用x[None] = 0
而不是x = 0
。 请 始终 使用索引访问张量中的条目。
张量元素所有会被初始化为0。
稀疏张量的元素最初是所有未激活的。
详情请见 Tensors of scalars 。
例如,这将建立一个具备四个 int32
做为元素的 稠密(dense) 张量:
x = ti.var(ti.i32, shape=4)
这将建立一个元素为 float32
类型的4x3 稠密 张量:
x = ti.var(ti.f32, shape=(4, 3))
若是 shape 是 ()
(空元组),则建立一个0-D张量(标量):
x = ti.var(ti.f32, shape=())
随后经过传递 None
做为索引来访问它:
x[None] = 2
若是形状参数 未提供 或指定为 None
,则其后用户必须在手动放置 (place) 它:
x = ti.var(ti.f32) ti.root.dense(ti.ij, (4, 3)).place(x) # 等价于: x = ti.var(ti.f32, shape=(4, 3))
在任何内核调用或变量访问以前,全部变量都必须被建立和放置完毕。例如:
x = ti.var(ti.f32) x[None] = 1 # 错误:x没有放置! ------------------------ x = ti.var(ti.f32, shape=()) @ti.kernel def func(): x[None] = 1 func() y = ti.var(ti.f32, shape=()) # 错误:内核调用后不能再建立新的变量! x = ti.var(ti.f32, shape=()) --------------------- x[None] = 1 y = ti.var(ti.f32, shape=()) # 错误:任一变量访问事后不能再建立新的变量!
假设你有一个名为 A
的 128 x 64
张量,每一个元素都包含一个 3 x 2
矩阵。 要分配 3 x 2
矩阵的 128 x 64
张量,请使用声明 A = ti.Matrix(3, 2, dt=ti.f32, shape=(128, 64))
。
i, j
的矩阵,请使用 mat = A[i, j]
。 mat
只是一个 3 x 2
矩阵mat[0, 1]
或者 A[i, j][0, 1]
。[]
:第一个用于张量索引,第二个用于矩阵索引。ti.Vector
实际上是 ti.Matrix
的别名。因为性能缘由,矩阵运算将被展开,所以咱们建议仅使用小型矩阵。 例如,2x1
, 3x3
, 4x4
矩阵还好,但 32x6
可能太大了。
警告
因为展开机制,在大型矩阵(例如 32x128
)上进行操做会致使很长的编译时间和较低的性能。
若是你的矩阵有个维度很大(好比 64
),最好定义一个大小为 64
的张量。好比,声明一个 ti.Matrix(64, 32, dt=ti.f32, shape=(3, 2))
是不合理的,能够试着用 ti.Matrix(3, 2, dt=ti.f32, shape=(64, 32))
代替——始终把大的维度放在张量里。
用来计算的函数,kernel的代码会Just in time的方式高性能编译,自动并行,静态类型,可微分的
计算发生在 Taichi 的 内核(kernel) 中。内核的参数必须显式指定类型。Taichi 内核与函数中所用的语法,看起来和 Python 的很像,然而 Taichi 的前端编译器会将其转换为 编译型,静态类型,有词法做用域,并行执行且可微分 的语言。
一句头
@ti.kernel @ti.func
ti.func (device func)能够被kernel(global func)调用,python能够调用kernel,kernel不能调用python
kernel不能调用kernel
func 是强行inline的不支持递归
一个内核能够有一个 标量 返回值。若是内核有一个返回值,那它必须有类型提示。这个返回值会自动转换到所提示的类型。例如,
@ti.kernel def add_xy(x: ti.f32, y: ti.f32) -> ti.i32: return x + y # 等价于: ti.cast(x + y, ti.i32) res = add_xy(2.3, 1.1) print(res) # 3,由于返回值类型是 ti.i32
Unlike functions, kernels do not support vectors or matrices as arguments:
@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 ...
Taichi 做用域与 Python 做用域:任何被 @ti.kernel
和 @ti.func
修饰的函数体都处于 Taichi 做用域中,这些代码会由 Taichi 编译器编译。而在 Taichi 做用域以外的就都是 Python 做用域了,它们是单纯的 Python 代码。
Everything outside Taichi-scopes (ti.func
and ti.kernel
) is simply Python code. In Python-scopes, you can access Taichi tensor elements using plain indexing syntax. For example, to access a single pixel of the rendered image in Python-scope, simply use:
import taichi as ti pixels = ti.var(ti.f32, (1024, 512)) pixels[42, 11] = 0.7 # store data into pixels print(pixels[42, 11]) # prints 0.7
Taichi 内核只有在 Python 做用域中才能调用,也就是说,咱们不支持嵌套内核。同时,虽然不一样函数能够嵌套调用,但 Taichi 暂不支持递归函数 。
Taichi 函数只有在 Taichi 做用域中才能调用。
当使用可微编程时,对内核数据结构有一些约定。参见 Differentiable programming (WIP) 中的 内核简化规则(Kernel Simplicity Rule) 。
请不要在可微编程中使用内核返回值,由于这种返回值并不会被自动微分追踪。取而代之,能够把结果存入全局变量(例如 loss[None]
)。
func函数的参数是以值传递的。
强行内联,不支持递归
目前不支持具备多个 return
语句的函数。请用 局部变量 暂存结果,以便最终只有一个 return
语句:
# 错误示范 - 两个返回语句 @ti.func def safe_sqrt(x): if x >= 0: return ti.sqrt(x) else: return 0.0 # 正确示范 - 一个返回语句 @ti.func def safe_sqrt(x): rst = 0.0 if x >= 0: rst = ti.sqrt(x) else: rst = 0.0 return rst
Taichi 支持的标量函数:
ti.sin
(x)
ti.cos
(x)
ti.asin
(x)
ti.acos
(x)
ti.atan2
(x, y)
ti.cast
(x, data_type)
ti.sqrt
(x)
ti.rsqrt
(x)
ti.floor
(x)
ti.ceil
(x)
ti.tan
(x)
ti.tanh
(x)
ti.exp
(x)
ti.log
(x)
ti.random
(data_type)
abs
(x)
int
(x)
float
(x)
max
(x, y)
min
(x, y)
pow
(x, y)
除法:Python 3 中 /
(浮点数除法)和 //
(整数除法)是区分开来的。例如,1.0 / 2.0 = 0.5
,1 / 2 = 0.5
,1 // 2 = 0
,4.2 // 2 = 2
。Taichi 也遵循了这个设计:
- true divisions on integral types will first cast their operands to the default float point type.自动转换成整型
- floor divisions on float-point types will first cast their operands to the default integer type.自动转换成浮点
为避免这样的隐式转换,你能够手动使用 ti.cast
将你的操做数转换为你须要的类型。参见 默认精度 获取数字类型的更多细节。
当这些标量函数被做用在 Matrices 或 向量 上时,它们会被逐个做用到全部元素,例如:
B = ti.Matrix([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) C = ti.Matrix([[3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]) A = ti.sin(B) # is equivalent to for i in ti.static(range(2)): for j in ti.static(range(3)): A[i, j] = ti.sin(B[i, j]) A = ti.pow(B, 2) # is equivalent to for i in ti.static(range(2)): for j in ti.static(range(3)): A[i, j] = ti.pow(B[i, j], 2) A = ti.pow(B, C) # is equivalent to for i in ti.static(range(2)): for j in ti.static(range(3)): A[i, j] = ti.pow(B[i, j], C[i, j]) A += 2 # is equivalent to for i in ti.static(range(2)): for j in ti.static(range(3)): A[i, j] += 2 A += B # is equivalent to for i in ti.static(range(2)): for j in ti.static(range(3)): A[i, j] += B[i, j]
小数组而言,用vector(表示一个列向量)或matrix(3x3 4x4),大数组用tensor(10x10)
element-wise product * and matrix product *,两种乘法不同
返回一个对象的方法,不是对矩阵自身的改变
矩阵和数组都要注意做用域,有全局和临时局部变量的
注意区分逐元素的乘法 *
和矩阵乘法 @
。
# Taichi 做用域 v0 = ti.Vector([1.0, 2.0, 3.0]) v1 = ti.Vector([4.0, 5.0, 6.0]) v2 = ti.Vector([7.0, 8.0, 9.0]) # 指定行中的数据 a = ti.Matrix.rows([v0, v1, v2]) # 指定列中的数据 a = ti.Matrix.cols([v0, v1, v2]) # 能够用列表代替参数中的向量 a = ti.Matrix.rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
transpose方法,转置
trace 方法,矩阵的迹(对角线元素和)
determinant ,矩阵的行列式
inverse 逆矩阵
ti.Vector.var
(n, dt, shape = None, offset = None)
参数:n – (标量) 向量中的份量数目dt – (数据类型) 份量的数据类型shape – (可选,标量或元组)张量的形状(其中的元素是向量), 请参阅 张量与矩阵offset – (可选,标量或元组)请参阅 Coordinate offsets例如, 这里咱们建立了一个5x4的张量,张量中的元素都是3维的向量:
# Python 做用域 a = ti.Vector.var(3, dt=ti.f32, shape=(5, 4))
注解
在 Python 做用域中, ti.var
声明 Tensors of scalars, 而 ti.Vector
声明了由向量构成的张量。
ti.Vector
([x, y, ...])
参数:x – (标量)向量的第一个份量y – (标量)向量的第二个份量例如, 咱们可使用 (2, 3, 4)建立一个三维向量:
# Taichi 做用域 a = ti.Vector([2, 3, 4])
norm方法,返回向量的长度
a.norm(eps)至关于 ti.sqrt(a.dot(a) + eps)
例如能够经过设置 eps = 1e-5
,对可微编程中零向量上的梯度值计算进行保护。
norm_sqr方法,长度的平方
dot方法,点乘
cross方法,叉乘,对2d的返回是标量,对3d的返回是3d向量
outer_product方法,返回张量积,以下
a = ti.Vector([1, 2]) b = ti.Vector([4, 5, 6]) c = ti.outer_product(a, b) # 注意: c[i, j] = a[i] * b[j] # c = [[1*4, 1*5, 1*6], [2*4, 2*5, 2*6]]
cast转换份量的数据类型
a.n 返回向量的维度
Range for loop 最外层的for loop会自动并行,range for loop能够嵌套
不会自动并行的状况
是最外层 做用域 的循环并行执行,而不是最外层的循环。
@ti.kernel def foo(): for i in range(10): # 并行 :-) … @ti.kernel def bar(k: ti.i32): if k > 42: for i in range(10): # 串行 :-( …
遍历稀疏张量的全部元素
结构 for 循环 在遍历(稀疏)张量元素的时候颇有用。例如在上述的代码中,for i, j in pixels
将遍历全部像素点坐标, 即 (0, 0), (0, 1), (0, 2), ... , (0, 319), (1, 0), ..., (639, 319)
。
结构 for 循环是 Taichi 稀疏计算(Sparse computation (WIP))的关键,它只会遍历稀疏张量中的活跃元素。对于稠密张量而言,全部元素都是活跃元素。
结构 for 循环只能使用在内核的最外层做用域。
是最外层 做用域 的循环并行执行,而不是最外层的循环。
@ti.kernel def foo(): for i in x: … @ti.kernel def bar(k: ti.i32): # 最外层做用域是 `if` 语句 if k > 42: for i in x: # 语法错误。结构 for 循环 只能用于最外层做用域 …
+= 自动的原子操做
approach 2会返回total[none]加以前的值
有时没有原子操做会有data race
并行修改全局变量时,请确保使用原子操做。 例如,合计 x
中的全部元素,
@ti.kernel def sum(): for i in x: # 方式 1: 正确 total[None] += x[i] # 方式 2: 正确 ti.atomic_add(total[None], x[i]) # 方式 3: 非原子操做于是会获得错误结果 total[None] = total[None] + x[i]
taichi scope的代码会通过taichi编译器在并行设备上运行
python scope能够按照python本来的运行方式运行,二者能够交互
运行阶段
debugmode
init的时候debug参数设置为true,会额外作不少检查
cpu的边界检查
这里的调试能够print调试,结果写到tensor里而后print出来
Taichi provides helper functions such as from_numpy
and to_numpy
for transfer data between Taichi tensors and NumPy arrays, So that you can also use your favorite Python packages (e.g. numpy
, pytorch
, matplotlib
) together with Taichi. e.g.:
import taichi as ti pixels = ti.var(ti.f32, (1024, 512)) import numpy as np arr = np.random.rand(1024, 512) pixels.from_numpy(arr) # load numpy data into taichi tensors import matplotlib.pyplot as plt arr = pixels.to_numpy() # store taichi data into numpy arrays plt.imshow(arr) plt.show() import matplotlib.cm as cm cmap = cm.get_cmap('magma') gui = ti.GUI('Color map') while gui.running: render_pixels() arr = pixels.to_numpy() gui.set_image(cmap(arr)) gui.show()