news 2026/9/18 11:37:25

CANN ops-transformer 算子详解:MoeInitRoutingQuant 的 MoE 路由量化实现与 aclnn 调用实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CANN ops-transformer 算子详解:MoeInitRoutingQuant 的 MoE 路由量化实现与 aclnn 调用实践

CANN ops-transformer 算子详解:MoeInitRoutingQuant 的 MoE 路由量化实现与 aclnn 调用实践

【免费下载链接】ops-transformer本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-transformer

导读

MoeInitRoutingQuant是 CANN ops-transformer 仓库(moe/moe_init_routing_quant)中面向 MoE(Mixture of Experts,混合专家)大模型的关键算子,它在 MoE 路由(routing)流程中承担"按专家排序 + token 特征扩展 + 量化"三位一体的职责:基于aclnnMoeGatingTopKSoftmax输出的专家选择结果,将原始 token 特征按专家序号重排为专家连续分块的布局,同时完成 FP16/BF16/FP32 到 INT8 的在线量化,为后续专家并行计算提供紧凑、可直接喂给下游算子的输入。读完本文,你将掌握该算子的计算公式与数据流、全部入参与出参的语义与约束、两段式 aclnn 接口的调用方法,以及从 Host 侧 shape 推导、Tiling 到 Kernel 侧排序/重排/量化实现的完整源码脉络。

算子定位:MoE 路由链路中的"排序 + 扩展 + 量化"

在 MoE 大模型中,每一层通常包含多个专家子网络,输入 token 并非全部经过所有专家,而是通过门控网络挑选 Top-K 个专家。本算子位于这条链路的关键节点:

  1. 门控层先通过 aclnnMoeGatingTopKSoftmax 计算每个 token 的 Top-K 专家得分与索引;
  2. MoeInitRoutingQuant拿到expertIdx(每个 token 对应的 K 个专家)与rowIdx(原始行位置)后,将全部NUM_ROWS × K个"(token, 专家)"组合按专家序号排序,使同一个专家处理的 token 连续排布;
  3. 同时按排序后的顺序将原始 token 特征x复制(gather)到扩展后的位置,并就地完成 INT8 量化,输出expandedXOut等三个结果。

从仓库文件布局看,该算子是一个标准的 CANN 算子工程:算子定义与 shape 推导、Tiling 计算、AscendC Kernel 实现、aclnn 调用示例 一应俱全,并配套了 UT/ST 测试。

计算公式与数据流

设输入 token 数为NUM_ROWS,每个 token 选择K个专家,expertIdx的 shape 为[NUM_ROWS, K]。算子将其展平为一行后按专家序号排序,核心公式如下:

$$ expandedExpertIdxOut,\ sortedRowIdx=keyValueSort(expertIdx,rowIdx) $$

$$ expandedRowIdxOut[sortedRowIdx[i]]=i $$

$$ expandedXOut[i]=quant(x[sortedRowIdx[i]%NUM_ROWS]) $$

三个公式分别对应三个输出:

公式含义对应输出
keyValueSortexpertIdx为键、rowIdx为值联合排序,返回排序后的专家序号(键)与排序下标序列(值)expandedExpertIdxOut、排序下标sortedRowIdx
逆映射记录"排序后位置 → 原始位置"的映射,即expandedXi行取自x的哪一行expandedRowIdxOut
扩展 + 量化按排序结果从x中取出行(sortedRowIdx[i] % NUM_ROWS还原原始行号),并对每个元素做量化expandedXOut

其中quant为线性对称/非对称量化,量化参数为scaleoffset

$$ quant(v)=round(v \times scale + offset) $$

典型量化参数取值(来自 aclnnMoeInitRoutingQuant 文档)为 $S=(R_{max}-R_{min})/(Q_{max}-Q_{min})$,其中 $R_{max}/R_{min}$ 为输入浮点数的最大/最小值,$Q_{max}/Q_{min}$ 为定点表示的最大/最小可表示值。

一个直观的小例子

