FusedDeepMoE 通信模式 & SHMEM 编译机制深度剖析
代码深读报告 | 聚焦 FusedDeepMoE 的 SHMEM vs 非 SHMEM 通信路径、raw_distributed 通信原语、三条执行路径差异、Workspace 内存布局,以及编译系统的 SHMEM 注入机制。
1. 总体架构概览
FusedDeepMoE 是 CAM 中最复杂的融合算子,将 Dispatch → GMM1 → SwiGLU → GMM2 → Combine 五个阶段融合进单一 kernel。其核心挑战在于:
- 通信与计算的融合:dispatch/combine 需要跨 EP rank 搬运 token,同时 GMM 需要密集 Cube 计算
- 三条执行路径:非深度融合 / 深度融合 / 共享专家,各自通信模式不同
- SHMEM vs HCCL Window:当前默认使用 HCCL Window 通信,SHMEM 路径为未来预留
┌─────────────────────────────────────────────────────────────────────┐
│ FusedDeepMoE 融合流程 │
│ │
│ x ──→ Dispatch ──→ GMM1 ──→ SwiGLU ──→ GMM2 ──→ Combine ──→ out │
│ (HCCL Window) (Cube) (Vector) (Cube) (HCCL Window) │
│ │
│ shared_x ──→ ShareGMM1 ──→ SwiGLU ──→ ShareGMM2 ──→ share_out │
│ (Cube) (Vector) (Cube) │
└─────────────────────────────────────────────────────────────────────┘
2. FusedDeepMoE 通信模式实现
2.1 核心入口:fused_deep_moe.h
文件:op_kernel/fused_deep_moe.h
通信完全通过 raw_distributed 子模块实现,主 kernel 类 FusedDeepMoe 本身不包含任何通信逻辑:
// fused_deep_moe.h 第30行
#include "fused_deep_moe/raw_distributed/cam_moe_distribute_dispatch.h"
// 注意:combine 的 include 在 GMM2 kernel 内部(通过 combiner 指针)
Process() 函数揭示的三阶段执行模型(第354-448行):
template <TemplateMC2TypeClass>
__aicore__ inline void FusedDeepMoe<TemplateMC2TypeFunc>::Process()
{
// 阶段1: Dispatch(仅非深度融合路径)
if constexpr ((EXEC_FLAG & EXEC_FLAG_DEEP_FUSE) == 0) {
MoeDistributeDispatchImpl::CamMoeDistributeDispatch<...> dispatcher;
dispatcher.Init(...);
dispatcher.Process(); // ← HCCL Window AlltoAll
}
// 阶段2: GMM1 + SwiGLU(Cube 计算,无通信)
GmmDeqSwigluQuant<...>(...);
// 阶段3: GMM2 + Combine(融合通信计算)
MoeDistributeCombineImpl::CamMoeDistributeCombine<...> combiner;
GmmDeq<...>(..., &combiner); // ← combiner 在 GMM2 的 AIV 侧执行
}
2.2 通信上下文获取:HCCL Window 模式
关键发现:当前代码中没有任何 SHMEM 分支。 所有通信通过 HcclOpResParam 结构体(HCCL Window 描述符)实现:
// cam_moe_distribute_dispatch.h 第236-237行
winContext_[COMM_EP_IDX] = (__gm__ HcclOpResParam *)AscendC::GetHcclContext<HCCL_GROUP_ID_0>();
winContext_[COMM_TP_IDX] = (__gm__ HcclOpResParam *)AscendC::GetHcclContext<1>();
// cam_moe_distribute_combine.h 第245-246行
auto contextGM0 = AscendC::GetHcclContext<HCCL_GROUP_ID_0>();
epWinContext_ = (__gm__ HcclOpResParam *)contextGM0;
HCCL Window 地址解析(dispatch 第83-91行):
__aicore__ inline GM_ADDR GetWindAddrByRankId(uint8_t ctxIdx, const int32_t rankId)
{
uint32_t curRankId = ctxIdx == COMM_EP_IDX ? epRankId_ : tpRankId_;
if (curRankId == rankId) {
// 本地 Window:直接访问 localWindowsIn
return (GM_ADDR)(winContext_[ctxIdx]->localWindowsIn) + winDataSizeOffset_ + rankId * OPT_RANK_OFFSET;
}
// 远端 Window:通过 remoteRes 指针访问对端 windowsIn
return (GM_ADDR)(((HcclRankRelationResV2 *)(winContext_[ctxIdx]->remoteRes[rankId].nextDevicePtr))->windowsIn)
+ winDataSizeOffset_ + rankId * OPT_RANK_OFFSET;
}
通信模式总结:
- 数据通道:windowsIn — 存放实际 token 数据(int8/half/bf16)
- 状态通道:windowsExp — 存放同步状态标志(flag/sumTarget)
- 同步机制:通过 DataCopy 在 Window 上写入 flag,轮询对端 flag 实现握手
3. SHMEM vs 非 SHMEM 代码路径
3.1 当前状态:无 SHMEM 分支
对整个 fused_deep_moe/ 目录进行全量 grep:
grep -rn "aclshmem\|SHMEM\|shmem_" src/cam/comm_operator/ascend_kernels/fused_deep_moe/
# 结果:0 命中
结论:FusedDeepMoE kernel 代码中不存在任何 SHMEM 相关代码路径。 所有通信均通过 HCCL Window(HcclOpResParam)实现。
3.2 编译系统的 SHMEM 预留
虽然 kernel 代码没有 SHMEM 分支,但编译系统已完整预留 SHMEM 注入点:
| 层级 | 文件 | SHMEM 机制 |
|---|---|---|
| 算子选择 | select_ops.py |
requires_shmem() 过滤 |
| 编译脚本 | compile_ascend_proj.sh |
SHMEM_HOME_PATH 检测 + include 注入 |
| CMake 工具 | ascendc_impl_build.py |
SHMEM include 路径硬编码注入 |
3.3 抽象层设计
当前通信原语通过 HcclOpResParam 抽象,没有引入统一的 CommunicationInterface。dispatch/combine 直接依赖 HCCL 的 Window 结构体:
// 通信上下文是 HCCL 强绑定的
__gm__ HcclOpResParam *winContext_[COMM_NUM]; // dispatch
__gm__ HcclOpResParam *epWinContext_; // combine
__gm__ HcclOpResParam *tpWinContext_; // combine (TP domain)
如果要支持 SHMEM,需要:
1. 引入 ICommContext 抽象基类
2. 派生 HcclWindowContext 和 ShmemContext
3. dispatch/combine 通过基类指针访问通信原语
4. raw_distributed 子模块:通信原语详解
4.1 cam_moe_distribute_dispatch.h
文件:op_kernel/fused_deep_moe/raw_distributed/cam_moe_distribute_dispatch.h
类模板参数(第19-24行 fused_deep_moe_base.h):
#define TemplateDispatchTypeClass
typename XType, typename ExpandXOutType, bool StaticQuant,
bool DynamicQuant, bool IsSmoothScaleExist, bool IsNeedAllgater, uint32_t EXEC_FLAG
核心通信流程(第1023-1039行):
void Process()
{
if ASCEND_IS_AIV {
if constexpr (EXEC_FLAG & EXEC_FLAG_SHARED_EXPERT) {
QuantForShareExpert(); // 共享专家量化(本地操作)
}
AlltoAllDispatch(); // ← 核心:AlltoAll 数据搬运
SetStatus(); // 写入本 rank 状态到各远端 Window
WaitDispatch(); // 轮询等待所有远端状态就绪
LocalWindowCopy(); // 从本地 Window 读取数据到 workspace
if constexpr (IsNeedAllgater) {
AllGatherSetStatusAndWait();
AllgatherProcessOut();
}
UpdataTokenNumsOut(); // 更新 expertTokenNums 计数
}
}
AlltoAllDispatch 关键路径(第575-602行):
void AlltoAllDispatch()
{
// 1. 加载 expertIds(决定每个 token 去哪个 expert/rank)
DataCopyPad(expertIdsTensor_, expertIdsGMTensor_, expertIdsCntParams, copyPadParams);
// 2. 计算每个 expert 的 token 计数 + 状态更新
if (enableAivOpt_) {
CountAndPrepareStatusWithAivOpt(expertIdsCnt); // 优化路径
} else {
for (uint32_t tokenIndex = 0; tokenIndex < expertIdsCnt; ++tokenIndex) {
expertCountTensor_(tokenIndex) = statusTensor_(expertId * INT32_NUM_PER_BLOCK + 1);
statusTensor_(expertId * INT32_NUM_PER_BLOCK + 1)++; // 原子计数
}
}
// 3. 将 token 数据写入目标 rank 的 Window
SendToMoeExpert();
}
SendToMoeExpert 数据搬运(第433-487行):
void SendToMoeExpert()
{
for (uint32_t tokenIndex = startTokenId; tokenIndex < endTokenId; ++tokenIndex) {
int32_t dstExpertId = expertIdsTensor_(tokenIndex);
uint32_t tempRankId = dstExpertId / moeExpertNumPerRank_;
// 计算目标 Window 地址
GM_ADDR rankGM = GetWindAddrByRankId(COMM_EP_IDX, tempRankId)
+ expertPerSizeOnWin_ * (epRankId_ * moeExpertNumPerRank_ + dstExpertId % moeExpertNumPerRank_)
+ hCommuSize_ * expertCountTensor_(tokenIndex);
// 量化 + 写入目标 Window
if constexpr (DynamicQuant || StaticQuant) {
QuantProcess(expertIndex, scalesGMTensor_);
DataCopy(dstWinGMTensor, xOutTensor_, axisHCommu_);
} else {
DataCopy(dstWinGMTensor, xTmpTensor_, axisHCommu_);
}
}
}
WaitDispatch 同步机制(第684-726行):
void WaitDispatch()
{
// 轮询 windowInstatusFp32Tensor_ 中的 flag
while ((sumOfFlag < minTarget) || (sumOfFlag > maxTarget)) {
DataCopy(statusFp32Tensor_, windowInstatusFp32Tensor_[startStatusIndex * stateOffset_ / sizeof(float)], intriParams);
GatherMask(gatherMaskOutTensor, statusFp32Tensor_, gatherTmpTensor, true, mask, ...);
Sum(statusSumOutTensor, gatherMaskOutTensor, sumParams);
sumOfFlag = statusSumOutTensor.GetValue(0);
}
SyncAll<true>; // 全核同步
}
4.2 cam_moe_distribute_combine.h
文件:op_kernel/fused_deep_moe/raw_distributed/cam_moe_distribute_combine.h
类模板参数(与主 kernel 共享 TemplateMC2TypeClass):
#define TemplateMC2TypeClass
typename ExpandXType, typename W1ScaleType, typename W2ScaleType,
typename ExpandIdxType, bool IsNeedReduceScatter, uint32_t EXEC_FLAG
CombineCalcInfo 结构体(第46-56行)— 传递给 GMM2 AIV 侧的关键信息:
struct CombineCalcInfo {
uint64_t expertPerSizeOnWin_; // 每个 expert 在 Window 上占用的字节数
uint32_t epRankId_;
uint32_t epWorldSize_;
uint32_t moeExpertPerRankNum_;
uint32_t axisH_;
uint32_t moeSendNum_;
GM_ADDR epSendCount_;
__gm__ HcclOpResParam *epWinContext_; // ← HCCL 强绑定
uint64_t winDataSizeOffset_;
};
Process 流程(第719-734行):
void Process()
{
SyncAll<true>;
if constexpr (IsNeedReduceScatter) {
ReduceScatterTrans(); // TP 域 ReduceScatter
}
if constexpr ((EXEC_FLAG & EXEC_FLAG_DEEP_FUSE) == 0) {
BuffInit();
SetWaitTpStatusAndDisPatch(); // 非融合路径的 AlltoAll
}
AlltoAllBuffInit();
SetStatus(); // 写入 EP 状态
WaitDispatch(); // 等待 EP 域数据就绪
LocalWindowCopy(); // 从 Window 读取 + 加权求和
}
LocalWindowCopy 核心逻辑(第627-716行):
void LocalWindowCopy()
{
for (uint32_t tokenIndex = beginIndex; tokenIndex < endIndex; tokenIndex++) {
Duplicate(sumFloatBufLocal, (float)0, axisH_); // 清零累加器
for (uint32_t i = 0; i < axisK_; i++) {
int32_t moeExpert = expertIdsLocal.GetValue(index);
float scaleVal = expandScalesLocal.GetValue(index);
// 从 Window 读取该 expert 的 token 数据
GM_ADDR wAddr = epWindowGM_ + expertPerSizeOnWin_ * moeExpert
+ indexCountsLocal.GetValue(index) * axisHExpandXTypeSize_ + tokenOffset;
rowTmpGlobal_.SetGlobalBuffer((__gm__ ExpandXType *)wAddr);
// 量化反量化 + 缩放 + 累加
Cast(rowTmpFloatLocal, tmpUb, CAST_NONE, processLen);
Muls(mulBufLocal, rowTmpFloatLocal, scaleVal, processLen);
Add(sumFloatBufLocal, sumFloatBufLocal, mulBufLocal, processLen);
}
Cast(sumBufLocal, sumFloatBufLocal, CAST_RINT, processLen);
DataCopy(expandOutGlobal_[tokenIndex * axisH_ + tokenOffset], sumBufLocal, processLen);
}
}
5. 三条执行路径的通信差异
5.1 路径判定机制
EXEC_FLAG 位图定义(fused_deep_moe_tiling.h 第104-108行):
constexpr uint32_t EXEC_FLAG_DEEP_FUSE = (1U << 0); // 深度融合
constexpr uint32_t EXEC_FLAG_TENSOR_LIST = (1U << 1); // 权重 tensor list
constexpr uint32_t EXEC_FLAG_X_ACTIVE_MASK = (1U << 2); // 激活 mask
constexpr uint32_t EXEC_FLAG_SHARED_EXPERT = (1U << 3); // 共享专家
constexpr uint32_t EXEC_FLAG_SMOOTH_QUANT = (1U << 4); // 平滑量化
Tiling 阶段计算 EXEC_FLAG(fused_deep_moe_tiling.cpp 第35-40行):
constexpr uint32_t EXEC_FLAG =
(TPL_IS_DEEP_FUSE ? EXEC_FLAG_DEEP_FUSE : 0) |
(TPL_IS_TENSOR_LIST ? EXEC_FLAG_TENSOR_LIST : 0) |
(TPL_IS_X_ACTIVE_MASK ? EXEC_FLAG_X_ACTIVE_MASK : 0) |
(TPL_IS_SHARED_EXPERT ? EXEC_FLAG_SHARED_EXPERT : 0) |
(TPL_IS_SMOOTH_QUANT ? EXEC_FLAG_SMOOTH_QUANT : 0);
5.2 路径对比
| 特性 | 非深度融合 | 深度融合 (DEEP_FUSE) | 共享专家 (SHARED_EXPERT) |
|---|---|---|---|
| 触发条件 | moeExpertNumPerRank == 1 |
moeExpertNumPerRank > 1 |
share_gmm1_weight != nullptr |
| Dispatch | 独立执行(AIV 上) | 跳过(由 GMM2 AIV 侧融合) | 独立执行 |
| Combine | 独立执行(AIV 上) | 融合进 GMM2 AIV 侧 | 融合进 GMM2 AIV 侧 |
| GMM1 后同步 | PipeBarrier + CrossCoreFlag |
同左 | 同左 |
| GMM2 AIV 角色 | 仅做 epilogue | 执行 AllToAllSend + ReducePermute |
执行 ProcessCombine |
| 共享专家 GMM | 无 | 无 | 有(额外 GMM1+GMM2) |
| 核分配 | AIC + AIV 1:1 | AIC + AIV 1:2 (1C2V) | AIC + AIV 1:2 |
5.3 深度融合路径的通信融合
GMM2 AIV 侧执行流程(grouped_matmul_slice_m_per_token_dequant_multistage_workspace.h 第291-458行):
template <>
CATLASS_DEVICE void operator()<AscendC::AIV>(Params const ¶ms)
{
auto *combiner = (CamMoeDistributeCombine *)params.combiner;
// 1. 先执行 GMM2 的 epilogue(反量化 + SwiGLU 后处理)
for (uint32_t groupIdx = 0; groupIdx < params.problemCount; ++groupIdx) {
for (uint32_t loopIdx = startLoopIdx; loopIdx < coreLoops; loopIdx += coreNum) {
CheckSyncFlag(syncGmAddr + SOFT_SYNC_OFFSET, coreIdx, target); // 等待 AIC 完成
blockEpilogue(gmGroupOffsetD, groupIdx, ...); // epilogue 计算
}
}
// 2. 共享专家 GMM2(如有)
if constexpr (EXEC_FLAG & EXEC_FLAG_SHARED_EXPERT) {
// ... 执行共享专家 GMM2 epilogue
}
// 3. 融合 Combine 通信
if constexpr (EXEC_FLAG & EXEC_FLAG_SHARED_EXPERT) {
if (GetSubBlockIdx() == 1) {
combiner->ProcessCombine(); // ← 在 GMM2 AIV 上执行 Combine
}
} else if constexpr (EXEC_FLAG & EXEC_FLAG_DEEP_FUSE) {
if (GetSubBlockIdx() == 0) {
combiner->AllToAllSend(); // ← 发送阶段
} else {
combiner->ReducePermute(); // ← 接收+累加阶段
}
} else {
combiner->Process(); // ← 非融合:完整 Combine
}
}
关键同步机制:
- SEND_SYNC_EVENT_ID = 6 — GMM2 epilogue 完成 → Combine 开始
- RECV_SYNC_EVENT_ID = 7 — Combine 接收完成 → 主 kernel 继续
- CheckSyncFlag — AIV 等待 AIC 的 Cube 计算完成(soft sync)
6. Workspace 内存布局
6.1 WorkSpaceOffset 结构体
文件:fused_deep_moe_tiling.h 第19-40行
struct WorkSpaceOffset {
// MM1/GMM1-Swiglu input
int64_t shareX1TokenOffset; // 共享专家 GMM1 输入
int64_t x1TokenOffset; // MoE GMM1 输入(dispatch 后)
int64_t shareX1ScaleOffset; // 共享专家量化 scale
int64_t x1ScaleOffset; // MoE 量化 scale
// MM1/GMM1-Swiglu output
int64_t shareSwigluOffset; // 共享专家 SwiGLU 输出
int64_t swigluOffset; // MoE SwiGLU 输出
// MM2/GMM2 input
int64_t shareX2TokenOffset; // 共享专家 GMM2 输入
int64_t x2TokenOffset; // MoE GMM2 输入
int64_t shareX2ScaleOffset; // 共享专家 GMM2 scale
int64_t x2ScaleOffset; // MoE GMM2 scale
int64_t swapSpaceOffset; // C↔V 数据交换区(GMM1 输出/GMM2 输入)
int64_t y2TokenOffset; // 浅融合路径 GMM2 输出
int64_t groupListOffset; // expert token 计数前缀和
int64_t expandIdxOffset; // dispatch 远端 token 索引
int64_t epSendCountOffset; // 每个 expert 从每个 rank 接收的 token 数
int64_t reservedOffset; // 保留区
};
6.2 内存复用策略(ENABLE_REUSE_MEMORY)
文件:fused_deep_moe_tiling.cpp 第671-711行
#ifdef ENABLE_REUSE_MEMORY
// shareX1 和 shareX2 复用同一块内存(因为 SwiGLU 后不再需要 X1)
tilingData.workSpaceOffset.shareX1TokenOffset = offset;
tilingData.workSpaceOffset.shareX2TokenOffset = offset;
tilingData.workSpaceOffset.x1TokenOffset = offset + shareExpertTokenNum * h * sizeof(int8_t);
tilingData.workSpaceOffset.x2TokenOffset = offset + shareExpertTokenNum * shareGmm2HLen * sizeof(int8_t);
offset += maxTokenSize; // 取 x1/x2 中较大者
// scale 同样复用
tilingData.workSpaceOffset.shareX1ScaleOffset = offset;
tilingData.workSpaceOffset.shareX2ScaleOffset = offset;
tilingData.workSpaceOffset.x1ScaleOffset = offset + shareExpertTokenNum * sizeof(float);
tilingData.workSpaceOffset.x2ScaleOffset = offset + shareExpertTokenNum * sizeof(float);
offset += tokenScaleSize;
// swapSpace 和 shareSwiglu 复用
tilingData.workSpaceOffset.swapSpaceOffset = offset;
tilingData.workSpaceOffset.shareSwigluOffset = offset;
tilingData.workSpaceOffset.swigluOffset = offset + shareExpertTokenNum * shareGmm1HLen * sizeof(float);
offset += maxSwapSwigluSize;
tilingData.workSpaceOffset.y2TokenOffset = offset;
#else
// 非复用模式:各区域独立分配
// ... 每个区域单独 CeilUp 对齐
#endif
6.3 Workspace 总大小计算
// 第716-717行
size_t usrSize = tilingData.workSpaceOffset.reservedOffset + reservedSize;
workSpaces[0] = SYSTEM_NEED_WORKSPACE + usrSize; // 16MB 系统预留 + 用户区
各区域大小公式:
| 区域 | 大小公式 | 说明 |
|---|---|---|
| x1/x2 token | max(shareBs*h, maxTokenNum*h) |
int8 存储 |
| scale | (shareBs + maxTokenNum) * sizeof(float) |
per-token scale |
| swap/swiglu | max(maxTokenNum*gmm1HLen, maxTokenNum*h) * sizeof(int32/float) |
GMM1 输出 |
| y2 | maxTokenNum * h * 2 (if moeExpertNumPerRank==1) |
GMM2 输出 |
| groupList | moeExpertNumPerRank * sizeof(int64_t) |
前缀和 |
| expandIdx | bs * topK * sizeof(int32_t) |
远端索引 |
| epSendCount | epRankSize * moeExpertNumPerRank * sizeof(int32_t) |
接收计数 |
| reserved | 256KB | 保留 |
6.4 数据流转图
HBM (GM)
│
├── x [bs, h] ─────────────────────────────────────────────────────────────┐
├── expert_ids [bs, k] ─────────────────────────────────────────────────────┤
├── gmm1_weight [moeExpertNumPerRank, h, gmm1HLen] ─────────────────────────┤
├── gmm2_weight [moeExpertNumPerRank, gmm1HLen/2, h] ───────────────────────┤
└── workspace ──────────────────────────────────────────────────────────────┘
│
├── shareX1Token (Window Copy ← Dispatch)
│ ↓ GMM1 (Cube) + SwiGLU (Vector)
├── shareSwigluOut
│ ↓
├── shareX2Token ──────────────────────────→ ShareGMM2 → share_output
│
├── x1Token (Window Copy ← Dispatch)
│ ↓ GMM1 (Cube) + SwiGLU (Vector)
├── swigluOut
│ ↓
├── x2Token
│ ↓ GMM2 (Cube, 输出到 swapSpace)
│ ↓ epilogue (Vector, 反量化 + 写回 Window)
│ ↓ Combine (AllToAllSend → Window → ReducePermute → output)
│
├── swapSpace (GMM2 AIC 输出 → AIV epilogue 输入)
├── y2Token (浅融合路径 GMM2 最终输出)
├── groupList (expert token 前缀和)
├── expandIdx (远端 token 索引)
├── epSendCount (各 rank 接收计数)
└── reserved
7. 编译系统 SHMEM 机制
7.1 select_ops.py — SHMEM 算子过滤
文件:build/cam/comm_operator/select_ops.py
requires_shmem() 函数(第55-56行):
def requires_shmem(registry, op):
return bool(get_meta(registry, op).get("requires_shmem", False))
SHMEM 过滤逻辑(第116-121行):
# 3. SHMEM filter: drop requires_shmem operators when SHMEM is not installed.
if not shmem:
dropped = [o for o in candidates if requires_shmem(registry, o)]
for o in dropped:
sys.stderr.write(f"NOTE: dropping {o} (requires SHMEM, which is not installed)\n")
candidates = [o for o in candidates if not requires_shmem(registry, o)]
完整选择管线:
1. 确定候选集(全 SOC 列表 或 用户 -a 列表)
↓
2. fused_deep_moe base/w4a8 互斥规则
-q → 选 w4a8,剔除 base
非 -q → 选 base,剔除 w4a8
fwk 不受影响
↓
3. SHMEM 过滤
shmem=0 → 剔除 requires_shmem=true 的算子
↓
4. 按 registry 顺序排列输出
7.2 compile_ascend_proj.sh — SHMEM 路径注入
文件:build/cam/comm_operator/compile_ascend_proj.sh
SHMEM_HOME_PATH 检测(第19-22行):
# Whether SHMEM is installed (determined by the SHMEM_HOME_PATH env variable)
shmem_installed=1
if [ -z "${SHMEM_HOME_PATH}" ]; then
shmem_installed=0
fi
SHMEM include 注入(第179-192行):
KERNEL_COMPILE_OPTS=""
if [ -n "${SHMEM_HOME_PATH}" ]; then
KERNEL_COMPILE_OPTS="-I${SHMEM_HOME_PATH}/shmem/include -I${SHMEM_HOME_PATH}/shmem/src/device"
fi
if [ -n "${CATLASS_HOME_PATH}" ]; then
KERNEL_COMPILE_OPTS="${KERNEL_COMPILE_OPTS} -I${CATLASS_HOME_PATH}/include"
fi
if [ -n "${KERNEL_COMPILE_OPTS}" ]; then
echo "Patching kernel compile options: ${KERNEL_COMPILE_OPTS}"
find build_out/op_kernel/CMakeFiles -name "build.make" -exec sed -i \
"s|--compile-options=\"\"|--compile-options=\\\"${KERNEL_COMPILE_OPTS}\\\"|g" {} +
fi
关键机制:通过 sed 替换 .make 文件中的 --compile-options="" 为实际的 include 路径。这是因为 npu_op_* 构建系统的 simple_kernel_compile 不读取 custom_compile_options.ini。
7.3 ascendc_impl_build.py — SHMEM 编译选项生成
文件:src/cam/comm_operator/ascend_kernels/cmake_files/cmake/util/ascendc_impl_build.py
SHMEM include 路径硬编码(第167-179行):
SHMEM_HOME_PATH = os.environ.get('SHMEM_HOME_PATH')
if SHMEM_HOME_PATH is None:
SHMEM_HOME_PATH = os.path.realpath("/usr/local/Ascend/shmem/latest")
options.append("-I" + SHMEM_HOME_PATH + "/shmem/include")
options.append("-I" + SHMEM_HOME_PATH + "shmem/include/device")
options.append("-I" + SHMEM_HOME_PATH + "/shmem/include/host_device")
options.append("-I" + SHMEM_HOME_PATH + "/shmem/include/internal")
options.append("-I" + SHMEM_HOME_PATH + "/shmem/src")
options.append("-I" + SHMEM_HOME_PATH + "/shmem/src/device")
options.append("-I" + SHMEM_HOME_PATH + "/memfabric_hybrid/include/smem/device")
注意:这里有一个 bug — 第172行缺少 / 分隔符:
options.append("-I" + SHMEM_HOME_PATH + "shmem/include/device") # 缺少 /
# 应该是:
options.append("-I" + SHMEM_HOME_PATH + "/shmem/include/device")
7.4 operator_registry.json — SHMEM 标记
文件:src/cam/comm_operator/ascend_kernels/operator_registry.json
{
"operator_meta": {
"dispatch_layout": { "requires_shmem": false },
"fused_deep_moe": {
"requires_shmem": false,
"family": "fused_deep_moe",
"variant": "base"
},
"moe_combine_normal": { "requires_shmem": false },
"moe_dispatch_normal": { "requires_shmem": false },
"notify_dispatch": { "requires_shmem": false }
}
}
当前所有算子的 requires_shmem 均为 false,即 SHMEM 过滤当前不影响任何算子。
8. SHMEM_HOME_PATH 环境变量
8.1 作用域
| 阶段 | 使用方式 | 默认值 |
|---|---|---|
| 编译时 | compile_ascend_proj.sh 检测是否设置 |
未设置 → shmem_installed=0 |
| 编译时 | ascendc_impl_build.py 读取路径 |
/usr/local/Ascend/shmem/latest |
| 运行时 | kernel 代码中 未使用 | N/A |
8.2 编译时行为
SHMEM_HOME_PATH 未设置:
├── select_ops.py: shmem=0 → 剔除 requires_shmem=true 的算子
├── compile_ascend_proj.sh: 不注入 SHMEM include
└── ascendc_impl_build.py: 使用默认路径 /usr/local/Ascend/shmem/latest
SHMEM_HOME_PATH 已设置:
├── select_ops.py: shmem=1 → 保留所有算子
├── compile_ascend_proj.sh: 注入 -I${SHMEM_HOME_PATH}/shmem/include 等
└── ascendc_impl_build.py: 使用 SHMEM_HOME_PATH 的值
8.3 运行时行为
当前 kernel 代码不读取 SHMEM_HOME_PATH。SHMEM 的运行时行为(如 aclshmem_* API 的链接)由 CANN 的 SHMEM 库在算子加载时动态解析。
9. 未来 SHMEM 集成路径
9.1 需要修改的代码
要让 FusedDeepMoE 支持 SHMEM,需要以下改动:
9.1.1 通信抽象层(新增)
新增文件:
op_kernel/fused_deep_moe/comm/icomm_context.h # 抽象基类
op_kernel/fused_deep_moe/comm/hccl_window_context.h # HCCL 实现
op_kernel/fused_deep_moe/comm/shmem_context.h # SHMEM 实现
// icomm_context.h (概念设计)
class ICommContext {
public:
virtual GM_ADDR GetDataAddr(int rankId) = 0;
virtual GM_ADDR GetStateAddr(int rankId) = 0;
virtual void SetFlag(int rankId, int value) = 0;
virtual int WaitFlag(int rankId, int target) = 0;
virtual void DataCopy(void* dst, void* src, size_t len) = 0;
};
9.1.2 dispatch/combine 改造
// cam_moe_distribute_dispatch.h 改造方向
template <typename CommContext>
class CamMoeDistributeDispatch {
CommContext* commCtx_; // 替代 winContext_[COMM_NUM]
// ...
};
9.1.3 编译系统改动
- operator_registry.json — 新增 SHMEM 变体:
{
"fused_deep_moe_shmem": {
"requires_shmem": true,
"family": "fused_deep_moe",
"variant": "shmem"
}
}
- select_ops.py — 新增 SHMEM 变体互斥规则:
SHMEM = "fused_deep_moe_shmem"
# base/w4a8/shmem 三者互斥
- compile_ascend_proj.sh — 已支持,无需改动(通过 SHMEM_HOME_PATH 自动注入)
9.1.4 运行时 SHMEM API 调用
SHMEM 路径的核心变化:
// HCCL Window 模式(当前)
GM_ADDR dstAddr = GetWindAddrByRankId(COMM_EP_IDX, rankId);
DataCopy(dstWinGMTensor, xOutTensor_, axisHCommu_);
// SHMEM 模式(未来)
// 使用 aclshmem_* API 直接 RDMA
aclshmem_put(dstAddr, srcAddr, len, dstRankId);
aclshmem_fence();
aclshmem_wait_flag(flagAddr, target);
9.2 SHMEM 集成挑战
| 挑战 | 说明 |
|---|---|
| 同步语义差异 | HCCL Window 是双向对称的(localWindowsIn + remoteRes),SHMEM 是单边 RDMA |
| 状态通道设计 | 当前 windowsExp 用于 flag 同步,SHMEM 需要额外设计 fence/notify 机制 |
| 多域通信 | dispatch 使用 COMM_EP_IDX + COMM_TP_IDX 两个域,SHMEM 需要对应两个通信组 |
| 地址计算 | expertPerSizeOnWin_、winDataSizeOffset_ 等偏移量需要重新映射到 SHMEM 内存池 |
| 量化融合 | dispatch 中量化+Window 拷贝是融合的,SHMEM 路径需要保持同样的融合度 |
9.3 推荐集成策略
- Phase 1:引入
ICommContext抽象,HCCL 路径保持不变 - Phase 2:实现
ShmemContext,在notify_dispatch等简单算子上验证 - Phase 3:将 SHMEM 路径扩展到 FusedDeepMoE 的 dispatch/combine
- Phase 4:性能调优,利用 SHMEM 的单边 RDMA 降低同步开销
10. 关键代码索引
| 文件 | 关键函数/结构 | 行号 |
|---|---|---|
fused_deep_moe.h |
FusedDeepMoe::Process() |
354-448 |
fused_deep_moe.h |
GmmDeqSwigluQuant() |
56-152 |
fused_deep_moe.h |
GmmDeq() |
154-238 |
fused_deep_moe_tiling.h |
WorkSpaceOffset |
19-40 |
fused_deep_moe_tiling.h |
EXEC_FLAG_* 定义 |
104-108 |
fused_deep_moe_tiling.cpp |
SetWorkSpace() |
636-719 |
fused_deep_moe_tiling.cpp |
SetHcommCfg() |
605-616 |
fused_deep_moe_tiling_key.h |
ASCENDC_TPL_SEL |
34-55 |
cam_moe_distribute_dispatch.h |
GetWindAddrByRankId() |
83-91 |
cam_moe_distribute_dispatch.h |
AlltoAllDispatch() |
575-602 |
cam_moe_distribute_dispatch.h |
SendToMoeExpert() |
433-487 |
cam_moe_distribute_dispatch.h |
WaitDispatch() |
684-726 |
cam_moe_distribute_combine.h |
CombineCalcInfo |
46-56 |
cam_moe_distribute_combine.h |
GetWinAddrByRankId() |
96-111 |
cam_moe_distribute_combine.h |
LocalWindowCopy() |
627-716 |
cam_moe_distribute_combine.h |
Process() |
719-734 |
grouped_matmul_slice_m_per_token_dequant_multistage_workspace.h |
operator()<AIV>() |
291-458 |
select_ops.py |
requires_shmem() |
55-56 |
select_ops.py |
SHMEM 过滤逻辑 | 116-121 |
compile_ascend_proj.sh |
SHMEM_HOME_PATH 检测 | 19-22 |
compile_ascend_proj.sh |
SHMEM include 注入 | 179-192 |
ascendc_impl_build.py |
SHMEM include 路径 | 167-179 |
operator_registry.json |
operator_meta | 12-30 |
11. 结论
-
FusedDeepMoE 当前完全基于 HCCL Window 通信,kernel 代码中不存在任何 SHMEM 分支。
-
通信原语通过
HcclOpResParam强绑定,dispatch/combine 直接操作windowsIn(数据)和windowsExp(状态)。 -
三条执行路径的通信差异: - 非深度融合:dispatch 和 combine 独立执行 - 深度融合:combine 融合进 GMM2 AIV 侧,通过
AllToAllSend+ReducePermute实现 - 共享专家:额外执行共享专家 GMM1+GMM2,combine 通过ProcessCombine执行 -
Workspace 内存布局采用复用策略(
ENABLE_REUSE_MEMORY),shareX1/X2 共用、shareX1Scale/X2Scale 共用、swapSpace/shareSwiglu 共用。 -
编译系统已完整预留 SHMEM 注入点: -
select_ops.py通过requires_shmem过滤 -compile_ascend_proj.sh通过SHMEM_HOME_PATH注入 include -ascendc_impl_build.py硬编码 SHMEM include 路径(含一个路径 bug) -
SHMEM 集成需要引入通信抽象层,将
HcclOpResParam替换为ICommContext接口,并在 dispatch/combine 中通过模板参数化通信后端。 -
ascendc_impl_build.py第172行存在路径 bug:SHMEM_HOME_PATH + "shmem/include/device"缺少/分隔符。