CANN pyasc 多核全核同步接口 asc.language.basic.sync_all 详解:硬同步与软同步的使用与约束
【免费下载链接】pyasc本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc
导读
asc.language.basic.sync_all是 CANN pyasc 为昇腾 AI 处理器提供的多核全核同步接口,用于解决不同 AI Core(计算核)之间访问同一块全局内存时可能出现的读后写(WAR)、写后读(RAW)、写后写(WAW)等数据依赖问题。本文以该接口的官方文档为核心,结合 block_sync.py、OpBlockSync.td 等仓库源码与 MLIR 测试用例,系统讲解硬同步与软同步的机制差异、完整参数语义、内存申请约束以及纯 Vector / Cube-Vector 融合等场景下的正确用法。读完本文,你将能够在 pyasc 算子内核中正确选择并插入全核同步语句,避免多核并发访问全局内存引发的数据读写错误。
一、接口概述:为什么需要全核同步
在昇腾 AI 处理器的多核并行执行模型中,算子会被切分到多个 AI Core 上并行执行。当不同核操作同一块全局内存且存在数据依赖时,各核的执行进度无法保证一致,就可能出现:
- 读后写(WAR):一个核还未完成读取,另一个核已经写入,读取到被覆盖的数据;
- 写后读(RAW):一个核需要读取的数据,另一个核尚未写入完成;
- 写后写(WAW):多个核同时写入同一地址,最终结果不可预期。
asc.language.basic.sync_all的作用正是在内核中插入同步语句,强制相关核在同步点对齐,从而避免上述数据读写错误。该接口属于 basic/block_sync.py 模块中的核同步(Block Sync)能力族,与set_flag/wait_flag、ib_set/ib_wait、cross_core_set_flag/cross_core_wait_flag等接口并列,可通过asc.sync_all直接调用(在 python/asc/language/basic/init.py 中导出)。
二、两种同步机制:硬同步与软同步
目前多核同步分为硬同步与软同步两类:
- 硬同步:利用硬件自带的全核同步指令,由硬件保证多核同步。无需额外工作空间,性能高。
- 软同步:使用软件算法模拟实现同步,需要全局与局部工作空间保存各核状态标记,性能相对较低。
对应到 pyasc 的 IR 层,两者分别是两个独立的算子定义(见 OpBlockSync.td):
def SyncAllSoftOp : APIOp<"sync_all_soft", "SyncAll", [AscFunc]> { let arguments = (ins AscendC_GlobalTensor:$gmWorkspace, AscendC_LocalTensor:$ubWorkspace, AnyType:$usedCores, UnitAttr:$isAIVOnly ); let paramTypeLists = [0, 0, 0]; } def SyncAllHardOp : APIOp<"sync_all_hard", "SyncAll", [AscFunc]> { let arguments = (ins UnitAttr:$isAIVOnly); let paramTypeLists = []; let assemblyFormat = "attr-dict"; }从源码结构可以推断:软同步算子的操作数中必须携带gmWorkspace、ubWorkspace、usedCores三个参数,而硬同步算子不需要任何工作空间操作数,仅通过属性isAIVOnly区分同步范围。这也与文档中“硬同步利用硬件自带指令、软同步需要缓存保存状态标记”的描述一致。
2.1 Python 侧的接口分发逻辑
Python 前端通过重载与可选参数实现两种同步方式的统一入口。核心实现位于 block_sync.py:
@overload def sync_all(is_aiv_only: bool = True) -> None: ... @overload def sync_all(gm_workspace: GlobalTensor, ub_workspace: LocalTensor, used_cores: int = 0, is_aiv_only: bool = True) -> None: ... @require_jit @set_common_docstring(api_name="sync_all") def sync_all(gm_workspace: Optional[GlobalTensor] = None, ub_workspace: Optional[LocalTensor] = None, used_cores: RuntimeInt = 0, is_aiv_only: bool = True) -> None: builder = global_builder.get_ir_builder() if gm_workspace is None or ub_workspace is None: if is_aiv_only: builder.create_asc_SyncAllHardOp() else: builder.create_asc_SyncAllHardOp(is_aiv_only=True) else: used_cores_ir = _mat(used_cores).to_ir() if is_aiv_only: builder.create_asc_SyncAllSoftOp(gm_workspace.to_ir(), ub_workspace.to_ir(), used_cores_ir) else: builder.create_asc_SyncAllSoftOp(gm_workspace.to_ir(), ub_workspace.to_ir(), used_cores_ir, is_aiv_only=True)关键点:
- 当不传
gm_workspace和ub_workspace时,走硬同步路径,创建asc_SyncAllHardOp; - 当传入了
gm_workspace与ub_workspace时,走软同步路径,创建asc_SyncAllSoftOp; - 无论哪种路径,
is_aiv_only都会作为属性传入 IR 构建器,控制同步作用范围。
三、函数原型与参数说明
3.1 Python 接口原型
# 硬同步(不传工作空间) asc.language.basic.sync_all(is_aiv_only: bool = True) -> None # 软同步(传入工作空间与参与核数) asc.language.basic.sync_all(gm_workspace: GlobalTensor, ub_workspace: LocalTensor, used_cores: int = 0, is_aiv_only: bool = True) -> None3.2 对应的 Ascend C 函数原型
文档中给出的 C++ 侧对应原型如下,软同步版本与硬同步版本分别对应上述两个 Python 重载:
// 软同步 template <bool isAIVOnly = true> __aicore__ inline void SyncAll( const GlobalTensor<int32_t>& gmWorkspace, const LocalTensor<int32_t>& ubWorkspace, const int32_t usedCores = 0) // 硬同步 template <bool isAIVOnly = true> __aicore__ inline void SyncAll()3.3 参数语义详解
| 参数 | 类型 | 说明 |
|---|---|---|
gmWorkspace | GlobalTensor | 用户定义的全局(Global)空间,作为所有核共用的缓存,用于保存每个核的状态标记。支持的数据类型为int32_t。 |
ubWorkspace | LocalTensor | 用户定义的局部(Local)空间,每个核单独自用,用于标记当前核的状态。支持的TPosition为VECIN/VECCALC/VECOUT,支持的数据类型为int32_t。 |
usedCores | int | 指定多少个核之间同步,传入数值不能超过算子调用时指定的逻辑blockNum。此为默认参数,不传时表示全核软同步(默认值 0)。 |
isAIVOnly | bool | 控制SyncAll作用于纯 Vector 算子还是融合(Cube 和 Vector 融合)算子。可选值见下。 |
isAIVOnly参数取值说明:
- true(默认值):纯 Vector 算子的全核同步,仅执行 Vector 核的全核同步;
- false:融合算子的全核同步,先分别完成 Vector 核和 Cube 核的全核同步,再执行两者之间的同步(软同步接口不支持此功能,即
false仅适用于硬同步场景)。
四、约束说明与内存申请要求
使用该接口前必须满足以下约束,否则可能出现数据竞争甚至内核“卡死”:
gmWorkspace 空间大小:缓存申请的空间大小要求大于等于
核数 × 32Bytes,并且缓存的值需要初始化为 0。常见的两种初始化方式:- 在host 侧进行初始化操作,确保传入接口时
gmWorkspace缓存已经初始化为 0; - 在kernel 侧初始化时对
gmWorkspace缓存进行初始化,需要注意:每个核上都需要初始化全部的gmWorkspace缓存空间。
- 在host 侧进行初始化操作,确保传入接口时
ubWorkspace 空间大小:申请的空间大小要求大于等于
核数 × 32Bytes。逻辑 blockNum 限制:使用该接口进行多核控制时,算子调用时指定的逻辑
blockNum必须不大于实际运行该算子的 AI 处理器核数,否则框架进行多轮调度时会插入异常同步,导致 Kernel “卡死”现象。分离模式下的使用建议:在分离模式下,建议使用硬同步接口而非软同步接口。软同步接口仅适用于纯 Vector 场景,且性能较低。使用硬同步接口时,需根据场景设置 Kernel 类型:
- 纯 Vector / Cube 场景:设置 Kernel 类型为
KERNEL_TYPE_MIX_AIV_1_0或KERNEL_TYPE_MIX_AIC_1_0; - Vector 和 Cube 混合场景:根据实际情况灵活配置 Kernel 类型。
- 纯 Vector / Cube 场景:设置 Kernel 类型为
五、调用示例与源码佐证
5.1 软同步调用示例
软同步需要先创建全局与局部工作空间,再调用sync_all:
gm = asc.GlobalTensor() gm.set_global_buffer(x) ub = asc.LocalTensor(dtype=asc.int32, pos=asc.TPosition.VECIN, addr=0, tile_size=32) asc.sync_all(gm, ub, used_cores=0)其中used_cores=0表示全核软同步。该写法与单元测试 test_common_api.py 中的test_sync_all_soft完全一致:
def test_sync_all_soft(mock_launcher_run): @asc.jit def kernel_sync_all_soft(x: asc.GlobalAddress) -> None: gm = asc.GlobalTensor() gm.set_global_buffer(x) ub = asc.LocalTensor(dtype=asc.int32, pos=asc.TPosition.VECIN, addr=0, tile_size=32) asc.sync_all(gm, ub, used_cores=0) x = MockTensor(asc.int32) kernel_sync_all_soft1 assert mock_launcher_run.call_count == 1可以看出:软同步接口通常与asc.jit装饰器配合,在内核函数体内使用,GlobalTensor通过set_global_buffer绑定全局内存,LocalTensor显式指定dtype=asc.int32、TPosition.VECIN位置与 32 字节的tile_size。
5.2 硬同步调用示例
硬同步不需要任何工作空间,直接调用即可:
asc.sync_all()单元测试 test_sync_all_hard 验证了这一用法:
def test_sync_all_hard(mock_launcher_run): @asc.jit def kernel_sync_all_hard() -> None: asc.sync_all() kernel_sync_all_hard[1]() assert mock_launcher_run.call_count == 1注意硬同步接口的内核函数无需任何参数,asc.sync_all()被直接插入内核体。
5.3 MLIR 层面的发射验证
在 test/Target/AscendC/basic/common.mlir 中可以看到两种同步方式从 MLIR 算子到 Ascend C 代码的完整发射路径:
// 软同步:sync_all_soft 携带 gm/ub/cores 三个操作数与 isAIVOnly 属性 func.func @emit_sync_all_soft( %gm: !ascendc.global_tensor<*xui8>, %ub: !ascendc.local_tensor<*xui8>, %cores: i32 ) { ascendc.sync_all_soft %gm, %ub, %cores {isAIVOnly} : !ascendc.global_tensor<*xui8>, !ascendc.local_tensor<*xui8>, i32 return } // CHECK: AscendC::SyncAll(v1, v2, v3); // 硬同步:sync_all_hard 无操作数,仅携带 isAIVOnly 属性 func.func @emit_sync_all_hard() { ascendc.sync_all_hard {isAIVOnly} return } // CHECK: AscendC::SyncAll();这一测试同时印证了文档中的两条事实:软同步发射为携带gmWorkspace、ubWorkspace、usedCores三个参数的AscendC::SyncAll(v1, v2, v3)调用;硬同步发射为无参的AscendC::SyncAll()调用;且isAIVOnly属性在两种同步方式中都会传递到最终代码。
六、使用场景与选型建议
综合文档与源码,可将使用场景归纳如下:
| 场景 | 推荐同步方式 | 说明 |
|---|---|---|
| 纯 Vector 算子,多核访问同一全局内存 | 软同步或硬同步 | 软同步仅适用于纯 Vector 场景,但性能较低;硬同步性能更优 |
| 分离模式 / 融合算子(Cube + Vector) | 硬同步(isAIVOnly=False) | 软同步接口不支持 Vector/Cube 核间同步;硬同步需配合设置 Kernel 类型(如KERNEL_TYPE_MIX_AIV_1_0或KERNEL_TYPE_MIX_AIC_1_0) |
| 指定部分核同步 | 软同步(设置used_cores) | 传入的核数不能超过算子调用时的逻辑blockNum |
七、总结
asc.language.basic.sync_all是 pyasc 多核编程中处理全局内存数据依赖的关键同步原语:
- 硬同步(
asc.sync_all())依赖硬件全核同步指令,无需工作空间,性能高,是分离模式与融合算子的首选; - 软同步(
asc.sync_all(gm, ub, used_cores=0))通过gmWorkspace(全局共享状态缓存)与ubWorkspace(每核局部状态标记)以软件算法模拟同步,适合纯 Vector 场景,且支持通过used_cores限定参与同步的核数; - 使用前务必满足内存约束(两个工作空间均不小于
核数 × 32Bytes、gmWorkspace初始化为 0)与blockNum不超过实际核数的限制,否则可能导致数据竞争或内核“卡死”。
建议读者结合 block_sync.py 源码、OpBlockSync.td 算子定义以及 test_common_api.py 与 common.mlir 测试用例,进一步理解同步接口从 Python 到 IR 再到 Ascend C 代码的完整实现链路。
【免费下载链接】pyasc本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考