- 算子库
- 人工智能
- 深度学习
- Ascend
【免费下载链接】ops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
导读
aclnnMoeTokenUnpermuteWithRoutingMap是 CANN ops-transformer 大模型算子库中用于 MoE(Mixture of Experts)路由结果回写的关键算子。在 MoE 网络中,Token 会先被aclnnMoeTokenPermuteWithRoutingMap按路由映射打散(Permute)到各专家槽位,本算子则负责将专家计算完成后的permutedTokens依据sortedIndices记录的映射关系逆置换(Unpermute)并累加回原始的unpermutedTokens,同时支持按probs路由权重对 token 进行加权。读完本文,你将掌握该算子的计算公式、两段式 aclnn 接口原型、全部参数约束、返回码语义,以及一份可直接参考编译运行的完整调用示例。
产品支持情况
根据 aclnnMoeTokenUnpermuteWithRoutingMap 接口文档 与算子目录下的 README,该算子在不同产品上的支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR / Ascend 950DT | 支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 不支持 |
| Atlas 训练系列产品 | 不支持 |
| Kirin X90 处理器系列产品 | 支持(不支持 BFLOAT16) |
| Kirin 9030 处理器系列产品 | 支持(不支持 BFLOAT16) |
从算子定义源码 op_host/moe_token_unpermute_with_routing_map_def.cpp 可以看到,AICore().AddConfig分别注册了ascend910b、ascend910_93、ascend950、kirinx90、kirin9030等硬件配置,与上表的产品支持情况一一对应;其中 Kirin 系列的配置额外声明了PrecisionReduceFlag(true)且输入数据类型仅支持FLOAT16、FLOAT,这正是 README 中"Kirin 系列不支持 BFLOAT16"的源码依据。
功能说明与计算公式
接口功能
aclnnMoeTokenUnpermuteWithRoutingMap对经过aclnnMoeTokenPermuteWithRoutingMap处理的permutedTokens执行逆置换,将其累加回原unpermutedTokens:
- 根据
sortedIndices存储的下标,取出permutedTokens中存储的输入数据; - 如果存在
probs数据,permutedTokens会先与probs相乘; - 最后进行累加求和,输出计算结果。
该算子与正向的aclnnMoeTokenPermuteWithRoutingMap形成"打散—计算—还原"的完整闭环,是 MoE 网络中每个专家并行计算完成后,将结果按 token 维度聚合回原始布局的关键一步。
基础量定义
计算公式中涉及的基础量定义如下:
$$ topK_num = permutedTokens.size(0) // routingMapOptional.size(0) $$
其中topK_num表示每个 token 预留的最大专家槽位数。当paddedMode为false时,每个 token 实际选择的专家数可以小于等于topK_num;未使用的槽位在sortedIndices中以-1表示,计算时跳过该槽位。
$$ numExperts = probs.size(1) $$
$$ numTokens = probs.size(0) $$
$$ capacity = sortedIndices.size(0) // numExperts $$
其中capacity表示在paddedMode为true时每个专家能够处理的 token 个数。从 tiling 源码 op_host/moe_token_unpermute_with_routing_map_tiling.cpp 中可以看到,numOutTokens取自permutedTokens第 0 维,totalLength取自sortedIndices第 0 维,tokensNum(即 numTokens)与numExperts在存在probs时取自probs的 shape,这些定义与文档公式完全一致。
四种计算分支
根据probs是否为 None 以及paddedMode的取值,算子存在四种计算路径:
(1)probs 不为 None,paddedMode 为 true 时:
$$ permuteProbs[i//capacity, sortedIndices[i]] = probs[i] $$
$$ permutedTokens = permutedTokens * permuteProbs $$
$$ unpermutedTokens = zeros(restoreShape, dtype=permutedTokens.dtype, device=permutedTokens.device) $$
$$ permuteTokenId, outIndex = sortedIndices.sort(dim=-1) $$
$$ unpermutedTokens[permuteTokenId[i]] += permutedTokens[outIndex[i]] $$
(2)probs 不为 None,paddedMode 为 false 时(T 为转置操作):
$$ permuteProbs = probs.T.maskedSelect(routingMap.T) $$
$$ permutedTokens = permutedTokens * permuteProbs $$
$$ unpermutedTokens = zeros(restoreShape, dtype=permutedTokens.dtype, device=permutedTokens.device) $$
$$ if\ sortedIndices[i] >= 0:\quad unpermutedTokens[i//topK_num] += permutedTokens[sortedIndices[i]] * permuteProbs[i] $$
(3)probs 为 None,paddedMode 为 true 时:
$$ permuteTokenId, outIndex = sortedIndices.sort(dim=-1) $$
$$ unpermutedTokens[permuteTokenId[i]] += permutedTokens[outIndex[i]] $$
(4)probs 为 None,paddedMode 为 false 时:
$$ if\ sortedIndices[i] >= 0:\quad unpermutedTokens[i//topK_num] += permutedTokens[sortedIndices[i]] $$
源码实现路径印证
四种计算分支在算子内核入口 op_kernel/moe_token_unpermute_with_routing_map.cpp 中通过 tilingKey 进行分发:
TILING_KEY_IS(1000):走KernelMoeTokenUnpermuteWithRoutingMapPad(paddedMode 为 true 的路径,对应源码 op_kernel/moe_token_unpermute_with_routing_map_pad.h,其 Init 中直接以sortedIndices、probs为入参);TILING_KEY_IS(1):haveProbs=true,即 probs 不为 None 的非 padding 路径;TILING_KEY_IS(0):haveProbs=false,即 probs 为 None 的非 padding 路径(对应源码 op_kernel/moe_token_unpermute_with_routing_map_not_pad.h)。
非 padding 路径会先通过KernelMaskedSelectV3执行maskedSelect得到permute_probs(对应公式中的probs.T.maskedSelect(routingMap.T)),再由主 kernel 完成乘权与累加。
在 aclnn 接口层 op_host/op_api/aclnn_moe_token_unpermute_with_routing_map.cpp 中,paddedMode 为 true 时通过l0op::Sort对sortedIndices排序得到permuteTokenId与outIndex,随后调用l0op::InplaceIndexAddWithSorted(Ascend 910B/910_93/DAV_3510 架构且满足 dtype 条件时)或l0op::InplaceIndexAddAiCore完成"排序后按索引累加";paddedMode 为 false 时则由l0op::MoeTokenUnpermuteWithRoutingMap内核直接产出unpermutedTokens。这种"排序 + 索引累加"的组合从底层印证了公式(1)(3)中sort + index_add的语义。
两段式接口与函数原型
与其他 aclnn 算子一致,本算子采用两段式接口调用方式:必须先调用aclnnMoeTokenUnpermuteWithRoutingMapGetWorkspaceSize接口获取计算所需 workspace 大小以及包含了算子计算流程的执行器,再调用aclnnMoeTokenUnpermuteWithRoutingMap接口执行计算。
第一段接口(获取 workspace 大小与执行器):
aclnnStatus aclnnMoeTokenUnpermuteWithRoutingMapGetWorkspaceSize( const aclTensor *permutedTokens, const aclTensor *sortedIndices, const aclTensor *routingMapOptional, const aclTensor *probsOptional, bool paddedMode, const aclIntArray *restoreShapeOptional, aclTensor *unpermutedTokens, aclTensor *outIndex, aclTensor *permuteTokenId, aclTensor *permuteProbs, uint64_t *workspaceSize, aclOpExecutor **executor);第二段接口(执行计算):
aclnnStatus aclnnMoeTokenUnpermuteWithRoutingMap( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream);两段式接口的实现在 op_host/op_api/aclnn_moe_token_unpermute_with_routing_map.cpp 中:第一段接口完成参数校验、构图(CREATE_EXECUTOR)并通过uniqueExecutor->GetWorkspaceSize()返回 workspace 大小;第二段接口则通过CommonOpExecutorRun完成实际计算下发。另外,tiling 阶段在 op_host/moe_token_unpermute_with_routing_map_tiling.cpp 中申请了16 * 1024 * 1024 * 2字节的系统 workspace,用于内核计算中的中间数据交换。
aclnnMoeTokenUnpermuteWithRoutingMapGetWorkspaceSize 参数说明
第一段接口的完整参数说明如下表:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| permutedTokens(aclTensor*) | 输入 | 表示输入 token | Shape 中的 capacity 表示每个专家能够处理的 token 个数 | BFLOAT16、FLOAT16、FLOAT | ND | paddedMode 为 false:(tokens_num * topK_num, hidden_size);paddedMode 为 true:(experts_num * capacity, hidden_size) | √ |
| sortedIndices(aclTensor*) | 输入 | 表示输入输出梯度的映射关系 | paddedMode 为 false 时要求索引取值范围 [0, tokens_num * topK_num - 1];paddedMode 为 true 时索引取值范围 [0, tokens_num - 1] | INT32 | ND | paddedMode 为 false:(tokens_num * topK_num);paddedMode 为 true:(experts_num * capacity) | √ |
| routingMapOptional(aclTensor*) | 输入 | 计算公式中的 routingMapOptional,代表对应位置的 Token 是否被对应专家处理 | 当输入 probsOptional 为空指针时不需要此输入,应该传入空指针。当数据类型为 INT8,取值支持 0、1;当数据类型为 bool,取值支持 true、false | INT8、BOOL | ND | (tokens_num, experts_num) | √ |
| probsOptional(aclTensor*) | 输入 | 计算公式中的 probsOptional,代表对应位置的 Token 被对应专家处理后的结果在最终结果中的权重 | 数据类型与 permutedTokens 相同,或者当 permutedTokens 是 BFLOAT16 时 probsOptional 支持 FLOAT | BFLOAT16、FLOAT16、FLOAT | ND | 与 routingMapOptional 一致 | √ |
| paddedMode(bool) | 输入 | 表示填充模式是否开启 | true 表示开启 paddedMode;false 表示关闭 paddedMode | - | - | - | - |
| restoreShapeOptional(aclIntArray*) | 输入 | 表示 unpermutedTokens 的 shape | size 大小为 2 | INT64 | - | - | - |
| unpermutedTokens(aclTensor*) | 输出 | 正向输出结果,计算公式中的 unpermutedTokens | - | BFLOAT16、FLOAT16、FLOAT | ND | (tokens_num, hidden_size) | √ |
| outIndex(aclTensor*) | 输出 | 表示输出的索引值,计算公式中的 outIndex | 当 paddedMode 为 false 时,outIndex 表示槽位索引,取值范围 [0, tokens_num * topK_num - 1],sortedIndices 中的 -1 仅表示无效输入槽位,不改变 outIndex 的槽位索引范围;当 paddedMode 为 true 时,索引取值范围 [0, experts_num * capacity - 1] | INT32 | ND | paddedMode 为 false:(tokens_num * topK_num);paddedMode 为 true:(experts_num * capacity) | √ |
| permuteTokenId(aclTensor*) | 输出 | 计算公式中的 permuteTokenId | 索引取值范围 [0, tokens_num - 1] | INT32 | ND | paddedMode 为 false:(tokens_num * topK_num);paddedMode 为 true:(experts_num * capacity) | √ |
| permuteProbs(aclTensor*) | 输出 | 计算公式中的 permuteProbs,表示输出经过排序后的 probs | 与 probsOptional 相同 | BFLOAT16、FLOAT16、FLOAT | ND | 1 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
关于"非连续 Tensor"列:接口在内部会通过l0op::Contiguous将各输入统一转为连续内存再参与计算(见 op_host/op_api/aclnn_moe_token_unpermute_with_routing_map.cpp 中l0op::Contiguous(permutedTokens, ...)等调用),因此支持非连续张量作为输入;输出侧则通过l0op::ViewCopy将计算结果写回用户指定的可能非连续的输出张量。
数据类型与 shape 的源码校验
接口第一段会对参数进行严格的 dtype 与 shape 校验(对应 op_host/op_api/aclnn_moe_token_unpermute_with_routing_map.cpp 中的CheckDtypeValid与CheckShapeValid):
permutedTokens、unpermutedTokens必须是二维(TRANSPOSE_SHAPE_SIZE == 2),且 dtype 属于 {FLOAT16, FLOAT, BF16};sortedIndices、outIndex、permuteTokenId必须是一维(INDEX_SHAPE == 1),且 dtype 为 INT32;routingMapOptional的 dtype 支持 INT8、UINT8、BOOL;- 存在
probsOptional时,其 dtype 必须与permutedTokens一致,唯一例外是permutedTokens为 BF16 时probsOptional允许为 FLOAT(即混合精度场景,接口内通过l0op::Cast将 FP32 中间结果转回 BF16 输出,对应源码中的isMixed逻辑); probsOptional与routingMapOptional同时存在时,两者的 shape 需保持一致;- 格式方面,各输入若为私有格式会输出 warning,建议使用 ND 格式。
返回值与错误码
接口返回aclnnStatus状态码,具体含义参见 aclnn 返回码。
第一段接口完成入参校验,出现以下场景时报错:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 必选输入或必选输出的 Tensor 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | 输入或输出的数据类型或 shape 不在支持的范围内 |
| ACLNN_ERR_INNER_NULLPTR | 561103 | topK_num > 512 |
| ACLNN_ERR_INNER_NULLPTR | 561103 | probsOptional 的 shape 不在支持的范围内 |
上述校验逻辑同样出现在 tiling 阶段的InputParamCheck中:当topK > 512时直接返回ge::GRAPH_FAILED,并打印"topK can not larger than 512"(见 op_host/moe_token_unpermute_with_routing_map_tiling.cpp)。此外,CheckNotNull还规定probsOptional与restoreShapeOptional不能同时为空指针。
另外,源码中还对空 Tensor 场景做了专门处理:当sortedIndices为空,或paddedMode == false且permutedTokens为空时,第一段接口会直接构造ZerosLike的零值图,将各输出置零后即返回,无需下发实际计算。
aclnnMoeTokenUnpermuteWithRoutingMap 参数说明
第二段接口参数相对简单,均由第一段接口产生或指定:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnMoeTokenUnpermuteWithRoutingMapGetWorkspaceSize 获取 |
| executor | 输入 | op 执行器,包含了算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
返回值同样为aclnnStatus,具体参见 aclnn 返回码。
约束说明
使用该算子时需要遵守以下约束:
- 确定性计算:
aclnnMoeTokenUnpermuteWithRoutingMap默认采用非确定性实现,支持通过aclrtCtxSetSysParamOpt开启确定性。从 op_host/op_api/aclnn_moe_token_unpermute_with_routing_map.cpp 可以看到,接口会通过aclrtGetSysParamOpt(ACL_OPT_DETERMINISTIC, ...)查询确定性开关,在 Ascend 910B/910_93 上开启确定性时,会切换到InplaceIndexAddWithSorted的确定性实现路径。 - topK_num <= 512:paddedMode 为 false 时,每个 token 最多预留 topK_num 个专家槽位,routingMap 中每行为 1(或 true)的个数小于等于 topK_num;sortedIndices 中允许使用 -1 表示无效槽位。
- 以下场景后续版本会拦截,如果提示 warning,建议整改:
- paddedMode 为 true,且 topK_num > experts_num(tiling 阶段会打印
"topK is larger than experts_num, which may cause unexpected behavior"); - paddedMode 为 true,且 capacity > tokens_num;
- routingMap 的数据类型或 shape 不符合要求;
- 输入 tensor 的数据格式不为 ND。
- paddedMode 为 true,且 topK_num > experts_num(tiling 阶段会打印
- Kirin 系列限制:Kirin X90 / Kirin 9030 处理器系列产品不支持 BFLOAT16 输入(参见 README 与算子定义中
config_kirin仅注册DT_FLOAT16、DT_FLOAT的声明)。
调用示例
以下为完整调用示例代码(仅供参考,具体编译和执行过程请参考编译与运行样例)。该示例的独立可编译版本位于 examples/test_aclnn_moe_token_unpermute_with_routing_map.cpp,UT 测试版本位于 tests/ut/op_host/op_api/test_aclnn_moe_token_unpermute_with_routing_map.cpp。
#include "acl/acl.h" #include "aclnnop/aclnn_moe_token_unpermute_with_routing_map.h" #include <iostream> #include <vector> #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vector<int64_t>& shape) { int64_t shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法,资源初始化 auto ret = aclInit(nullptr); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret); ret = aclrtSetDevice(deviceId); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret); ret = aclrtCreateStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret); return 0; } template <typename T> int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size = GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); // 计算连续tensor的strides std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1. 固定写法,device/stream初始化,参考acl对外接口列表 // 根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); // check根据自己的需要处理 CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口定义构造 std::vector<int64_t> permutedTokensShape = {2, 2}; std::vector<int64_t> sortedIndicesShape = {2}; std::vector<int64_t> routingMapOptionalShape = {2, 2}; std::vector<int64_t> probsShape = {2, 2}; std::vector<int64_t> unpermutedTokensShape = {2, 2}; std::vector<int64_t> outIndexShape = {2}; std::vector<int64_t> permuteTokenIdShape = {2}; std::vector<int64_t> permuteProbsShape = {2}; void* permutedTokensDeviceAddr = nullptr; void* sortedIndicesDeviceAddr = nullptr; void* routingMapOptionalDeviceAddr = nullptr; void* probsDeviceAddr = nullptr; void* unpermutedTokensDeviceAddr = nullptr; void* outIndexDeviceAddr = nullptr; void* permuteTokenIdDeviceAddr = nullptr; void* permuteProbsDeviceAddr = nullptr; //in aclTensor* permutedTokens = nullptr; aclTensor* sortedIndices = nullptr; aclTensor* routingMapOptional = nullptr; aclTensor* probs = nullptr; aclTensor* unpermutedTokens = nullptr; aclTensor* outIndex = nullptr; aclTensor* permuteTokenId = nullptr; aclTensor* permuteProbs = nullptr; bool paddedMode = true; std::vector<int64_t> restoreShapeOptionalData = {2, 2}; aclIntArray *restoreShapeOptional = aclCreateIntArray(restoreShapeOptionalData.data(), restoreShapeOptionalData.size()); //构造数据 std::vector<float> permutedTokensHostData = {1.0, 1.0, 1.0, 1.0}; std::vector<int> sortedIndicesHostData = {1, 1}; std::vector<char> routingMapOptionalHostData = {1, 1, 1, 1}; std::vector<float> probsHostData = {1, 1, 1, 1}; std::vector<float> unpermutedTokensHostData = {0, 0, 0, 0}; std::vector<int> outIndexHostData = {0, 0}; std::vector<int> permuteTokenIdHostData = {0, 0}; std::vector<float> permuteProbsHostData = {0, 0}; // 创建self aclTensor ret = CreateAclTensor(permutedTokensHostData, permutedTokensShape, &permutedTokensDeviceAddr, aclDataType::ACL_FLOAT, &permutedTokens); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(sortedIndicesHostData, sortedIndicesShape, &sortedIndicesDeviceAddr, aclDataType::ACL_INT32, &sortedIndices); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(routingMapOptionalHostData, routingMapOptionalShape, &routingMapOptionalDeviceAddr, aclDataType::ACL_INT8, &routingMapOptional); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(probsHostData, probsShape, &probsDeviceAddr, aclDataType::ACL_FLOAT, &probs); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建out aclTensor ret = CreateAclTensor(unpermutedTokensHostData, unpermutedTokensShape, &unpermutedTokensDeviceAddr, aclDataType::ACL_FLOAT, &unpermutedTokens); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(outIndexHostData, outIndexShape, &outIndexDeviceAddr, aclDataType::ACL_INT32, &outIndex); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(permuteTokenIdHostData, permuteTokenIdShape, &permuteTokenIdDeviceAddr, aclDataType::ACL_INT32, &permuteTokenId); CHECK_RET(ret == ACL_SUCCESS, return ret); ret = CreateAclTensor(permuteProbsHostData, permuteProbsShape, &permuteProbsDeviceAddr, aclDataType::ACL_FLOAT, &permuteProbs); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的API uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnMoeTokenUnpermuteWithRoutingMap第一段接口 ret = aclnnMoeTokenUnpermuteWithRoutingMapGetWorkspaceSize(permutedTokens, sortedIndices, routingMapOptional, probs, paddedMode, restoreShapeOptional, unpermutedTokens, outIndex, permuteTokenId, permuteProbs, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnMoeTokenUnpermuteWithRoutingMapGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } ret = aclnnMoeTokenUnpermuteWithRoutingMap(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnMoeTokenUnpermuteWithRoutingMap failed. ERROR: %d\n", ret); return ret); // 4. 固定写法,同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. 获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改 auto unpermutedTokensSize = GetShapeSize(unpermutedTokensShape); std::vector<float> unpermutedTokensData(unpermutedTokensSize, 0); ret = aclrtMemcpy(unpermutedTokensData.data(), unpermutedTokensData.size() * sizeof(unpermutedTokensData[0]), unpermutedTokensDeviceAddr, unpermutedTokensSize * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < unpermutedTokensSize; i++) { LOG_PRINT("unpermutedTokensData[%ld] is: %f\n", i, unpermutedTokensData[i]); } // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensor(permutedTokens); aclDestroyTensor(sortedIndices); aclDestroyTensor(routingMapOptional); aclDestroyTensor(probs); aclDestroyTensor(unpermutedTokens); aclDestroyTensor(outIndex); aclDestroyTensor(permuteTokenId); aclDestroyTensor(permuteProbs); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(permutedTokensDeviceAddr); aclrtFree(sortedIndicesDeviceAddr); aclrtFree(routingMapOptionalDeviceAddr); aclrtFree(probsDeviceAddr); aclrtFree(unpermutedTokensDeviceAddr); aclrtFree(outIndexDeviceAddr); aclrtFree(permuteTokenIdDeviceAddr); aclrtFree(permuteProbsDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例要点解读
对照上表参数说明,示例中的关键点如下:
- 示例采用
paddedMode = true,此时permutedTokens的 shape 应为(experts_num * capacity, hidden_size),示例中取(2, 2)对应experts_num=1、capacity=2或experts_num=2、capacity=1的等价情形; routingMapOptional使用char(INT8)构造,取值全为 1,表示每个 token 均被对应专家处理;restoreShapeOptional通过aclCreateIntArray创建,值为{2, 2},即unpermutedTokens的目标 shape 为(tokens_num=2, hidden_size=2);permuteProbs输出 shape 为{2}(一维,与参数表中"维度(shape) 为 1"的说明一致);- workspace 只有在
workspaceSize > 0时才需要aclrtMalloc申请,使用完毕后必须aclrtFree释放。
测试与验证
仓库为该算子提供了完整的多层测试覆盖,可用于验证调用与结果正确性:
- ST 用例:位于 tests/st/aclnnMoeTokenUnpermuteWithRoutingMap/executor_aclnnMoeTokenUnpermuteWithRoutingMap.py,其用例模式枚举了
padded、non_padded、non_padded_minus_one三种场景(CASE_MODES),其中non_padded_minus_one专门覆盖sortedIndices中含-1无效槽位的分支,对应的数据生成配置见 atk_aclnnMoeTokenUnpermuteWithRoutingMap.json; - UT 用例:tiling 单测见 tests/ut/op_host/test_moe_token_unpermute_with_routing_map_tiling.cpp,aclnn 接口单测见 tests/ut/op_host/op_api/test_aclnn_moe_token_unpermute_with_routing_map.cpp,kernel 单测见 tests/ut/op_kernel/test_moe_token_unpermute_with_routing_map.cpp。
总结
aclnnMoeTokenUnpermuteWithRoutingMap是 ops-transformer 中 MoE 路由数据回写链路的核心算子。它通过sortedIndices与routingMap精确还原每个 token 与其专家槽位的映射关系,支持paddedMode开关以适配"按专家填充容量"与"按 token 预留 topK 槽位"两种主流 MoE 布局,并支持可选的probs权重乘加与 BF16+FLOAT 混合精度计算。在使用时,重点关注topK_num <= 512的硬约束、probsOptional与restoreShapeOptional不能同时为空的入参约束,以及 paddedMode 下topK_num > experts_num、capacity > tokens_num等会被后续版本拦截的告警场景。结合本文的两段式接口说明与完整调用示例,即可在支持的 Ascend A2/A3/950 及 Kirin 系列产品上正确接入该算子。
- 算子库
- 人工智能
- 深度学习
- Ascend
【免费下载链接】ops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
相关推荐
MoeTokenUnpermuteWithRoutingMap 算子全解析:CANN ops-transformer 中 MoE Token 反置换与加权累加实现
MoeTokenUnpermuteWithRoutingMap 算子全解析:CANN ops transformer 中 MoE Token 反置换与加权累加实
算子库人工智能深度学习AscendMoeTokenUnpermute 算子深度指南:CANN ops-transformer 中的 MoE Token 去重排与加权聚合实现
MoeTokenUnpermute 算子深度指南:CANN ops transformer 中的 MoE Token 去重排与加权聚合实现 导读 MoeToke
算子库人工智能深度学习AscendCANN ops-transformer MoeTokenUnpermute 算子深度解析:MoE Token 反置换与加权归约的 NPU 实现及 aclnn 调用实战
CANN ops transformer MoeTokenUnpermute 算子深度解析:MoE Token 反置换与加权归约的 NPU 实现及 aclnn
算子库人工智能深度学习Ascend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考