尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

PyPTO-Pro 跨核同步设计指南:set_cross_core/wait_cross_core 的同步点、pipe 与 event_id 规划

发布时间:2026/9/19 16:56:00

资讯中心
01
ARTICLE

PyPTO-Pro 跨核同步设计指南:set_cross_core/wait_cross_core 的同步点、pipe 与 event_id 规划

PyPTO-Pro 跨核同步设计指南:set_cross_core/wait_cross_core 的同步点、pipe 与 event_id 规划
PyPTO-Pro 跨核同步设计指南set_cross_core/wait_cross_core 的同步点、pipe 与 event_id 规划【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym本篇技术指南以 PyPTO-Pro 融合算子的跨核cross-core同步设计为主题系统讲解在 Cube 执行域与 Vector 执行域AIC 与 AIV之间、以及跨 Block/subblock 场景下如何判断同步需求、规划共享 TileGroup 槽位、确定 set/wait 同步点与 pipe、分配event_id并完成双向同步协议与事件配对核查。读者学完后可直接参照本文方法为自己的融合算子设计可验证、可运行的跨核同步方案并结合仓库中 matmul_softmax、vec_cube_abs_sqrt_matmul、KDA chunk_h 与 Engram 前向 等真实算子核对同步结构。跨核同步是什么事件只传递信号不搬运数据在 PyPTO-Pro 中pl.system.set_cross_core与pl.system.wait_cross_core用于保证生产者和消费者之间的数据读写顺序。最常见的场景是同一个 AI Core block 内Cube 执行域与 Vector 执行域协同处理一组中间数据接口也提供跨 Block 或 subblock 的同步模式。关键语义必须牢记同步事件只传递可以继续执行的信号不负责搬运数据也不分配共享缓冲。数据仍由pl.load/pl.store/pl.move/pl.insert等搬运指令负责事件只是在这些搬运之间建立 happens-before 顺序。设计跨核同步时需要依次完成四件事判断是否需要 cross-core 同步确定中间数据存放在 GM workspace、MatL1还是 VecUB确定生产者和消费者放置 set/wait 并规划event_id记录方案并逐项核查配对关系。接口参数细节可查阅$PYPTO_DEVKIT_DIR/docs/pypto_pro/api/SIMD-API/synchronization/下的set_cross_core.md与wait_cross_core.md该环境变量指向 CANN 开发套件文档目录本文不再展开。从仓库的工程落地看跨核同步在 PyPTO-Pro 工作流中处于手动流水的核心位置。编排层约束文档 performance-constraints.md 明确区分了两条路径自动流水跨核 TileGroup 配置fwd_ids/bwd_ids由框架生成set_cross_core/wait_cross_coreevent_id∈[0, 16)需复核生成代码手动流水显式调用set_cross_core/wait_cross_coreevent_id∈[0, 16)auto_mutex不覆盖跨核依赖事件位置和 pipe 按实际数据操作设计。本文聚焦手动流水场景——这是设计者必须完全掌控的部分。判断是否需要 cross-core 同步消费者读取本次 Kernel 中由另一个执行域或另一个 Block/subblock 写入的数据时需要设计 cross-core 同步。可以按下面的顺序判断与核内同步的边界mutex_ids、mem_bar 与 unit_flag数据依赖没有跨 Cube/Vector 执行域也没有跨 Block/subblock 时按普通核内依赖处理此时不应引入 cross-core 事件带非空mutex_ids的 TileGroup 由auto_mutex管理跨 Pipe 依赖。例如 KDA 的 chunk_h_kda_impl.py 中每个 L1/L0/UB TileGroup 都显式声明了mutex_ids[0..21]auto_mutex自动处理 Section 内部的跨 Pipe 顺序未配置mutex_ids时按实际数据路径手工插入核内同步如pl.system.sync_src/sync_dstVF 函数中的局部内存读写顺序使用vf.mem_bar配置了matmul phase时M 与 FIX 之间由unit_flag配对。这些机制不生成 Cube 与 Vector 之间的事件mutex_id也不能替代 cross-core 使用的event_id。两者是两套独立的资源体系数值相同也不会自动建立联系。需要同步与不需要同步的情形需要 cross-core 同步Cube 写出中间结果后由 Vector 读取或者 Vector 准备数据后由 Cube 读取。典型如矩阵乘结果Acc/L0C经 GM workspace 交给 Vector 做 softmax或 Vector 先计算左操作数再交给 Cube 做 matmul不需要同步多个逻辑 Block 只写各自独立的输出区域时不存在跨核读写依赖跨 Block 共享状态应根据算法使用原子操作、分阶段 Kernel 或目标产品支持的INTER_BLOCK同步而不是简单地在两个 Block 之间插事件。缓冲复用时必须双向同步Cube 与 Vector 循环复用同一组缓冲时必须同时建立正向同步和反向同步正向同步READY保证消费者在数据写完后再读取反向同步RELEASE保证生产者在消费者读完后再覆盖该缓冲。只建立正向同步无法保护正在被消费的数据可能导致后续写入覆盖尚未读完的数据。事件的具体放置和配对规则见下文缓冲复用时的双向同步。手动同步set 在最后一次写之后wait 在第一次读之前当前跨核同步统一使用set_cross_core和wait_cross_core放置规则非常明确生产者在最后一个写操作之后set消费者在第一个读操作之前wait循环复用同一槽位时还要增加消费者到生产者的反向事件RELEASE。wait 可以先执行并阻塞直至配对的 set 到达但反过来set 之后对应的 wait 必须最终在某条执行路径上被执行否则会造成永久等待或遗留信号。明确共享 TileGroup 的槽位TileGroup 既可以通过next()、current()、previous()访问也可以用group[i]直接选择槽位。二者语义有重要差别group[i]不读取也不推进轮转游标i可以是运行时整数表达式但框架不会自动取模——设计时要明确写出槽位表达式并证明所有运行时取值都在[0, depth)内。手动同步复用同一个多槽缓冲时生产者、消费者以及 READY/RELEASE 事件必须指向同一个逻辑槽位。先定义槽位映射再让两侧按同一映射访问slot_idx task_idx % depth tile shared_group[slot_idx] READY READY_IDS[slot_idx] RELEASE RELEASE_IDS[slot_idx]这是一条手动同步的设计关系不表示mutex_id与event_id变成了同一种资源。group[slot_idx]选中的 Tile 仍可携带 mutex 元数据由auto_mutex处理各 Section 内部的跨 Pipe 依赖READY 和 RELEASE 仍由 cross-core 事件处理 Section 之间的顺序。继续使用next()也可以但设计文档必须证明生产者和消费者的访问次数、初始游标和分支路径会选中同一个物理槽位。存在以下情况时显式下标通常更容易核对预取prefetch尾块分支两侧循环次数不同同一轮同时访问多个槽位。下标访问不会改变游标与next()混用时两套状态要分别推导。这一要求在 design-template.md 的 §6核间同步模板中也被固化为必填字段每个共享 TileGroup 要记录depth、访问方式current()/next()/group[i]、槽位表达式如task_idx % 2以及槽位一致性依据。根据数据路径确定同步点pipe 表示硬件流水pipe表示执行set或wait的硬件流水不表示 Section 名称。发送和等待两侧可以使用不同 pipe取值由同步点紧邻的数据操作决定——这是最常见的出错点之一。pipe必须与同步点相邻的数据操作一致仓库中的典型对应关系如下数据路径pipe 配对仓库实例Cube 将 Acc 写入 GM workspaceVector 再从 GM 加载到 UBFIX → MTE2matmul_softmax_impl.pyCubeset_cross_core(pipeFIX, event_id0)Vectorwait_cross_core(pipeV, event_id0)Cube 将 Acc 搬到 Vec 后由 Vector 计算FIX → VKDA chunk_h_kda_impl.pyCubeset_cross_core(pipeFIX, event_id0)Vectorwait_cross_core(pipeV, event_id0)Vector 通过 MTE3 写入 MatL1Cube 再搬入 L0MTE3 → MTE1vec_cube_abs_sqrt_matmul_impl.pyVectorset_cross_core(pipeMTE3, event_id2)Cubewait_cross_core(pipeMTE1, event_id2)具体接口规定其他 pipe 时以该接口文档为准。示例Cube 整体写完 workspace 后通知 Vector单向以下片段展示 Cube 整体写完 workspace 后通知 Vector 的单向事件位置Tile 和 Tensor 声明已省略with pl.section_cube(): ... pl.store(workspace, acc_tile, [m_off, n_off]) pl.system.set_cross_core( pipepl.PipeType.FIX, event_id0, sync_modepl.CrossCoreSyncMode.INTRA_BLOCK, ) with pl.section_vector(): pl.system.wait_cross_core( pipepl.PipeType.MTE2, event_id0, sync_modepl.CrossCoreSyncMode.INTRA_BLOCK, ) pl.load(vec_tile, workspace, [m_off, n_off]) ...仓库中 matmul_softmax_impl.py 就是这一模式的完整实现Cube 侧完成 matmul 后pl.move(mm1_res, tile_c1, acc_to_vec_modepl.AccToVecMode.DualModeSplitM)随后set_cross_core(pipepl.PipeType.FIX, event_id0)Vector 侧每个 subblock 先wait_cross_core(pipepl.PipeType.V, event_id0)再对 mm1_res 做 row_max/exp/row_sum 等 softmax 运算。注意该算子在 Cube 内部还用了多组sync_src/sync_dst如MTE2→MTE1、MTE1→M、M→FIX这些是核内同步与跨核事件职责不同。上例是一条单向依赖。如果 Cube 在循环内逐槽位生产而 Vector 逐槽位消费事件也必须按相同粒度建立把唯一的set放在生产循环之后只能形成Cube 全部结束后 Vector 整体开始的阶段屏障不能实现逐 Tile 流水——这是性能优化的关键取舍。反向示例Vector 准备数据后交给 CubeVector → Cube 方向vec_cube_abs_sqrt_matmul_impl.py 展示了相反的握手方向Vector 在 UB 中计算sqrt(|x|)转 NZ 后pl.insert(v1_mat, tile_nz, [off, 0])写入 L1Mat随后set_cross_core(pipepl.PipeType.MTE3, event_id2)Cube 侧wait_cross_core(pipepl.PipeType.MTE1, event_id2, sync_modepl.CrossCoreSyncMode.INTRA_BLOCK)后再pl.move(v1_left, v1_mat)将 L1 数据搬入 L0A 作为 matmul 左操作数。该样例头部注释明确记录了cross-core event 2 is signalled by vector MTE3 and awaited by cube MTE1并在 64×64 FP32 用例上以maxdiff3.815e-06容差 1e-3通过 Gate A 验证。sync_mode 的四种取值发送和等待两侧的sync_mode必须一致。按参与者拓扑选择sync_mode适用场景INTRA_BLOCKAIC 与两个 AIV 子核协同AIC 向 Vector 发送的事件供两个 AIV 子核分别等待AIC 等待 Vector 事件时需要等待两个 AIV 子核都完成INTER_SUBBLOCK两个 AIV 子核之间同步INTER_BLOCK跨物理 block 同步必须同时满足目标产品与算法的适用条件UNICAST_BLOCK只与一个 AIV 子核同步必须同时满足目标产品与算法的适用条件INTER_BLOCK和UNICAST_BLOCK属于受限模式使用时需确认目标产品支持且算法语义匹配。规划 event_id当前接口的event_id范围为[0, 16)。两条硬性规则一对 set 和 wait 必须使用相同的 ID 与sync_mode一个 set 发送后在对应 wait 消费该信号之前event_id不得分配给可能产生错配的另一条事件。event_id与 TileGroup 的mutex_id是两套独立机制应分别规划二者数值相同不会自动建立联系也不能相互替代。单向同步只有 READY一次性 workspace 或每轮使用独立地址时只需建立生产者到消费者的 READY 事件。生产者在完成写操作后发送 READY消费者在读取数据前等待同一个 READY。wait 可以先执行并阻塞直至配对的 set 到达。时间向下 Cube/FIX生产者 Vector/MTE2消费者 写workspace set READY0 --------------------- wait READY0 读workspaceREADY 保证 Vector 不会在 Cube 写完之前读取 workspace。每轮使用不同地址时Cube 不再覆盖 Vector 正在读取的数据因此不需要反向事件。缓冲复用时的双向同步READY RELEASE生产者和消费者循环复用同一组缓冲时还需要反方向的 RELEASE 事件READY表示本轮数据已经写好RELEASE表示上一轮数据已经读完这个槽位可以再次写入。下图以槽位 0 为例展示该槽位每次写入、读取和释放的顺序时间自上而下协议要点循环开始前Vector 发送初始 RELEASECube 等待该事件后获得槽位 0 的首次写入权限每次使用该槽位时Cube 写完后发送 READY步骤①→ Vector 等待 READY步骤②→ Vector 读取数据步骤③完成后发送 RELEASE→ Cube 在下次覆盖前等待 RELEASE步骤④槽位 0仍需复用时步骤④位于下一次写入之前槽位 0不再复用时步骤④位于 Cube 侧结束之前用于确认最后一次读取已经完成ping-pong 缓冲的槽位 1 采用相同过程并使用另一组event_id。循环前的 RELEASE 为生产者提供首次写入权限循环中的 RELEASE 保护下一次复用。也可以不预发初始 RELEASE此时生产者第一次写入不等待从第二次使用该槽位开始等待。两种方案的要点是初始化、循环主体和循环结束必须采用同一套配对方式不能中途更换。事件表按共享缓冲 槽位访问 数据方向记录事件。以下示例把每个物理槽位直接写开如果源码使用动态下标表中还应补充统一的slot_idx表达式事件组共享缓冲及访问方向槽位event_idset位置/pipewait位置/pipe复用条件QK_READYqk_group[0]Cube→Vector00FIX写槽位0后V读槽位0前对应wait已消费QK_READYqk_group[1]Cube→Vector11FIX写槽位1后V读槽位1前对应wait已消费QK_RELEASEqk_group[0]Vector→Cube02V读槽位0后FIX覆盖槽位0前对应wait已消费QK_RELEASEqk_group[1]Vector→Cube13V读槽位1后FIX覆盖槽位1前对应wait已消费前一个信号由配对 wait 消费后event_id 才可以复用前一个信号尚未消费时新的逻辑通道必须使用其他 ID。动态 event 表达式的所有运行时取值都必须位于[0, 16)。源码实例Engram 前向算子的 READY/ACK 双事件仓库中的 engram_forward_impl.py 是双向同步协议的完整落地。它定义了K_READY_IDS (0, 1)与ACK_IDS (2, 3)两组事件 ID按 head 奇偶h % 2轮转Cube 侧section_cube每个 M-tile 的 head 完成pl.store(key_back, ac, ...)落 GM 后set_cross_core(pipepl.PipeType.FIX, event_idK_READY_IDS[h % 2], sync_modeINTRA_BLOCK)通知 Vector 消费而在消费同槽上一轮数据之前先wait_cross_core(pipepl.PipeType.FIX, event_idACK_IDS[h % 2], sync_modeINTRA_BLOCK)等待 Vector 的背压信号Vector 侧section_vector先wait_cross_core(pipepl.PipeType.MTE2, event_idK_READY_IDS[h % 2])等 Cube 写完消费完毕后set_cross_core(pipepl.PipeType.MTE3, event_idACK_IDS[h % 2])回发 ACK。其中K_READY对应正向 READYACK对应反向 RELEASE正好构成同一逻辑槽位上的双向事件 奇偶轮转的两组 ID。源码注释还点出了一个冷启动细节mi core_id and h 2时本核第一轮的前两个 head不执行 ACK wait否则 core 1 的 head 0 会等待一个永远不来的 ACK 导致死锁——这正是文档初始化、循环主体必须采用同一套配对方式且各分支路径都必须能配对的工程实证。源码实例KDA chunk_h 的多事件流水chunk_h_kda_impl.py 展示了一个循环内多个 Phase 交替、多个event_id并行工作的复杂场景event_id3Vector 侧store(ws_w_f16, ...)后set_cross_core(pipeMTE3, event_id3)Cube 侧wait_cross_core(pipeMTE2, event_id3)后加载w_l1/s_l1做WS W Sevent_id0Cube 完成pl.move(cur_ub_ws, cur_ws_acc, acc_to_vec_mode...)后set_cross_core(pipeFIX, event_id0)Vector 侧wait_cross_core(pipeV, event_id0)后执行ws kv - wsevent_id1Vector 计算 v_corr 完成后set_cross_core(pipeMTE3, event_id1)Cube 侧wait_cross_core(pipeMTE2, event_id1)后加载k_l1/v_l1做KV k_rest^T v_correvent_id2Cube 完成 KV 搬入 UB 后set_cross_core(pipeFIX, event_id2)Vectorwait_cross_core(pipeV, event_id2)后做S exp(g_total)*S KV。四个事件分别对应四条独立的数据通道w/s 准备、WS 结果、v_corr 结果、KV 结果各自在最后一次写之后 set、第一次读之前 wait互不复用 ID形成了 Cube/Vector 在单个 chunk 循环内的多阶段软件流水。这验证了事件按数据方向与槽位逐一对应指令总数相等也不能代替这项检查的设计原则。记录并验证同步方案六项核查同步方案应记录共享数据对象、set/wait 位置、pipe、sync_mode、event_id 和事件复用条件。共享对象是多槽 TileGroup 时还要记录缓冲深度以及生产者、消费者的槽位访问表达式。design-template.md 的 §6 提供了落地的表格框架cross_core 涉及判定、共享数据与槽位映射表、同步点表事件组/共享缓冲/方向/set 位置与 pipe/wait 位置与 pipe/sync_mode/event_id/复用条件、event_id 分配表。完成设计后按以下顺序检查检查槽位映射。对同一逻辑任务确认生产者和消费者最终选中同一个物理槽位READY/RELEASE 也按该槽位选 ID。动态下标的所有取值必须位于[0, depth)使用next()时要把初始游标、调用次数和分支影响写清楚。检查事件位置。对照 READY/RELEASE 配对图中的①④确认READY 的 set 位于最后一个生产操作之后READY 的 wait 位于第一个消费操作之前RELEASE 的 set 位于最后一个消费操作之后RELEASE 的 wait 位于下一次覆盖之前。需要确认最后一次消费完成时对应的 RELEASE wait 位于 Cube 侧结束之前——Section 开头的 wait 只能消费预发信号或上一轮信号不能代替末尾等待。检查配对关系。每个 wait 都应明确对应哪个方向、哪个槽位和哪一轮的 set。set 可以晚于 wait 到达但在所有执行路径上都必须最终执行整个等待关系不能形成环。检查执行次数。设某槽位使用 n 次预发初始 RELEASE 时初始授权和 n 次消费完成通知共执行n1次 set首次写前、n-1次复用前和末尾确认共执行n1次 wait不预发初始 RELEASE 时RELEASE 方向执行 n 次 set 和 n 次 wait——第一次写不等待后续n-1次写前等待末尾再等待最后一次消费完成两种方案的 READY 方向都是 n 次 set 和 n 次 wait。检查边界分支。零次、一次、整除和尾块迭代中的每个 wait 都必须能够获得配对信号。任一侧因条件分支少执行一次都可能造成永久等待或遗留信号零次使用的槽位还要检查预发信号是否会影响后续 event_id 复用。检查参与者。Vector 侧有两个 subblock 时应记录各自访问的地址范围。使用INTRA_BLOCK时Cube 等待两个 AIV 子核都完成使用UNICAST_BLOCK时只等待参与该事件的 AIV 子核两个 AIV 子核之间的屏障使用INTER_SUBBLOCK。上述计数按每个槽位的源码执行次数统计。使用INTRA_BLOCK时还要分别核对两个 AIV 子核的实际执行情况——例如 Engram 中 Vector 侧按sub_idx切分vm_start段偶数段归 subblock 0、奇数段归 subblock 1两侧的 wait/set 必须与这种切分保持一致。官方指定算子示例核对完整同步协议下面这些示例来自 PyPTO Pro 工作流维护的官方指定算子清单路径均相对于$PYPTO_DEVKIT_DIR。实际设计时以PRO_MATERIAL_INDEX.md§B 中存在的文件为准并优先选择与目标算子缓冲数量、数据方向和循环拓扑相近的示例。示例适合核对的同步结构pro_ops/lightning_indexer/test_quant_lightning_indexer_vf.pyCube 与 Vector 之间的双向 READY/RELEASE、ping-pong 槽位和 Cube 侧末尾等待pro_ops/fa/test_fa_tilingkey_attn_mask.pyQK、P、PV 多组跨 Section 数据依赖以及按槽位分配正反向事件pro_ops/fa/test_fa_perf_tkv_preload_dn_vf_bufid_dynrank.py动态循环中的 Cube/Vector 协作、预发反向事件、双缓冲复用和最后一批消费确认pro_ops/fa/test_fa_with_mask.pymask 分支下的多组事件、双缓冲与三缓冲并存以及不同 pipe 上的 set/wait 位置这些示例用于核对完整同步协议不是event_id、pipe 或缓冲深度的固定模板。设计时必须按本算子的生产者、消费者、槽位和循环次数重新推导事件表单个接口的pipe与sync_mode约束仍以当前 API 文档为准。设计流程总结回到 PyPTO-Pro 迭代式方案设计工作流见 SKILL.md 的 R6 轮次跨核同步设计遵循一条可复用的流水线在 R0 模块划分阶段确认是否存在 Cube↔Vector 或跨 Block/subblock 的数据依赖没有则 §6 填不涉及 cross_core不能仅凭 Section 数量判定在 R4 循环与 Section 结构阶段确定自动流水还是手动流水手动流水时在 R6 按本文方法先确定共享数据与槽位映射 → 再确定每个事件的 set/wait 位置与 pipe → 分配event_id→ 填写同步点表和 event_id 分配表完成设计后逐槽位、逐轮次核对 set/wait 一一对应检查动态下标落在[0, depth)、各边界分支事件可配对、生产者结束前等待最后一次消费完成。跨核同步的本质是把数据就绪和缓冲可复用两个事实以事件的形式在正确的硬件流水上、以正确的时序传递给正确的参与者。把握住事件只传信号、不搬数据以及READY 保护读、RELEASE 保护覆盖、配对必须按槽位和轮次逐一对应这三条主线就能为任何融合算子设计出正确且可验证的同步方案。【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。