ZB (Zero-Buffer) Combine 算子深度分析

项目: UMDK/CAM — Communication Acceleration for Matrix on Ascend NPU
分支: cam_feature (remotes/origin/cam_feature)
分析日期: 2026-07-21
分析范围: moe_combine_normal_zb + moe_combine_lowlatency_zb 两个 Zero-Buffer Combine 算子


目录

  1. ZB Combine 设计哲学
  2. moe_combine_normal_zb 完整分析
  3. moe_combine_lowlatency_zb 完整分析
  4. Combine 阶段的 SHMEM 通信
  5. 与 master 版 moe_combine_normal 的对比
  6. Tiling 策略
  7. 关键数据结构与内存布局

1. ZB Combine 设计哲学

1.1 Zero-Buffer 的核心思想

Zero-Buffer 是 CAM 库在 cam_feature 分支上引入的全新 MoE 通信范式。其核心洞察是:

传统 MoE Combine 需要两次数据搬运: 1. 每个 rank 将本地 GMM 输出 CopyBufferToShare 写入远端 rank 的 HCCL Window 2. 通过 polling WaitBuffCopy 等待所有源 rank 的数据到位 3. 最后执行加权求和

Zero-Buffer 将其简化为一次直接读取: - 数据在 GMM 完成后直接写入 SHMEM window(由 dispatch 阶段完成) - Combine 阶段通过 shmem_ptr() 直接读取远端 SHMEM 中的数据 - 通过 ZeroBufferSyncFlag 的 magic + phase 协议保证数据可见性

1.2 设计原则

原则 说明
零拷贝 Combine 不需要额外的 buffer 中转,直接从 SHMEM window 读取
Magic 单调递增 每个 kernel 入口调用 IncrementMagic(),保证 flag 唯一性
Ping-Pong 双缓冲 magic % 2 选择 buffer,避免连续 kernel 调用间的 flag 冲突
MTE3 Fence 保序 SetFlag 前强制 MTE3 fence,确保数据写入先于 flag 可见
全 AIV 并行 所有 AIV 核同时参与计算,SplitCoreCal 均匀分配 token

1.3 文件结构

moe_combine_normal_zb/
├── op_kernel/          # 设备端 kernel 代码
│   ├── moe_combine_normal_zero_buffer.cpp    # kernel 入口 + 实现
│   ├── moe_combine_normal_zero_buffer.h      # 类定义
│   └── moe_combine_normal_zero_buffer_tiling.h  # tiling 数据结构
├── op_host/            # 主机端 tiling 代码
│   ├── moe_combine_normal_zero_buffer.cpp    # tiling 入口
│   └── moe_combine_normal_zero_buffer_tiling.cc  # tiling 实现
└── op_api/             # aclnn API 注册
    ├── aclnn_moe_combine_normal_zero_buffer.cpp
    └── aclnn_moe_combine_normal_zero_buffer.h

moe_combine_lowlatency_zb/
├── op_kernel/
│   ├── moe_combine_lowlatency_zero_buffer.cpp
│   ├── moe_combine_lowlatency_zero_buffer.h
│   └── moe_combine_lowlatency_zero_buffer_tiling.h
├── op_host/
│   ├── moe_combine_lowlatency_zero_buffer.cpp
│   └── moe_combine_lowlatency_zero_buffer_tiling.cpp
└── op_api/
    ├── aclnn_moe_combine_lowlatency_zero_buffer.cpp
    └── aclnn_moe_combine_lowlatency_zero_buffer.h

utils/op_kernel/        # 公共工具
├── zero_buffer_api.h         # SHMEM API 封装 (include shmem.h)
├── zero_buffer_sync_flag.h   # 跨卡 flag 同步机制
├── comm_args_zb.h            # ZB 版通信参数
├── sync_collectives_zb.h     # 同步原语
├── moe_lowlatency_zero_buffer_base.h  # lowlatency 共享工具
└── data_copy_zb.h            # 数据拷贝辅助

2. moe_combine_normal_zb 完整分析

2.1 算子签名与 I/O

Kernel 入口 (moe_combine_normal_zero_buffer.cpp):

extern "C" __global__ __aicore__ void moe_combine_normal_zero_buffer(
    GM_ADDR recvX,           // [bs*k, h] 本 rank 收到的所有 token(各 expert 输出)
    GM_ADDR epRecvCount,     // [moeExpertNum, epWorldSize] 每个 expert 从各 rank 收到的 token 数
    GM_ADDR topkWeights,     // [bs, k] top-k 权重
    GM_ADDR topkIdx,         // [bs, k] top-k expert ID
    GM_ADDR sendTokenIdx,    // [bs, k] 在远端 recvX 中的偏移索引
    GM_ADDR probGrad,        // [bs, h] 概率梯度输入(可选)
    GM_ADDR XOut,            // [bs, h] 输出
    GM_ADDR sendCostStatsOut,// [epWorldSize] 诊断统计(可选)
    GM_ADDR gradOut,         // [bs, k] 梯度输出(可选)
    GM_ADDR workspaceGM,
    GM_ADDR tilingGM)

关键输入语义: - recvX: 本 rank 上所有 expert 的 GMM 输出,按 [token_idx, topk_idx] 排列 - epRecvCount: 全局接收计数,epRecvCount[expertId][rankId] 表示该 expert 从 rankId 收到的 token 数 - sendTokenIdx: 每个 (token, topk) 对在其目标 rank 的 recvX 中的位置索引

