Closed
[Bug-Report|缺陷反馈]: aclnnMoeTokenUnpermuteWithRoutingMap 在 paddedMode=false 下 outIndex/permuteTokenId 从不写出(返回 0,输出为脏数据) #6607
liuhanwen8Add on  17 days agoClosed at  10 days ago
liuhanwen8
liuhanwen8
17 days ago Create

环境:Atlas A2(910B2)×8,CANN 9.2.0,npu-smi 26.1.1;源码 cann/ops-transformer master 23cb0599(moe/moe_token_unpermute_with_routing_map)

问题现象(主)

paddedMode=false 时,两个输出 outIndex 与 permuteTokenId 从头到尾没有被写过:两段接口都返回 0,两个 buffer 保持调用方预填的内容(脏数据)。

用 canary 复现(两输出 device 内存预填 0x5a5a5a5a):

paddedMode=false, probs=nullptr, restoreShape=[3,4] -> GetWorkspaceSize rc = 0
outIndex       = [1515870810 x6]   仍为 canary: 6/6      # 1515870810 == 0x5a5a5a5a
permuteTokenId = [1515870810 x6]   仍为 canary: 6/6

对照:同样输入换成 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())

实测输出:

paddedMode=false, probs=nullptr, restoreShape=[3,4] -> GetWorkspaceSize rc = 0
outIndex       = [1515870810, 1515870810, 1515870810, 1515870810, 1515870810, 1515870810]  仍为 canary: 6/6
permuteTokenId = [1515870810, 1515870810, 1515870810, 1515870810, 1515870810, 1515870810]  仍为 canary: 6/6

根因线索

  • 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),稳定复现(连跑两次一致):

probs!=null, routingMap=null -> rc=-1  msg=aclrtSynchronizeStream 返回 507057

routingMap 是「probs 非空时构造 permuteProbs 的掩码」,缺失应在第一段返回 161001/161002,而不是把空指针带进 kernel 让设备侧报错。

影响

  • 主问题:凡用 paddedMode=false 的调用方,读 outIndex/permuteTokenId 都会拿到未初始化显存,值随显存历史变化、非确定性且不报错;下游若拿它做反查/断言会随机出错。
  • 附带问题:非法组合落成设备侧错误,掩盖真正的参数问题。

master 复核

master 23cb0599 的 moe/moe_token_unpermute_with_routing_map 与本次定位版本一致(op_kernel/ 内仍无 outIndex/permuteTokenId 写出),缺陷仍在。

likedislike
周云飞
周云飞Collaborators
17 days ago Comments:

感谢提供完整的复现脚本、预填值对照和环境信息。这两个现象需要分别处理:一是成功返回后索引输出仍未更新,二是可选输入组合的错误直到设备同步阶段才暴露。

问题分析

接口头文件将 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 分支的输出写回路径,以及附带组合的输入依赖。

likedislike
游震Collaborators
17 days ago Comments:
likedislike
CANN-robotCANN-robotCollaborators
17 days ago Assign kdy18482276080 as responsible person
Kkdy18482276080Collaborators
12 days ago Associated pull request
CANN-robotCANN-robotCollaborators
10 days ago Close issue
CANN-robotCANN-robotCollaborators
10 days ago Added label: resolved