跳转至

语言语法

普通 Python 语法写在 kernel 里是什么意思:下标、运算符,以及来自外层作用域的名字。

前置类型

Concept

装饰器解析你的源码,从不把它当 Python 执行。所以 kernel 体里那些眼熟的语法是被改写成 IR 操作,而不是被执行:

  • 下标不是一次索引操作 —— 它会变成 slice、read 或 assemble。
  • 运算符不是 Python 的 + —— 它会根据操作数类型变成某个 IR 算子。
  • 来自外层作用域的名字由解析器在解析期解析,而不是在调用时由闭包捕获。

实际后果是:这些构造要么在解析期带着行号报错,要么根本编不过,而不会在运行期给你惊喜。

Quickstart:同一个切片的两种写法

import pypto.language as pl

@pl.jit.incore
def head(x: pl.Tensor[[128, 64], pl.FP32],
         out: pl.Out[pl.Tensor[[16, 64], pl.FP32]]):
    top = x[0:16, :]              # sugar for pl.slice(x, [16, 64], [0, 0])
    out[:] = pl.mul(top, 2.0)        # sugar-free: an explicit operator call
    return out

两行都会被解析成 IR 调用。第一行展示了改写本身,第二行就是改写在别处所产生的东西。

Mechanics

下标语法糖

解析器会改写 TensorTile 值上的下标:

写法 变成
A[0:16, :] pl.slice(A, [16, N], [0, 0])
A[i, j] pl.tensor.read(A, [i, j]) / pl.tile.read(A, [i, j])
A[0:16, 0:32] pl.slice(A, [16, 32], [0, 0])
dst[i:i+16, j:j+32] = src dst = pl.assemble(dst, src, [i, j])

写形式会重绑 dst,这与严格 SSA 不兼容。在 @pl.function(strict_ssa=True) 下 —— 或任何 SSA 之后的上下文 —— 请显式调用 pl.assemble(...)

Python 运算符

标准运算符在 TensorTileScalar 值上映射到 IR 操作:

Python 操作
a + b / a - b / a * b / a / b add / sub / mul / div
a == b / a != b eq / ne
a < b / a > b lt / gt

任一侧是标量都会被识别并派发到标量操作数形式(pl.add(a, 1.0)adds)。

闭包捕获

装饰器解析的是源码,所以来自外层 Python 作用域的名字是被解析器解析的,而不是在调用时由闭包捕获的。整数常量和常量算术会折叠进 IR。无法折叠的捕获值在解析期就是错误,而不是运行期的意外。

有一个后果值得知道:像 pl.system.available_cluster_count() 这样的表达式应当在调用点内联书写,而不要先绑定到一个名字上。绑定之后编译与降级都正确,但被 outline 的包装函数的 printed IR 会引用一个定义在调用方的变量,因而无法被重新解析。

边界情况

症状 可能原因 修复
dst[...] = src 被拒绝 strict_ssa=True 禁止该重绑 改用 pl.assemble(dst, src, [...])
本想切片,下标却给了一次读 A[i, j] 读单个元素,A[i:i+1, :] 才是切片 想要一块区域时用切片语法
printed IR 无法被重新解析 设备规模查询在使用前被绑定到了名字上 在使用处内联书写该调用
某个捕获值在解析期被拒绝 它无法折叠进 IR 把它作为参数传入,而不是捕获

配套示例

examples/utils/error_handling.py —— 裸重绑定的代价:函数体重绑定了 result 而不是写穿它, 先前的值被丢弃,codegen 把它报成结构性错误,因为改名后的局部变量根本没到达 Out 参数。

See Also