2.2 执行流程

Process():
│
├── syncFlag_.IncrementMagic()     // Step 1: 获取 magic M,递增
├── syncFlag_.BarrierAll()         // Step 2: 全 rank 屏障,确保所有 GMM 完成
├── ReadBufferFromRemote()         // Step 3: 从远端 SHMEM 读取并加权求和
└── SyncAll<true>()                // Step 4: 全核同步

Step 1: IncrementMagic

__aicore__ inline void IncrementMagic() {
    GM_ADDR magicAddr = flagBaseGM_ + coreIdx_ * MAGIC_SLOT_ALIGN;  // 512B 对齐
    // DataCacheCleanAndInvalid → 读 magic
    magic_ = static_cast<int32_t>(magicGT(0));
    magicGT(0) = magic_ + 1;  // 递增
    // DataCacheCleanAndInvalid → 写回
    barrierSeq_ = 0;
}

每个 core 有独立的 magic slot(512B 对齐),避免 false sharing。

Step 2: BarrierAll

__aicore__ inline void BarrierAll() {
    SyncAll<true>();                              // 核内同步
    SplitCoreCal(epWorldSize_, ...);              // 分配远端 rank
    // Write: 向所有分配的远端 rank 写 barrier flag
    for (r = startRank; r < endRank; r++) {
        DataCopy(remoteSlot, ubFlag, ...);        // 写 flag 到远端 SHMEM
    }
    // Wait: 轮询本地 slot 等待所有远端 rank 的 flag
    for (r = startRank; r < endRank; r++) {
        do { observed = localSlot; } while (observed != barrierFlag);
    }
    SyncAll<true>();
}

关键保证: BarrierAll 返回时,所有 rank 的 GMM 输出已完整写入 SHMEM。

Step 3: ReadBufferFromRemote

__aicore__ inline void ReadBufferFromRemote() {
    SplitCoreCal(axisBS_, ...);  // 将 bs 个 token 均匀分配到各 AIV core
    // 初始化所有 buffer
    tpipe_->InitBuffer(xOutBuf_, h32AlignRecvXLen_);         // 14KB (h=7168, bf16)
    tpipe_->InitBuffer(tokenFloatBuf_, h32AlignFloatLen_);   // 28KB (fp32)
    tpipe_->InitBuffer(weightedMulBuf_, h256AlignFloatLen_); // 28KB
    tpipe_->InitBuffer(sumFloatBuf_, h32AlignFloatLen_);     // 28KB
    tpipe_->InitBuffer(weightedSumQueue_, DOUBLE_BUFFER, h32AlignRecvXLen_); // 28KB
    // ... topkWeightsBuf_, sendTokenIdxBuf_, topkIdxBuf_, allRecvCountBuf_

    // 读取全局 recvCount 到本地
    DataCopyPad(allRecvCountLocal, epRecvCountGT_, ...);

    for (tokenIndex = startTokenIndex; tokenIndex < endTokenIndex; tokenIndex++) {
        // 读取当前 token 的 topkWeights, topkIdx, sendTokenIdx
        DataCopyPad(topkWeightsLocal, topkWeightsGT_[tokenIndex * axisK_], ...);
        DataCopyPad(topkIdxLocal, topkIdxGT_[tokenIndex * axisK_], ...);
        DataCopyPad(sendTokenIdxLocal, sendTokenIdxGT_[tokenIndex * axisK_], ...);

        ReadBufferAndWeightedSum(tokenIndex, startTokenIndex);
    }
}

2.3 加权求和核心逻辑

__aicore__ inline void ReadBufferAndWeightedSum(uint32_t tokenIndex, uint32_t startTokenIndex) {
    Duplicate(sumFloatBufLocal, 0.0f, axisH_);  // 清零累加器

    for (uint32_t topkId = 0U; topkId < axisK_; topkId++) {
        float scale = topkWeightsLocal.GetValue(topkId);
        int32_t expertId = topkIdxLocal.GetValue(topkId);
        int32_t remoteReadOffset = sendTokenIdxLocal(topkId);
        int32_t remoteReadBase = allRecvCountLocal(expertId * epWorldSize_ + epRankId_);
        uint64_t remoteReadAddr = (remoteReadBase + remoteReadOffset) * hRecvXTypeLen_;

        int32_t dstRankId = expertId / moeExpertPerRankNum_;
        auto ptr = reinterpret_cast<__gm__ uint8_t *>(shmem_ptr(recvXGM_, dstRankId));
        dstGT.SetGlobalBuffer((__gm__ XType *)(ptr + remoteReadAddr));

        // 从远端 SHMEM 读取 token
        LocalTensor<XType> tmpToken = weightedSumQueue_.AllocTensor<XType>();
        DataCopyPad(tmpToken, dstGT, xOutCopyParams, ...);
        weightedSumQueue_.EnQue(tmpToken);
        tmpToken = weightedSumQueue_.DeQue<XType>();

        // bf16 → fp32 → 乘权重 → 累加
        Cast(tokenFloatLocal, tmpToken, CAST_NONE, axisH_);      // bf16 → fp32
        Muls(weightedMulBufLocal, tokenFloatLocal, scale, axisH_); // × weight
        Add(sumFloatBufLocal, sumFloatBufLocal, weightedMulBufLocal, axisH_); // 累加

        weightedSumQueue_.FreeTensor<XType>(tmpToken);
    }

    // fp32 → bf16 → 写输出
    Cast(xOutLocal, sumFloatBufLocal, CAST_RINT, axisH_);
    SyncFunc<V_MTE3>();
    DataCopyPad(xOutGlobal_[tokenIndex * axisH_], xOutLocal, ...);
}