以官方示例test_aclnn_moe_init_routing_quant.cpp中的数据(NUM_ROWS=3, K=2, H=4)推演:

  • x:shape[3, 4],三行特征分别为0.1/0.2/0.3
  • expertIdx:shape[3, 2],即[[1,2],[0,1],[0,2]],表示第 0 行 token 去专家 1、2,第 1 行去专家 0、1,第 2 行去专家 0、2;
  • rowIdx:shape[3, 2],即[[0,3],[1,4],[2,5]],其中第 1 列3/4/5表示这些位置对应的原始行是1/2/0(可视为已做过多轮 swap 后的行标记)。

(expertIdx, rowIdx)键值对按专家序号升序排序后,expandedExpertIdxOut得到[0,0,1,1,2,2]expandedRowIdxOut给出各排序位置对应的原始行索引,expandedXOut按该索引从x中取值并量化(示例中scale=1.0, offset=1.0,即v+1取整),最终输出 shape 为[6, 4]的 INT8 张量。这一布局使得"同一专家的 token 连续存放",是后续专家计算高效并行的前提。

参数说明

算子共有 3 个输入、3 个属性、3 个输出(详见 README):

参数名输入/输出/属性描述数据类型数据格式
x输入MOE 的输入,即 token 特征输入,对应公式中的xFLOAT16、BFLOAT16、FLOAT32ND
rowIdx输入指示每个位置对应的原始行位置,对应公式中的rowIdxINT32ND
expertIdx输入aclnnMoeInitRoutingQuantSoftmax的输出,即每一行特征对应的 K 个处理专家,对应公式中的expertIdxINT32ND
activeNum属性表示总的最大处理 row 数且大于等于 0,expandedXOut只有这么多行是有效的INT64-
scale属性量化计算需要DOUBLE-
offset属性量化计算需要DOUBLE-
expandedXOut输出根据expertIdx扩展过的特征,对应公式中的expandedXINT8ND
expandedRowIdxOut输出expandedXx的映射关系,对应公式中的expandedRowIdxINT32ND
expandedExpertIdxOut输出输出expertIdx排序后的结果,对应公式中的expandedExpertIdxINT32ND

各参数的 shape 与使用约束(API 文档细化)

依据 aclnnMoeInitRoutingQuant 接口文档,各 tensor 的维度要求如下:

  • x:2D,shape(NUM_ROWS, H),支持空 tensor;
  • rowIdx:2D,shape 与expertIdx一致,数值从 0 开始沿第 1 维递增,支持空 tensor;
  • expertIdx:2D,shape(NUM_ROWS, K),支持空 tensor;
  • activeNum:标量,值域>= 0expandedXOut仅有activeNum行有效;
  • expandedXOut:2D,shape(min(NUM_ROWS, activeNum) * K, H),支持空 tensor;
  • expandedRowIdxOut:1D,shapeNUM_ROWS * K,支持空 tensor;
  • expandedExpertIdxOut:1D,shapeNUM_ROWS * K,支持空 tensor。

xrowIdxexpertIdx支持非连续 tensor(对应 API 文档中"非连续 Tensor"标记为 √),而三个输出要求连续内存。activeNumscaleoffset在 aclnn 接口中作为标量参数直接传入。

来自算子定义文件的佐证

在 moe_init_routing_quant_def.cpp 中,算子原型与 README 完全对应:

  • 输入x支持DT_FLOAT16 / DT_BF16 / DT_FLOATrow_idxexpert_idxDT_INT32,均使用 ND 格式并声明AutoContiguous
  • 输出expanded_x固定为DT_INT8expanded_row_idxexpanded_expert_idxDT_INT32
  • 属性active_num声明为 Int,scaleoffset声明为 Float;
  • 注册了ascend910bascend910_93两套 AICore 配置,并在 op_host/config 下提供了对应moe_init_routing_quant_binary.jsonmoe_init_routing_quant_simplified_key.ini配置文件。

产品支持情况与约束

根据算子 README 的产品支持矩阵:

产品是否支持
Ascend 950PR/Ascend 950DT×
Atlas A3 训练系列产品/Atlas A3 推理系列产品×
Atlas A2 训练系列产品/Atlas A2 推理系列产品
Atlas 200I/500 A2 推理产品×
Atlas 推理系列产品×
Atlas 训练系列产品×

需要说明的是,接口级文档 aclnnMoeInitRoutingQuant.md 中列出 Atlas A3 系列为"支持",而算子 README 标记为"×",两处口径存在差异;从算子定义注册的ascend910b(对应 A2 系列)与ascend910_93(对应 A3 系列)AICore 配置看,A3 侧代码路径已具备。实际使用时请以当前 CANN 版本配套的支持列表为准。

