- 人工智能
- 编译器
- 模型编译
- 高性能计算
- 深度学习
- CANN
【免费下载链接】pypto
PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。
导读
pl.struct是 PyPTO(Parallel Tensor/Tile Operation)语言前端pypto_pro.language提供的编译期具名结构体创建接口,用于在 SIMD 内核中把批次号、块号、地址偏移等少量元数据按字段名组织起来,并支持标量字段赋值与一维数组字段按索引读写。本文基于 struct.md 官方文档,结合仓库前端实现与 NPU 测试用例,完整讲解函数原型、参数约束、底层编译机制与实战写法,帮助你写出可编译、可上板验证的 struct 内核代码。
产品支持情况
根据 struct.md 的标注,pl.struct的产品支持范围如下:
| 产品形态 | 支持情况 |
|---|---|
| Ascend 950PR / Ascend 950DT | 支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 不支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 不支持 |
仓库中的 ST 测试用例也通过@pytest.mark.soc("950")标记为仅在 950 系列上运行(见 test_struct.py),与文档声明一致。编写内核前请先确认目标设备的支持情况。
功能说明
pl.struct用于创建字段布局在编译期确定的具名结构体变量。字段声明顺序与关键字参数传入顺序一致。典型用途包括:
- 组织批次号(batch id)、块号(block id);
- 携带地址偏移(offset)等少量元数据;
- 在循环或 if/else 控制流中作为累加器、快照、记录器等状态载体。
与pl.make_tuple(见 make_tuple.md)不同,pl.struct会真正生成一个 C++ 结构体,可用于跨 Pipeline 传递数据(例如通过 SSBUF 通信),而pl.make_tuple仅在编译期把多个 IR 变量按字段名聚合、不产生运行时开销。若仅需在同一 Pipeline 内聚合变量,优先使用pl.make_tuple。
函数原型
pypto_pro.language.struct( type_name: str, **fields, ) -> Struct参数说明
| 参数 | 输入/输出 | 说明 |
|---|---|---|
| type_name | 输入 | 结构体类型名。 - 名称必须是字符串常量,不能是变量。 - 名称只能包含字母、数字和下划线,不能以数字开头,且不能是 C++ 关键字。 |
| fields | 输入 | 结构体成员变量名称和初始值(关键字参数)。 - 至少包含一个成员变量,成员变量名称不可重复。 - 成员变量名称只能包含字母、数字和下划线、不能以数字开头,且不能是 C++ 关键字。 - 成员变量仅支持如下类型: -标量:初始值支持整数、浮点数、布尔值或 Scalar 表达式。 -一维标量数组:一维非空标量数组,数组长度和元素类型必须在编译期确定,元素应为同一数据类型。不支持将嵌套的具名结构体(通过 make_tuple、struct 创建)作为成员变量。 - 标量成员变量的值可通过 arr.field = value修改,数组成员变量可通过arr.field[index]读写,仅支持相同类型的赋值操作。 |
为什么对命名如此严格:名称会被原样写入 C++
从源码看,结构体类型名与字段名会被原样拼接到生成的目标代码中(形如class Name { ... }与s.field)。仓库在 _struct_parser.py 中维护了一份完整的 C++ 关键字表(int、true、delete、struct、template等),凡是命中关键字的类型名或字段名都会在解析阶段直接抛错:
InvalidOperation: ... is a C++ keyword and cannot be used as a struct type name or field name hint: Rename it, e.g. 'int_'对应的 UT 用例覆盖了类型名、字段名为 C++ 关键字的各种报错场景(见 test_struct_field_validation.py)。因此,即使名称在 Python 中是合法标识符,也必须避开 C++ 关键字,否则生成代码无法通过编译。
字段值的类型校验规则(编译期强制执行)
_struct_parser.py 中的_check_scalar_or_array_field会对每个字段值做编译期校验:
- 非数组字段值必须是标量(
ScalarType),传入 Tensor/Tile 等非标量值会抛InvalidType; - 数组字段字面量必须非空,空列表(如
[])抛InvalidVal; - 数组元素必须全部是标量,且元素数据类型必须一致,混用
int与float(如[1, 2.5])会抛InvalidVal(mixed element types); - 多维列表会被视为"包含非标量元素"而拒绝;
- 通过
pl.make_tuple/pl.struct创建的嵌套具名结构体作为字段值会抛NotSupported(nested named tuple/struct)。
这些约束均被 test_struct_field_validation.py 中的 UT 用例逐一覆盖,你可以在写内核前用这些规则快速自检。
约束说明
- 对结构体进行赋值时,必须保证等式两边的结构体类型名、成员变量的名称、顺序、标量类型和数组长度完全相同。例如,对同一个变量在 if/else 分支中分别进行 struct 赋值,这两个 struct 必须满足上述条件。
- 在控制流(if/else/for/while)中,如果用值拷贝的方式创建 struct,得到的是独立副本,修改它不会影响到原始 struct。
- 用下标访问数组成员时,需要确保
0 <= index < size(越界访问未定义)。
返回值说明
返回一个具名 struct 变量:通过s.field访问/修改标量字段,通过s.field[index]读写数组成员。
底层实现与编译流程
运行时形态:SimpleNamespace
在 Python 运行时(JIT 之外),language/__init__.py 中的struct函数把关键字参数打包进SimpleNamespace返回;真正被编译的是内核函数体内的pl.struct(...)调用——解析器通过 AST 识别调用形式,将其降级为 IR 层的struct.createExpression op,并登记结构体名与字段名:
struct.create(elements, {"name": struct_name, "fields": field_names})解析期的降级路径
StructParserMixin 完整实现了pl.struct的解析:
- 校验第一个位置参数必须是字符串常量(不允许变量),否则抛
InvalidVal; - 校验结构体名不是 C++ 关键字;
- 要求至少一个关键字字段;
- 逐字段做标识符与值类型校验;
- 调用
_make_struct_create生成struct.create调用并注册结构体类型。
在控制流(if/else/for/while)内对 struct 进行赋值时,解析器会跟踪字段名,保证分支内外的结构体布局一致,从机制上落实文档的约束说明。
与 struct_array / make_tuple 的分工
仓库中与pl.struct同族的还有两个接口(同目录文档见 struct_array.md 与 make_tuple.md):
pl.struct_array(size, "Name", field=val, ...):创建 N 个相同 struct 组成的数组,按arr[i].field/arr[i].field[j]存取。解析期会把 N 个struct.create槽位包进一个MakeTuple(见_parse_struct_array_expr),并要求size为正整数常量。pl.make_tuple(**kwargs):编译期命名元组,字段访问被常量折叠回原值,不生成 C++ 结构体;仅用于同一 Pipeline 内聚合变量。
选择建议:需要跨 Pipeline 传数据用pl.struct,仅需函数内聚合用pl.make_tuple,需要多个同构结构体按索引存取用pl.struct_array。
调用示例
示例 1:循环读写数组字段(官方示例)
import pypto_pro.language as pl @pl.jit() def struct_field_kernel(out: pl.Tensor[[5], pl.DT_INT32]): # 创建带数组字段的结构体 s = pl.struct("RunInfo", batch_id=0, offsets=[0, 0, 0, 0]) with pl.section_vector(): # 数组字段元素赋值(s.arr_field[idx] = val) s.offsets[0] = 10 s.offsets[1] = 20 s.offsets[2] = 30 s.offsets[3] = 40 # 数组字段元素读取(s.arr_field[idx]) total = 0 for i in pl.range(0, 4): total = total + s.offsets[i] pl.setval(out, 0, s.offsets[0]) pl.setval(out, 1, s.offsets[3]) pl.setval(out, 2, total) pl.setval(out, 3, s.batch_id) pl.setval(out, 4, s.offsets[1] + s.offsets[2])要点说明:
- 类型名
"RunInfo"与字段名batch_id、offsets均满足"字母数字下划线、非数字开头、非 C++ 关键字"的约束; - 数组字段
offsets以列表字面量初始化,长度为 4,元素全部为整型常量,符合编译期定长、同类型的校验要求; - 数组元素的赋值与读取都发生在
pl.section_vector()向量节内,与 SIMD 向量化执行模型一致; pl.setval用于把标量写入输出张量指定位置。
示例 2:基础赋值与字段求和(仓库 NPU 测试)
来自 test_struct.py 的最简用例,展示标量字段的赋值与读取:
@pl.jit() def struct_basic_kernel(out: pl.Tensor[[1], pl.DT_INT32]): ctx = pl.struct("Ctx1", val=0, base=0) with pl.section_vector(): ctx.val = 100 ctx.base = 200 pl.setval(out, 0, ctx.val + ctx.base)该用例在 950 设备上验证out[0] == 300(@pytest.mark.soc("950")),可直接作为在真实 NPU 上运行pl.struct的最小验证模板。
示例 3:for 循环字段累加与 if/else 分支(仓库 NPU 测试)
同一测试文件中的累积与条件分流写法:
@pl.jit() def struct_for_accum_kernel(out: pl.Tensor[[1], pl.DT_INT32]): acc = pl.struct("Accum", total=0) with pl.section_vector(): for i in pl.range(1, 6): acc.total = acc.total + i pl.setval(out, 0, acc.total) # 期望 15 @pl.jit() def struct_conditional_kernel(out: pl.Tensor[[2], pl.DT_INT32]): br = pl.struct("Branch", cnt=0, part=0) with pl.section_vector(): for i in pl.range(0, 6): br.cnt = br.cnt + 1 if i < 3: br.part = br.part + i pl.setval(out, 0, br.cnt) # 期望 6 pl.setval(out, 1, br.part) # 期望 3这两个用例分别验证了"循环内字段自增"与"if/else 分支内按条件修改字段"两种典型模式,说明 struct 在控制流中作为可变状态载体是安全的——前提是各分支中的 struct 布局保持一致。
示例 4:struct 别名与多变量交叉赋值(仓库 NPU 测试)
测试文件还覆盖了变量别名与多 struct 嵌套循环交叉赋值等进阶场景:
@pl.jit() def struct_alias_for_kernel(out: pl.Tensor[[2], pl.DT_INT32]): s = pl.struct("AliasA", val=0, acc=0) t = s # 别名:t 与 s 指向同一结构体 with pl.section_vector(): for i in pl.range(0, 5): t.val = i t.acc = t.acc + i pl.setval(out, 0, s.val) # 期望 4(通过别名修改,原变量可见) pl.setval(out, 1, s.acc) # 期望 10注意区分:t = s是别名传递(修改对原变量可见),而文档约束说明中"在控制流内用值拷贝方式创建 struct 得到独立副本"指的是重新调用pl.struct(...)创建新实例的情况。测试文件后续的struct_multi_cross_nested_kernel还用 4 个 struct 在双层 for + if/else 中做交叉赋值,验证了复杂场景下的字段独立性,需要更完整示例可阅读 test_struct.py。
常见错误与排查建议
结合解析期校验(test_struct_field_validation.py)中的报错矩阵,常见错误可按下表快速定位:
| 错误类型 | 触发写法 | 建议 |
|---|---|---|
| C++ 关键字 | 类型名或字段名用int、true、delete等 | 改名,例如加后缀int_ |
| 嵌套具名结构体 | 字段值传入pl.make_tuple(...)或pl.struct(...)的结果 | 改用标量或一维标量数组 |
| 空数组字段 | offsets=[] | 提供至少一个初始值,如[0, 0, 0] |
| 混合元素类型 | [1, 2.5] | 统一元素类型,如[1, 2]或[1.0, 2.0] |
| 多维/非标量元素 | [[1, 2], [3, 4]]或 Tensor 值 | 只用一维标量数组或标量 |
| 类型名非字符串常量 | pl.struct(name_var, ...) | 第一个参数必须是字符串字面量 |
总结
pl.struct为 PyPTO SIMD 内核提供了一种类型安全、布局编译期确定的元数据组织方式:字段名与类型名被校验后原样进入生成的 C++ 结构体,字段值限定为标量与定长一维标量数组,并完整支持在 for/if-else 控制流中作为状态载体使用。需要跨 Pipeline(如 SSBUF)传递数据时选择pl.struct,仅做同 Pipeline 内变量聚合时优先pl.make_tuple,同构多实例场景使用pl.struct_array。官方文档与仓库测试用例(struct.md、test_struct.py、_struct_parser.py)可进一步作为编写与验证参考。
- 人工智能
- 编译器
- 模型编译
- 高性能计算
- 深度学习
- CANN
【免费下载链接】pypto
PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。
相关推荐
Agentic 测试三步跑通:这个 MCP 网关 Monorepo 的质量关怎么过
Agentic 测试三步跑通:这个 MCP 网关 Monorepo 的质量关怎么过 Agentic 把任意 API 变成可收费的 MCP 工具,整个仓库用 pn
人工智能编译器模型编译高性能计算深度学习CANNPippo监控与度量:集成Dropwizard Metrics实现应用监控的终极指南
Pippo监控与度量:集成Dropwizard Metrics实现应用监控的终极指南 在当今微服务架构盛行的时代, 应用监控 已成为保证系统稳定性的关键环节。P
后端开发工具PyPTO `pl.const` 详解:在昇腾并行编程中创建指定数据类型的编译期常量标量
PyPTO pl.const 详解:在昇腾并行编程中创建指定数据类型的编译期常量标量 导读 pypto_pro.language.const 是 CANN Py
人工智能编译器模型编译高性能计算深度学习CANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考