加权求和公式:

$$X_{out}[i] = \text{Cast}{fp32 \to bf16}\left(\sum{j=0}^{K-1} w_j \cdot \text{Cast}{bf16 \to fp32}(X{recv}[expert_j, token_j])\right)$$

2.4 地址计算详解

对于 token i 的第 j 个 top-k 选择:

expertId = topkIdx[i][j]                    // 目标 expert
dstRankId = expertId / moeExpertPerRankNum_  // expert 所在的 rank
remoteReadBase = epRecvCount[expertId][epRankId_]  // 本 rank 在该 expert 上的基址偏移
remoteReadOffset = sendTokenIdx[i][j]        // 在该 expert 接收列表中的位置
remoteReadAddr = (remoteReadBase + remoteReadOffset) * hRecvXTypeLen_

// 最终地址 = 远端 rank 的 SHMEM window + remoteReadAddr
finalAddr = shmem_ptr(recvXGM_, dstRankId) + remoteReadAddr

2.5 UB 内存预算

Buffer 大小 说明
xOutBuf_ 14KB 输出 token (h=7168 × bf16)
tokenFloatBuf_ 28KB fp32 中间结果
weightedMulBuf_ 28KB 256 对齐的乘法结果
sumFloatBuf_ 28KB 累加器
weightedSumQueue_ 28KB 双缓冲 DMA queue
topkWeightsBuf_ 32B k 个 float 权重
sendTokenIdxBuf_ 32B k 个 int32 索引
topkIdxBuf_ 32B k 个 int32 expert ID
allRecvCountBuf_ 128KB epWorldSize × moeExpertNum × 4B (max 512×64×4=128KB)
总计 ~254KB

3. moe_combine_lowlatency_zb 完整分析

3.1 算子签名与 I/O

Kernel 入口 (moe_combine_lowlatency_zero_buffer.cpp):

extern "C" __global__ __aicore__ void moe_combine_lowlatency_zero_buffer(
    GM_ADDR expandX,            // [A*tpWorldSize, h] 展开的 expert 输出
    GM_ADDR expertIds,          // [bs, k] top-k expert ID
    GM_ADDR assistInfoForCombine, // [A*128] 辅助信息
    GM_ADDR epSendCount,        // [moeExpertNum*epWorldSize] EP 发送计数
    GM_ADDR scales,             // [bs, k] top-k 权重
    GM_ADDR tpSendCount,        // [tpWorldSize] TP 发送计数
    GM_ADDR xActiveMask,        // [bs] 或 [bs, k] 活跃 mask(可选)
    GM_ADDR activationScale,    // 预留
    GM_ADDR weightScale,        // 预留
    GM_ADDR groupList,          // 预留
    GM_ADDR expandScales,       // 预留
    GM_ADDR sharedExpertX,      // [bs, h] 共享专家输出(可选)
    GM_ADDR elasticInfo,        // 弹性信息(可选)
    GM_ADDR oriX,               // [bs, h] 原始输入(copy expert 用)
    GM_ADDR constExpertAlpha1,  // [constExpertNum] 常量专家参数
    GM_ADDR constExpertAlpha2,  // [constExpertNum]
    GM_ADDR constExpertV,       // [constExpertNum, h]
    GM_ADDR XOut,               // [bs, h] 输出
    GM_ADDR workspaceGM, GM_ADDR tilingGM)

TilingKey 编码:

10000: tp=1, IsInt8Quant=0
10100: tp=2, IsInt8Quant=0
10020: tp=1, IsInt8Quant=1
10120: tp=2, IsInt8Quant=1

3.2 执行流程

Process():
│
├── CopyValidExpandXToRemote()    // Step 1: 拷贝本地 expert 数据到 SHMEM
├── SyncAll<true>()               // Step 2: 全核同步
├── tpipe_->Reset()               // 重置 pipe
├── SetSyncFlag()                 // Step 3: 向远端 rank 写 flag
├── WaitSyncFlag()                 // Step 4: 轮询等待远端 flag
├── SyncAll<true>()               // Step 5: 全核同步
└── InputToDstOutput()            // Step 6: 从 SHMEM 读取并加权求和

Step 1: CopyValidExpandXToRemote

__aicore__ inline void CopyValidExpandXToRemote() {
    if (!needCopyExpandX_) return;  // oriX==0 或 validTokenNum==0 时跳过

    SplitToCore(validTokenNum_, aivNum_, ...);  // 分配 token
    tpipe_->InitBuffer(copyInBuf_, COPY_BUFFER_NUM * GM2GM_COPY_BLOCK_SIZE);  // 2×64KB

    // 双缓冲 GM→GM 拷贝:oriX → expandX (SHMEM)
    for (i = 0; dataSizeRemain > 0; ++i) {
        copySize = min(dataSizeRemain, 64KB);
        WaitFlag<MTE3_MTE2>(eventId);           // 等待 DMA 通道
        DataCopyPad(copyLocal[bufferOffset], oriXByteGT[srcOffset], ...);  // GM→UB
        SetFlag<MTE2_MTE3>(eventId);
        WaitFlag<MTE2_MTE3>(eventId);
        DataCopyPad(expandXByteGT[dstOffset], copyLocal[bufferOffset], ...);  // UB→GM(SHMEM)
        SetFlag<MTE3_MTE2>(eventId);
    }
}

