zoukankan      html  css  js  c++  java
  • Games201-Taichi语言


    taichi嵌入在python语言里面

    生产力(写图形学算法)、可移植性、面向对象的自动并行的、megakernel、Decouple 稀疏数据结构

    ti.init()

    指定在什么地方上运行,cpu gpu

    import taichi as ti

    Taichi 是一种嵌入在 Python 中的领域特定语言(Domain-Specific Language, DSL )。为了使 Taichi 能像 Python 包一样易于使用,基于这个目标我们做了大量的工程工作——使得每个 Python 程序员能够以最低的学习成本编写 Taichi 程序。你甚至可以选择你最喜欢的 Python 包管理系统、Python IDE 以及其他 Python 包和 Taichi 一起结合使用。

    可移植性

    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%.

    在其他平台上, 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()
    
  • 相关阅读:
    上下文管理器
    创建项目与介绍(2)
    虚拟环境的安装(1)
    爬虫-selenium(14-2)扩展
    爬虫10-1(协程)
    Python3笔记038
    Python3笔记037
    Python3笔记036
    Python3笔记035
    Python3笔记034
  • 原文地址:https://www.cnblogs.com/FlyingZiming/p/14055879.html
Copyright © 2011-2022 走看看