Merged
修改aclnnstack golden #6240
zhanglei842Add on 29 days ago
修改aclnnstack golden #6240
Merged
Merged fromzhanglei842/ops-math_packmasterintocann/ops-mathmaster
Total: 1 files changed+350-13
| @@ -9,31 +9,368 @@ | |||
| 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 | +"""Pack 多通路测试规范(TestSpec 格式)。 | ||
| 13 | + | ||
| 14 | +pack 是纯数据搬运算子(kernel Pack 等价 torch.stack),输出即输入按 axis 拼接的 | ||
| 15 | +逐 bit 拷贝,任意 dtype(含浮点)均二进制一致,因此: | ||
| 16 | + | ||
| 17 | +- golden 统一对标 torch 接口(kernel 流程 numpy 输入内部转 torch.stack 计算); | ||
| 18 | +- 精度标准统一为 binary_equal; | ||
| 19 | +- third_party 仅供远程 XPU 通路消费(--xpu-perf 三方性能采集 / cross_check 交叉 | ||
| 20 | + 校验),精度跑批不读它(binary_equal 路由不经过 third_party)。纯拷贝场景无需 | ||
| 21 | + cross_check;声明 third_party 是为 --xpu-perf 对标 GPU 性能时,让远程 XPU 知道 | ||
| 22 | + 执行哪个标杆 API(torch.stack)。 | ||
| 23 | + | ||
| 24 | +通路支持表: | ||
| 25 | + | ||
| 26 | +| 通路 | 支持 | 依据 | | ||
| 27 | +|--------|------|------| | ||
| 28 | +| kernel | 是 | op_host/pack_def.cpp 定义 Pack | | ||
| 29 | +| geir | 是 | op_graph/pack_proto.h | | ||
| 30 | +| aclnn | 是 | op_api/aclnn_stack.h 暴露 aclnnStack | | ||
| 31 | +""" | ||
| 32 | + | ||
| 12 | import numpy as np | 33 | import numpy as np |
| 13 | import torch | 34 | import torch |
| 14 | 35 | ||
| 36 | +__spec__ = { | ||
| 37 | + "pack": "PackTestSpec", | ||
| 38 | + "aclnnStack": "AclnnStackTestSpec", | ||
| 39 | + "torch.stack": "StackTorchTestSpec", | ||
| 40 | +} | ||
| 41 | + | ||
| 15 | __golden__ = { | 42 | __golden__ = { |
| 16 | - "aclnn": { | 43 | + "aclnn": {"aclnnStack": "aclnn_stack_golden"}, |
| 17 | - "aclnnStack": "aclnn_stack_golden", | ||
| 18 | - }, | ||
| 19 | "kernel": {"pack": "pack_golden"}, | 44 | "kernel": {"pack": "pack_golden"}, |
| 20 | } | 45 | } |
| 21 | 46 | ||
| 47 | +# torch.from_numpy 只认 numpy 原生 dtype;这些类型在 kernel 流程以 ml_dtypes/en_dtypes | ||
| 48 | +# 扩展 numpy 数组到达 golden,需走 numpy bit 路径(np.stack 与 torch.stack 逐 bit 等价) | ||
| 49 | +_NUMPY_ONLY_DTYPES = ("float8_e5m2", "float8_e4m3fn", "hifloat8", "bfloat16") | ||
| 50 | + | ||
| 51 | +_BINARY_TOLERANCE = { | ||
| 52 | + "float16": {"standard": "binary_equal"}, | ||
| 53 | + "float32": {"standard": "binary_equal"}, | ||
| 54 | + "bfloat16": {"standard": "binary_equal"}, | ||
| 55 | + "float64": {"standard": "binary_equal"}, | ||
| 56 | + "int8": {"standard": "binary_equal"}, | ||
| 57 | + "int16": {"standard": "binary_equal"}, | ||
| 58 | + "int32": {"standard": "binary_equal"}, | ||
| 59 | + "int64": {"standard": "binary_equal"}, | ||
| 60 | + "uint8": {"standard": "binary_equal"}, | ||
| 61 | + "uint16": {"standard": "binary_equal"}, | ||
| 62 | + "uint32": {"standard": "binary_equal"}, | ||
| 63 | + "uint64": {"standard": "binary_equal"}, | ||
| 64 | + "bool": {"standard": "binary_equal"}, | ||
| 65 | + "complex32": {"standard": "binary_equal"}, | ||
| 66 | + "complex64": {"standard": "binary_equal"}, | ||
| 67 | + "complex128": {"standard": "binary_equal"}, | ||
| 68 | + "hifloat8": {"standard": "binary_equal"}, | ||
| 69 | + "float8_e5m2": {"standard": "binary_equal"}, | ||
| 70 | + "float8_e4m3fn": {"standard": "binary_equal"}, | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | + | ||
| 74 | +def _flatten_strings(container): | ||
| 75 | + out = [] | ||
| 76 | + | ||
| 77 | + def _walk(node): | ||
| 78 | + if isinstance(node, (tuple, list)): | ||
| 79 | + for item in node: | ||
| 80 | + _walk(item) | ||
| 81 | + elif isinstance(node, str): | ||
| 82 | + out.append(node) | ||
| 83 | + | ||
| 84 | + _walk(container) | ||
| 85 | + return out | ||
| 86 | + | ||
| 87 | + | ||
| 88 | +def _is_complex32_storage(arrays, kwargs): | ||
| 89 | + """complex32 以 float16 + 尾维 [2] 存储(real/imag 交错),stack 轴须插在尾维之前。""" | ||
| 90 | + declared = _flatten_strings(kwargs.get("input_dtypes", ())) | ||
| 91 | + if "complex32" not in declared: | ||
| 92 | + return False | ||
| 93 | + first = arrays[0] | ||
| 94 | + return ( | ||
| 95 | + hasattr(first, "dtype") | ||
| 96 | + and first.dtype.name == "float16" | ||
| 97 | + and first.ndim >= 1 | ||
| 98 | + and first.shape[-1] == 2 | ||
| 99 | + ) | ||
| 100 | + | ||
| 22 | 101 | ||
| 23 | def pack_golden(x, axis: int = 0, N: int = 1, **kwargs): | 102 | def pack_golden(x, axis: int = 0, N: int = 1, **kwargs): |
| 24 | - """ | 103 | + """Kernel golden for pack,对标 torch.stack。 |
| 25 | - Kernel golden for pack. | ||
| 26 | - All the parameters follow @pack_def.cpp without outputs. | ||
| 27 | - All the input Tensors are numpy.ndarray. | ||
| 28 | - kwargs may contain: short_soc_version, input_ori_shapes, output_ori_shapes, | ||
| 29 | - input_formats, output_formats, input_ori_formats, output_ori_formats, | ||
| 30 | - input_dtypes, output_dtypes. | ||
| 31 | - """ | ||
| 32 | 104 | ||
| 33 | - return np.stack(x, axis=axis) | 105 | + 参数名与顺序跟随 pack_def.cpp(不含输出)。输入 x 为 numpy.ndarray 列表; |
| 106 | + kwargs 可能包含 input/output 的 dtypes、shapes、formats 等元信息。 | ||
| 107 | + | ||
| 108 | + fp8/bf16 的 numpy 数组为 ml_dtypes/en_dtypes 扩展 dtype,torch.from_numpy | ||
| 109 | + 不识别,走 np.stack(纯搬运,与 torch.stack 逐 bit 等价); | ||
| 110 | + complex32 为 float16+尾维[2] 存储,负 axis 需归一化到尾维之前。 | ||
| 111 | + """ | ||
| 112 | + del N | ||
| 113 | + arrays = list(x) | ||
| 114 | + if not arrays: | ||
| 115 | + return [np.empty(0)] | ||
| 116 | + if _is_complex32_storage(arrays, kwargs): | ||
| 117 | + view_ndim = arrays[0].ndim - 1 | ||
| 118 | + if axis < 0: | ||
| 119 | + axis = axis + view_ndim + 1 | ||
| 120 | + return [np.stack(arrays, axis=axis)] | ||
| 121 | + if any(a.dtype.name in _NUMPY_ONLY_DTYPES for a in arrays): | ||
| 122 | + return [np.stack(arrays, axis=axis)] | ||
| 123 | + return [torch.stack([torch.from_numpy(t) for t in arrays], dim=axis).numpy()] | ||
| 124 | + | ||
| 125 | + | ||
| 126 | +_ACLNN_TORCH_DTYPES = { | ||
| 127 | + "float16": torch.float16, | ||
| 128 | + "float32": torch.float32, | ||
| 129 | + "float64": torch.float64, | ||
| 130 | + "bfloat16": torch.bfloat16, | ||
| 131 | + "float8_e5m2": torch.float8_e5m2, | ||
| 132 | + "float8_e4m3fn": torch.float8_e4m3fn, | ||
| 133 | + "int8": torch.int8, | ||
| 134 | + "int16": torch.int16, | ||
| 135 | + "int32": torch.int32, | ||
| 136 | + "int64": torch.int64, | ||
| 137 | + "uint8": torch.uint8, | ||
| 138 | + "uint16": torch.uint16, | ||
| 139 | + "uint32": torch.uint32, | ||
| 140 | + "uint64": torch.uint64, | ||
| 141 | + "bool": torch.bool, | ||
| 142 | + "complex64": torch.complex64, | ||
| 143 | + "complex128": torch.complex128, | ||
| 144 | +} | ||
| 145 | + | ||
| 146 | + | ||
| 147 | +def _stack_output_dtype(tensor_dtypes): | ||
| 148 | + """从嵌套 tensor_dtypes 提取输出 dtype 名(输出恒位于结构末尾)。""" | ||
| 149 | + if not tensor_dtypes: | ||
| 150 | + return None | ||
| 151 | + out = tensor_dtypes[-1] | ||
| 152 | + if isinstance(out, (tuple, list)): | ||
| 153 | + out = out[0] if out else None | ||
| 154 | + return out | ||
| 155 | + | ||
| 156 | + | ||
| 157 | +def _resolve_np_dtype(dtype_name): | ||
| 158 | + """dtype 名 -> numpy dtype 对象(fp8/bf16 解析为 ml_dtypes/en_dtypes 扩展 dtype)。""" | ||
| 159 | + name = str(dtype_name).split(".")[-1] | ||
| 160 | + if name in ("double", "fp64", "f64"): | ||
| 161 | + name = "float64" | ||
| 162 | + if name == "bfloat16": | ||
| 163 | + from ml_dtypes import bfloat16 | ||
| 164 | + | ||
| 165 | + return np.dtype(bfloat16) | ||
| 166 | + if name == "float8_e5m2": | ||
| 167 | + from ml_dtypes import float8_e5m2 | ||
| 168 | + | ||
| 169 | + return np.dtype(float8_e5m2) | ||
| 170 | + if name == "float8_e4m3fn": | ||
| 171 | + from ml_dtypes import float8_e4m3fn | ||
| 172 | + | ||
| 173 | + return np.dtype(float8_e4m3fn) | ||
| 174 | + if name == "hifloat8": | ||
| 175 | + from en_dtypes import hifloat8 | ||
| 176 | + | ||
| 177 | + return np.dtype(hifloat8) | ||
| 178 | + return np.dtype(name) | ||
| 179 | + | ||
| 180 | + | ||
| 181 | +_INT_WIDTH = {"int8": 8, "int16": 16, "int32": 32, "int64": 64} | ||
| 182 | +_FP8_NAMES = ("float8_e5m2", "float8_e4m3fn", "hifloat8") | ||
| 183 | + | ||
| 184 | + | ||
| 185 | +def _cann_promote(dtype_a, dtype_b): | ||
| 186 | + """复刻 CANN op::PromoteType(kPromoteTypesLookup)的关键提升规则。""" | ||
| 187 | + a = str(dtype_a).split(".")[-1] | ||
| 188 | + b = str(dtype_b).split(".")[-1] | ||
| 189 | + if a == b: | ||
| 190 | + return a | ||
| 191 | + pair = (a, b) | ||
| 192 | + if "complex128" in pair: | ||
| 193 | + return "complex128" | ||
| 194 | + if "complex64" in pair: | ||
| 195 | + return "complex128" if "float64" in pair else "complex64" | ||
| 196 | + if a in _FP8_NAMES or b in _FP8_NAMES: | ||
| 197 | + return "float32" # CANN 查找表 fp8 行零值填充,与任意异类提升为 float | ||
| 198 | + if "float64" in pair: | ||
| 199 | + return "float64" | ||
| 200 | + if "bfloat16" in pair: | ||
| 201 | + other = b if a == "bfloat16" else a | ||
| 202 | + if other in ("float32", "float16"): | ||
| 203 | + return "float32" | ||
| 204 | + if other == "bool" or other in _INT_WIDTH or other == "uint8": | ||
| 205 | + return "bfloat16" | ||
| 206 | + raise RuntimeError(f"PromoteType({a}, {b}) is undefined in CANN.") | ||
| 207 | + if "float32" in pair: | ||
| 208 | + other = b if a == "float32" else a | ||
| 209 | + if other == "bool" or other in _INT_WIDTH or other == "uint8": | ||
| 210 | + return "float32" | ||
| 211 | + raise RuntimeError(f"PromoteType({a}, {b}) is undefined in CANN.") | ||
| 212 | + if "float16" in pair: | ||
| 213 | + other = b if a == "float16" else a | ||
| 214 | + if other == "bool" or other in _INT_WIDTH or other == "uint8": | ||
| 215 | + return "float16" | ||
| 216 | + raise RuntimeError(f"PromoteType({a}, {b}) is undefined in CANN.") | ||
| 217 | + if "uint8" in pair: | ||
| 218 | + other = b if a == "uint8" else a | ||
| 219 | + if other == "bool": | ||
| 220 | + return "uint8" | ||
| 221 | + if other in _INT_WIDTH: | ||
| 222 | + return "int16" if _INT_WIDTH[other] < 16 else other | ||
| 223 | + raise RuntimeError(f"PromoteType({a}, {b}) is undefined in CANN.") | ||
| 224 | + if a in _INT_WIDTH and b in _INT_WIDTH: | ||
| 225 | + return a if _INT_WIDTH[a] >= _INT_WIDTH[b] else b | ||
| 226 | + if "bool" in pair: | ||
| 227 | + other = b if a == "bool" else a | ||
| 228 | + if other in _INT_WIDTH: | ||
| 229 | + return other | ||
| 230 | + raise RuntimeError(f"PromoteType({a}, {b}) is undefined in CANN.") | ||
| 231 | + | ||
| 232 | + | ||
| 233 | +def _np_value_cast(arr, np_dtype): | ||
| 234 | + """numpy 值转换;ml_dtypes/en_dtypes 跨库不能直转时经 float32 桥接(无损等价)。""" | ||
| 235 | + if arr.dtype == np_dtype: | ||
| 236 | + return arr | ||
| 237 | + try: | ||
| 238 | + return arr.astype(np_dtype) | ||
| 239 | + except (TypeError, ValueError): | ||
| 240 | + return arr.astype(np.float32).astype(np_dtype) | ||
| 34 | 241 | ||
| 35 | 242 | ||
| 36 | def aclnn_stack_golden(tensors, dim, out, **kwargs): | 243 | def aclnn_stack_golden(tensors, dim, out, **kwargs): |
| 244 | + """aclnn golden for aclnnStack,对标 torch.stack。 | ||
| 245 | + | ||
| 246 | + aclnnStack 实现见 op_api/aclnn_stack.cpp:输入 cast 到 promoteType 后打包, | ||
| 247 | + 输出侧再 `l0op::Cast(packResult, out dtype)`(与 stack 后对结果 .to(out dtype) | ||
| 248 | + 等价)。因此 golden 在 stack 之后把输出转换为 CSV 声明的 out dtype。 | ||
| 249 | + | ||
| 250 | + 输入按类型分发(不再依赖 ttk 的 use_numpy 私有 kwarg):ttk 侧同一开关 | ||
| 251 | + (is_torch_dtype_support)同时决定 use_numpy 取值与输入以 numpy 还是 torch | ||
| 252 | + 张量下发,两者严格一一对应,isinstance 判断与之等价。hifloat8 等非 torch | ||
| 253 | + 原生 dtype 以 numpy 数组(ml_dtypes/en_dtypes 扩展 dtype)到达,分两种情况: | ||
| 254 | + - 输入与输出同 dtype:无值转换、纯逐 bit 搬移,把输入数组原始字节按 uint8 | ||
| 255 | + 载体张量做 stack,字节序列与设备输出一致(比对侧 requant 将双方 view 成 | ||
| 256 | + int8 按字节比对)。 | ||
| 257 | + - 输入与输出 dtype 不同(如混合 int16/bf16 输入转 hifloat8 输出):为值 | ||
| 258 | + 转换,按算子 cast 链走 numpy:各输入 astype(promoteType) -> np.stack -> | ||
| 259 | + astype(out dtype),与 NPU Cast 的 RNE 舍入按位一致。 | ||
| 260 | + """ | ||
| 261 | + del out | ||
| 37 | if hasattr(dim, "item"): | 262 | if hasattr(dim, "item"): |
| 38 | dim = dim.item() | 263 | dim = dim.item() |
| 39 | - return [torch.stack(tensors, dim=dim)] | 264 | + if tensors and isinstance(tensors[0], np.ndarray): |
| 265 | + out_name = _stack_output_dtype(kwargs.get("tensor_dtypes")) | ||
| 266 | + in_names = {t.dtype.name for t in tensors} | ||
| 267 | + if in_names == {out_name}: | ||
| 268 | + itemsize = tensors[0].dtype.itemsize | ||
| 269 | + if dim < 0: | ||
| 270 | + dim += tensors[0].ndim + 1 | ||
| 271 | + carriers = [ | ||
| 272 | + torch.frombuffer(t.tobytes(), dtype=torch.uint8).reshape( | ||
| 273 | + t.shape + (itemsize,) | ||
| 274 | + ) | ||
| 275 | + for t in tensors | ||
| 276 | + ] | ||
| 277 | + return [torch.stack(carriers, dim=dim)] | ||
| 278 | + promote = tensors[0].dtype.name | ||
| 279 | + for t in tensors[1:]: | ||
| 280 | + promote = _cann_promote(t.dtype.name, promote) | ||
| 281 | + stacked = np.stack( | ||
| 282 | + [_np_value_cast(t, _resolve_np_dtype(promote)) for t in tensors], axis=dim | ||
| 283 | + ) | ||
| 284 | + return [_np_value_cast(stacked, _resolve_np_dtype(out_name))] | ||
| 285 | + result = torch.stack(tensors, dim=dim) | ||
| 286 | + target = _ACLNN_TORCH_DTYPES.get(_stack_output_dtype(kwargs.get("tensor_dtypes"))) | ||
| 287 | + if target is not None and result.dtype != target: | ||
| 288 | + result = result.to(target) | ||
| 289 | + return [result] | ||
| 290 | + | ||
| 291 | + | ||
| 292 | +class _PackTorchStack: | ||
| 293 | + """kernel pack 的三方标杆(远程 XPU 上执行)。 | ||
| 294 | + | ||
| 295 | + pack 参数名(x/axis/N,随 pack_def.cpp)与 torch.stack(tensors/dim)不一致, | ||
| 296 | + 字符串形式无法按名绑定,用类形式适配:参数名对齐算子定义,服务端把输入转成 | ||
| 297 | + 目标设备上的 torch 张量后实例化调用(契约:参数名 ⊆ inputs∪attrs)。 | ||
| 298 | + """ | ||
| 299 | + | ||
| 300 | + def __call__(self, x, axis=0, N=1, **kwargs): | ||
| 301 | + del N | ||
| 302 | + return [torch.stack(list(x), dim=axis)] | ||
| 303 | + | ||
| 304 | + | ||
| 305 | +class PackTestSpec: | ||
| 306 | + """kernel/geir 共用 spec。golden 入口接收 numpy 数组。""" | ||
| 307 | + | ||
| 308 | + @staticmethod | ||
| 309 | + def golden(x, axis=0, N=1, **kwargs): | ||
| 310 | + return pack_golden(x, axis=axis, N=N, **kwargs) | ||
| 311 | + | ||
| 312 | + third_party = {"torch": _PackTorchStack} | ||
| 313 | + | ||
| 314 | + tolerance = _BINARY_TOLERANCE | ||
| 315 | + | ||
| 316 | + | ||
| 317 | +class AclnnStackTestSpec: | ||
| 318 | + """aclnnStack spec。golden 入口接收 torch 张量。""" | ||
| 319 | + | ||
| 320 | + @staticmethod | ||
| 321 | + def golden(tensors, dim, out, **kwargs): | ||
| 322 | + return aclnn_stack_golden(tensors, dim, out, **kwargs) | ||
| 323 | + | ||
| 324 | + # aclnnStack 输入名(tensors/dim)与 torch.stack 参数名一致,字符串形式可按名绑定 | ||
| 325 | + third_party = {"torch": "torch.stack"} | ||
| 326 | + | ||
| 327 | + tolerance = _BINARY_TOLERANCE | ||
| 328 | + | ||
| 329 | + | ||
| 330 | +def stack_torch_golden(tensors, dim=0, out=None, **kwargs): | ||
| 331 | + """e2e (framework-api) golden for torch.stack。 | ||
| 332 | + | ||
| 333 | + e2e golden 三级回退(framework_api/golden_generation.py):CSV golden_api 列 → | ||
| 334 | + 自定义插件(本函数,经 __spec__["torch.stack"] 注册)→ 同 API CPU 复算。 | ||
| 335 | + torch 原生 dtype 原本第三级即可覆盖,注册本 spec 后统一由其接管: | ||
| 336 | + | ||
| 337 | + - torch 张量输入:直通 torch.stack(含 out= 用例),与第三级 CPU 复算同语义; | ||
| 338 | + - numpy 输入(hifloat8 等非 torch 原生 dtype;第三级 is_dtype_support=False | ||
| 339 | + 会返回 UNSUPPORTED,必须由本插件兜底):同 dtype 纯位搬运,np.stack 与 | ||
| 340 | + torch.stack 逐 bit 等价;混合 dtype 不支持——torch 的 promote 语义无法在 | ||
| 341 | + numpy 侧对非原生 dtype 复刻,设备侧 torch_npu 对 hifloat8 的 promote 行为 | ||
| 342 | + 也无标杆,直接报 NotImplementedError 拒绝而非静默给错值。 | ||
| 343 | + | ||
| 344 | + 与 aclnnStack golden 的本质差异:e2e 对标框架 API 语义,无 aclnn 的 | ||
| 345 | + promote→stack→cast 链,输出 dtype 恒等于输入 dtype;torch.stack 的 out 为 | ||
| 346 | + keyword-only 且返回值型用例不传,签名须带默认值(e2e 还会额外下发 backend | ||
| 347 | + 等 kwarg,由 **kwargs 吸收)。 | ||
| 348 | + """ | ||
| 349 | + if hasattr(dim, "item"): | ||
| 350 | + dim = dim.item() | ||
| 351 | + if tensors and isinstance(tensors[0], np.ndarray): | ||
| 352 | + in_names = {t.dtype.name for t in tensors} | ||
| 353 | + if len(in_names) != 1: | ||
| 354 | + raise NotImplementedError( | ||
| 355 | + f"torch.stack e2e golden 不支持混合 numpy dtype {sorted(in_names)}:" | ||
| 356 | + "非 torch 原生 dtype 的 promote 语义无法在 numpy 侧复刻" | ||
| 357 | + ) | ||
| 358 | + # torch_npu 对 hifloat8 无真实 torch.dtype:设备侧 npu_dtype_cast 返回 | ||
| 359 | + # uint8 字节载体(torch_npu.hifloat8 为整型枚举 290)。e2e 比对按实际 | ||
| 360 | + # 输出 dtype(uint8)走 binary_equal,golden 须返回同形态字节视图。 | ||
| 361 | + return [np.ascontiguousarray(np.stack(list(tensors), axis=dim)).view(np.uint8)] | ||
| 362 | + if out is None: | ||
| 363 | + return [torch.stack(tensors, dim=dim)] | ||
| 364 | + return [torch.stack(tensors, dim=dim, out=out)] | ||
| 365 | + | ||
| 366 | + | ||
| 367 | +class StackTorchTestSpec: | ||
| 368 | + """e2e spec for torch.stack。golden 入口接收 torch 张量或 numpy 数组。""" | ||
| 369 | + | ||
| 370 | + @staticmethod | ||
| 371 | + def golden(tensors, dim=0, out=None, **kwargs): | ||
| 372 | + return stack_torch_golden(tensors, dim=dim, out=out, **kwargs) | ||
| 373 | + | ||
| 374 | + third_party = {"torch": "torch.stack"} | ||
| 375 | + | ||
| 376 | + tolerance = _BINARY_TOLERANCE | ||
确认下 aclnn 和 torch 的行为是否一致,一致的话,建议直接用 torch.stack,只对不支持的类型做处理