注意: 这是 lowlatency 特有的步骤。因为 lowlatency 模式下,本地 expert 的输出需要从 oriX(原始位置)拷贝到 expandX(SHMEM window)以供远端 rank 读取。normal_zb 不需要此步骤,因为数据已在 dispatch 阶段直接写入 SHMEM。

Step 3-4: SetSyncFlag / WaitSyncFlag

__aicore__ inline void SetSyncFlag() {
    // 构造 flag 值:0x3F800000 = 1.0f (IEEE 754)
    Duplicate(statusTensor, 0, rankNumPerBlock_ * 8);
    Duplicate(statusTensor, 0x3F800000, mask, ...);  // 按 mask 设置 1.0

    for (targetRankId = curBlockStartRankId_; targetRankId < curBlockEndRankId_; ++targetRankId) {
        auto ptr = GetSyncFlagAddrByRankId(targetRankId) + epRankId_ * COMBINE_SYNC_STATE_OFFSET;
        gmRemoteStatusGT.SetGlobalBuffer(ptr);
        DataCopy(gmRemoteStatusGT, statusTensor[...], 8UL);
    }
}

__aicore__ inline void WaitSyncFlag() {
    // 轮询本地 SHMEM 中的 flag 区域
    compareTarget = 1.0f * rankNumPerBlock_;
    while ((sumOfFlag < minTarget) || (sumOfFlag > maxTarget)) {
        DataCopy(statusFp32Tensor, statusFp32TensorGT[curBlockStartRankId_ * ...], ...);
        ReduceSum(statusSumOutTensor, statusFp32Tensor, ...);
        sumOfFlag = statusSumOutTensor.GetValue(0);
    }
    // 清理 flag
    Duplicate(cleanStateTensor, 0, ...);
    DataCopy(statusFp32TensorGT[...], cleanStateTensor.ReinterpretCast<float>(), ...);
}

同步协议: 每个 rank 向远端 rank 的 SHMEM flag 区域写入 1.0f。当本 rank 收到所有远端 rank 的 flag 后(sum == rankNumPerBlock),表示所有远端数据已就绪。

Step 6: InputToDstOutput

__aicore__ inline void InputToDstOutput() {
    SplitToCore(axisBS_, aivNum_, ...);  // 分配 token

    for (tokenIndex = startTokenId; tokenIndex < endTokenId; tokenIndex++) {
        DataCopyPad(expertIdsTensor, expertIdsGM_[tokenIndex * axisK_], ...);
        Duplicate(sumTokenTensor_, 0.0f, axisH_);

        for (topkId = 0U; topkId < axisK_; topkId++) {
            int32_t dstExpertId = expertIdsTensor.GetValue(topkId);
            int32_t srcRankId = dstExpertId / moeExpertPerRankNum_;
            uint32_t offsetIdx = dstExpertId * epWorldSize_ + epRankId_;
            uint32_t col = offsetIdx % moeExpertNum_;
            int32_t remoteBase = (col == 0) ? 0 : epSendCountGM_(offsetIdx - 1);
            int32_t remoteOffset = expandIdxLocal(localTokenId * axisK_ + topkId);
            float scale = expertScalesLocal_.GetValue(localTokenId * axisK_ + topkId);

            // 从远端 SHMEM 读取
            auto srcPtr = shmem_ptr(expandXGM_, srcRankId);
            srcWinGMTensor.SetGlobalBuffer(srcPtr + hExpandXTypeSize_ * (remoteBase + remoteOffset));

            // 加权求和(同 normal_zb)
            DataCopyPad(tmpToken, srcWinGMTensor, ...);
            Cast(tokenReduceTensor_, tmpToken, CAST_NONE, axisH_);
            Muls(weightedSumTensor_, tokenReduceTensor_, scale, axisH_);
            Add(sumTokenTensor_, sumTokenTensor_, weightedSumTensor_, axisH_);
        }

        Cast(xOutLocal, sumTokenTensor_, CAST_RINT, axisH_);
        DataCopyPad(XOutGT_[tokenIndex * axisH_], xOutLocal, ...);
    }
}

3.3 与 normal_zb 的关键差异

维度 normal_zb lowlatency_zb
同步机制 ZeroBufferSyncFlag (通用框架) 自实现 SetSyncFlag/WaitSyncFlag
Magic 管理 IncrementMagic() 自动管理 无 magic,使用固定 flag 值 1.0f
本地数据拷贝 不需要(dispatch 已写入 SHMEM) 需要 CopyValidExpandXToRemote
地址计算 epRecvCount 全局表 + sendTokenIdx epSendCount 前缀和 + expandIdx
TP 支持 仅 EP EP + TP (tpWorldSize=1 or 2)
共享专家 不支持 支持 (sharedExpertNum, sharedExpertRankNum)
弹性缩容 不支持 支持 (elasticInfo)
常量专家 不支持 支持 (constExpertAlpha1/2/V)
Copy Expert 不支持 支持 (oriX → expandX 拷贝)
量化通信 不支持 INT8 量化 (IsInt8Quant)
TilingKey 无 4 种组合 (tp × quant)
输入复杂度 6 个输入 17 个输入

