感谢提供完整的复现脚本、预填值对照和环境信息。这两个现象需要分别处理:一是成功返回后索引输出仍未更新,二是可选输入组合的错误直到设备同步阶段才暴露。
问题分析
接口头文件将 outIndex 和 permuteTokenId 都定义为必选的一维 int32 输出,并未注明仅在 paddedMode=true 时有效(见接口声明)。结合您引用的非 padded 模式输出语义,不能将这两个输出简单视为该模式下无意义的数据。
您提供的结果显示:在 paddedMode=false、probs=nullptr、restoreShape=[3,4] 时,两段接口及流同步均成功,但两个输出各 6 个元素全部保持 0x5a5a5a5a。这表明该用例未得到约定的索引结果;主输出正确也不能说明整个接口的输出都有效。该现象支持输出写回遗漏的排查方向,但具体遗漏是在执行任务构造、结果拷贝还是 kernel 写出环节,仍需确认,不能仅凭 kernel 中未出现同名参数确定根因。
附带问题中,您报告 probsOptional 非空而 routingMapOptional 为空时,错误出现在 aclrtSynchronizeStream,返回 507057。这需要与第一段接口的参数错误区分开来;仅凭该同步错误码,还不能确认是空指针访问导致。
处理方向
主问题可沿非 padded 分支检查两项索引结果的生成和写回。若缺少对应计算,一个可行方向是在该分支补齐索引生成,并将结果写入调用方输出;处理时需保留 tokens_num * topK_num 的输出长度、索引对应关系,以及示例中 sortedIndices=-1 的处理语义,不能用简单清零代替正确计算。直接取消这两个输出也会改变现有接口约定,不宜作为默认处理方式。
对于附带问题,如果构造 permuteProbs 的路径确实依赖 routingMapOptional,可在第一段接口中增加相应的组合校验,让缺失依赖在任务下发前返回参数错误。具体返回码需要与现有校验逻辑保持一致,目前不能直接确定为 161001 或 161002。
目前已有信息足以明确上述排查方向,无需重复补充环境或主问题复现脚本。我们会继续确认非 padded 分支的输出写回路径,以及附带组合的输入依赖。


/assign @kdy18482276080


