Games201-Taichi語言


taichi嵌入在python語言裏面

生產力(寫圖形學算法)、可移植性、面向對象的自動並行的、megakernel、Decouple 稀疏數據結構html

ti.init()

指定在什麼地方上運行,cpu gpu前端

import taichi as ti

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 將會使用它的自適應內存分配器來動態分配內存。

data

不一樣的後端支持不一樣的數據類型。具體須要查看文檔

類型系統

Taichi 支持常見的數據類型。每種類型都由一個字符表示,指明它的 類別精度位數,例如 i32f64

數據的 類別 能夠是如下其中之一:

  • i 用於有符號整數,例如233,-666
  • u 用於無符號整數,例如233,666
  • f 用於浮點數,例如2.33, 1e-4

數據的 精度位數 能夠是如下其中之一:

  • 8
  • 16
  • 32
  • 64

它表示存儲數據時使用了多少 。位數越多,精度越高。

例如,下列是兩種最經常使用的數據類型:

  • i32 表示一個32位有符號整數。
  • f32 表示一個32位浮點數。

支持的類型

目前,Taichi支持的基本類型有

  • int8 ti.i8
  • int16 ti.i16
  • int32 ti.i32
  • int64 ti.i64
  • uint8 ti.u8
  • uint16 ti.u16
  • uint32 ti.u32
  • uint64 ti.u64
  • float32 ti.f32
  • float64 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.i323.14 的類型爲 ti.f32

能夠在 Taichi 初始化時,指定默認的整數和浮點精度( 分別經過 default_ipdefault_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)

另外須要注意的是,你能夠在類型定義時使用 floatint 做爲默認精度的別名,例如:

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。 請謹慎使用此操做。

tensor

張量,多維向量,張量裏的每個元素均可以是別的張量

在以上代碼中,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=())
# 錯誤:任一變量訪問事後不能再建立新的變量!

張量元素是矩陣

假設你有一個名爲 A128 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 矩陣
  • 要獲取第1行第2列的矩陣元素,請用: mat[0, 1] 或者 A[i, j][0, 1]
  • 你可能已經注意到,當你從全局矩陣張量加載矩陣元素時,會有 兩個 索引運算符 []:第一個用於張量索引,第二個用於矩陣索引。
  • ti.Vector 實際上是 ti.Matrix 的別名。
  • 有關矩陣的更多信息,請參見 Matrices

因爲性能緣由,矩陣運算將被展開,所以咱們建議僅使用小型矩陣。 例如,2x13x34x4 矩陣還好,但 32x6 可能太大了。

警告

因爲展開機制,在大型矩陣(例如 32x128 )上進行操做會致使很長的編譯時間和較低的性能。

若是你的矩陣有個維度很大(好比 64),最好定義一個大小爲 64 的張量。好比,聲明一個 ti.Matrix(64, 32, dt=ti.f32, shape=(3, 2)) 是不合理的,能夠試着用 ti.Matrix(3, 2, dt=ti.f32, shape=(64, 32)) 代替——始終把大的維度放在張量裏。

kernels

用來計算的函數,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 代碼。

Python-scope data access

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.51 / 2 = 0.51 // 2 = 04.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]

matrix

小數組而言,用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 逆矩陣

vector

全局張量中的向量
  • 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

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): # 串行 :-(
            …

struct for loop 稀疏計算

遍歷稀疏張量的全部元素

結構 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]

Scope

taichi scope的代碼會通過taichi編譯器在並行設備上運行

python scope能夠按照python本來的運行方式運行,二者能夠交互

使用步驟

運行階段

debug

debugmode

init的時候debug參數設置爲true,會額外作不少檢查

cpu的邊界檢查

這裏的調試能夠print調試,結果寫到tensor裏而後print出來

編譯快一點的優化關閉

與機器學習結合

Sharing data with other packages

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()
相關文章
相關標籤/搜索