3.4 lowlatency 的地址计算

// 前缀和计算远端基址
offsetIdx = dstExpertId * epWorldSize_ + epRankId_
col = offsetIdx % moeExpertNum_
remoteBase = (col == 0) ? 0 : epSendCountGM_(offsetIdx - 1)
remoteOffset = expandIdxLocal(localTokenId * axisK_ + topkId)

// 最终地址
srcAddr = shmem_ptr(expandXGM_, srcRankId) + hExpandXTypeSize_ * (remoteBase + remoteOffset)

epSendCount 存储的是前缀和(prefix sum),epSendCount[i-1] 即为第 i 个 expert 在本 rank 上的数据基址。


4. Combine 阶段的 SHMEM 通信

4.1 SHMEM API 层次

shmem.h (CANN 提供)
    ├── shmem_ptr(baseGM, rankId)              → 获取远端 rank 的 SHMEM window 基址
    ├── aclshmem_half_put_nbi(...)             → 非阻塞 half 写入远端
    ├── aclshmem_bfloat16_put_nbi(...)         → 非阻塞 bf16 写入远端
    ├── aclshmem_float_put_nbi(...)            → 非阻塞 float 写入远端
    ├── aclshmem_half_get_nbi(...)             → 非阻塞 half 读取远端
    └── aclshmem_bfloat16_get_nbi(...)         → 非阻塞 bf16 读取远端

zero_buffer_api.h (CAM 封装)
    └── #include "shmem.h"

zero_buffer_sync_flag.h (同步框架)
    └── ZeroBufferSyncFlag 类

4.2 shmem_ptr 的使用模式

两个 combine 算子都使用 shmem_ptr 进行远端 SHMEM 读取:

// normal_zb
auto ptr = reinterpret_cast<__gm__ uint8_t *>(shmem_ptr(recvXGM_, dstRankId));
dstGT.SetGlobalBuffer((__gm__ XType *)(ptr + remoteReadAddr));

// lowlatency_zb
auto srcPtr = reinterpret_cast<__gm__ uint8_t *>(shmem_ptr(expandXGM_, srcRankId));
srcWinGMTensor.SetGlobalBuffer((__gm__ ExpandXType *)(srcPtr + hExpandXTypeSize_ * (remoteBase + remoteOffset)));

shmem_ptr(baseGM, rankId) 语义: 返回 rankId 的 SHMEM window 虚拟地址,该地址在本 rank 的地址空间中映射到远端 rank 的 SHMEM 物理内存(通过 HCCL 的 window 机制)。

4.3 ZeroBufferSyncFlag 内存布局

gva_gm + FLAG_AREA_BASE (100KB):
├── Magic Area (24KB = 48 cores × 512B)
│   ├── slot[0] @ 0×512B     ← core 0 的 magic
│   ├── slot[1] @ 1×512B     ← core 1 的 magic
│   └── ...
├── Barrier PingPong Buffer 0 (epWorldSize × 32B)
│   ├── slot[srcRank=0]       ← rank 0 写入的 barrier flag
│   ├── slot[srcRank=1]
│   └── ...
├── Barrier PingPong Buffer 1 (同上)
├── Flag PingPong Buffer 0 (epWorldSize × slotsPerRank × 32B)
│   ├── slot[srcRank=0][eventID=0]
│   ├── slot[srcRank=0][eventID=1]
│   └── ...
└── Flag PingPong Buffer 1 (同上)

4.4 同步协议详解

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 的数据保证可见

DAG 保证(无环依赖):

DataWrite(A→B) ── MTE3 fence ──► SetFlag(A→B)
                                    │
                                    ▼
                              WaitFlag(B←A) ──► DataRead(B←A)

4.5 lowlatency 的自定义同步

lowlatency_zb 没有使用 ZeroBufferSyncFlag 框架,而是实现了自己的同步:

// 同步区域: metaDataGvaGM_ + COMBINE_SYNC_FLAG_BASE_OFFSET (100KB)
// 每个 rank 占 COMBINE_SYNC_STATE_OFFSET (32B)

// SetSyncFlag: 向远端 rank 的 flag 区域写入 1.0f
GetSyncFlagAddrByRankId(rankId) = shmem_ptr(metaDataGvaGM_, rankId) + 100KB + epRankId_ * 32B

// WaitSyncFlag: 轮询本 rank 的 flag 区域,直到所有远端 rank 都写入
// 使用 ReduceSum 求和,期望值 = rankNumPerBlock_

与 normal_zb 的区别: - normal_zb 使用 magic + phase 的 64-bit flag,支持跨 kernel 同步 - lowlatency_zb 使用固定值 1.0f,仅支持单次 kernel 内同步 - lowlatency_zb 在等待完成后会清理 flag(写回 0),为下次同步做准备


5. 与 master 版 moe_combine_normal 的对比

5.1 架构差异总览

维度 master (HCCL Window) cam_feature (Zero-Buffer)
数据搬运 CopyBufferToShare → WaitBuffCopy 直接 SHMEM 读取
同步机制 polling WaitBuffCopy (per-token per-topk) ZeroBufferSyncFlag (per-rank)
Magic 管理 GetHcclContext + localWindowsExp IncrementMagic() + per-core slot
Window 访问 epWinContext_->localWindowsIn / remoteRes shmem_ptr(baseGM, rankId)
状态管理 per-token per-topk 的 32B status slot per-rank 的 64B flag slot
数据源 tokenSrcInfo (3-tuple: rankId, tokenIdx, topkIdx) sendTokenIdx + epRecvCount
Copy 方向 本地 → 远端 (CopyBufferToShare) 远端 → 本地 (DataCopyPad from SHMEM)

