已关闭
[Bug-Report|缺陷反馈]: aclnnMv cubeMathType=3(USE_HF32) + BF16 输入未报 161002(A2 文档声明不支持),HF32 被静默忽略 #7053
liuhanwen8创建于  9 天前关闭于  4 天前
liuhanwen8
liuhanwen8
9 天前 创建

问题描述

aclnnMv(ops-nn matmul/mv)在 cubeMathType=3(USE_HF32)且输入为 BF16 时,两段接口均返回 0(ACLNN_SUCCESS),仅输出 WARN 日志,HF32 语义被静默忽略(保持原 dtype 计算),未按 A2 文档约束返回 161002。

复现组合:self=bf16[4x4]ND | vec=bf16[4]ND | out=bf16[4]ND | cubeMathType=3。

文档依据:docs/aclnnMv.md「Atlas A2系列产品」节明确声明「cubeMathType=3,当输入数据类型为FLOAT32时,会转换为HFLOAT32计算,当输入为其他数据类型时不支持该选项」。

代码位置:matmul/mv/mv_common/cube_util.cpp CalcUseHf32PromoteType 保持原 dtype;CheckCubeMathTypeForMm 对 USE_HF32+BF16/FP16 仅 OP_LOGW 不拒。

控制组:self 3D → 161002、out dtype uint8 → 161002,报错通道正常。

环境信息

  • 昇腾硬件型号:Atlas A2 训练系列(910B2)
  • CANN 版本:9.2.0
  • 操作系统:Ubuntu 22.04 LTS / aarch64

重现步骤

  1. 保存下方脚本为 mv_repro.py,执行 source /usr/local/Ascend/ascend-toolkit/set_env.sh。
  2. python3 mv_repro.py 4(缺陷复现,期望 161002);python3 mv_repro.py 0(合法对照)。

实测输出(Atlas A2 / CANN 9.2.0):

case 0 (legal control): rc1=0 rc2=0 ws=0 out=[10.0, 26.0, 42.0, 58.0] (expect rc1=0 rc2=0 out=[10.0, 26.0, 42.0, 58.0])
case 4 (cubeMathType=3 USE_HF32 + BF16): rc1=0 rc2=0 ws=0 (expect 161002)
复现脚本 mv_repro.py(自包含 ctypes,仅依赖 CANN,无第三方库)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""aclnnMv 入参校验缺失最小复现(仅依赖 CANN,无第三方库)。

用法:
  python3 mv_repro.py 0   # 合法对照:fp32 [4x4]x[4] cmt=0,期望两段均 0
  python3 mv_repro.py 1   # BUG-1:self 为空(m=0) 且 dtype 与 vec/out 不一致,期望 161002
  python3 mv_repro.py 3   # BUG-3:cubeMathType=2(USE_FP16) + BF16,期望 161002
  python3 mv_repro.py 4   # BUG-4:cubeMathType=3(USE_HF32) + BF16,期望 161002

