已合并
test: update AddV2 and TensorRedirect golden specs #4540
raoliang_sac创建于 14 天前
test: update AddV2 and TensorRedirect golden specs #4540
已合并
共 2 个文件变更+232-25
| @@ -0,0 +1,121 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +# -*- coding: UTF-8 -*- | ||
| 3 | +# ---------------------------------------------------------------------------- | ||
| 4 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 5 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 6 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 7 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 8 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 9 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 10 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 11 | +# ---------------------------------------------------------------------------- | ||
| 12 | +"""Bit-exact kernel/GEIR golden for TensorRedirect in TTK TestSpec format.""" | ||
| 13 | + | ||
| 14 | +import numpy as np | ||
| 15 | +import torch | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +__spec__ = { | ||
| 19 | + # Kernel and GEIR share the snake-case registration and the same TestSpec. | ||
| 20 | + "tensor_redirect": "TensorRedirectKernelSpec", | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +# Retain the repository-facing legacy entry while consumers migrate to TestSpec. | ||
| 24 | +__golden__ = { | ||
| 25 | + "kernel": {"tensor_redirect": "tensor_redirect_golden"}, | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +_TOLERANCE = { | ||
| 30 | + "float16": {"standard": "binary_equal"}, | ||
| 31 | + "float32": {"standard": "binary_equal"}, | ||
| 32 | + "bfloat16": {"standard": "binary_equal"}, | ||
| 33 | + "int8": {"standard": "binary_equal"}, | ||
| 34 | + "uint8": {"standard": "binary_equal"}, | ||
| 35 | + "int16": {"standard": "binary_equal"}, | ||
| 36 | + "uint16": {"standard": "binary_equal"}, | ||
| 37 | + "int32": {"standard": "binary_equal"}, | ||
| 38 | + "uint32": {"standard": "binary_equal"}, | ||
| 39 | + "int64": {"standard": "binary_equal"}, | ||
| 40 | + "uint64": {"standard": "binary_equal"}, | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | + | ||
| 44 | +def _numpy_dtype(dtype): | ||
| 45 | + """Resolve TTK dtype values, including NumPy's optional bfloat16 dtype.""" | ||
| 46 | + name = getattr(dtype, "name", str(dtype)).lower() | ||
| 47 | + if name in ("bf16", "bfloat16"): | ||
| 48 | + try: | ||
| 49 | + from ml_dtypes import bfloat16 | ||
| 50 | + except ImportError as exc: | ||
| 51 | + raise RuntimeError( | ||
| 52 | + "TensorRedirect bfloat16 golden requires the optional ml-dtypes package" | ||
| 53 | + ) from exc | ||
| 54 | + return bfloat16 | ||
| 55 | + return np.dtype(dtype) | ||
| 56 | + | ||
| 57 | + | ||
| 58 | +def _output_dtypes(kwargs): | ||
| 59 | + values = kwargs.get("output_dtypes") or () | ||
| 60 | + return [ | ||
| 61 | + value[0] if isinstance(value, (list, tuple)) and value else value | ||
| 62 | + for value in values | ||
| 63 | + ] | ||
| 64 | + | ||
| 65 | + | ||
| 66 | +def _to_torch_bit_exact(array): | ||
| 67 | + """Convert NumPy to torch without changing any payload bits.""" | ||
| 68 | + array = np.asarray(array) | ||
| 69 | + if not array.flags.c_contiguous: | ||
| 70 | + array = np.ascontiguousarray(array) | ||
| 71 | + if array.dtype.name == "bfloat16": | ||
| 72 | + return torch.from_numpy(array.view(np.int16)).view(torch.bfloat16) | ||
| 73 | + return torch.from_numpy(array) | ||
| 74 | + | ||
| 75 | + | ||
| 76 | +def _to_numpy_bit_exact(tensor): | ||
| 77 | + """Convert torch to NumPy while preserving BF16 payload bits.""" | ||
| 78 | + tensor = tensor.detach().cpu().contiguous() | ||
| 79 | + if tensor.dtype == torch.bfloat16: | ||
| 80 | + return tensor.view(torch.int16).numpy().view(_numpy_dtype("bfloat16")) | ||
| 81 | + return tensor.numpy() | ||
| 82 | + | ||
| 83 | + | ||
| 84 | +def _compute(x): | ||
| 85 | + """Copy through the independent PyTorch reference interface.""" | ||
| 86 | + return [torch.clone(x)] | ||
| 87 | + | ||
| 88 | + | ||
| 89 | +def _kernel_golden(x, **kwargs): | ||
| 90 | + outputs = [ | ||
| 91 | + _to_numpy_bit_exact(output) for output in _compute(_to_torch_bit_exact(x)) | ||
| 92 | + ] | ||
| 93 | + output_dtypes = _output_dtypes(kwargs) | ||
| 94 | + return [ | ||
| 95 | + output.astype(_numpy_dtype(output_dtypes[index]), copy=False) | ||
| 96 | + if index < len(output_dtypes) | ||
| 97 | + else output | ||
| 98 | + for index, output in enumerate(outputs) | ||
| 99 | + ] | ||
| 100 | + | ||
| 101 | + | ||
| 102 | +class TensorRedirectKernelSpec: | ||
| 103 | + """TestSpec shared by the TensorRedirect kernel and GEIR pathways.""" | ||
| 104 | + | ||
| 105 | + | ||
| 106 | + def golden(x, **kwargs): | ||
| 107 | + return _kernel_golden(x, **kwargs) | ||
| 108 | + | ||
| 109 | + third_party = {"torch": "torch.clone"} | ||
| 110 | + tolerance = _TOLERANCE | ||
| 111 | + | ||
| 112 | + | ||
| 113 | +def tensor_redirect_golden(x, **kwargs): | ||
| 114 | + """Legacy kernel entry backed by the same TestSpec computation.""" | ||
| 115 | + if not kwargs.get("output_dtypes"): | ||
| 116 | + kwargs = {**kwargs, "output_dtypes": (x.dtype,)} | ||
| 117 | + return _kernel_golden(x, **kwargs)[0] | ||
| 118 | + | ||
| 119 | + | ||
| 120 | +# 【不存在】ACLNN 通路:CMakeLists.txt 显式配置 ACLNNTYPE aclnn_exclude。 | ||
| 121 | +# 【不存在】e2e 通路:本算子不交付 ACLNN 符号,torch_npu 无对应绑定入口。 | ||
| @@ -9,36 +9,122 @@ | |||
| 9 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | 9 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. |
| 10 | # See LICENSE in the root of the software repository for the full text of the License. | 10 | # See LICENSE in the root of the software repository for the full text of the License. |
| 11 | # ---------------------------------------------------------------------------- | 11 | # ---------------------------------------------------------------------------- |
| 12 | +"""Kernel/GEIR golden for AddV2 using the TTK TestSpec format.""" | ||
| 13 | + | ||
| 14 | +import numpy as np | ||
| 12 | import torch | 15 | import torch |
| 13 | 16 | ||
| 17 | + | ||
| 18 | +__spec__ = { | ||
| 19 | + # Kernel and GEIR share the snake-case registration and the same TestSpec. | ||
| 20 | + "add_v2": "AddV2KernelSpec", | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +# Retain the repository-facing legacy entry while consumers migrate to TestSpec. | ||
| 14 | __golden__ = { | 24 | __golden__ = { |
| 15 | "kernel": {"add_v2": "add_v2_golden"}, | 25 | "kernel": {"add_v2": "add_v2_golden"}, |
| 16 | } | 26 | } |
| 17 | 27 | ||
| 18 | 28 | ||
| 19 | -def add_v2_golden(x1, x2, **kwargs): | 29 | +_TOLERANCE = { |
| 20 | - """ | 30 | + "float16": {"standard": "cross_check", "level": "L1"}, |
| 21 | - Kernel golden for add_v2. | 31 | + "float32": {"standard": "cross_check", "level": "L1"}, |
| 22 | - All the parameters follow @add_v2_def.cpp without outputs. | 32 | + "bfloat16": {"standard": "cross_check", "level": "L1"}, |
| 23 | - All the input Tensors are numpy.ndarray. | 33 | + "int8": {"standard": "binary_equal"}, |
| 24 | - kwargs may contain: short_soc_version, input_ori_shapes, output_ori_shapes, | 34 | + "uint8": {"standard": "binary_equal"}, |
| 25 | - input_formats, output_formats, input_ori_formats, output_ori_formats, | 35 | + "int16": {"standard": "binary_equal"}, |
| 26 | - input_dtypes, output_dtypes. | 36 | + "int32": {"standard": "binary_equal"}, |
| 27 | - """ | 37 | + "int64": {"standard": "binary_equal"}, |
| 28 | - # 仅注册同 dtype 组合,x1/x2 dtype 恒等,输出 dtype 与 x1 一致 | 38 | + # TTK cross_check currently supports float16/bfloat16/float32 only. |
| 29 | - dtype = x1.dtype | 39 | + "complex64": {"standard": "stat_rel_err"}, |
| 30 | - if str(x2.dtype) != str(dtype): | 40 | +} |
| 31 | - raise ValueError( | ||
| 32 | - f"add_v2 only supports identical input dtypes, got x1={dtype}, x2={x2.dtype}" | ||
| 33 | - ) | ||
| 34 | - # torch 无原生 bfloat16 numpy 视图,先升 float32 计算再还原 | ||
| 35 | - if "bfloat16" in str(dtype): | ||
| 36 | - x1 = x1.astype("float32") | ||
| 37 | - x2 = x2.astype("float32") | ||
| 38 | - x = torch.from_numpy(x1) | ||
| 39 | - y = torch.from_numpy(x2) | ||
| 40 | - res = torch.add(x, y).numpy() | ||
| 41 | - if "bfloat16" in str(dtype): | ||
| 42 | - res = res.astype(dtype) | ||
| 43 | 41 | ||
| 44 | - return res | 42 | + |
| 43 | +def _numpy_dtype(dtype): | ||
| 44 | + """Resolve TTK dtype values, including NumPy's optional bfloat16 dtype.""" | ||
| 45 | + name = getattr(dtype, "name", str(dtype)).lower() | ||
| 46 | + if name in ("bf16", "bfloat16"): | ||
| 47 | + try: | ||
| 48 | + from ml_dtypes import bfloat16 | ||
| 49 | + except ImportError as exc: | ||
| 50 | + raise RuntimeError( | ||
| 51 | + "AddV2 bfloat16 golden requires the optional ml-dtypes package" | ||
| 52 | + ) from exc | ||
| 53 | + return bfloat16 | ||
| 54 | + return np.dtype(dtype) | ||
| 55 | + | ||
| 56 | + | ||
| 57 | +def _output_dtypes(kwargs): | ||
| 58 | + values = kwargs.get("output_dtypes") or () | ||
| 59 | + return [ | ||
| 60 | + value[0] if isinstance(value, (list, tuple)) and value else value | ||
| 61 | + for value in values | ||
| 62 | + ] | ||
| 63 | + | ||
| 64 | + | ||
| 65 | +def _to_torch(array): | ||
| 66 | + """Convert a contiguous NumPy tensor to torch without lowering precision.""" | ||
| 67 | + array = np.asarray(array) | ||
| 68 | + if not array.flags.c_contiguous: | ||
| 69 | + array = np.ascontiguousarray(array) | ||
| 70 | + if array.dtype.name == "bfloat16": | ||
| 71 | + # CPU torch cannot consume an ml_dtypes array directly. Computing AddV2 | ||
| 72 | + # in float32 and casting its output back is the established BF16 fallback. | ||
| 73 | + array = array.astype(np.float32) | ||
| 74 | + return torch.from_numpy(array) | ||
| 75 | + | ||
| 76 | + | ||
| 77 | +def _to_numpy(tensor): | ||
| 78 | + tensor = tensor.detach().cpu().contiguous() | ||
| 79 | + if tensor.dtype == torch.bfloat16: | ||
| 80 | + return tensor.view(torch.int16).numpy().view(_numpy_dtype("bfloat16")) | ||
| 81 | + return tensor.numpy() | ||
| 82 | + | ||
| 83 | + | ||
| 84 | +def _compute(x1, x2): | ||
| 85 | + """Compute AddV2 through the independent PyTorch reference interface.""" | ||
| 86 | + if x1.dtype != x2.dtype: | ||
| 87 | + raise ValueError( | ||
| 88 | + f"add_v2 only supports identical input dtypes, got x1={x1.dtype}, " | ||
| 89 | + f"x2={x2.dtype}" | ||
| 90 | + ) | ||
| 91 | + return [torch.add(x1, x2)] | ||
| 92 | + | ||
| 93 | + | ||
| 94 | +def _kernel_golden(x1, x2, **kwargs): | ||
| 95 | + if x1.dtype.name != x2.dtype.name: | ||
| 96 | + raise ValueError( | ||
| 97 | + f"add_v2 only supports identical input dtypes, got x1={x1.dtype}, " | ||
| 98 | + f"x2={x2.dtype}" | ||
| 99 | + ) | ||
| 100 | + | ||
| 101 | + outputs = [_to_numpy(output) for output in _compute(_to_torch(x1), _to_torch(x2))] | ||
| 102 | + output_dtypes = _output_dtypes(kwargs) | ||
| 103 | + return [ | ||
| 104 | + output.astype(_numpy_dtype(output_dtypes[index]), copy=False) | ||
| 105 | + if index < len(output_dtypes) | ||
| 106 | + else output | ||
| 107 | + for index, output in enumerate(outputs) | ||
| 108 | + ] | ||
| 109 | + | ||
| 110 | + | ||
| 111 | +class AddV2KernelSpec: | ||
| 112 | + """TestSpec shared by the AddV2 kernel and GEIR pathways.""" | ||
| 113 | + | ||
| 114 | + | ||
| 115 | + def golden(x1, x2, **kwargs): | ||
| 116 | + return _kernel_golden(x1, x2, **kwargs) | ||
| 117 | + | ||
| 118 | + third_party = {"torch": "torch.add"} | ||
| 119 | + tolerance = _TOLERANCE | ||
| 120 | + | ||
| 121 | + | ||
| 122 | +def add_v2_golden(x1, x2, **kwargs): | ||
| 123 | + """Legacy kernel entry backed by the same TestSpec computation.""" | ||
| 124 | + if not kwargs.get("output_dtypes"): | ||
| 125 | + kwargs = {**kwargs, "output_dtypes": (x1.dtype,)} | ||
| 126 | + return _kernel_golden(x1, x2, **kwargs)[0] | ||
| 127 | + | ||
| 128 | + | ||
| 129 | +# 【不存在】ACLNN 通路:CMakeLists.txt 显式配置 ACLNNTYPE aclnn_exclude。 | ||
| 130 | +# 【不存在】e2e 通路:本算子不交付 ACLNN 符号,torch_npu 无对应绑定入口。 | ||