5.2 master 版执行流程

// master: moe_combine_normal.cpp
Process():
├── CopyBufferToShareAndSetStatus()    // 将本地 token 拷贝到远端 window
│   └── for each token:
│       ├── CopyBufferToShare()        // DataCopyPad 到远端 SHMEM
│       └── SetStatusBySrcInfo()       // 写 status flag
└── ReadBufferFromRemote()             // 读取远端数据
    └── for each token:
        ├── WaitBuffCopy()             // 轮询等待所有源 rank 数据到位
        └── ReadBufferAndWeightedSum() // 加权求和

5.3 ZB 版执行流程

// cam_feature: moe_combine_normal_zero_buffer.cpp
Process():
├── syncFlag_.IncrementMagic()         // 获取 magic
├── syncFlag_.BarrierAll()             // 全 rank 屏障
├── ReadBufferFromRemote()             // 直接读取远端 SHMEM
│   └── for each token:
│       └── ReadBufferAndWeightedSum() // 直接从远端 SHMEM 读取 + 加权
└── SyncAll<true>()

5.4 关键差异分析

5.4.1 数据流方向

master: 推模式 (Push)

Rank A: CopyBufferToShare → 写入 Rank B 的 window
Rank B: WaitBuffCopy → 轮询等待 → 从本地 window 读取

ZB: 拉模式 (Pull)

Rank A: (dispatch 阶段已写入 SHMEM)
Rank B: shmem_ptr(recvXGM_, rankA) → 直接读取 Rank A 的 SHMEM

优势: ZB 消除了 CopyBufferToShare 阶段,Combine 不需要先将本地数据推送到远端。

5.4.2 同步粒度

master: per-token per-topk 粒度

// 每个 (token, topk) 对有一个 32B status slot
GM_ADDR stateGM = GetStateAddrByRankId(srcRankId) + (srcTokenId * axisK_ + srcTopkId) * UB_32_ALIGN;
// 轮询直到所有 K 个 source 都到位
while (current != target) {
    DataCopy(stateTensorLocal, stateGMTensor, calCount);
    Sum(tempStateTensorLocal, stateTensorLocal, sumPerKParams);
    current = tempStateTensorLocal(0);
}

ZB: per-rank 粒度

// 每个 rank 有一个 64B flag slot
// BarrierAll 一次等待所有 rank
syncFlag_.BarrierAll();

优势: ZB 的同步粒度更粗,减少了 polling 次数和 flag 写入次数。

5.4.3 Magic 管理

master: 使用 HCCL context 中的 magic

auto contextGM0 = AscendC::GetHcclContext<HCCL_GROUP_ID_0>();
epWinContext_ = (__gm__ HcclOpResParam *)contextGM0;
selfMagicTensor.SetGlobalBuffer(epWinContext_->localWindowsExp + MAGIC_WIN_OFFSET + coreIdx_ * WIN_512_ALIGN);
magic_ = selfMagicTensor(0);  // 0 或 1 翻转
selfMagicTensor(0) = ((magic_ == 0) ? 1 : 0);

ZB: 使用 ZeroBufferSyncFlag 的递增 magic

magic_ = static_cast<int32_t>(magicGT(0));
magicGT(0) = magic_ + 1;  // 单调递增

优势: ZB 的 magic 单调递增,可以区分任意历史 kernel 的 flag,不会产生 ABA 问题。

5.4.4 内存占用

master: - 需要 tokenSrcInfo 数组 (bs × k × 12B) - 需要 per-token per-topk 的 status slot (bs × k × 32B) - 需要 ping-pong 双缓冲

ZB: - 需要 sendTokenIdx 数组 (bs × k × 4B) - 需要 epRecvCount 表 (moeExpertNum × epWorldSize × 4B) - 只需要 per-rank 的 flag slot

5.5 性能提升预估

优化点 预估收益
消除 CopyBufferToShare 减少一次 GM→GM DMA,约节省 bs × k × h × sizeof(bf16) 的写入延迟
粗粒度同步 从 O(bs × K) 次 polling 降至 O(1) 次 BarrierAll
消除 WaitBuffCopy 消除 per-token 的 polling 循环(可能涉及数百次循环迭代)
减少 flag 写入 从 bs×K 次 flag 写入降至 epWorldSize 次

6. Tiling 策略

6.1 normal_zb Tiling

核心流程 (MoeCombineNormalZeroBufferA3TilingFuncImpl):

// 1. 获取属性
GetAttrAndSetTilingData(context, *tilingData, nodeName);
// → 读取 meta_data_ptr, ep_world_size, ep_rank_id, moe_expert_num, global_bs

// 2. 校验输入输出
TilingCheckMoeCombineNormalZeroBuffer(context, nodeName, isEnableDiagnose);
// → 检查 dim/format/dataType

// 3. 校验属性
CheckAttrs(context, *tilingData, nodeName, localMoeExpertNum);
// → moeExpertNum % epWorldSize == 0
// → tp=2 时 localMoeExpertNum == 1
// → globalBs == 0 || globalBs >= bs * epWorldSize

