生產力(寫圖形學算法)、可移植性、面向對象的自動並行的、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()