问题现象(主)
paddedMode=false时,两个输出outIndex与permuteTokenId从头到尾没有被写过:两段接口都返回 0,两个 buffer 保持调用方预填的内容(脏数据)。用 canary 复现(两输出 device 内存预填
0x5a5a5a5a):对照:同样输入换成
paddedMode=true,两输出均被正确写出(permuteTokenId=[0,0,0,1,1,1,2,2]、outIndex=[0,3,6,2,5,7,1,4],与numpy.sort/argsort逐元素一致);主输出unpermutedTokens在两种模式下都正确。资料明确描述了两输出在
paddedMode=false下的语义,说明不是“该模式下无意义”:outIndex:「当 paddedMode 为 false 时,outIndex 表示槽位索引,取值范围 [0,tokens_num * topK_num - 1]」,shape(tokens_num * topK_num);permuteTokenId:「索引取值范围 [0,tokens_num - 1]」,shape(tokens_num * topK_num)。调用方现在拿到的是未初始化显存(不是“值不对”,是“根本没算”),且不报错。
复现脚本
自包含(仅 CANN + python3):
#!/usr/bin/env python3 # aclnnMoeTokenUnpermuteWithRoutingMap:paddedMode=false 时 outIndex/permuteTokenId 不被写出 # 环境:Atlas A2 + CANN 9.2.0;运行:source $ASCEND_HOME_PATH/set_env.sh && python3 repro.py import ctypes as C, os, sys HOME = os.environ.get('ASCEND_HOME_PATH', '/usr/local/Ascend/ascend-toolkit/latest') LIB = os.path.join(HOME, 'aarch64-linux', 'lib64') if not os.path.isdir(LIB): LIB = os.path.join(HOME, 'lib64') acl = C.CDLL(os.path.join(LIB, 'libascendcl.so'), mode=C.RTLD_GLOBAL) nnop = C.CDLL(os.path.join(LIB, 'libnnopbase.so'), mode=C.RTLD_GLOBAL) opapi = C.CDLL(os.path.join(LIB, 'libopapi.so'), mode=C.RTLD_GLOBAL) F32, I32, ND = 0, 3, 2 H2D, D2H = 1, 2 acl.aclInit.argtypes = [C.c_char_p] acl.aclrtSetDevice.argtypes = [C.c_int32] acl.aclrtCreateStream.argtypes = [C.POINTER(C.c_void_p)] acl.aclrtMalloc.argtypes = [C.POINTER(C.c_void_p), C.c_size_t, C.c_int] acl.aclrtMemcpy.argtypes = [C.c_void_p, C.c_size_t, C.c_void_p, C.c_size_t, C.c_int] acl.aclrtSynchronizeStream.argtypes = [C.c_void_p] nnop.aclCreateIntArray.argtypes = [C.POINTER(C.c_int64), C.c_uint64] nnop.aclCreateIntArray.restype = C.c_void_p nnop.aclCreateTensor.restype = C.c_void_p nnop.aclCreateTensor.argtypes = [C.POINTER(C.c_int64), C.c_uint64, C.c_int32, C.POINTER(C.c_int64), C.c_int64, C.c_int32, C.POINTER(C.c_int64), C.c_uint64, C.c_void_p] CANARY = 0x5a5a5a5a def tensor(host, dtype, shape): nbytes = C.sizeof(host) dev = C.c_void_p() assert acl.aclrtMalloc(C.byref(dev), nbytes, 0) == 0 assert acl.aclrtMemcpy(dev, nbytes, host, nbytes, H2D) == 0 rank = len(shape) dims = (C.c_int64 * rank)(*shape) st = [1] * rank for i in range(rank - 2, -1, -1): st[i] = shape[i + 1] * st[i + 1] strides = (C.c_int64 * rank)(*st) t = nnop.aclCreateTensor(dims, rank, dtype, strides, 0, ND, dims, rank, dev) assert t, 'aclCreateTensor failed' return t, dev, host def main(): dev_id = int(os.environ.get('ACL_DEVICE_ID', '0')) assert acl.aclInit(None) == 0 and acl.aclrtSetDevice(dev_id) == 0 stream = C.c_void_p() assert acl.aclrtCreateStream(C.byref(stream)) == 0 tokens, topK, hidden = 3, 2, 4 rows = tokens * topK perm_host = (C.c_float * (rows * hidden))(*[float(i + 1) for i in range(rows * hidden)]) sidx_host = (C.c_int32 * rows)(0, 3, -1, 5, 1, 4) unperm_host = (C.c_float * (tokens * hidden))(*([0.0] * (tokens * hidden))) # 两个 int32 输出全部预填 canary 0x5a5a5a5a oi_host = (C.c_int32 * rows)(*([CANARY] * rows)) pt_host = (C.c_int32 * rows)(*([CANARY] * rows)) perm = tensor(perm_host, F32, (rows, hidden)) sidx = tensor(sidx_host, I32, (rows,)) unperm = tensor(unperm_host, F32, (tokens, hidden)) out_index = tensor(oi_host, I32, (rows,)) perm_id = tensor(pt_host, I32, (rows,)) restore_dims = (C.c_int64 * 2)(tokens, hidden) restore = nnop.aclCreateIntArray(restore_dims, 2) ws, ex = C.c_uint64(0), C.c_void_p() gws = opapi.aclnnMoeTokenUnpermuteWithRoutingMapGetWorkspaceSize gws.restype = C.c_int32 gws.argtypes = [C.c_void_p, C.c_void_p, C.c_void_p, C.c_void_p, C.c_bool, C.c_void_p, C.c_void_p, C.c_void_p, C.c_void_p, C.c_void_p, C.POINTER(C.c_uint64), C.POINTER(C.c_void_p)] # routingMap / probs / permuteProbs 全部传 nullptr(probs=None 路径,靠 restoreShape 提供输出形状) rc = gws(perm[0], sidx[0], None, None, False, restore, unperm[0], out_index[0], perm_id[0], None, C.byref(ws), C.byref(ex)) print('paddedMode=false, probs=nullptr, restoreShape=[%d,%d] -> GetWorkspaceSize rc = %d' % (tokens, hidden, rc)) if rc != 0: return 1 wsp = C.c_void_p() if ws.value: assert acl.aclrtMalloc(C.byref(wsp), ws.value, 0) == 0 exe = opapi.aclnnMoeTokenUnpermuteWithRoutingMap exe.restype = C.c_int32 exe.argtypes = [C.c_void_p, C.c_uint64, C.c_void_p, C.c_void_p] assert exe(wsp, ws.value, ex, stream) == 0 assert acl.aclrtSynchronizeStream(stream) == 0 oi = (C.c_int32 * rows)() pt = (C.c_int32 * rows)() acl.aclrtMemcpy(oi, C.sizeof(oi), out_index[1], C.sizeof(oi), D2H) acl.aclrtMemcpy(pt, C.sizeof(pt), perm_id[1], C.sizeof(pt), D2H) kept_i = sum(1 for v in oi if (v & 0xffffffff) == CANARY) kept_p = sum(1 for v in pt if (v & 0xffffffff) == CANARY) print('outIndex = %s 仍为 canary: %d/%d' % (list(oi), kept_i, rows)) print('permuteTokenId = %s 仍为 canary: %d/%d' % (list(pt), kept_p, rows)) print('结论:%s' % ('BUG 复现(两输出未被写出,两段均返回 0)' if kept_i or kept_p else '未复现')) return 0 if __name__ == '__main__': sys.exit(main())实测输出:
根因线索
op_kernel/下检索不到outIndex/permuteTokenId(grep -rn 'outIndex' op_kernel/无结果);这两个输出只在op_host/op_api/aclnn_moe_token_unpermute_with_routing_map.cpp的签名/校验/DFX 里出现。paddedMode=true分支能正确写出(走排序索引通道),非 padded 分支没有对应的填充逻辑。建议在非 padded 分支补上两输出的 kernel 写出;若确定该模式下无需返回,应在 host 侧置空并在资料中同步修改(当前资料是写了两者的语义的)。附带发现(同一接口的未校验组合)
probsOptional != nullptr而routingMapOptional == nullptr时,两段不报参数错误,任务下发后在aclrtSynchronizeStream报 507057(ACL_ERROR_RT_SUSPECT_REMOTE_ERROR),稳定复现(连跑两次一致):routingMap 是「probs 非空时构造 permuteProbs 的掩码」,缺失应在第一段返回 161001/161002,而不是把空指针带进 kernel 让设备侧报错。
影响
paddedMode=false的调用方,读outIndex/permuteTokenId都会拿到未初始化显存,值随显存历史变化、非确定性且不报错;下游若拿它做反查/断言会随机出错。master 复核
master
23cb0599的moe/moe_token_unpermute_with_routing_map与本次定位版本一致(op_kernel/内仍无outIndex/permuteTokenId写出),缺陷仍在。