// 4. 校验 shape
CheckTensorShape(context, *tilingData, nodeName, localMoeExpertNum);
// → k ∈ (0, min(16, moeExpertNum)]
// → h ∈ [1024, 7168]

// 5. 校验 win 区大小
tokenNeedSizeCombine = Ceil(h * 2 + 511, 512) * 512;  // 512 对齐
actualSize = (maxBs * k * tokenNeedSizeCombine + COMBINE_STATE_WIN_OFFSET + NOTIFY_DISPATCH_WIN_OFFSET) * 2;

// 6. 设置 blockDim 和 schedule mode
blockDim = ascendcPlatform.CalcTschBlockDim(aivNum, 0, aivNum);
context->SetScheduleMode(1);  // batch mode,所有核同时启动

约束条件: - epWorldSize ∈ [2, 384] - tpWorldSize ∈ [0, 2] - moeExpertNum ∈ (0, 512] - k ∈ (0, min(16, moeExpertNum)] - h ∈ [1024, 7168] - (moeExpertNum / epWorldSize) × epWorldSize ≤ 1280

6.2 lowlatency_zb Tiling

核心流程 (MoeCombineLowlatencyA3TilingFuncImpl):

// 1. 获取属性(更复杂)
GetAttrAndSetTilingData(context, *tilingData, nodeName, groupEp, groupTp, commQuantMode);
// → 额外读取 sharedExpertNum, sharedExpertRankNum, zeroExpertNum, copyExpertNum, constExpertNum

// 2. 计算 A(expandX 的 dim0)
if (isShared) {
    A = maxBs * maxSharedGroupNum;  // 共享专家卡
} else {
    A = globalBs * min(localMoeExpertNum, expertIdsDim1);  // MoE 专家卡
}

// 3. 校验 win 区大小
tokenNeedSizeCombine = Ceil(h * 2 + 511, 512) * 512;
tokenActualLen = Ceil(h * 2 + 31, 32) * 32 + 44;  // + scale(32B) + expandIdx(3*4B)
tokenNeedSizeDispatch = Ceil(tokenActualLen + 511, 512) * 512;
actualSize = ((maxBs * tokenNeedSizeDispatch * epWorldSize * localMoeExpertNum) +
              (maxBs * tokenNeedSizeCombine * (k + sharedExpertNum))) * 2;

// 4. 计算 TilingKey
tilingKey = 10000;
if (tpWorldSize == 2) tilingKey += 100;
if (commQuantMode == 2) tilingKey += 20;

// 5. 设置 Mc2CcTiling(HCCL 通信配置)
SetHCommCfg(context, tilingData, groupEp, groupTp);
// → AlltoAll: level0:fullmesh;level1:pairwise
// → ReduceScatter: level0:ring

额外约束: - epWorldSize ∈ [2, 768](支持更大规模) - moeExpertNum ∈ (0, 1024] - h ∈ [1024, 8192] - bs ∈ [1, 512] - sharedExpertNum ∈ [0, 4] - moeExpertNum % (epWorldSize - sharedExpertRankNum) == 0 - zeroBufferPtr != 0

6.3 Win 区大小计算对比

normal_zb:

actualSize = (maxBs × k × tokenNeedSizeCombine + COMBINE_STATE_WIN_OFFSET + NOTIFY_DISPATCH_WIN_OFFSET) × 2
           = (maxBs × k × Ceil(h×2, 512) + 5MB + 204KB) × 2

lowlatency_zb:

actualSize = ((maxBs × tokenNeedSizeDispatch × epWorldSize × localMoeExpertNum) +
              (maxBs × tokenNeedSizeCombine × (k + sharedExpertNum))) × 2

差异: lowlatency 需要同时容纳 dispatch 数据区和 combine 数据区,而 normal 只需要 combine 数据区(dispatch 数据区在 notify 算子中管理)。

6.4 Schedule Mode

两个算子都使用 SetScheduleMode(1) (batch mode): - 所有 AIV 核同时启动 - 核间通过 SyncAll<true>() 同步 - 不需要核间通信,完全并行


7. 关键数据结构与内存布局

7.1 Tiling 数据结构

normal_zb:

struct MoeCombineNormalZeroBufferInfo {
    uint32_t epWorldSize;        // EP 通信域大小
    uint32_t tpWorldSize;        // TP 通信域大小 (0 or 1 or 2)
    uint32_t epRankId;           // 本 rank ID
    uint32_t tpRankId;           // 本 TP rank ID
    uint32_t expertShardType;    // 专家分片类型
    uint32_t moeExpertNum;       // MoE 专家总数
    uint32_t moeExpertPerRankNum; // 每 rank 专家数
    uint32_t globalBs;           // 全局 batch size
    uint32_t bs;                 // 本 rank batch size
    uint32_t k;                  // top-K
    uint32_t h;                  // hidden size
    uint32_t aivNum;             // AIV 核数
    uint64_t totalUbSize;        // UB 总大小
    uint64_t totalWinSize;       // Window 总大小
    float armAvgFactor;          // ARM 平均因子
    float epsilon;               // 数值精度
    bool isEnableDiagnose;       // 是否启用诊断
    bool isGetProb;              // 是否获取概率梯度
};
struct MoeCombineNormalZeroBufferTilingData {
    MoeCombineNormalZeroBufferInfo moeCombineNormalInfo;
    uint64_t zeroBufferPtr;      // Zero-Buffer 对称地址
};

