ZB (Zero-Buffer) Dispatch 算子深度分析
项目: UMDK/CAM — Communication Acceleration for Matrix on Ascend NPU 分支:
remotes/origin/cam_feature分析日期: 2026-07-21 分析范围: 4 个 ZB (Zero-Buffer) Dispatch 系列算子 + 共享基础设施
目录
- ZB (Zero-Buffer) 设计哲学
- 共享基础设施分析
- moe_dispatch_layout_zb — 布局算子
- moe_dispatch_normal_zb — 标准分发算子
- moe_dispatch_lowlatency_zb — 低延迟分发算子
- moe_notify_dispatch_zb — 元数据通知算子
- SHMEM 集成方式
- 性能优势分析
- 关键数据结构与常量汇总
1. ZB (Zero-Buffer) 设计哲学
1.1 什么是 Zero-Buffer?
ZB (Zero-Buffer) 是 CAM 库在 cam_feature 分支上引入的下一代 MoE Dispatch 通信范式。其核心思想是:
完全摒弃传统的 HCCL Window 通信模式,改用 CANN SHMEM(用户态 RDMA)API 直接在卡间对称内存(symmetric memory)上进行数据搬运和同步。
1.2 传统 HCCL Window 模式的问题
在传统的 MoE Dispatch 流程中(参见 07_hccl_window_deepdive.md),通信依赖 HCCL Window 机制:
| 维度 | HCCL Window 模式 | Zero-Buffer 模式 |
|---|---|---|
| 内存占用 | 每卡预分配 ~200MB HCCL Buffer 作为通信窗口 | 零额外内存,直接使用对称内存布局 |
| 通信路径 | 数据 → HCCL Window → RDMA → 对端 HCCL Window → 用户 Buffer | 数据 → RDMA → 对端用户 Buffer(直达) |
| 同步机制 | HCCL Collective + 信号量 | SHMEM flag 轮询 + Magic 机制 |
| 延迟 | 多次内存拷贝 + HCCL 软件栈开销 | 单次 RDMA + 用户态 flag 同步 |
| 灵活性 | 窗口大小固定,需预分配 | 按需使用,无固定窗口限制 |
1.3 Zero-Buffer 的核心优势
- 零额外内存: 不需要预分配 HCCL Window Buffer,节省 ~200MB/卡
- 单次 RDMA 直达: 数据直接从源卡写入目标卡的用户可见内存,无需中间拷贝
- 用户态同步: 通过 SHMEM flag + Magic 机制实现轻量级跨卡同步,避免重量级 HCCL Collective
- 对称内存布局: 所有卡上的 flag 区域布局完全一致,通过
shmem_ptr(base, rankId)即可定位对端内存
1.4 代码组织
ZB 系列算子在 cam_feature 分支上的文件组织遵循 CANN 算子标准三层结构:
moe_dispatch_XXX_zb/
├── op_api/ # aclnn 对外接口层
│ ├── aclnn_XXX_zero_buffer.cpp
│ └── aclnn_XXX_zero_buffer.h
├── op_host/ # Host 端 tiling 与算子注册
│ ├── XXX.cpp # OpDef 注册 + 算子配置
│ └── XXX_tiling.cc # Tiling 逻辑 + 工作空间计算
└── op_kernel/ # Device 端 kernel 实现
├── XXX.cpp # 入口函数
├── XXX.h # Kernel 核心逻辑
└── XXX_tiling.h # TilingData 结构定义
共享工具位于 utils/:
utils/op_kernel/
├── zero_buffer_api.h # SHMEM API 封装(#include "shmem.h")
├── zero_buffer_sync_flag.h # 跨卡 flag 同步机制(核心!)
├── comm_args_zb.h # 通信参数(#include "comm_args.h")
├── data_copy_zb.h # 数据拷贝(#include "data_copy.h")
├── moe_lowlatency_zero_buffer_base.h # 低延迟基类工具
└── sync_collectives_zb.h # 集合同步(旧版,部分算子仍使用)
2. 共享基础设施分析
2.1 zero_buffer_sync_flag.h — 核心同步机制
这是整个 ZB 体系最关键的共享组件。ZeroBufferSyncFlag 类实现了基于对称共享内存的跨卡 flag 同步。
内存布局
gva_gm + FLAG_AREA_BASE (100KB):
┌──────────────────────────────────────────────────────────┐
│ Magic Area (24 KB) │
│ slot[coreIdx] at coreIdx × 512B │
│ 每核单调递增,每次 IncrementMagic() +1 │
├──────────────────────────────────────────────────────────┤
│ Barrier PingPong Buffer 0 │
│ slot[srcRank] (32B each), epWorldSize slots │
├──────────────────────────────────────────────────────────┤
│ Barrier PingPong Buffer 1 (同上) │
├──────────────────────────────────────────────────────────┤
│ Flag PingPong Buffer 0 │
│ flag[srcRank][eventID] (32B each) │
│ srcRank ∈ [0, epWorldSize) │
│ eventID ∈ [0, slotsPerRank) │
├──────────────────────────────────────────────────────────┤
│ Flag PingPong Buffer 1 (同上) │
└──────────────────────────────────────────────────────────┘
Total = Magic(24KB) + 2×BarrierBuf + 2×FlagBuf
Magic 机制
// 高 32 位存储 magic,低 32 位存储 phase
__aicore__ inline int64_t MakeFlagValue(int32_t magic, int32_t value)
{
return (static_cast<int64_t>(static_cast<uint32_t>(magic)) << 32) |
static_cast<int64_t>(static_cast<uint32_t>(value));
}
- 每个 kernel 入口调用
IncrementMagic()一次 - Magic 单调递增:kernel K 得到 M,kernel K+1 得到 M+1
- Ping-pong buffer 选择:
magic % 2 - 关键不变量: 单调 magic 保证任何旧 kernel 的 stale flag 都会被拒绝
同步协议
Writer (rank A, core i):
1. 完成数据写入(DMA 到远端 shmem)
2. SetFlag(destRank=B, eventID=i, phase)
→ MTE3 fence(flush 所有先前的写入)
→ 写 flag 到 B 的本地 shmem slot [A][i]
Reader (rank B, core j):
1. WaitFlag(srcRank=A, eventID=i, phase)
→ 轮询本地 shmem 直到 flag 匹配(magic, phase)
2. 返回后,rank A 的数据保证可见
同步粒度
| 模式 | slotsPerRank | 使用场景 |
|---|---|---|
| Per-core | blockNum | 每个 core 向所有 rank 发信号 |
| Per-slot | N (任意) | 细粒度事件同步 |
| Per-rank | 1 | 粗粒度 rank 间同步 |
2.2 zero_buffer_api.h — SHMEM API 封装
// zero_buffer_api.h
#include "shmem.h" // CANN SHMEM 提供的用户态 RDMA API
shmem.h 是 CANN 工具链提供的外部头文件,提供:
- aclshmem_ptr(baseAddr, rankId) — 获取对端 rank 的远端内存指针
- aclshmem_half_put_nbi() — half 类型非阻塞 RDMA put
- aclshmem_bfloat16_put_nbi() — bf16 类型非阻塞 RDMA put
- aclshmem_float_put_nbi() — float 类型非阻塞 RDMA put
- shmem_get_int32_mem_nbi() — int32 类型非阻塞 RDMA get
2.3 sync_collectives_zb.h — 旧版集合同步
SyncCollectives 是一个更早期的同步实现,使用双内存区域设计:
- Memory A(basicSyncAddr): 卡内同步 flag
- Memory B(blockOuterSyncAddr): 卡间同步 flag
部分 ZB 算子(如 lowlatency_zb)仍混合使用 SyncCollectives 和 ZeroBufferSyncFlag。
2.4 moe_lowlatency_zero_buffer_base.h — 低延迟基类
提供 ZeroBufferInitWinState() 函数,用于初始化数据状态(dataState)并原子递增操作计数。
3. moe_dispatch_layout_zb — 布局算子
3.1 算子概述
moe_dispatch_layout_zb 是 MoE Dispatch 流程的第一阶段,负责:
1. 统计每个 rank 和每个 expert 接收的 token 数量
2. 计算 isTokenInRank 标记矩阵
3. 计算 sendTokenIdxSmall(token 在目标 expert 内的局部序号)
关键特征: 此算子不涉及跨卡通信,纯本地计算。它使用 SyncAll<true>() 进行卡内多核同步。
3.2 算子注册 (op_host/dispatch_layout.cpp)
// 输入
this->Input("topkIdx") // DT_INT64, [numTokens, numTopk]
this->Attr("num_tokens").Int()
this->Attr("num_ranks").Int()
this->Attr("num_experts").Int()
this->Attr("num_topk").Int()
this->Attr("local_ranksize").Int()
// 输出
this->Output("numTokensPerRank") // DT_INT32, [numRanks]
this->Output("numTokensPerExpert") // DT_INT32, [numExperts]
this->Output("isTokenInRank") // DT_INT32, [numTokens, numRanks]
this->Output("notifySendData") // DT_INT32, 复合缓冲区
this->Output("sendTokenIdxSmall") // DT_INT32, [numTokens, numTopk]
// 平台配置
this->AICore().AddConfig("ascend910_93", a3_config); // 910_93
this->AICore().AddConfig("ascend910b", a2_config); // 910B (A2)
3.3 Tiling 策略 (op_host/dispatch_layout_tiling.cc)
// 约束检查
const int64_t MAX_COMM_WORLD_SIZE = 384;
const int64_t MAX_MOE_EXPERTS_NUM = 512;
const int64_t MAX_LOCAL_RANKSIZE = 8;
const uint32_t K_MAX = 16;
// 工作空间
constexpr uint32_t SYSTEM_NEED_WORKSPACE = 16 * 1024 * 1024; // 16MB
constexpr uint32_t KERNEL_USE_WORKSPACE = 1 * 1024 * 1024; // 1MB
constexpr uint32_t KERNEL_A2_ARG_SIZE = 1 * 1024 * 1024; // 1MB
// Tiling Key
constexpr static int TILING_KEY_INT = 23; // A1 (910_93)
constexpr static int TILING_KEY_A2_TYPE = 100; // A2 (910B) → key = 123
平台检测: 通过 CheckIfA2Machine() 检测 SoC 版本:
std::string socVersion;
platformInfo.GetPlatformResWithLock("version", "Short_SoC_version", socVersion);
if (socVersion == "Ascend910B") { return true; }
3.4 Kernel 实现 — A1 版本 (dispatch_layout.h)
核心算法:两阶段 token 计数
Phase 1: CountTokensInRound() — 分轮次统计
// 每轮处理 tokensPerRound 个 token
// UB 预算: 190KB max
uint32_t fixedSize = numTokensPerRank32AlignIntLen_ + numTokensPerExpert32AlignIntLen_ +
Ceil(numRanks_ * sizeof(T), UB_32_ALIGN) * UB_32_ALIGN;
uint32_t perTokenSize = Ceil(numTopk_ * sizeof(int64_t), UB_32_ALIGN) * UB_32_ALIGN +
Ceil(numRanks_ * sizeof(T), UB_32_ALIGN) * UB_32_ALIGN +
Ceil(numTopk_ * sizeof(T), UB_32_ALIGN) * UB_32_ALIGN;
uint32_t tokensPerRound = (UB_MAX_SIZE - fixedSize) / perTokenSize;
每个 token 的处理逻辑:
for (uint32_t j = 0; j < numTopk_; ++j) {
int64_t expertIdx = topkIdxLocal.GetValue(i * numTopk_ + j);
expertLocal.SetValue(expertIdx, expertLocal.GetValue(expertIdx) + 1);
int rankId = expertIdx / expertsPerRank;
if (!seenLocal.GetValue(rankId)) {
rankLocal.SetValue(rankId, rankLocal.GetValue(rankId) + 1);
isTokenLocal.SetValue(i * numRanks_ + rankId, 1);
seenLocal.SetValue(rankId, 1);
}
}
Phase 2: CalcSendTokenIdxInRound() — 计算发送序号
for (uint32_t i = 0; i < roundTokens; ++i) {
for (uint32_t j = 0; j < numTopk_; ++j) {
int64_t expertIdx = topkIdxLocal.GetValue(i * numTopk_ + j);
T val = expertLocal(expertIdx);
sendIdxLocal(i * numTopk_ + j) = val;
expertLocal(expertIdx) = val + 1; // 递增,下一个 token 的序号
}
}
跨核原子累加
Phase 1 中,每个 core 计算完自己的 numTokensPerExpert 后,通过 SetAtomicAdd 将结果写入后续 core 的临时区域,实现跨核累加:
AscendC::SetAtomicAdd<T>();
for (uint32_t i = coreIdx_ + 1; i < aivNum_; ++i) {
DataCopyPad(tempExpertGM_[i * numExperts_], expertLocal, tempExpertParams);
}
AscendC::SetAtomicNone();
3.5 Kernel 实现 — A2 变体 (dispatch_layout_a2.h)
A2 变体(DispatchLayoutA2)是为 Ascend910B (A2) 芯片设计的增强版本,引入了服务器层级(server-level hierarchy)概念。
关键差异
| 维度 | A1 (DispatchLayout) | A2 (DispatchLayoutA2) |
|---|---|---|
| 处理模式 | 分轮次 (round-based) | 单次全量 (single-pass) |
| 层级 | Rank 级别 | Server + Rank 级别 |
| 新增输出 | 无 | localTokenServer* 系列 |
| 同步 | SyncAll (多轮) | 单次 SyncAll |
A2 新增数据结构
// Server 级别元数据
GlobalTensor<T> localTokenServerUniqCountGM_; // 每个 server 的唯一 token 数
GlobalTensor<T> localTokenServerTotalCountGM_; // 每个 token 在每个 server 的总数
GlobalTensor<T> localTokenServerNumGM_; // 每个 token 需要发送的 server 数
GlobalTensor<T> localTokenServerOffsetGM_; // server 内偏移
GlobalTensor<T> expertRankTokenIdxGM_; // expert 内 token 索引
GlobalTensor<T> sendTokenIdxGM_; // 全局发送序号
GlobalTensor<T> tempServerGM_; // server 临时缓冲区
A2 核心计算
// 计算 server ID
int server_id = rank_id / localRankSize_;
// 统计每个 token 涉及的 server 数量
if (!seenServerTensor.GetValue(server_id)) {
uint32_t uniqCount = localTokenServerUniqCountTensor.GetValue(server_id) + 1;
localTokenServerUniqCountTensor.SetValue(server_id, uniqCount);
localTokenServerOffsetTensor.SetValue(i * serverNum_ + server_id, 1);
seenServerTensor.SetValue(server_id, 1);
uint32_t sendServerNum = localTokenServerNumTensor.GetValue(i);
localTokenServerNumTensor.SetValue(i, sendServerNum + 1);
}
A2 前缀和计算
A2 使用向量指令计算前缀和,为每个 token 在目标 expert 内的位置生成全局唯一偏移:
// 前缀和:计算每个 token 在 expert 内的全局偏移
DataCopy(prefixCountPerExpertTensor, numTokensPerExpertTensor, numExperts_);
for (int i = 0; i < tempTokens_; ++i) {
for (int j = 0; j < serverNum_; ++j) {
int value1 = localTokenServerOffsetTensor.GetValue(i * serverNum_ + j);
int value2 = tempServerTensor.GetValue(j);
localTokenServerOffsetTensor.SetValue(i * serverNum_ + j, value1 * value2 + value1);
tempServerTensor.SetValue(j, value2 + value1);
}
// 向量乘法 + 加法计算 sendTokenIdx
AscendC::Mul(sendTokenIdxTensor[i * numExperts_], prefixCountPerExpertTensor, intermediateExpertTensor, numExperts_);
AscendC::Add(sendTokenIdxTensor[i * numExperts_], tempExpertTensor, intermediateExpertTensor, numExperts_);
AscendC::Add(prefixCountPerExpertTensor, intermediateExpertTensor, tempExpertTensor, numExperts_);
}
3.6 入口函数 (op_kernel/dispatch_layout.cpp)
#define TILING_KEY_INT 23
#define TILING_KEY_A2_INT 123
extern "C" __global__ __aicore__ void dispatch_layout(...) {
if (TILING_KEY_IS(TILING_KEY_INT)) {
MoeDispatchLayout::DispatchLayout<int32_t> op;
op.Init(...);
op.Process();
} else if (TILING_KEY_IS(TILING_KEY_A2_INT)) {
MoeDispatchLayoutA2::DispatchLayoutA2<int32_t> op;
op.Init(...);
op.Process();
}
}
3.7 aclnn 接口层
// 注意:设置 HCCL Server Type 为 MTE(而非 AICPU)
aclnnStatus aclnnDispatchLayoutZeroBuffer(void *workspace, uint64_t workspaceSize,
aclOpExecutor *executor, aclrtStream stream)
{
if (NnopbaseSetHcclServerType) {
NnopbaseSetHcclServerType(executor, NNOPBASE_HCCL_SERVER_TYPE_MTE);
}
return aclnnInnerDispatchLayout(workspace, workspaceSize, executor, stream);
}
关键设计: NnopbaseSetHcclServerType(executor, MTE) 将算子执行模式设置为 MTE(Memory Transfer Engine),而非默认的 AICPU。这确保算子通过硬件 MTE 通道执行,减少 CPU 干预。
4. moe_dispatch_normal_zb — 标准分发算子
4.1 算子概述
moe_dispatch_normal_zb 是 MoE Dispatch 流程的第二阶段(数据搬运阶段),负责:
1. 从 layout 算子获取 put_offset(目标偏移)和 send_tokenIdx(发送序号)
2. 通过 SHMEM RDMA 将 token 数据直接写入对端 rank 的 expandXOut 缓冲区
3. 支持可选的 DynamicQuant(INT8 动态量化)
关键特征: 使用 ZeroBufferSyncFlag 进行跨卡同步,使用 shmem_ptr() 获取远端指针。
4.2 算子注册 (op_host/moe_dispatch_normal_zero_buffer.cpp)
// 输入
this->Input("x") // DT_BF16/DT_FLOAT16, [bs, h]
this->Input("topk_idx") // DT_INT32, [bs, k]
this->Input("send_tokenIdx") // DT_INT32, [bs, k]
this->Input("put_offset") // DT_INT32, [moeExpertNum * epWorldSize]
// 输出
this->Output("recv_x") // DT_BF16/DT_INT8/DT_FLOAT16
this->Output("recv_x_scale") // DT_FLOAT (量化时)
this->Output("assist_info_for_combine") // DT_INT32
this->Output("waitRecvCostStats") // DT_INT32 (诊断用)
// 属性
this->Attr("ep_world_size").Int()
this->Attr("ep_rank_id").Int()
this->Attr("tp_world_size").Int()
this->Attr("tp_rank_id").Int()
this->Attr("moe_expert_num").Int()
this->Attr("quant_mode").Int()
this->Attr("global_bs").Int()
this->Attr("zero_buffer_ptr").Int() // ← ZB 核心:对称内存基地址
4.3 Tiling 数据
struct MoeDispatchNormalZeroBufferInfo {
uint32_t epWorldSize;
uint32_t tpWorldSize;
uint32_t epRankId;
uint32_t tpRankId;
uint32_t moeExpertNum;
uint32_t quantMode;
uint32_t globalBs;
uint32_t bs;
uint32_t k;
uint32_t h;
uint32_t aivNum;
bool isQuant;
bool isEnableDiagnose;
uint64_t totalUbSize;
uint64_t totalWinSize;
};
struct MoeDispatchNormalZeroBufferTilingData {
MoeDispatchNormalZeroBufferInfo moeDispatchNormalInfo;
uint64_t zeroBufferPtr; // ← ZB 对称内存基地址
};
4.4 Kernel 核心实现
模板参数
template <typename XType, typename ExpandXOutType, bool DynamicQuant,
bool IsSmoothScaleExist, bool IsShareExpertRank>
class MoeDispatchNormalZeroBuffer {
constexpr static int32_t PHASE_ENTRY = 1;
constexpr static int32_t PHASE_DONE = 2;
// ...
};
内存布局常量
constexpr uint64_t NOTIFY_MAGIC_OFFSET = 50UL * 1024UL; // 50KB
constexpr uint64_t WIN_MAGIC_OFFSET = 100UL * 1024UL; // 100KB
constexpr uint64_t HALF_WIN_STATE_OFFSET = 8 * 1024UL * 1024UL; // 8MB
constexpr uint64_t NOTIFY_WIN_STATE_OFFSET = 2 * 1024UL * 1024UL;// 2MB
远端地址计算
__aicore__ inline GM_ADDR GetWindStateAddrByRankId(const int32_t rankId)
{
auto ptr = shmem_ptr(gva_gm, rankId); // 获取对端 rank 的对称内存指针
return (GM_ADDR)(ptr) + WIN_MAGIC_OFFSET + NOTIFY_WIN_STATE_OFFSET + dataState * HALF_WIN_STATE_OFFSET;
}
核心执行流程 (Process)
template <CamTypeClass>
__aicore__ inline void MoeDispatchNormalZeroBuffer<CamTypeFunc>::Process()
{
if ASCEND_IS_AIV {
// Step 1: IncrementMagic — 获取 magic M+1
syncFlag_.IncrementMagic();
// Step 2: BarrierAll — 确保所有 rank 的 Notify 结果可见
// (putOffset, recvData 等已被 Notify 计算完成)
syncFlag_.BarrierAll();
// Step 3: 通过 shmem 写 token 数据到远端 rank
InputToDstOutput();
// Step 4: BarrierAll — 确保所有 rank 完成写入
// 后续 GMM kernel 可以安全消费 expandXOut
syncFlag_.BarrierAll();
}
}
数据搬运核心 (InputToDstOutput)
for (int32_t tokenIndex = startTokenId; tokenIndex < endTokenId; ++tokenIndex) {
uint32_t dstExpertId = expertIdsTensor(tokenIndex - startTokenId);
uint32_t dstRankId = dstExpertId / moeExpertNumPerRank;
int32_t curExpertIdx = sendTokenIdxTensor(tokenIndex - startTokenId);
int32_t dstExpertOffset = putOffsetTensor(dstExpertId * epWorldSize + epRankId);
// ★ 关键:通过 shmem_ptr 获取对端 rank 的远端内存指针
auto ptr = reinterpret_cast<__gm__ uint8_t *>(shmem_ptr(expandXOutGM, dstRankId));
uint64_t expandXByteOffset = static_cast<uint64_t>(hUBAlignSize) *
static_cast<uint64_t>(dstExpertOffset + curExpertIdx);
dstGT.SetGlobalBuffer((__gm__ ExpandXOutType *)(ptr + expandXByteOffset));
// 写入数据(支持量化路径)
if constexpr (DynamicQuant) {
// INT8 量化路径:量化后写入 + 写入 scale
QuantProcess();
DataCopyPad(dstGT, xOutTensor, xOutCopyParams);
DataCopyPad(dstScaleOutGT, xOutFp32Tensor[hUBAlignSize / sizeof(float)], scaleCopyParams);
} else {
// BF16/FP16 直接写入
DataCopyPad(dstGT, xTmpTensor, xOutCopyParams);
}
}
量化处理 (QuantProcess)
// DynamicQuant: BF16/FP16 → INT8 动态量化
Cast(floatLocalTemp, xInTensor, RoundMode::CAST_NONE, h); // 转 float
Abs(floatLocalAbsTemp, floatLocalTemp, h); // 取绝对值
ReduceMaxInplace(floatLocalAbsTemp, h); // 求 max
dynamicScale = float(127.0) / (floatLocalAbsTemp.GetValue(0) + 1e-12f);
Muls(floatLocalTemp, floatLocalTemp, dynamicScale, h); // 缩放
Cast(int32LocalTemp, floatLocalTemp, RoundMode::CAST_RINT, h); // 转 int32
Cast(halfLocalTemp, int32LocalTemp, RoundMode::CAST_ROUND, h); // 转 half
Cast(xOutTensor, halfLocalTemp, RoundMode::CAST_TRUNC, h); // 转 int8
4.5 ZeroBufferSyncFlag 初始化
// 每个 core 的 syncFlagBuf 仅 32B(一个 flag slot 大小)
tpipe_->InitBuffer(syncFlagBuf_, ZeroBufferSyncFlagImpl::FLAG_SLOT_SIZE);
// per-core 粒度:slotsPerRank = blockNum
syncFlag_.Init(gva_gm, epRankId, epRankSize, blockNum, syncFlagBuf_);
4.6 与 master 版 moe_dispatch_normal 的差异
| 维度 | master 版 (HCCL Window) | ZB 版 (Zero-Buffer) |
|---|---|---|
| 远端访问 | 通过 HCCL Window 间接访问 | shmem_ptr() 直接访问对端内存 |
| 同步 | HCCL Collective + 信号量 | ZeroBufferSyncFlag + Magic |
| 内存开销 | ~200MB HCCL Buffer | 零额外内存 |
| 数据路径 | 源 → Window → RDMA → 对端 Window → 目标 | 源 → RDMA → 对端目标(直达) |
| 量化支持 | 无 | DynamicQuant (INT8) |
| TP 支持 | 无 | 有 (tpWorldSize/tpRankId) |
5. moe_dispatch_lowlatency_zb — 低延迟分发算子
5.1 算子概述
moe_dispatch_lowlatency_zb 是 ZB 系列中最复杂的算子(869 行 header),专为低延迟 MoE Dispatch 设计。它整合了 Notify + Dispatch 的功能,支持:
1. Shared Expert + MoE Expert 混合路由
2. 多种量化模式(StaticQuant / DynamicQuant / SmoothScale)
3. AllGather 模式(IsNeedAllgather)
4. 弹性专家(zero/copy/const expert)
5.2 算子注册 (op_host/moe_dispatch_lowlatency_zero_buffer.cpp)
// 输入
this->Input("x") // DT_BF16/DT_FLOAT16, [bs, h]
this->Input("expert_ids") // DT_INT32, [bs, k]
this->Input("scales") // DT_FLOAT, optional (静态量化)
this->Input("x_active_mask") // DT_BOOL, optional
this->Input("elastic_info") // DT_INT32, optional
// 输出
this->Output("expand_x") // DT_BF16/DT_INT8/DT_FLOAT16
this->Output("dynamic_scales") // DT_FLOAT
this->Output("assist_info_for_combine") // DT_INT32 (expandIdx)
this->Output("expert_token_nums") // DT_INT64
this->Output("ep_send_counts") // DT_INT32
this->Output("tp_send_counts") // DT_INT32
// 属性
this->Attr("ep_world_size").Int()
this->Attr("ep_rank_id").Int()
this->Attr("moe_expert_num").Int()
this->Attr("expert_shard_type").Int()
this->Attr("shared_expert_num").Int()
this->Attr("shared_expert_rank_num").Int()
this->Attr("quant_mode").Int()
this->Attr("expert_token_nums_type").Int()
this->Attr("zero_expert_num").Int()
this->Attr("copy_expert_num").Int()
this->Attr("const_expert_num").Int()
5.3 Tiling Key 矩阵
低延迟算子支持多种 Tiling Key,覆盖不同量化/AllGather 组合:
| Tiling Key | StaticQuant | DynamicQuant | SmoothScale | AllGather |
|---|---|---|---|---|
| 10000 | false | false | false | false |
| 10100 | false | false | false | true |
| 10011 | true | false | false | false |
| 10002 | false | true | false | false |
| 10012 | false | true | true | false |
| 10111 | true | false | false | true |
| 10102 | false | true | false | true |
| 10112 | false | true | true | true |
5.4 Kernel 核心实现
模板参数
template <typename XType, typename ExpandXOutType, bool StaticQuant,
bool DynamicQuant, bool IsSmoothScaleExist, bool IsNeedAllgather>
class MoeDispatchLowlatencyZeroBuffer {
// ...
};
内存布局常量
// Dispatch 同步 flag 区域从 FLAG_AREA_BASE + 64KB 开始
// 原因:某些 rank 可能已进入 combine 阶段,而其他 rank 仍在 dispatch
// 使用独立 flag slot 避免与 cleanup 竞争
constexpr uint64_t DISPATCH_SYNC_FLAG_BASE_OFFSET = ZeroBufferSyncFlagImpl::FLAG_AREA_BASE + 64UL * 1024UL;
constexpr uint32_t DISPATCH_SYNC_STATE_OFFSET = 32U;
元数据布局
metaDataGvaGM_ 布局:
┌──────────────────────────────────────────────────────────┐
│ Per-core magic value (aivNum × 32B) │
├──────────────────────────────────────────────────────────┤
│ Per-rank layout flags (coreNum × 32B) │
├──────────────────────────────────────────────────────────┤
│ Per-core send-count flags (coreNum × 64B) │
├──────────────────────────────────────────────────────────┤
│ Send-count data (LocalNotifyDataTensor_) │
└──────────────────────────────────────────────────────────┘
核心执行流程 (Process)
template <TemplateMC2TypeClass>
__aicore__ inline void MoeDispatchLowlatencyZeroBuffer<TemplateMC2TypeFunc>::Process()
{
if ASCEND_IS_AIV {
// Phase 1: 本地统计每个 expert 的 token 发送数量
LocalLayout();
SyncAll<true>();
// Phase 2: 通知所有 rank 本地统计完成
SetLayoutStatus();
SendCountNotify(); // 等待所有 rank 的统计结果
SyncAll<true>();
// Phase 3: 处理接收到的全局统计信息
WaitNotify();
SyncAll<true>();
// Phase 4: 实际数据搬运
InputToDstOutput();
// Phase 5: 同步确保数据写入完成
SetSyncFlag();
WaitSyncFlag();
SyncAll<true>();
// Phase 6: 清理
CleanUp();
}
}
Phase 1: LocalLayout — 本地统计
__aicore__ inline void LocalLayout()
{
// 1. 加载 expert_ids
DataCopyPad(expertIdsTensor_, expertIdsGMTensor_, expertIdsCntParams, expertIdsCntCopyPadParams);
// 2. 按 core 分片统计每个 expert 的 token 数
for (int32_t i = startTokenId; i < endTokenId; ++i) {
int32_t expertIdx = static_cast<int32_t>(expertIdsTensor_(i));
int32_t curCnt = sendCountLt.GetValue(expertIdx) + 1;
sendCountLt.SetValue(expertIdx, curCnt);
}
// 3. 原子写入共享元数据区域
AscendC::SetAtomicAdd<int32_t>();
DataCopyPad(LocalNotifyDataTensor_, sendCountLt, sendCountDataCopyParams);
AscendC::SetAtomicNone();
}
Phase 2: SetLayoutStatus — 通知统计完成
__aicore__ inline void SetLayoutStatus()
{
// 每个 AIV 负责通知一部分 rank
for (uint32_t targetRankId = startRankId; targetRankId < endRankId; targetRankId++) {
// ★ 使用 aclshmem_float_put_nbi 写入对端 rank 的 layoutStatus
aclshmem_float_put_nbi(layoutStatusTensor_[epRankId_], statusTensor_, 1, targetRankId);
}
}
Phase 3: SendCountNotify — 等待并获取对端统计
__aicore__ inline void SendCountNotify()
{
for (uint32_t targetRankId = startRankId; targetRankId < endRankId; targetRankId++) {
// 1. 轮询等待对端 rank 的 layout flag 匹配当前 magic
while ((curVal < minFlagVal) || (curVal > maxFlagVal)) {
DataCopy(layoutStatusFp32Tensor_, layoutStatusTensor_[targetRankId], intriParams);
curVal = layoutStatusFp32Tensor_.GetValue(0);
}
// 2. ★ 使用 shmem_get_int32_mem_nbi 从对端 rank 读取 send count
shmem_get_int32_mem_nbi(allExpertTokenNumsGMTensor_[targetRankId * moeExpertNum_],
LocalNotifyDataTensor_, moeExpertNum_, targetRankId);
}
}
Phase 4: InputToDstOutput — 数据搬运
__aicore__ inline void SendToMoeExpert()
{
for (int32_t index = startTokenId; index < endTokenId; ++index) {
int32_t tokenIndex = index / axisK_;
uint32_t dstExpertId = expertIdsTensor_(index);
int32_t dstRankId = dstExpertId / moeExpertNumPerRank_ + sharedExpertRankNum_;
// 计算当前 token 在目标 expert 内的序号
CalTokenSendExpertCnt(dstExpertId, index, curExpertIdx);
// 计算目标偏移
int32_t dstExpertOffset = sendCountsGlobal(offsetIdx - 1);
// ★ 通过 shmem_ptr 获取对端 rank 的远端内存指针
auto dstPtr = reinterpret_cast<__gm__ uint8_t *>(shmem_ptr(expandXOutGM_, dstRankId));
dstWinGMTensor.SetGlobalBuffer(
(__gm__ ExpandXOutType *)(dstPtr + hOutSizeAlign_ * (dstExpertOffset + curExpertIdx)));
// 写入数据
if constexpr (DynamicQuant || StaticQuant) {
// 量化路径
QuantProcess();
DataCopyPad(dstWinGMTensor, xOutTensor_, expandXCopyParams_);
DataCopyPad(dstScaleGMTensor, xOutFp32Tensor[hOutSizeAlign_ / sizeof(float)], scaleCopyParams_);
} else {
// 直接写入
DataCopyPad(dstWinGMTensor, xTmpTensor_, expandXCopyParams_);
}
}
}
Phase 5: SetSyncFlag / WaitSyncFlag — 数据写入同步
__aicore__ inline void SetSyncFlag()
{
// 每个 core 向负责的 rank 写入 flag
for (uint32_t targetRankId = curBlockStartRankId_; targetRankId < curBlockEndRankId_; ++targetRankId) {
auto ptr = GetSyncFlagAddrByRankId(targetRankId) + epRankId_ * DISPATCH_SYNC_STATE_OFFSET;
gmRemoteStatusGT.SetGlobalBuffer((__gm__ int32_t *)(ptr));
DataCopy<int32_t>(gmRemoteStatusGT, statusTensor[...], 8UL);
}
}
__aicore__ inline void WaitSyncFlag()
{
// 轮询等待所有对端 rank 的 flag
while ((sumOfFlag < minTarget) || (sumOfFlag > maxTarget)) {
DataCopy(statusFp32Tensor, statusFp32TensorGT[...], intriParams);
ReduceSum(statusSumOutTensor, statusFp32Tensor, gatherMaskOutTensor, mask, rankNumPerBlock_, 1);
sumOfFlag = statusSumOutTensor.GetValue(0);
}
// 清理 flag
Duplicate<int32_t>(cleanStateTensor, 0, ...);
DataCopy(statusFp32TensorGT[...], cleanStateTensor.ReinterpretCast<float>(), intriOutParams);
}
5.5 AIV 分配策略
低延迟算子将 AIV 分为两组:
// Shared Expert 使用的 AIV 数量
if (sharedExpertRankNum_ != 0U) {
sharedUsedAivNum_ = (aivNum_ * sharedExpertNum) / (axisK_ + sharedExpertNum);
if (sharedUsedAivNum_ == 0) { sharedUsedAivNum_ = 1; }
}
// MoE Expert 使用的 AIV 数量
moeUsedAivNum_ = aivNum_ - sharedUsedAivNum_;
aivId < moeUsedAivNum_: 处理 MoE Expert 数据搬运aivId >= moeUsedAivNum_: 处理 Shared Expert 数据搬运
5.6 与 normal 版的差异
| 维度 | normal_zb | lowlatency_zb |
|---|---|---|
| Notify 阶段 | 独立算子 (moe_notify_dispatch_zb) | 内联在算子中 |
| 同步机制 | ZeroBufferSyncFlag | SyncCollectives + 自定义 flag |
| Shared Expert | 不支持 | 支持 |
| 量化 | DynamicQuant | StaticQuant + DynamicQuant + SmoothScale |
| AllGather | 不支持 | 支持 (IsNeedAllgather) |
| AIV 分配 | 全部用于数据搬运 | 分 shared/moe 两组 |
| 执行阶段 | 2 步 (BarrierAll → 写数据 → BarrierAll) | 6 步 |
6. moe_notify_dispatch_zb — 元数据通知算子
6.1 算子概述
moe_notify_dispatch_zb 是 MoE Dispatch 流程的元数据交换阶段,负责:
1. AllGather 每个 rank 的 tokenPerExpertData(每个 expert 的 token 数)
2. 计算 recvData(全局 rank × expert 矩阵)
3. 计算 totalRecvTokens(最大接收 token 总数)
4. 计算 maxBs(最大 batch size)
5. 计算 recvTokensPerExpert(每个 expert 接收的 token 数)
6. 计算 putOffset(前缀和偏移表)
6.2 算子注册 (op_host/notify_dispatch_zero_buffer.cpp)
// 输入
this->Input("tokenPerExpertData") // DT_FLOAT16/DT_FLOAT/DT_INT32
// 输出
this->Output("recvData") // 全局 rank × expert 矩阵
this->Output("totalRecvTokens") // 最大接收 token 总数
this->Output("maxBs") // 最大 batch size
this->Output("recvTokensPerExpert") // DT_INT64, 每个 expert 接收的 token 数
this->Output("putOffset") // DT_INT32, 前缀和偏移表
// 属性
this->Attr("send_count").Int()
this->Attr("rank_size").Int()
this->Attr("rank_id").Int()
this->Attr("local_rank_size").Int()
this->Attr("local_rank_id").Int()
this->Attr("topk_num").Int()
this->Attr("zero_buffer_ptr").Int() // ← ZB 对称内存基地址
6.3 Tiling 数据
struct NotifyDispatchZeroBufferInfo {
uint32_t rankSize;
uint32_t rankId;
uint32_t localRankSize;
uint32_t localRankId;
uint32_t sendCount; // = numExperts
uint32_t topkNum;
uint32_t aivNum;
uint64_t totalUbSize;
};
struct NotifyDispatchZeroBufferTilingData {
NotifyDispatchZeroBufferInfo notifyDispatchInfo;
uint64_t zeroBufferPtr; // ← ZB 对称内存基地址
};
6.4 Kernel 核心实现
核心执行流程 (Process)
template <typename T>
__aicore__ inline void NotifyDispatchZeroBuffer<T>::Process()
{
// Step 1: IncrementMagic — 获取 magic M
syncFlag_.IncrementMagic();
// Step 2: BarrierAll — 确保所有 rank 输入就绪
syncFlag_.BarrierAll();
// Step 3: AllGather 每个 rank 的 tokenPerExpertData
AllGatherSendData();
SyncAll<true>();
// Step 4: 后处理(本地计算,无跨卡依赖)
ReloadRecvData();
BuildTotalRecvCount();
BuildMaxBs();
BuildTotalRecvTokens();
BuildRecvTokenPerExp();
SyncAll<true>();
}
AllGatherSendData — 跨卡数据收集
__aicore__ inline void AllGatherSendData()
{
// 按 core 分片负责一部分 rank
SplitCoreCal(epWorldSize_, rankNumPerBlock, startRankId, endRankId);
for (uint32_t targetRankId = startRankId; targetRankId < endRankId; targetRankId++) {
// ★ 通过 shmem_ptr 获取对端 rank 的 tokenPerExpertData
auto ptr = shmem_ptr(tokenPerExpertData_, targetRankId);
gmRemoteDataGt.SetGlobalBuffer(reinterpret_cast<__gm__ int32_t *>(ptr));
// GM→GM 拷贝(支持类型转换)
CpGM2GMPingPong<int32_t>(numExperts * sizeof(int32_t), gmRemoteDataGt,
recvDataGt_[targetRankId * numExperts], COPYONLY);
}
}
CpGM2GMPingPong — Ping-Pong GM→GM 拷贝
template <typename K, typename U>
__aicore__ inline void NotifyDispatchZeroBuffer<T>::CpGM2GMPingPong(
int64_t dataSizeRemain, const GlobalTensor<U> &sendDataInputGt,
const GlobalTensor<K> &recvDataOutputGT, int op)
{
// 双 buffer ping-pong: UB_HEAD_OFFSET 和 UB_MID_OFFSET
__ubuf__ U *inputUB[2] = {UB_HEAD_OFFSET, UB_MID_OFFSET};
__ubuf__ K *outputUB[2] = {inputUB[0], inputUB[1]};
SetAtomic<K>(op);
for (int64_t i = 0; dataSizeRemain > 0; i++) {
event_t eventId = (i & 1) ? EVENT_ID0 : EVENT_ID1;
CpGM2UB(inputUB[i&1], input + inputOffsetNum, size); // GM→UB
// 类型转换(如需要)
CastImpl(outputUB[i&1], inputUB[i&1], RoundMode::CAST_NONE, size / sizeof(K));
CpUB2GM(output + outputOffsetNum, outputUB[i&1], size); // UB→GM
dataSizeRemain -= size;
}
UnsetAtomic(op);
}
BuildMaxBs — 计算最大 batch size
__aicore__ inline void BuildMaxBs()
{
int32_t maxBsNum = 0;
for (uint32_t srcRankId = 0; srcRankId < epWorldSize_; srcRankId++) {
DataCopy(tokenPerExpertDataLt, recvDataTensor_[numExperts * srcRankId], numExperts);
Cast(floatExpTokenCntLt, tokenPerExpertDataLt, RoundMode::CAST_NONE, numExperts);
ReduceSum(floatExpTokenSumCntLt, floatExpTokenCntLt, sharedTmpBuffer, numExperts);
int32_t curRankBsNum = static_cast<int32_t>(floatExpTokenSumCntLt(0));
maxBsNum = curRankBsNum > maxBsNum ? curRankBsNum : maxBsNum;
}
maxBsGt.SetValue(0, maxBsNum / topkNum_);
}
BuildTotalRecvCount — 计算前缀和偏移表
__aicore__ inline void BuildTotalRecvCount()
{
for (uint32_t rank = startRankId; rank < endRankId; ++rank) {
// 转置并计算前缀和
ReorderRecvDataOutput(rank, recvTokenLt, true); // isCumSum = true
DataCopyPad(recvCntGt[rank * numExperts], recvTokenLt, copyParams);
}
}
ReorderRecvDataOutput — 矩阵转置 + 前缀和
__aicore__ inline void ReorderRecvDataOutput(int32_t rankId, LocalTensor<int32_t> &transLt, bool isCumSum)
{
uint32_t moeExpertPerRankNum = numExperts / epWorldSize_;
uint32_t startExpId = rankId * moeExpertPerRankNum;
uint32_t endExpId = rankId * moeExpertPerRankNum + moeExpertPerRankNum;
int32_t prefixSum = 0;
for (uint32_t expId = startExpId; expId < endExpId; ++expId) {
for (uint32_t srcRank = 0; srcRank < epWorldSize_; ++srcRank) {
uint32_t index = (expId - startExpId) * epWorldSize_ + srcRank;
uint32_t pairIdx = srcRank * numExperts + expId;
int32_t curRecvCount = recvDataTensor_(pairIdx);
transLt(index) = isCumSum ? prefixSum : curRecvCount;
prefixSum += curRecvCount;
}
}
}
6.5 远端地址计算
template <typename T>
__aicore__ inline GM_ADDR NotifyDispatchZeroBuffer<T>::GetWindStateAddrByRankId(const int32_t rankId)
{
auto ptr = shmem_ptr((__gm__ T *)gva_gm, rankId);
return (GM_ADDR)(ptr) + WIN_MAGIC_OFFSET + bufferId_ * HALF_WIN_STATE_OFFSET;
}
6.6 ZeroBufferSyncFlag 初始化
// 使用 8KB 的 flag 缓冲区
constexpr static uint32_t UB_FLAG_SIZE = 8U * 1024U;
pipe_.InitBuffer(tBuf, UB_FLAG_SIZE);
// per-core 粒度:slotsPerRank = blockNum
syncFlag_.Init(gva_gm, epRankId_, epWorldSize_, blockNum_, tBuf);
7. SHMEM 集成方式
7.1 SHMEM API 调用汇总
ZB 系列算子使用以下 SHMEM API(来自 CANN shmem.h):
| API | 功能 | 使用算子 |
|---|---|---|
aclshmem_ptr(base, rankId) |
获取对端 rank 的远端内存指针 | 全部 |
aclshmem_half_put_nbi(dst, src, count, dstRank) |
half 类型非阻塞 RDMA put | lowlatency_zb |
aclshmem_bfloat16_put_nbi(dst, src, count, dstRank) |
bf16 类型非阻塞 RDMA put | lowlatency_zb |
aclshmem_float_put_nbi(dst, src, count, dstRank) |
float 类型非阻塞 RDMA put | lowlatency_zb |
shmem_get_int32_mem_nbi(dst, src, count, srcRank) |
int32 类型非阻塞 RDMA get | lowlatency_zb |
7.2 类型分发宏 (lowlatency_zb)
#define ZERO_BUFFER_PUT_BY_DTYPE(dtype, ...) \
do { \
if constexpr (std::is_same_v<dtype, half>) { \
aclshmem_half_put_nbi(__VA_ARGS__); \
} else if constexpr (std::is_same_v<dtype, bfloat16_t>) { \
aclshmem_bfloat16_put_nbi(__VA_ARGS__); \
} else if constexpr (std::is_same_v<dtype, float>) { \
aclshmem_float_put_nbi(__VA_ARGS__); \
} \
} while (0)
7.3 远端指针获取模式
所有 ZB 算子使用相同的模式获取远端指针:
// 1. 获取对端 rank 的对称内存基地址
auto ptr = reinterpret_cast<__gm__ uint8_t *>(shmem_ptr(baseAddr, dstRankId));
// 2. 计算偏移量
uint64_t offset = ...; // 根据 expertId、tokenIdx 等计算
// 3. 设置 GlobalTensor 指向远端地址
dstGT.SetGlobalBuffer((__gm__ DataType *)(ptr + offset));
// 4. 使用 DataCopyPad 进行 RDMA 写入
DataCopyPad(dstGT, srcTensor, copyParams);
7.4 同步原语
ZB 算子使用两种同步原语:
- 卡内同步:
SyncAll<true>()— 确保所有 AIV core 完成当前阶段 - 跨卡同步:
ZeroBufferSyncFlag— 基于共享内存 flag 的轻量级跨卡同步
8. 性能优势分析
8.1 内存占用对比
| 组件 | HCCL Window 模式 | Zero-Buffer 模式 |
|---|---|---|
| HCCL Buffer | ~200MB/卡 | 0 |
| Flag 区域 | N/A | ~100KB/卡 (Magic + Barrier + Flag) |
| 通信窗口 | 预分配固定大小 | 按需使用,无固定窗口 |
| 总计 | ~200MB/卡 | ~100KB/卡 |
内存节省: 每卡节省约 200MB,对于大规模 EP(如 256 卡)集群,总计节省约 50GB 内存。
8.2 通信延迟对比
| 阶段 | HCCL Window 模式 | Zero-Buffer 模式 |
|---|---|---|
| 数据写入 | 用户 Buffer → HCCL Window | 用户 Buffer → RDMA → 对端用户 Buffer |
| 同步信号 | HCCL Collective | SHMEM flag 轮询 |
| 数据读取 | 对端 HCCL Window → 对端用户 Buffer | 直接读取(已在用户 Buffer) |
延迟降低原因: 1. 消除中间拷贝: 数据直接从源写入目标,无需经过 Window 中转 2. 用户态同步: SHMEM flag 轮询避免 HCCL Collective 的软件栈开销 3. 硬件直通: RDMA 引擎直接访问用户内存,无需 CPU 干预
8.3 同步机制优势
Magic + Ping-Pong 机制:
- 每个 kernel 调用获取唯一 magic 值
- magic % 2 选择 ping-pong buffer,避免与上一次调用的 flag 冲突
- 单调递增 magic 保证 stale flag 被自动拒绝
- 无需显式清理 flag 区域(ping-pong 自动覆盖)
BarrierAll 协议:
SyncAll (卡内) → Write flags to all ranks → Wait all ranks' flags → SyncAll (卡内)
- 卡内同步确保所有 core 完成计算
- 分 core 策略:每个 core 负责一部分 rank,避免热点
- 无环依赖保证:DAG 结构,不会死锁
8.4 量化加速
normal_zb 和 lowlatency_zb 支持 INT8 动态量化: - 将 BF16/FP16 数据量化为 INT8 传输,带宽需求减半 - 在目标卡上反量化恢复精度 - 量化/反量化使用向量单元并行处理
8.5 AIV 并行策略
ZB 算子充分利用 Ascend 910 的 AIV(AI Vector)架构:
- 最多 48 个 AIV core 并行工作
- 每个 core 负责一部分 token 或一部分 rank
- 分片策略:SplitToCore() / SplitCoreCal() 均匀分配负载
9. 关键数据结构与常量汇总
9.1 共享常量
// zero_buffer_sync_flag.h
constexpr uint32_t FLAG_SLOT_SIZE = 32U; // 每个 flag slot 32B
constexpr uint32_t FLAG_ELEM_NUM = 4; // 4 × int64_t
constexpr uint64_t FLAG_AREA_BASE = 100UL * 1024UL; // Flag 区域起始偏移 100KB
constexpr uint32_t MAGIC_SLOT_ALIGN = 512U; // 每个 core 的 magic slot 512B
constexpr uint32_t MAX_CORE_NUM_SYNC = 48U; // 最大 48 个 core
constexpr uint64_t MAGIC_AREA_SIZE = 48UL * 512UL; // Magic 区域 24KB
// comm_args.h
constexpr int CAM_MAX_RANK_SIZE = 384; // 最大 384 卡
constexpr int64_t UB_SINGLE_DMA_SIZE_MAX = 190 * 1024; // UB 单次 DMA 最大 190KB
constexpr int UB_ALIGN_SIZE = 32; // UB 32B 对齐
9.2 算子特定常量
| 算子 | 关键常量 | 值 |
|---|---|---|
| layout_zb | UB_MAX_SIZE | 190KB |
| layout_zb | TILING_KEY_INT / TILING_KEY_A2_INT | 23 / 123 |
| normal_zb | NOTIFY_MAGIC_OFFSET | 50KB |
| normal_zb | WIN_MAGIC_OFFSET | 100KB |
| normal_zb | HALF_WIN_STATE_OFFSET | 8MB |
| normal_zb | NOTIFY_WIN_STATE_OFFSET | 2MB |
| lowlatency_zb | DISPATCH_SYNC_FLAG_BASE_OFFSET | FLAG_AREA_BASE + 64KB |
| lowlatency_zb | DISPATCH_SYNC_STATE_OFFSET | 32B |
| notify_dispatch_zb | WIN_MAGIC_OFFSET | 100KB |
| notify_dispatch_zb | HALF_WIN_STATE_OFFSET | 8MB |
| notify_dispatch_zb | UB_FLAG_SIZE | 8KB |
9.3 工作空间需求
| 算子 | SYSTEM | KERNEL | A2_ARGS | 总计 |
|---|---|---|---|---|
| layout_zb | 16MB | 1MB | 1MB | 18MB |
| normal_zb | - | - | - | 由 tiling 计算 |
| lowlatency_zb | - | - | - | 由 tiling 计算 |
| notify_dispatch_zb | 16MB | 1MB | 1MB | 18MB |
9.4 文件路径汇总
src/cam/comm_operator/ascend_kernels/
├── moe_dispatch_layout_zb/
│ ├── op_api/aclnn_dispatch_layout_zero_buffer.{cpp,h}
│ ├── op_host/dispatch_layout.{cpp, tiling.cc}
│ └── op_kernel/dispatch_layout.{cpp,h, tiling.h, a2.h}
├── moe_dispatch_normal_zb/
│ ├── op_api/aclnn_moe_dispatch_normal_zero_buffer.{cpp,h}
│ ├── op_host/moe_dispatch_normal_zero_buffer.{cpp, tiling.cc}
│ └── op_kernel/moe_dispatch_normal_zero_buffer.{cpp,h, tiling.h}
├── moe_dispatch_lowlatency_zb/
│ ├── op_api/aclnn_moe_dispatch_lowlatency_zero_buffer.{cpp,h}
│ ├── op_host/moe_dispatch_lowlatency_zero_buffer.{cpp, tiling.cpp}
│ └── op_kernel/moe_dispatch_lowlatency_zero_buffer.{cpp,h, tiling.h}
├── moe_notify_dispatch_zb/
│ ├── op_api/aclnn_notify_dispatch_zero_buffer.{cpp,h}
│ ├── op_host/notify_dispatch_zero_buffer.{cpp, tiling.cc}
│ └── op_kernel/notify_dispatch_zero_buffer.{cpp,h, tiling.h}
└── utils/op_kernel/
├── zero_buffer_api.h
├── zero_buffer_sync_flag.h
├── comm_args_zb.h
├── data_copy_zb.h
├── moe_lowlatency_zero_buffer_base.h
└── sync_collectives_zb.h
附录:ZB Dispatch 完整执行流程
┌─────────────────────────────────────────────────────────────────────────┐
│ ZB MoE Dispatch Pipeline │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ layout_zb │ │ notify_dispatch │ │ normal_zb / │ │
│ │ (本地计算) │ │ _zb (元数据交换) │ │ lowlatency_zb │ │
│ │ │ │ │ │ (数据搬运) │ │
│ │ 1. 统计token数 │───▶│ 1. AllGather │───▶│ 1. RDMA写入 │ │
│ │ 2. 计算isToken │ │ 2. 计算偏移表 │ │ 2. 跨卡同步 │ │
│ │ 3. 计算sendIdx │ │ 3. 计算maxBs │ │ 3. 清理 │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ numTokensPerRank recvData expandXOut │
│ numTokensPerExpert totalRecvTokens dynamicScalesOut │
│ isTokenInRank maxBs assistInfoForCombine │
│ notifySendData recvTokensPerExpert expertTokenNums │
│ sendTokenIdxSmall putOffset epSendCounts │
│ │
│ ★ 所有跨卡通信通过 SHMEM RDMA 完成 │
│ ★ 所有跨卡同步通过 ZeroBufferSyncFlag 完成 │
│ ★ 零额外内存开销(无需 HCCL Window) │
└─────────────────────────────────────────────────────────────────────────┘
分析说明: 本报告基于
remotes/origin/cam_feature分支的实际代码分析,所有函数名、常量值、数据结构均来自源码。SHMEM API (shmem.h) 为 CANN 工具链外部头文件,其具体实现未在 CAM 仓库中。