实测(Atlas A2 训练系列 / 910B2,CANN 9.2.0):case 1/3/4 两段接口均返回 0;
文档 docs/aclnnMv.md(Atlas A2 系列产品节 + 错误码表)声明上述场景应报 161002。
"""
import ctypes
import struct
import sys

ACL_SUCCESS = 0
ACL_FLOAT = 0
ACL_BF16 = 27
ACL_FORMAT_ND = 2
ACL_MEM_MALLOC_HUGE_FIRST = 0
ACL_MEMCPY_HOST_TO_DEVICE = 1
ACL_MEMCPY_DEVICE_TO_HOST = 2

acl = ctypes.CDLL("libascendcl.so", mode=ctypes.RTLD_GLOBAL)
opapi = ctypes.CDLL("libopapi.so", mode=ctypes.RTLD_GLOBAL)

# 关键:必须显式声明 argtypes,否则 ctypes 按 32 位 int 传指针会导致段错误
acl.aclInit.argtypes = [ctypes.c_char_p]
acl.aclrtSetDevice.argtypes = [ctypes.c_int32]
acl.aclrtCreateStream.argtypes = [ctypes.POINTER(ctypes.c_void_p)]
acl.aclrtMalloc.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_size_t, ctypes.c_int32]
acl.aclrtMemcpy.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p,
                            ctypes.c_size_t, ctypes.c_int32]
acl.aclrtSynchronizeStream.argtypes = [ctypes.c_void_p]
acl.aclrtDestroyStream.argtypes = [ctypes.c_void_p]
acl.aclrtResetDevice.argtypes = [ctypes.c_int32]
acl.aclrtFree.argtypes = [ctypes.c_void_p]

opapi.aclCreateTensor.restype = ctypes.c_void_p
opapi.aclCreateTensor.argtypes = [ctypes.POINTER(ctypes.c_int64), ctypes.c_uint64,
                                  ctypes.c_int32, ctypes.POINTER(ctypes.c_int64),
                                  ctypes.c_int64, ctypes.c_int32,
                                  ctypes.POINTER(ctypes.c_int64), ctypes.c_uint64,
                                  ctypes.c_void_p]

gws = opapi.aclnnMvGetWorkspaceSize
run = opapi.aclnnMv
gws.restype = ctypes.c_int
run.restype = ctypes.c_int
gws.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int8,
                ctypes.POINTER(ctypes.c_uint64), ctypes.POINTER(ctypes.c_void_p)]
run.argtypes = [ctypes.c_void_p, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p]


def chk(rc, what):
    if int(rc) != 0:
        raise RuntimeError("%s rc=%d" % (what, int(rc)))


class RT:
    def __init__(self):
        chk(acl.aclInit(None), "aclInit")
        chk(acl.aclrtSetDevice(0), "aclrtSetDevice")
        self.stream = ctypes.c_void_p()
        chk(acl.aclrtCreateStream(ctypes.byref(self.stream)), "aclrtCreateStream")

    def tensor(self, raw, shape, dtype):
        """raw: host bytes;shape: 维度列表;dtype: ACL_FLOAT / ACL_BF16。"""
        ptr = ctypes.c_void_p()
        chk(acl.aclrtMalloc(ctypes.byref(ptr), ctypes.c_size_t(max(len(raw), 8)),
                            ACL_MEM_MALLOC_HUGE_FIRST), "aclrtMalloc")
        if raw:
            chk(acl.aclrtMemcpy(ptr, ctypes.c_size_t(len(raw)),
                                ctypes.cast(ctypes.c_char_p(raw), ctypes.c_void_p),
                                ctypes.c_size_t(len(raw)), ACL_MEMCPY_HOST_TO_DEVICE),
                "H2D")
        dims = (ctypes.c_int64 * len(shape))(*shape)
        strides = [1] * len(shape)
        for i in range(len(shape) - 2, -1, -1):
            strides[i] = shape[i + 1] * strides[i + 1]
        st = (ctypes.c_int64 * len(shape))(*strides)
        h = opapi.aclCreateTensor(dims, ctypes.c_uint64(len(shape)),
                                  ctypes.c_int32(dtype), st, ctypes.c_int64(0),
                                  ctypes.c_int32(ACL_FORMAT_ND), dims,
                                  ctypes.c_uint64(len(shape)), ptr)
        return ctypes.c_void_p(h), ptr

    def read_f32(self, ptr, n):
        buf = ctypes.create_string_buffer(4 * n)
        chk(acl.aclrtMemcpy(ctypes.cast(buf, ctypes.c_void_p), ctypes.c_size_t(4 * n),
                            ptr, ctypes.c_size_t(4 * n), ACL_MEMCPY_DEVICE_TO_HOST), "D2H")
        return list(struct.unpack("<%df" % n, buf.raw))

    def done(self):
        acl.aclrtDestroyStream(self.stream)
        acl.aclrtResetDevice(0)


BF16_ONE = struct.pack("<H", 0x3F80)  # bf16(1.0)
F32_ONE = struct.pack("<f", 1.0)


def call(rt, self_raw, self_shape, self_dt, vec_raw, vec_shape, vec_dt,
         out_raw, out_shape, out_dt, cmt):
    s_h, s_p = rt.tensor(self_raw, self_shape, self_dt)
    v_h, v_p = rt.tensor(vec_raw, vec_shape, vec_dt)
    o_h, o_p = rt.tensor(out_raw, out_shape, out_dt)
    ws = ctypes.c_uint64(0)
    ex = ctypes.c_void_p()
    rc1 = gws(s_h, v_h, o_h, ctypes.c_int8(cmt), ctypes.byref(ws), ctypes.byref(ex))
    rc2 = None
    if int(rc1) == 0 and ex.value:
        wsbuf = ctypes.c_void_p()
        if ws.value > 0:
            chk(acl.aclrtMalloc(ctypes.byref(wsbuf), ctypes.c_size_t(ws.value),
                                ACL_MEM_MALLOC_HUGE_FIRST), "ws malloc")
        rc2 = run(wsbuf, ctypes.c_uint64(ws.value), ex, rt.stream)
        acl.aclrtSynchronizeStream(rt.stream)
        if wsbuf.value:
            acl.aclrtFree(wsbuf)
    out = None
    if int(rc1) == 0 and int(rc2 or 0) == 0 and out_dt == ACL_FLOAT and out_shape:
        out = rt.read_f32(o_p, out_shape[0])
    return rc1, rc2, ws.value, out


def main():
    sel = int(sys.argv[1]) if len(sys.argv) > 1 else 0
    rt = RT()
    try:
        if sel == 0:
            self_raw = struct.pack("<16f", *[float(i + 1) for i in range(16)])
            rc1, rc2, ws, out = call(rt, self_raw, [4, 4], ACL_FLOAT,
                                     F32_ONE * 4, [4], ACL_FLOAT,
                                     b"\xAA" * 16, [4], ACL_FLOAT, 0)
            print("case 0 (legal control): rc1=%d rc2=%d ws=%d out=%s "
                  "(expect rc1=0 rc2=0 out=[10.0, 26.0, 42.0, 58.0])" % (rc1, rc2, ws, out))
        elif sel == 1:
            # BUG-1:self 空(m=0) 且 dtype 与 vec/out 不一致
            rc1, rc2, ws, _ = call(rt, b"", [4, 0], ACL_FLOAT,
                                   b"", [0], ACL_BF16,
                                   b"\xAA" * 8, [4], ACL_BF16, 0)
            print("case 1 (empty self m=0, dtype mismatch fp32 vs bf16): "
                  "rc1=%d rc2=%d ws=%d (expect 161002)" % (rc1, rc2, ws))
        elif sel == 3:
            # BUG-3:cmt=2(USE_FP16) + BF16
            rc1, rc2, ws, _ = call(rt, BF16_ONE * 16, [4, 4], ACL_BF16,
                                   BF16_ONE * 4, [4], ACL_BF16,
                                   b"\xAA" * 8, [4], ACL_BF16, 2)
            print("case 3 (cubeMathType=2 USE_FP16 + BF16): "
                  "rc1=%d rc2=%d ws=%d (expect 161002)" % (rc1, rc2, ws))
        elif sel == 4:
            # BUG-4:cmt=3(USE_HF32) + BF16
            rc1, rc2, ws, _ = call(rt, BF16_ONE * 16, [4, 4], ACL_BF16,
                                   BF16_ONE * 4, [4], ACL_BF16,
                                   b"\xAA" * 8, [4], ACL_BF16, 3)
            print("case 4 (cubeMathType=3 USE_HF32 + BF16): "
                  "rc1=%d rc2=%d ws=%d (expect 161002)" % (rc1, rc2, ws))
        else:
            print("unknown case %d" % sel)
            return 2
        return 0
    finally:
        rt.done()


if __name__ == "__main__":
    sys.exit(main())

预期结果

第一段返回 161002(ACLNN_ERR_PARAM_INVALID,对应文档错误码表「传入的cubeMathType不符合约束」)。

备注

  • 相关问题 #2623 曾反馈 aclnnMv 缺少 cubeMathType 拦截(环境 Ascend950,已关闭);本 issue 为 A2 文档条款(cmt=3 + 非 FP32 不支持)在 A2 + CANN 9.2.0 上仍未强制的实证。
  • 用户视角影响:请求了 HF32 降精度语义但被静默忽略,精度行为与请求不符且无错误码。
  • 自动化检测线(error-scenario)在 det=none / det=acl 两种确定性开关下结论一致(npu:6)。
likedislike
liuhanwen8liuhanwen8
9 天前 添加了label:bug-report
yuning_chen
yuning_chen成员
9 天前 评论:

初步分析

感谢反馈。

现象理解:aclnnMv 在 cubeMathType 为 3、输入为 BF16 时没有报 161002。A2 文档声明不支持,HF32 被静默处理。

当前判断:初步判断,待复现确认。

涉及范围:mv 在 A2 上的 HF32 检查。

下一步:用该组合再看返回码。确认后按文档返回 161002。

likedislike
yuning_chenyuning_chen成员
9 天前 将 he_kan 设为负责人
yuning_chen
yuning_chen成员
8 天前 评论:

您好,该问题已收到,当前问题 @he_kan 处理中

likedislike
yuning_chenyuning_chen成员
8 天前 移除了负责人 he_kan
yuning_chenyuning_chen成员
8 天前 将 yolic 设为负责人
yuning_chenyuning_chen成员
7 天前 移除了负责人 yolic
yuning_chenyuning_chen成员
7 天前 将 zhengyuhao3 设为负责人
Zzhengyuhao3成员
4 天前 关联了pull request:修复matmul算子Issue问题
Zzhengyuhao3成员
4 天前 删除了关联的pull request:修复matmul算子Issue问题
CANN-robotCANN-robot成员
4 天前 关闭了 issue
CANN-robotCANN-robot成员
4 天前 添加了label:resolved