CANN SHMEM 算子性能优化模式:从瓶颈定位到通信、流水线与分核策略的实战指南
【免费下载链接】shmemCANN SHMEM 是面向昇腾平台的多机多卡内存通信库,基于OpenSHMEM 标准协议,实现跨设备的高效内存访问与数据同步。项目地址: https://gitcode.com/cann/shmem
本文是 .agents/skills/shmem-ops-performance-optim/references/optimization-patterns.md 的完整展开版。文中
examples/、include/等路径均以 CANN SHMEM 仓库根目录为起点;阅读前建议先通过 shmem-repo-resolution.md 定位仓库结构。
CANN SHMEM 是基于 OpenSHMEM 标准的多机多卡内存通信库,其算子的性能上限往往取决于 kernel 内数据搬运、同步与分核方式的设计。本文面向在昇腾 NPU 上编写或优化 SHMEM 通信算子的开发者,系统梳理了六类典型瓶颈及其对应的优化模式:通信重叠、流水线与 Double Buffer、同步替换、内存路径精简、分核策略重构以及优先级排序。读完本文,你将能够根据 prof 数据快速判断瓶颈类型,并在 examples/allgather、examples/kv_shuffle 等仓内示例中找到可复用的工程化写法。
优化总览:先定位瓶颈类型,再选择手段
优化的第一原则是每轮先定位瓶颈类型,再选择对应手段,而不是盲目套用技巧。根据瓶颈所处的位置,可把优化方向分为六类:
| 类别 | 典型瓶颈 | 对应章节 |
|---|---|---|
| 通信优化 | 数据必须先落 symmetric heap 再由远端读取,拷贝成为串行开销;大消息双拷贝导致带宽封顶 | §1 |
| 流水线优化 | 单 buffer 时 NBI 搬运与使用必须串行,搬运气泡明显 | §2 |
| 同步优化 | 全局 barrier 让所有 PE 等待最慢者;整块传完才 signal 导致消费者空等 | §3 |
| 内存优化 | 远端 get 落 GM、再经 DataCopy 进 UB 的多余读写 | §4 |
| 分核优化 | 单 AIV 内for peer in 0..n_pes串行搬运,无法利用多条链路并行 | §5 |
| 综合排序 | 多个优化点并存时收益与风险不同 | §6 |
下面逐类展开,每一类都给出 ❌ 错误做法、✅ 正确做法、关键点以及仓内源码佐证。
1. 通信优化
1.1 Copy-to-Symmetric-Heap Overlap
瓶颈:数据MUST先从本地 GM 拷贝到 symmetric heap,再由远端 PE 读取;在没有重叠设计时,拷贝时间成为串行开销。
优化:将 core 分为 sender 组和 receiver 组。sender 按 chunk 把数据拷贝到 symmetric heap,并逐 chunk 发 signal;receiver 轮询 signal,一旦某 chunk 就绪立即拉取。该模式源码见 examples/allgather/allgather_kernel.cpp 中的all_gather_originkernel。
❌ 错误做法——所有数据拷贝完 → 全局 barrier → 再开始通信:
copy_all_to_symmetric(input, gva_data, total_size); aclshmem_barrier_all(); fetch_all_from_peers(output, gva_data, total_size);✅ 正确做法——sender 逐 chunk 拷贝 + 逐 chunk signal,receiver 轮询就绪即拉取:
// Sender core:逐 chunk 拷贝 + 逐 chunk signal if (aivIndex < core_group_num) { while (remaining >= chunk_size) { aclshmemx_mte_put_nbi(gva_data + offset, input + offset, tmp_buff, ub_size, chunk_elems, my_rank, EVENT_ID0); SetFlag<HardEvent::MTE3_S>(EVENT_ID0); WaitFlag<HardEvent::MTE3_S>(EVENT_ID0); flag = ++times + magic; aclshmemx_signal_op(gva_sync + flag_offset, flag, SIGNAL_SET, my_rank); } } // Receiver core:轮询 signal,就绪即拉取 while (!all_done) { for (int g = 0; g < core_group_num; g++) { aclshmem_int32_get_nbi(flag_ub, gva_sync + g * INTERVAL, 1, peer); PipeBarrier<PIPE_ALL>(); int64_t ready = *flag_ub - magic; if (ready > fetched[g]) { // 立即拉取已就绪 chunk,使用 ping-pong buffer fetch_chunks(output, gva_data, fetched[g], ready, ping_pong_buf); } } }在 allgather_kernel.cpp 中可以看到该模式的关键实现参数:
int core_group_num = aivNum / 2;:sender 和 receiver 的 core 数量各占一半;aclshmemx_mte_put_nbi(..., my_rank, EVENT_ID0)搭配SetFlag/WaitFlag<HardEvent::MTE3_S>(EVENT_ID0)保证逐 chunk 拷贝完成;flag = times + magic; aclshmemx_signal_op(gva_sync_gm + flag_offset, flag, ACLSHMEM_SIGNAL_SET, my_rank);:signal 使用magic + chunk_count编码,receiver 通过ready_flag - magic解码得到已就绪 chunk 数;- receiver 侧用
aclshmem_int32_get_nbi轮询远端 flag,并用 ping/pong buffer(ping_buff/pong_buff,各约 95KB)交替接收。
关键点:
- sender 和 receiver 的 core 数量各占一半;
- signal 使用
magic + chunk_count编码,receiver 通过解码判断就绪程度,同时用(ready_flag >> shift) == (magic >> shift)校验轮次有效性; - 不需要全局 barrier,数据逐 chunk 流式可用。
1.2 AllToAllV 大消息 — 禁止双拷贝 staging
瓶颈:sendbuf → 本地 symm → put 远端 symm这条路径使有效带宽封顶约 50% 目标值——同样的数据被搬运了两次。
优化:远端直接mte_put(sendbuf + displ, remote_symm + put_off, pe = dst),只有dst == my_rank时才 staging 到本地symm_off,避免对每个 dst 都先拷贝到symm_off再拷贝到远端put_off的双倍流量。
同步:send 半核quiet后执行全体aclshmem_barrier_all(),再让 recv 半核发起 get。
❌ 错误:对每个 dst 先 Copy 到symm_off,再 Copysymm_off到远端put_off(双倍流量)。 ✅ 正确:远端一次 put;半核 send/recv 之间aclshmem_barrier_all()。
引擎选择:数据量、拓扑与推荐 Engine 的对应关系如下:
| 数据量 | 拓扑 | 推荐 Engine | 原因 |
|---|---|---|---|
| < 2MB | 任意 | MTE(aclshmemx_mte_put_nbi/aclshmemx_mte_get_nbi) | 延迟低,setup 开销小 |
| ≥ 2MB | 节点内 P2P 可达 | SDMA(aclshmemx_sdma_put_nbi/aclshmemx_sdma_get_nbi) | 带宽高,不占 MTE 通道 |
| ≥ 2MB | 跨节点 / P2P 不可达 | RDMA/RoCE(aclshmemx_roce_put_nbi/aclshmemx_roce_get_nbi) | 唯一可达路径 |
| 混合场景 | 节点内+跨节点 | MTE 节点内 + RDMA 跨节点 | 按拓扑分层选择 |
Engine 类型在 include/host_device/shmem_common_types.h 中定义:
enum data_op_engine_type_t { ACLSHMEM_DATA_OP_MTE = 0x01, ACLSHMEM_DATA_OP_SDMA = 0x02, ACLSHMEM_DATA_OP_ROCE = 0x04, ACLSHMEM_DATA_OP_UDMA = 0x08, ACLSHMEM_DATA_OP_MAX = 0x08, };MTE 接口的签名(见 include/device/gm2gm/engine/shmem_device_mte.h)为:
template <typename T> ACLSHMEM_DEVICE void aclshmemx_mte_put_nbi( __gm__ T* dst, __gm__ T* src, __ubuf__ T* buf, uint32_t ub_size, uint32_t elem_size, int pe, uint32_t sync_id);其中buf为本地 UB 临时缓冲、ub_size为 UB 缓冲字节数、elem_size为搬运元素个数、sync_id为流水线同步 ID;同文件还提供了non_contiguous_copy_param形式的非连续搬运重载。
注意:
- SDMA 需要预留至少 64B 的 UB buffer;
- RDMA 不支持同 PE 上的 RMA/AMO 并发乱序写;
option_attr.data_op_engine_type可设置默认引擎;- SDMA 路径 MUST 先保证 MTE 路径大档 PASS,再启用 SDMA;
- 验证建议用 uniform
base_count = 8388608的正确性用例 + perf-workflow.md 的带宽表。
1.3 Chunk Size 调优
| 规则 | 说明 |
|---|---|
| MTE 最优 chunk | 190KB(190 * 1024bytes),allgather example 经验值 |
| 最小搬运 | ≥ 16KB 才能接近峰值带宽 |
| 最大单次 | 受 UB 可用空间限制;double buffer 时为 UB/2 |
| 调优方向 | chunk 太小 → 增大减少 setup 开销;chunk 太大 → 缩小增加流水级数 |
仓库实现中该值被定义为编译期常量(见 examples/allgather/allgather_kernel.cpp):
constexpr int64_t UB_DMA_MAX_SIZE = 190 * 1024;receiver 侧 double buffer 时单 buffer 使用UB_DMA_MAX_SIZE / 2(约 95KB),保证搬运期间另一块 buffer 可被使用。
1.4 非连续搬运优化
| 场景 | 优化 |
|---|---|
| 固定 stride 访问 | 使用iput/iget或non_contiguous_copy_param(repeat + length + src_ld + dst_ld) |
| 多段小块 | 合并为连续 buffer 后一次搬运,减少 put/get 调用次数 |
| 分散 gather | 在本 PE 做 local pack → symmetric → 一次 put 到远端 |
non_contiguous_copy_param重载同样定义在 shmem_device_mte.h 中,核心思想是用repeat(重复次数)、length(单次长度)与src_ld/dst_ld(源/目的 stride)描述非连续排布,把多次小搬运合并为一次 DMA 描述。
2. 流水线与 Double Buffer
2.1 Ping-Pong Buffer 模式
瓶颈:单 buffer 时,NBI 搬运和使用MUST串行,硬件在等待期间空转。
优化:两组 UB buffer + 两组 event id 交替使用——搬运 buffer A 时使用 buffer B。标准写法可参考 examples/kv_shuffle/kv_shuffle_kernel.cpp,其缓冲区布局为 K 缓存 ping/pong(UB 0 / 32KB)与 V 缓存 ping/pong(UB 64KB / 96KB),共 4 个 event id:
constexpr uint64_t kPingUb = 0; constexpr uint64_t kPongUb = 32 * 1024; constexpr uint32_t kUbSize = 32 * 1024; int ping_pong_flag = 0; for (int block = 0; block < block_nums; ++block) { uint64_t ub_offset = ping_pong_flag == 0 ? kPingUb : kPongUb; TEventID event = ping_pong_flag == 0 ? EVENT_ID0 : EVENT_ID1; WaitFlag<HardEvent::MTE3_MTE2>(event); aclshmemx_mte_put_nbi(dst, src + block * chunk, ub_offset, kUbSize, chunk_elems, target_pe, event); SetFlag<HardEvent::MTE3_MTE2>(event); ping_pong_flag = 1 - ping_pong_flag; }关键约束:
- 两个 event idMUST成对
SetFlag/WaitFlag,不跨未完成 NBI 复用(kv_shuffle 中 K/V 各用一对,即 EVENT_ID0/1 与 EVENT_ID2/3,互不干扰); - 首次进入循环前需预设 flag(或首轮跳过 Wait);
- buffer 大小相等,避免 tail 时一侧溢出。
2.2 通信-计算 Overlap
瓶颈:通信和计算串行执行,硬件利用率低。优化:让 chunk k 的通信和 chunk k-1 的计算重叠执行。前提:≥ 2 个 chunk 迭代,且通信和计算时间均不可忽略。
时间线示意:
comm: [chunk0] [chunk1] [chunk2] [chunk3] compute: [chunk0] [chunk1] [chunk2] [chunk3]实现要求:
- 需要两组 data buffer(compute 用当前、comm 填下一组);
- 使用 event 或 signal/wait 在 chunk 完成时通知计算端;
- 尾部需 drain:最后一个 chunk 通信完成后单独计算。
2.3 Multi-Stage Pipeline(STAGES > 2)
场景:存储层次多级(如 L1 → L0A → L0B → Compute)时,两级 ping-pong 不够用,需要更深流水。模式:使用 circular index 管理多级 buffer。
constexpr uint32_t STAGES = 2; // 可扩展为 3、4 uint32_t bufId = 0; for (uint32_t iter = 0; iter < total_iters; ++iter) { auto &tile = tensorList[bufId]; WaitFlag<HardEvent::M_MTE1>(eventList[bufId]); // 使用当前 buffer 计算 compute(tile); SetFlag<HardEvent::M_MTE1>(eventList[bufId]); bufId = (bufId + 1 < STAGES) ? (bufId + 1) : 0; // circular }每个 stage 有独立的 buffer 与 event,bufId循环递增,使搬运(stage k+1 的填充)与计算(stage k 的使用)深度重叠。
3. 同步优化
3.1 Barrier → Signal/Wait
瓶颈:全局 barrier 让所有 PE 等待最慢者,且与数据依赖无关的 PE 也被拖住。优化:替换为 producer-consumer 点对点 signal/wait。
❌ 错误做法:
aclshmem_barrier_all(); // 每个 phase 全员等待✅ 正确做法:
// Producer 完成后只通知消费者 aclshmemx_signal_op(remote_sig, magic, ACLSHMEM_SIGNAL_SET, target_pe); // Consumer 只等自己需要的数据就绪 aclshmem_signal_wait_until(local_sig, ACLSHMEM_CMP_EQ, magic);这两个接口在 include/host/data_plane/shmem_host_so.h(signal_op)与 include/host/data_plane/shmem_host_p2p_sync.h(signal_wait_until)中有对应实现;ACLSHMEM_CMP_EQ为比较操作符枚举值。allgather 的 small-data 路径也展示了aclshmem_quiet()+SyncAll()+signal_wait_until的组合用法(见 allgather_kernel.cpp)。
3.2 Per-Chunk Signaling
瓶颈:整块数据传输完才发 signal,消费者长时间空等。优化:每个 chunk 传输完立即 signal,消费者可逐 chunk 消费。
signal 编码建议:
flag = chunk_count + magic:magic 区分轮次,chunk_count 表示已就绪数量;- receiver 通过
(flag >> shift) == (magic >> shift)验证轮次有效性; - 每个 core 使用独立 flag offset:
flag_offset = aivIndex * SYNC_FLAG_INTERVAL。
仓内 allgather 定义了SYNC_FLAG_INTERVAL = 16(allgather_kernel.cpp),flag_offset = aivIndex * SYNC_FLAG_INTERVAL,每轮 sender 发flag = times + magic;receiver 解码ready_num = ready_flag - magic后与本地*flags_ub1[group_idx]比较,即可知道本次该拉取哪些新 chunk(allgather_kernel.cpp)。
3.3 Phase 合并
| 场景 | 合并方式 |
|---|---|
| reduce-scatter + allgather | 合并为一个 fused kernel,phase 间在 Device 侧同步 |
| copy-to-symmetric + barrier + fetch | 合并为 sender/receiver overlap 模式(见 §1.1) |
| compute + comm epilogue | 计算 kernel 直接写 symmetric state,通信 epilogue 在同一 kernel 完成 |
Phase 合并的核心收益是消除 kernel 间往返与多次全局同步;allgather 的all_gather_origin就是 copy-to-symmetric、per-chunk signal、remote fetch 三个阶段在一个 kernel 内合并的实例。
4. 内存优化
4.1 Avoid GM Scratch
瓶颈:远端 get → GM tmp → DataCopy to UB → compute → DataCopy to GM,产生了多余的 GM 读写。优化:使用get_nbi直接把数据搬到 UB,在 UB 上做 streaming reduce。
❌ 错误做法:
aclshmem_getmem(gm_tmp, remote_src, size, peer); // 落 GM aclshmemx_mte_quiet(); DataCopy(ub_buf, gm_tmp, size); // 再搬 UB compute(ub_buf);✅ 正确做法:
// 直接搬到 UB(UB2GM get) aclshmem_float_get_nbi((__ubuf__ float *)ub_buf, remote_src, count, peer); SetFlag<HardEvent::MTE2_MTE3>(EVENT_ID0); WaitFlag<HardEvent::MTE2_MTE3>(EVENT_ID0); compute(ub_buf);注意这里使用MTE2_MTE3事件:get 数据到达 UB 后,用该事件把 MTE2(搬运)与 MTE3(计算)串起来,确保 compute 读到的是完整数据。
4.2 Buffer 对齐
| 对齐要求 | 带宽影响 |
|---|---|
| 512B 对齐 | 接近峰值带宽(Atlas A2 可比 32B 对齐提升 30%) |
| 32B 对齐 | 最低要求,带宽受损 |
// 分配时指定对齐 void *sym_buf = aclshmem_align(512, total_bytes);aclshmem_align在 include/host/mem/shmem_host_heap.h 中声明(并提供了shmem_align别名),分配的是对称内存,对齐参数alignment需为 2 的幂且不小于默认对齐。
4.3 最小搬运量
| 规则 | 说明 |
|---|---|
| 单次搬运 ≥ 16KB | 低于此值 DMA setup 开销占比过大 |
| 合并小块 | 多个小搬运合并为一次大搬运 |
| padding 对齐 | 必要时 padding 到 16KB 倍数,搬运后忽略 padding 部分 |
4.4 UB Buffer Fusion
| 项目 | 说明 |
|---|---|
| 瓶颈 | 多步 Vector 计算之间把中间结果写回 GM 再读入 |
| 优化 | 中间结果保留在 UB,连续执行多步 Vector 操作 |
UB Buffer Fusion 与 §4.1 一脉相承:凡是中间结果只被后续计算使用、不跨 PE 消费的数据,都应留在 UB 内完成整个计算链,避免任何 GM 往返。
5. 分核策略优化
分核策略优化的出发点是检查当前 kernel 是否存在单 AIV 内for peer in 0..n_pes的串行循环:同一个 chunk 或同一段任务,需要该 AIV 逐个、串行访问不同 PE 的数据。如果 block_dim 和 chunk size 已调到局部最优但性能仍未达标,瓶颈大概率就在这条 AIV 内跨 PE 串行链路上。以下模式给出把 AIV 内串行链路转化为多核并行的方案。
5.1 Sender/Receiver Core Split
| 项目 | 说明 |
|---|---|
| 模式 | 一半 core 做 local→symmetric 拷贝(sender),另一半做 remote fetch(receiver) |
| 来源 | allgatherall_gather_origin:core_group_num = aivNum / 2 |
| 适用 | 数据量大、需要 overlap local copy 和 remote fetch 的场景 |
| 不适用 | 小数据量(所有 core 做同一操作更快) |
5.2 AIV 内跨 PE 串行链路消除
瓶颈:单个 AIV 内存在for peer in 0..n_pes的串行循环,每次迭代发起一次跨 PE 搬运后必须等待该次搬运完成才能进入下一 peer,无法利用多条 HCCS 链路同时传输。
检测:在 kernel 的 prof 打点范围内查找逐 peer 的 get/put 循环,且循环体内部有WaitFlag或PipeBarrier等同步点阻断并发。
影响:8PE reduce_scatter 实测 bus bandwidth 仅 65%~71% HCCL,根源即 AIV 内逐 peer 串行 get+add。
优先级:block_dim 和 chunk size 调至局部最优后若未达标,MUST优先尝试本节方案。
当前典型串行模式:
// AIV 内逐 peer 串行:get PE0 → wait → add → get PE1 → wait → add ... for (int peer = 0; peer < n_pes; ++peer) { get 当前 chunk 从 peer → tmp_ub; WaitFlag(完成); acc_ub += tmp_ub; }以下两种子模式将这条串行链路拆分到多核或多阶段。
子模式 1:按源 PE 分组并行拉取
将 AIV 按源 PE 分组,每组专责从一个 PE 拉取数据,各组并行拉取完成后做一次本地跨 PE 汇总。
// n_pes 组 AIV 并行:组 0 拉 PE0、组 1 拉 PE1、... int peer = aiv_idx / core_per_peer; // 该 AIV 属于哪组(负责哪个 PE) int lane = aiv_idx % core_per_peer; // 组内编号 int my_len = chunk_len / core_per_peer + (lane < extra); // 该 AIV 处理的数据量 int my_off = 该 lane 的数据起始偏移; get symm[peer][shard_off + my_off] → local_scratch[lane]; // 全部组完成拉取后做一次本地跨 PE 汇总: for (int p = 0; p < n_pes; ++p) { acc += local_scratch[p * core_per_peer + lane]; }要求:AIV 总数 ≥ n_pes 且能被 n_pes 整除。原本 1 个 AIV 串行 8 次 get+wait+add,现在变为 8 组 AIV 并行 1 次 get+wait,汇总阶段改为本地读。
子模式 2:Sender put + Receiver 本地汇总
将 AIV 对半分为 sender 和 receiver。sender 不等待远端读请求——它主动把自己的 contributionput到 owner PE 的对称内存,完毕后 signal 对应的 receiver。receiver 在本 PE 的对称内存中直接读取各 sender 推来的数据并汇总,无需发起跨 PE 读取、无需逐 peer 等待。
// Sender AIV:推数据到 owner PE if (aiv_idx < sender_num) { put 我的 contribution → owner_pe.sym_buf[my_slot]; signal(receiver_on_owner_pe, my_slot_ready); } // Receiver AIV:在本地对称内存中汇总 else { for (int src = 0; src < n_pes; ++src) { wait(signal from src); acc += owner_pe.sym_buf[src * slot_size + my_offset]; } }注意:每个 sender 需要独立的对称内存槽位(slot),由 receiver 侧预先分配好,避免多 sender 写同一地址冲突。
子模式选择:
| 条件 | 选择 |
|---|---|
| AIV 总数 ≥ n_pes 且可整除 | 子模式 1(按源 PE 分组) |
| AIV 总数 < n_pes 或需要避免多路并行 get 的链路拥塞 | 子模式 2(Sender put + Receiver 本地汇总) |
| n_pes ≥ 8 且对称内存有限 | 子模式 1,若 AIV 不足则配合 §5.3 分层规约 |
5.3 分层规约
瓶颈:全量 peer 参与单级规约,AIV 不足时要么串行要么分配不均。优化:先做 PE pair 局部规约得到中间结果,再对中间结果做 reduce_scatter。适用:n_pes ≥ 4,AIV 不足以按 §5.2 子模式 1 均分到每组至少 1 核时。约束:每层增加一次 barrier,层级过多反而抵消收益。先在最小 PE 数(如 2PE 或 4PE)验证,再用到大的 PE 配置。
8PE 两层示例: Layer 1(PE pair):PE0+PE1 局部 sum → PE0,PE2+PE3 → PE2,... Layer 2(RS):4 个 pair owner 做 4-PE reduce_scatter → 各自 shard5.4 Dynamic Block Dim
| 数据量 | Block Dim 策略 |
|---|---|
| < 2MB | 固定 8 核(小数据 setup 开销大于并行收益) |
| ≥ 2MB | 2 * n_pes或按 chunk 数动态计算 |
| 纯通信 | AIV 按 PE、数据维度均分 |
仓内 allgather 的做法可作为参考:kernel 入口按数据量分流——elements * sizeof(type) < 2097152(即 < 2MB)走all_gather_small_data,否则走all_gather_big_data,后者把大 buffer 切成 100MiB 窗口(GVA_BUFF_MAX_SIZE)分批执行(见 allgather_kernel.cpp 与 allgather_kernel.cpp)。
5.5 负载均衡
❌ 错误做法:
int per_core = total / core_num; // tail 核可能负载翻倍✅ 正确做法:
int base = total / core_num; int extra = total % core_num; int my_count = base + (core_idx < extra ? 1 : 0); int my_offset = core_idx * base + min(core_idx, extra);核心思想是把余数extra平摊到前extra个 core,避免最后一个 core 独自承担全部余量,从而让各核负载差不超过 1 个单元。
5.6 Tail 合并
| 策略 | 适用场景 |
|---|---|
| 合并到最后一个正常 chunk | tail chunk 极小,独立一轮循环开销不值得 |
| 特殊处理 | tail chunk 大小差异大,需要单独搬运参数 |
| 填充到对齐 | tail 很小但对齐后可复用标准路径 |
allgather 的 sender 侧对尾部做了典型处理:主循环按UB_DMA_MAX_SIZE满 chunk 搬运,剩余部分用copy_total_size / sizeof(T)作为elem_size单独发最后一次 put(见 allgather_kernel.cpp),保证尾块不浪费也不越界。
6. 优化优先级
对于典型 SHMEM 通信算子,建议按以下顺序尝试优化:
| 优先级 | 方向 | 预期收益 | 风险 |
|---|---|---|---|
| 1 | 串行 Peer 迭代消除(§5.2) | 拆 AIV 内逐 peer 串行链路为多核并行,reduce 类预期 20%+ | 分核策略变更大,验证正确性 |
| 2 | Copy-to-symmetric overlap(§1.1) | 掩盖对称内存拷贝延迟 | 分核策略变更大 |
| 3 | Double buffer / ping-pong(§2.1) | 掩盖搬运气泡 | 需要 2× UB 空间 |
| 4 | Barrier → signal/wait(§3.1) | 减少全局等待 | 正确性风险较高 |
| 5 | Engine 选择 + chunk 调优(§1.2/§1.3) | 提升有效带宽 | 不同拓扑结果不同 |
| 6 | 动态 block_dim / 负载均衡(§5.4/§5.5) | 平衡各核负载 | 需要多组实验 |
| 7 | 内存对齐 + 减少 GM 中转(§4) | 提升搬运效率 | 收益相对固定 |
排优先级的原则是先解决结构性瓶颈,再处理参数类调优:串行链路消除和通信重叠解决的是“链路结构”问题,收益最大但改动面也大,需要充分的正确性验证(例如先跑通 uniform 大数据用例,再对比 perf-workflow.md 的带宽基准表);Double buffer、Engine 选择、block_dim、内存对齐等则是改动局部、可快速实验的参数类优化。每一轮优化完成后都应回到起点重新 prof 定位瓶颈,再迭代下一轮。
【免费下载链接】shmemCANN SHMEM 是面向昇腾平台的多机多卡内存通信库,基于OpenSHMEM 标准协议,实现跨设备的高效内存访问与数据同步。项目地址: https://gitcode.com/cann/shmem
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考