- 人工智能
- 编译器
- 模型编译
- 高性能计算
- 深度学习
- CANN
【免费下载链接】pypto
PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。
PyPTO(Parallel Tensor/Tile Operation 编程范式)在pypto_pro.language.simt命名空间下提供了完整的 SIMT 原子操作族(atomic_add、atomic_exch、atomic_cas、atomic_inc等)。其中atomic_cas(Compare-And-Swap,比较并交换)是最基础也最通用的同步原语:它以原子方式比较目标元素与期望值,相等时写入新值,并返回比较前的旧值,是自旋锁、单次初始化、唯一 Winner 选举等并发算法的核心构件。本文基于 atomic_cas API 文档,结合仓库内 IR 构建、CCE 后端代码生成与端到端测试源码,完整讲解该接口的调用约束、数据类型规则、返回值语义,并给出可直接运行的 Kernel 示例与源码级实现路径。
功能语义:一次不可分割的"读-比较-写"
atomic_cas对目的操作数target执行如下不可分割(原子)的步骤序列:
- 读取
target的旧值; - 将旧值与
compare(期望值)比较; - 若二者相等,将
value写入target;否则target保持不变; - 返回比较发生前的
target旧值。
整个过程对 SIMT 线程网格中的其他线程完全不可见中间状态,因此并发线程同时调用atomic_cas时,只会有一个线程在某个时刻"赢得"写入权,其余线程要么看到旧值已被改写而放弃,要么在后续轮次中重新尝试。仓库中的 API 声明(python/pypto_pro/language/_simt_api.py)将其概括为:"Atomically compare and exchange one Tile or Tensor element, returning its old value."
函数原型
pypto_pro.language.simt.atomic_cas( target: Scalar, compare: Scalar, value: Scalar, ) -> Scalar参数说明
| 参数 | 输入/输出 | 说明 |
|---|---|---|
| target | 输入 | 目的操作数,Scalar 类型。必须直接传入 Tile 或 Tensor 的单元素下标访问表达式,例如ub_tile[0, 0]或gm_tensor[0, 0]。- UB Tile:必须位于 UB,使用 ND 格式,支持 DT_INT32、DT_UINT32、DT_FP32。 - GM Tensor:必须为 ND 格式,支持 DT_INT32、DT_UINT32、DT_FP32、DT_INT64、DT_UINT64。 |
| compare | 输入 | 源操作数,Scalar 类型,表示期望值。数据类型必须与 target 一致;数值字面量按 target 的数据类型处理,整数目的操作数不接受浮点字面量。 |
| value | 输入 | 源操作数,Scalar 类型,表示比较相等时写入的新值。数据类型必须与 target 一致;数值字面量按 target 的数据类型处理,整数目的操作数不接受浮点字面量。 |
两点关键细节需要特别强调:
- target 必须"直接"写成下标访问表达式。
atomic_cas的底层实现需要同时拿到"容器引用(Tile/Tensor)"和"元素偏移量"两个信息,因此不能先取出元素再传入,例如tmp = tile[0, 0]; atomic_cas(tmp, ...)这类间接写法是不符合接口约定的。这一约束在 IR 层有明确体现:atomic_cas构建函数接收的是container(容器)与offset(偏移)两个独立参数(见下文源码分析)。 - 字面量按 target 数据类型解释。
compare/value传数值字面量时不需要显式 cast,框架按target的数据类型解释该字面量;但对于整数类型的目标元素,传入浮点字面量(如0.0)是不被接受的,应保持类型一致。
数据类型与产品支持矩阵
atomic_cas对不同存储位置的数据类型支持并不完全相同,使用前请对照下表:
| 存储位置 | 格式要求 | 支持的数据类型 |
|---|---|---|
| UB Tile | 必须位于 UB、ND 格式 | DT_INT32、DT_UINT32、DT_FP32 |
| GM Tensor | 必须为 ND 格式 | DT_INT32、DT_UINT32、DT_FP32、DT_INT64、DT_UINT64 |
产品支持情况(以当前仓库文档为准):
- Ascend 950PR / Ascend 950DT:支持
- Atlas A3 训练系列产品 / Atlas A3 推理系列产品:不支持
- Atlas A2 训练系列产品 / Atlas A2 推理系列产品:不支持
仓库端到端测试(python/tests/st/pypto_pro/frontend/simt/atomic/test_atomic_cas.py)在启动前会检查设备名是否包含Ascend950(_require_a5函数),不满足则跳过测试,与文档声明的产品支持范围一致。
调用约束:SIMT 上下文
atomic_cas只能在由@pypto_pro.language.vector_function(mode="simt")定义的SIMT 入口函数或SIMT 辅助函数中调用,不能在标量/向量上下文或其他编程范式下直接使用。这是因为该操作是 SIMT 线程级并发语义的一部分:只有在一个由多线程并行执行的 SIMT 函数中,"多个线程竞争同一元素"的场景才有意义。
一个典型的 SIMT Kernel 需要两层结构:
- 外层
@pl.jit()函数负责声明 Tensor 参数、创建 UB Tile(如需)、编排pl.section_vector()等流水区域,并以simt_func线程数的语法启动 SIMT 函数; - 内层
@pl.vector_function(mode="simt", max_threads=...)函数内部编写逐线程执行的代码,atomic_cas就写在这里。
返回值说明
atomic_cas返回比较发生前的target值,返回值类型与target一致。因此可以通过返回值判断本次交换是否成功:
- 返回值
== compare:说明比较时旧值恰好等于期望值,本次写入成功,该线程"赢得"竞争; - 返回值
!= compare:说明旧值已被其他线程改写,本次未写入,调用者应决定是重试还是放弃。
这一"返回值即旧值"的语义与 CUDA 的atomicCAS保持一致,也使得atomic_cas可以天然地用来实现"只允许一个线程成功"的选举逻辑。
调用示例:多线程竞争下的唯一 Winner
以下示例来自 API 文档(与仓库端到端测试中的atomic_cas_winner_gm完全一致):32 个线程并发对state[0, 0]执行atomic_cas(state[0, 0], 0, 1),将old_values[0, tid]记录各自观察到的旧值。
import pypto_pro.language as pl @pl.vector_function(mode="simt", max_threads=32) def atomic_cas_winner_gm( state: pl.Tensor[[1, 1], pl.DT_INT32], old_values: pl.Tensor[[1, 32], pl.DT_INT32], ): tid = pl.simt.linear_thread_idx() old_values[0, tid] = pl.simt.atomic_cas(state[0, 0], 0, 1) @pl.jit() def simt_atomic_cas_winner_gm( state: pl.Tensor[[1, 1], pl.DT_INT32], old_values: pl.Tensor[[1, 32], pl.DT_INT32], ): with pl.section_vector(): atomic_cas_winner_gm32运行逻辑推演:初始state[0, 0] == 0。32 个线程同时执行 CAS,最终只有一个线程能看到旧值0(等于compare),它把state写为1并成功;其余 31 个线程看到的旧值都是1(不等于compare),写入失败但state保持不变。因此最终state[0, 0] == 1,且old_values中恰好有一个0、其余为1——这正是"唯一 Winner 选举"的验证方式。
示例中两个值得注意的写法:
pl.simt.linear_thread_idx()获取当前线程的一维线性编号,用于区分每个线程写入old_values的列位置;- 外层
pl.section_vector()将 SIMT 启动放入向量流水区域,atomic_cas_winner_gm32的方括号语法表示以 32 线程启动该 SIMT 函数。
源码级实现路径:从 Python API 到 CCE 指令
atomic_cas的完整调用链在仓库中可分为三层,理解这条路径有助于排查问题和二次开发:
1. Python DSL 层:API 声明
pypto_pro.language.simt.atomic_cas的声明位于 python/pypto_pro/language/_simt_api.py,通过@staticmethod+@_api_decl装饰器暴露给 DSL 前端。与atomic_add、atomic_exch、atomic_max等同族操作并列,构成完整的 SIMT 原子操作集合。
2. IR 构建层:容器 + 偏移的拆分
前端解析器将atomic_cas(tile[0, 0], compare, value)这种下标表达式拆解为"容器 + 偏移 + 操作数"三部分。IR 构建函数位于 python/pypto_pro/ir/op/simt_ops.py:
def atomic_cas(container, offset, compare, value, span=None): """Build an atomic compare-and-swap on one SIMT element.""" return _create_atomic_call("atomic_cas", container, offset, compare, value, span=span)_create_atomic_call最终生成 IR 调用simt.atomic_cas(simt_ops.py),操作数顺序为[container, offset, compare, value]。AST 解析器通过@op_impl("simt.atomic_cas")注册(simt_ops.py),将源码中的调用语法映射到该 IR 节点——这也印证了文档"必须直接传下标表达式"的要求来自 IR 层的容器/偏移二元表示。
3. 后端代码生成层:映射到 atomicCAS
CCE 后端在 framework/src/interface/pypto_pro/backend/backend_cce_simt_ops.cpp 中把simt.atomic_cas映射为 CCE 内置函数:
if (op_name == "simt.atomic_cas") { return {"atomicCAS", 2}; }并在同文件末尾注册该操作,指定其运行在S(标量)流水(backend_cce_simt_ops.cpp):
REGISTER_BACKEND_OP(BackendCCE, "simt.atomic_cas").set_pipe(ir::PipeType::S).f_codegen(MakeSimtAtomicCodegenCCE);即atomic_cas最终以标量原子指令的形式下发给 AICore,这也是其返回"单个旧值"而非批量结果的直接原因。
端到端测试验证:语义正确性的四重保障
仓库为atomic_cas提供了完整的 ST 测试(python/tests/st/pypto_pro/frontend/simt/atomic/test_atomic_cas.py),覆盖四个关键维度:
UB Tile 全 dtype 测试(
test_atomic_cas_ub_all_supported_dtypes):对 DT_INT32、DT_UINT32、DT_FP32 三种 UB Tile 元素执行atomic_cas(..., 0, 7),断言最终值均为 7。注意 UB 用例需要先用pl.load把数据从 GM 搬入 UB Tile,并配合pl.system.sync_src/sync_dst做流水同步,最后pl.store写回。GM Tensor 全 dtype 测试(
test_atomic_cas_gm_all_supported_dtypes):覆盖 GM 侧全部五种 dtype(INT32/UINT32/FP32/INT64/UINT64),同样断言写 7 成功。返回值与旁路元素保持测试(
test_atomic_cas_returns_old_value_and_preserves_other_elements):将state[0, 0]预置为 10,调用atomic_cas(state[0, 0], 10, 13),断言返回旧值 10 被写入old_values[0, 0]、目标元素变为 13,且同 Tensor 其他元素保持原值 123 不变——完整验证了"返回比较前旧值 + 仅修改目标元素"的语义。竞争唯一 Winner 测试(
test_atomic_cas_contention_has_exactly_one_winner):32 线程竞争,断言最终state == 1、old_values中恰好一个元素为 0、其余均为 1,从并发层面验证了原子性与"单次成功"保证。
此外,UT 层还有语法解析测试(python/tests/ut/pypto_pro/language/parser/test_simt_atomic.py)和 CCE 代码生成测试(python/tests/ut/pypto_pro/codegen/test_cce_simt.py),共同守护从语法解析到指令发射的全链路正确性。
使用建议与注意事项
- 用返回值判断成败,不要依赖状态再检查:CAS 的成功判定应使用返回值与
compare的比较,避免额外的读操作引入新的竞争窗口。 - 整数目标不要传浮点字面量:如
atomic_cas(state[0, 0], 0.0, 1.0)对 INT32 目标不合法,应写为0、1。 - UB 与 GM 的数据类型能力不同:UB Tile 仅支持 32 位三种类型;需要 64 位原子 CAS 时必须走 GM Tensor。
- 仅在 SIMT 函数内使用:
atomic_cas依赖 SIMT 线程级并发,脱离mode="simt"的上下文调用不会被接受。 - 配合流水同步使用:当
atomic_cas作用于 UB Tile 时,写回 GM 前需通过pl.system.sync_*确保原子操作完成后才发起pl.store(参考 UB 测试用例的同步写法)。 - 从源码阅读入口:接口语义见 python/pypto_pro/language/_simt_api.py,IR 构建见 python/pypto_pro/ir/op/simt_ops.py,CCE 发射见 framework/src/interface/pypto_pro/backend/backend_cce_simt_ops.cpp。
综上,atomic_cas为 PyPTO 的 SIMT 编程范式提供了底层、原子、可判定成败的同步原语,适用于跨线程计数、状态机迁移、唯一初始化等需要"比较-写回"一体的并发场景;结合文档、IR 源码与端到端测试,开发者可以放心地将其作为自旋锁与选举算法的地基。
- 人工智能
- 编译器
- 模型编译
- 高性能计算
- 深度学习
- CANN
【免费下载链接】pypto
PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。
相关推荐
PyPTO Tensor.logical_not 逻辑非运算接口详解:从函数原型到 Ascend 算子实现
PyPTO Tensor.logical_not 逻辑非运算接口详解:从函数原型到 Ascend 算子实现 pypto.Tensor.logical_not 是
人工智能编译器模型编译高性能计算深度学习CANNPyPTO Pro vf.exp:SIMD 寄存器级指数运算接口详解(Ascend 950)
PyPTO Pro vf.exp:SIMD 寄存器级指数运算接口详解(Ascend 950) vf.exp 是 PyPTO Pro( pypto_pro )SI
人工智能编译器模型编译高性能计算深度学习CANNHeadlamp 前端时间格式化接口 TimeAgoOptions 详解:从 API 定义到源码实现
Headlamp 前端时间格式化接口 TimeAgoOptions 详解:从 API 定义到源码实现 TimeAgoOptions 是 Headlamp 前端工
云原生开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考