其余约束:README 中"约束说明"为无,API 文档则补充了一条确定性约束——aclnnMoeInitRoutingQuant默认采用确定性实现(与 确定性计算说明 相关),便于结果复现与调试。

调用说明:两段式 aclnn 接口

MoeInitRoutingQuant遵循 CANN 算子库通用的两段式接口调用范式:先调用GetWorkspaceSize接口完成入参校验、推导 workspace 大小并创建执行器;再调用执行接口真正下发计算。

函数原型

aclnnStatus aclnnMoeInitRoutingQuantGetWorkspaceSize( const aclTensor *x, const aclTensor *rowIdx, const aclTensor *expertIdx, int64_t activeNum, double scale, double offset, const aclTensor *expandedXOut, const aclTensor *expandedRowIdxOut, const aclTensor *expandedExpertIdxOut, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnMoeInitRoutingQuant( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);

第一段接口:aclnnMoeInitRoutingQuantGetWorkspaceSize

输入为xrowIdxexpertIdxactiveNumscaleoffset与三个输出 tensor,输出为workspaceSize(需要在 Device 侧申请的 workspace 大小)与executor(封装了算子计算流程的执行器)。

返回aclnnStatus状态码(错误码定义见 aclnn 返回码),第一段接口完成入参校验,常见报错场景:

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001输入和输出的 Tensor 是空指针
ACLNN_ERR_PARAM_INVALID161002输入和输出的数据类型不在支持的范围内
ACLNN_ERR_INNER_TILING_ERROR561002x的 shape 维度不为 2
rowIdx的 shape 不为 2 或rowIdxexpertIdx的 shape 不相等
activeNum小于 0
expandedRowIdxOutexpandedExpertIdxOut的 shape 不相等,且不等于NUM_ROWS * K
expandedXOut的 shape 不等于(min(NUM_ROWS, activeNum) * K, H)

第二段接口:aclnnMoeInitRoutingQuant

参数为第一段接口返回的workspace内存地址、workspaceSizeexecutor,以及指定执行任务的stream流;同样返回aclnnStatus状态码。

完整调用示例与运行步骤

示例代码位于 examples/test_aclnn_moe_init_routing_quant.cpp,其完整编译与执行流程请参考仓库的编译与运行样例。核心流程如下:

#include "acl/acl.h" #include "aclnnop/aclnn_moe_init_routing_quant.h" #include <iostream> #include <vector> // ... CHECK_RET / LOG_PRINT / GetShapeSize / Init / CreateAclTensor 等辅助函数见示例文件 ... int main() { int32_t deviceId = 0; // 根据实际 device 填写 aclrtStream stream; auto ret = Init(deviceId, &stream); // aclInit + aclrtSetDevice + aclrtCreateStream // 1. 构造输入与输出 std::vector<int64_t> xShape = {3, 4}; // NUM_ROWS=3, H=4 std::vector<int64_t> idxShape = {3, 2}; // NUM_ROWS=3, K=2 std::vector<int64_t> expandedXOutShape = {6, 4}; // min(3, activeNum=3)*2=6 std::vector<int64_t> idxOutShape = {6}; // NUM_ROWS*K=6 int64_t activeNum = 3; double scale = 1.0; double offset = 1.0; std::vector<float> xHostData = {0.1,0.1,0.1,0.1, 0.2,0.2,0.2,0.2, 0.3,0.3,0.3,0.3}; std::vector<int> expertIdxHostData = {1,2, 0,1, 0,2}; std::vector<int> rowIdxHostData = {0,3, 1,4, 2,5}; std::vector<int8_t> expandedXOutHostData(24, 0); std::vector<int> expandedRowIdxOutHostData(6, 0); std::vector<int> expandedExpertIdxOutHostData(6, 0); // 通过 CreateAclTensor 完成 aclrtMalloc + aclrtMemcpy(H2D) + aclCreateTensor(ACL_FORMAT_ND) // x: ACL_FLOAT;rowIdx/expertIdx: ACL_INT32;expandedXOut: ACL_INT8;两个 idxOut: ACL_INT32 // 2. 两段式调用 uint64_t workspaceSize = 0; aclOpExecutor* executor; ret = aclnnMoeInitRoutingQuantGetWorkspaceSize(x, rowIdx, expertIdx, activeNum, scale, offset, expandedXOut, expandedRowIdxOut, expandedExpertIdxOut, &workspaceSize, &executor); void* workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } ret = aclnnMoeInitRoutingQuant(workspaceAddr, workspaceSize, executor, stream); // 3. 同步等待并取回结果(D2H) ret = aclrtSynchronizeStream(stream); // aclrtMemcpy 分别取回 expandedXData(INT8)、expandedRowIdxData、expandedExpertIdxData 并打印 // 4. 释放资源 aclDestroyTensor(x); aclDestroyTensor(rowIdx); aclDestroyTensor(expertIdx); aclDestroyTensor(expandedXOut); aclDestroyTensor(expandedRowIdxOut); aclDestroyTensor(expandedExpertIdxOut); aclrtFree(xDeviceAddr); /* ... 其余 device 内存 ... */ if (workspaceSize > 0) aclrtFree(workspaceAddr); aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

要点提示:

  • 调用头文件为aclnnop/aclnn_moe_init_routing_quant.h
  • expandedXOut的 shape 必须等于(min(NUM_ROWS, activeNum) * K, H),示例中min(3,3)*2=6
  • workspace 仅在workspaceSize > 0时需要申请,结束后一并释放;
  • 输出结果通过aclrtMemcpy(..., ACL_MEMCPY_DEVICE_TO_HOST)回拷到 Host 侧打印验证。

源码级原理:从 shape 推导到 Kernel 流水线

Host 侧:InferShape 与 Tiling

在 moe_init_routing_quant_infershape.cpp 中,shape 推导逻辑与 API 文档约束一一对应:

  • 校验xrowIdxexpertIdx的维度为 2(或动态维度 -2),三者第一维必须一致,rowIdxexpertIdx第二维(K)必须一致;
  • 校验activeNum >= 0
  • 推导输出:expanded_x的 shape 为(min(n, activeNum) * k, cols)expanded_row_idxexpanded_expert_idx的 shape 为(n * k),其中k = max(rowIdx_k, expertIdx_k)
  • 输出数据类型固定为expanded_x → INT8、两个 idx 输出 → INT32。

Tiling 数据结构 表明该算子把任务拆成了四个子阶段分别做 Tiling:QuantVBSComputeTilingData(排序核心数/每核元素数/每核循环数)、QuantVMSMiddleComputeTilingDataQuantSortOutComputeTilingData(单次循环最大元素数)与QuantGatherOutComputeTilingData(gather 阶段的每核行数、每核 K、每循环行列数、maxColsOneLoopsplitFlag等),并在MoeInitRoutingQuantTilingData中统一封装coreNum/n/cols/k/scale/offset与各子结构,体现了"按行、按 K、按列分片"的多核并行切分思路。

Kernel 侧:三段式流水线

Kernel 入口在 moe_init_routing_quant.cpp,moe_init_routing_quant函数按TILING_KEY分发,依次执行三个算子阶段:

  1. 排序阶段(Sort):根据 Tiling 结果,小规模数据走MoeSortOneCore(moe_quant_sort_one_core.h),大规模数据走MoeSortMultiCore(moe_quant_sort_multi_core.h),内部复用多路归并排序MoeMrgsort(moe_quant_mrgsort.h)。从moe_quant_common.h的常量可见其面向昇腾向量指令做了精细设计:ONE_REPEAT_SORT_NUM = 32BLOCK_BYTES = 32(32 字节对齐,即 8 个 INT32/32 个 INT8 一个 block)、归并路数支持 2/3/4 路、并提供 256 长度的assist辅助索引表。归并排序循环内以CopyIn → MrgsortCompute → UpdateSortInfo → CopyOut流水化推进,直到所有元素排完。
  2. 源到目标映射阶段(SrcToDst)MoeSrcToDstOp(moe_quant_src_to_dst_op.h)利用排序阶段写入 workspace 的expandDstToSrcRow中间结果,反推并写出expandedRowIdxOut(即"排序后位置 → 原始行"的映射表),支持多核下按blockIdx分片、SyncAll全局同步。
  3. Gather + 量化阶段(GatherOut)MoeGatherOut<T>(moe_quant_gather_out.h)与面向少 active 行的MoeGatherOutSmallActiveRow(moe_quant_gather_out_small_activate_row.h)按expandedRowIdxOutx中 gather 对应行,并在Compute中完成量化:对 FP16 直接Muls(scale)+Adds(offset)+Cast(INT8, CAST_RINT);对 FP32 先转 FP16 再做同样的乘加与取整转换;对 BF16 则先Cast到 FP32 再经 FP16 中转完成量化。量化全程使用CAST_RINT舍入模式,与文档中"确定性实现"的要求一致。

值得注意的是,moe_quant_common.hMoeInitRoutingQuant命名空间下的公共工具(CeilAlignAlignBytesMin/Max及辅助常量)同时被上述各阶段头文件复用,整条流水线体现了"排序 — 映射 — 扩展量化"的清晰分层,也从源码层面印证了 README 三组公式的实现路径。

配套测试与验证

仓库为该算子提供了完整的测试保障:

  • UT(单元测试):tests/ut/op_host 下的test_moe_init_routing_quant_infershape.cpptest_moe_init_routing_quant_tiling.cpp覆盖 shape 推导与 Tiling 计算;tests/ut/op_kernel 下的test_moe_init_routing_quant.cpp通过moe_init_routing_quant_data/gen_data.pygen_tiling.py生成测试数据与 Tiling 参数,验证 Kernel 计算结果。
  • ST(系统测试):tests/st/aclnnMoeInitRoutingQuant 下的atk_aclnnMoeInitRoutingQuant.jsonexecutor_aclnnMoeInitRoutingQuant.py提供了基于 ATK 框架的端到端用例,可直接作为回归样例参考。

延伸阅读

  • 上游门控算子:aclnnMoeGatingTopKSoftmax,本算子的expertIdx即来源于此;
  • 接口调用范式:两段式接口说明;
  • 错误码含义:aclnn 返回码;
  • 样例编译执行:编译与运行样例;
  • 同系列 MoE 算子可参考 moe_init_routing(非量化版本)、moe_finalize_routing 与 moe_token_permute,对比理解 MoE 路由后 token 重排的完整家族。

【免费下载链接】ops-transformer本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-transformer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 11:34:12

WRF-CMAQ从零配置到跑通测试案例:环境搭建与编译运行全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 11:34:03

Python 基础语法(2)

顺序语句 默认情况下, Python 的代码执行顺序是按照从上到下的顺序, 依次执行的. print("1") print("2") print("3")执行结果一定为 “123”, 而不会出现 “321” 或者 “132” 等. 这种按照顺序执行的代码, 我们称为 顺序语句.这个顺序是很关键…

作者头像 李华
网站建设 2026/9/18 11:33:02

M2DGR多模态SLAM数据集:地面机器人退化场景评测与实战

去年年底接了个室内外混合场景的巡检机器人项目&#xff0c;客户要求方案在电梯、走廊、地下车库、园区道路这几类环境里都能稳定定位&#xff0c;我第一反应是找数据集做离线验证——毕竟真车跑一遍的成本太高&#xff0c;出了问题还复现不了。翻了一圈发现自己手里的存货都不…

作者头像 李华
网站建设 2026/9/18 11:29:58

AI对话系统中的动态记忆机制设计与实现

1. 项目背景与核心挑战在人工智能领域&#xff0c;记忆机制一直是制约系统智能水平的关键瓶颈。传统AI系统往往表现出两种极端&#xff1a;要么完全依赖即时输入做出反应&#xff08;如简单聊天机器人&#xff09;&#xff0c;要么过度依赖长期训练形成的固定模式&#xff08;如…

作者头像 李华
网站建设 2026/9/18 11:29:56

从灾难性遗忘到持续学习:模型可持续更新实战解析

持续学习&#xff08;Continual Learning&#xff09;最近两年在AI从业者圈子里被反复提起&#xff0c;但它并不是什么新概念。核心问题其实很朴素&#xff1a;一个已经训练好的模型&#xff0c;在部署之后遇到新任务、新数据&#xff0c;能不能在不彻底重训、不丢失旧知识的前…

作者头像 李华