lowlatency_zb:

struct MoeCombineLowlatencyZeroBufferInfo {
    // ... (同上,额外字段)
    uint32_t sharedExpertNum;    // 共享专家数
    uint32_t sharedExpertRankNum; // 共享专家所在 rank 数
    uint32_t zeroExpertNum;      // 零专家数
    uint32_t copyExpertNum;      // 拷贝专家数
    uint32_t constExpertNum;     // 常量专家数
    bool isTokenMask;            // token 级 mask
    bool isExpertMask;           // expert 级 mask
    bool hasSharedExpertX;       // 是否有共享专家输入
    bool hasElasticInfo;         // 是否有弹性信息
    uint64_t zeroBufferPtr;      // Zero-Buffer 地址
};
struct MoeCombineLowlatencyZeroBufferTilingData {
    Mc2InitTiling mc2InitTiling;     // MC2 初始化 tiling
    Mc2CcTiling mc2CcTiling1;        // MC2 CC tiling 1 (AlltoAll)
    Mc2CcTiling mc2CcTiling2;        // MC2 CC tiling 2 (ReduceScatter)
    MoeCombineLowlatencyZeroBufferInfo moeCombineLowlatencyZeroBufferInfo;
};

7.2 常量定义

// normal_zb 内存布局常量
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 = 8UL * 1024UL * 1024UL; // 8MB
constexpr uint64_t COMBINE_WIN_STATE_OFFSET = 5UL * 1024UL * 1024UL; // 5MB
constexpr uint64_t WIN_ADDR_ALIGN = 512UL;

// lowlatency_zb 同步常量
constexpr uint64_t COMBINE_SYNC_FLAG_BASE_OFFSET = 100UL * 1024UL;  // 100KB
constexpr uint64_t COMBINE_SYNC_STATE_OFFSET = 32U;                  // 32B per rank

// 通用常量
constexpr uint32_t UB_32_ALIGN = 32U;
constexpr uint32_t MUL_256_ALIGN = 256U;
constexpr uint8_t DOUBLE_BUFFER = 2;

7.3 类继承/依赖关系

MoeCombineNormalZeroBuffer<RecvXType, XType, SrcInfoType>
├── 使用 ZeroBufferSyncFlag (通过 TBuf)
├── 使用 shmem_ptr() 访问远端 SHMEM
└── 使用 TQue/TBuf 管理 UB

MoeCombineLowlatencyZeroBuffer<ExpandXType, XType, ExpandIdxType, IsNeedReduceScatter, IsInt8Quant>
├── 自实现 SetSyncFlag/WaitSyncFlag
├── 使用 shmem_ptr() 访问远端 SHMEM
├── 使用 moe_lowlatency_zero_buffer_base.h 工具
└── 支持 TP=2, 共享专家, 弹性缩容, 常量专家

附录:关键函数索引

normal_zb

函数 文件 行号 说明
moe_combine_normal_zero_buffer cpp:18 kernel 入口
Init() h:108 初始化所有 buffer 和地址
Process() h:175 主执行流程
IncrementMagic() h:117 获取并递增 magic
ReadBufferFromRemote() h:143 从远端 SHMEM 读取数据
ReadBufferAndWeightedSum() h:124 加权求和核心逻辑
SplitCoreCal() h:82 均匀分配 token 到各 core
InitMagic() h:113 读取并翻转 magic

lowlatency_zb

函数 文件 行号 说明
moe_combine_lowlatency_zero_buffer cpp:37 kernel 入口
Init() h:135 初始化
Process() h:286 主执行流程
CopyValidExpandXToRemote() h:214 拷贝本地数据到 SHMEM
SetSyncFlag() h:172 写同步 flag
WaitSyncFlag() h:193 轮询等待 flag
InputToDstOutput() h:241 从 SHMEM 读取并加权求和
SplitToCore() h:155 分配 token 到各 core

工具类

类/函数 文件 说明
ZeroBufferSyncFlag zero_buffer_sync_flag.h 通用跨卡 flag 同步
IncrementMagic() zero_buffer_sync_flag.h:103 magic 管理
SetFlag() zero_buffer_sync_flag.h:127 写 flag
WaitFlag() zero_buffer_sync_flag.h:153 轮询等待 flag
BarrierAll() zero_buffer_sync_flag.h:238 全 rank 屏障
SyncCollectives sync_collectives_zb.h 底层同步原语
shmem_ptr() shmem.h (CANN) 获取远端 SHMEM 地址

总结

CAM 库的 ZB (Zero-Buffer) Combine 算子代表了 MoE 通信的一次范式转变:

  1. 从推模式到拉模式: 消除了 Combine 阶段的 CopyBufferToShare,改为直接从远端 SHMEM 读取
  2. 从细粒度到粗粒度同步: 从 per-token per-topk 的 polling 升级为 per-rank 的 BarrierAll
  3. 从固定 magic 到递增 magic: 解决了 ABA 问题,支持跨 kernel 同步
  4. 从单一场景到多样化: lowlatency 版支持 TP=2、共享专家、弹性缩容、常量专家等高级特性

这些优化使得 Combine 阶段的延迟显著降低,尤其在大规模 EP 场景下(epWorldSize 达到 384/768),粗粒度同步的优势更加明显。

← ZB Dispatch 算子族 📑 索引 FusedDeepMoE 三变体 →