已合并
test(golden): 升级 INInferV2 TestSpec #8513
test(golden): 升级 INInferV2 TestSpec #8513
已合并
raoliang_sac创建于 8月11日
1 个文件变更+231-84
@@ -1,6 +1,5 @@
1#!/usr/bin/env python31#!/usr/bin/env python3
2-# -*- coding: UTF-8 -*-2+# -*- coding: utf-8 -*-
3-# ----------------------------------------------------------------------------
4# Copyright (c) 2026 Huawei Technologies Co., Ltd.3# 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 of4# 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").5# CANN Open Software License Agreement Version 2.0 (the "License").
@@ -9,123 +8,271 @@
9# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.8# 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.9# See LICENSE in the root of the software repository for the full text of the License.
11# ----------------------------------------------------------------------------10# ----------------------------------------------------------------------------
12-"""Golden plugin for INInferV2 (instance normalization inference),torch 竞品算子拼接实现。
13 11 
14- y = (x - mean) * (gamma / sqrt(variance + epsilon)) + beta # gamma/beta 可选12+"""INInferV2 kernel/GEIR golden in the TestSpec multi-path format.
15- 无 gamma/beta 时 y = (x - mean) / sqrt(variance + epsilon)
16- batch_mean = mean (透传拷贝)
17- batch_variance = variance (透传拷贝)
18 13 
19- kernel 一致:全部按 float32 计算(fp16 x 先升 fp32),y 单次舍入写回输入 dtype;14+For an ND input laid out as ``[N, C, R...]`` the operator computes::
20-scale = gamma / sqrt(var + eps) 先算(对齐 910b high_performance 语义)。
21 15 
22-格式说明:算子 ND-only(tiling 仅接受 ND/NCHW 标签),C 恒在 dim1;16+ y = (x - mean) * (gamma / sqrt(variance + epsilon)) + beta
23-_is_channels_last 仅为防御性保留。17+ 
18+When ``gamma`` and ``beta`` are both absent, the scale/add branch is omitted and
19+``y = (x - mean) / sqrt(variance + epsilon)``. ``batch_mean`` and
20+``batch_variance`` are copies of the corresponding inputs.
21+ 
22+The CPU true-value path is a Torch competitor composition. It lifts fp16 to at
23+least fp32 and preserves fp64 inputs supplied by TTK Promote; promoted values are
24+never narrowed. The independent third-party composition mirrors the arch35
25+kernel's float32 arithmetic and operation order before casting ``y`` back to the
26+input dtype.
24"""27"""
25 28 
26import numpy as np29import numpy as np
27import torch30import torch
28 31 
32+ 
33+# Kernel and GEIR resolve the same snake-case operator key and share one Spec.
34+__spec__ = {"in_infer_v2": "INInferV2KernelSpec"}
35+ 
36+# Compatibility entry for the historical kernel golden loader.
29__golden__ = {37__golden__ = {
30 "kernel": {"in_infer_v2": "in_infer_v2_golden"},38 "kernel": {"in_infer_v2": "in_infer_v2_golden"},
31- "aclnn": {"aclnnINInferV2": "aclnn_in_infer_v2_golden"},
32}39}
33 40 
34 41 
35-def _to_torch_f32(tensor):42+_TOL = {
36- """输入归一为 torch float32(接受 numpy / torch tensor,None 透传;ml_dtypes.bfloat16 等 numpy 扩展 dtype 先升 fp32)。"""43+ "float16": {"standard": "cross_check", "level": "L1"},
37- if tensor is None:44+ "float32": {"standard": "cross_check", "level": "L1"},
38- return None45+}
39- if isinstance(tensor, torch.Tensor):
40- return tensor.detach().cpu().to(torch.float32)
41- arr = np.asarray(tensor)
42- if arr.dtype not in (
43- np.float16,
44- np.float32,
45- np.float64,
46- np.int32,
47- np.int64,
48- np.int16,
49- np.int8,
50- np.uint8,
51- ):
52- arr = arr.astype(np.float32)
53- return torch.from_numpy(arr).to(torch.float32)
54 46 
55 47 
56-def _out_dtype(x):48+def _attr(kwargs, name, default):
57- if isinstance(x, torch.Tensor):49+ """Read a scalar attribute, including the legacy nested-attributes form."""
58- return x.detach().cpu().numpy().dtype50+ value = kwargs.get(name)
59- return np.asarray(x).dtype51+ if value is None and isinstance(kwargs.get("attributes"), dict):
52+ value = kwargs["attributes"].get(name)
53+ if value is None:
54+ return default
55+ if isinstance(value, str):
56+ try:
57+ return type(default)(value)
58+ except (TypeError, ValueError):
59+ return default
60+ return value
60 61 
61 62 
62-def _is_channels_last(x_shape, c):63+def _resolve_epsilon(epsilon, kwargs):
63- """判断 C 是否在末维(NHWC);C 在 dim1(ND/NCHW)时返回 False。64+ values = dict(kwargs)
65+ values.setdefault("epsilon", epsilon)
66+ return float(_attr(values, "epsilon", 1e-5))
64 67 
65- 用例 shape 设计保证 d1 与末维只有一个等于 C,无歧义。68+ 
66- """69+def _as_tensor(value):
67- if len(x_shape) < 3:70+ """Convert a Kernel/GEIR NumPy input to a CPU Torch tensor losslessly."""
68- return False71+ if isinstance(value, torch.Tensor):
69- return x_shape[1] != c and x_shape[-1] == c72+ return value.detach().cpu()
73+ return torch.from_numpy(np.ascontiguousarray(np.asarray(value)))
74+ 
75+ 
76+def _reference_dtype(*tensors):
77+ """Select at least fp32 while retaining any wider promoted float dtype."""
78+ dtype = torch.float32
79+ for tensor in tensors:
80+ if tensor is not None and tensor.dtype.is_floating_point:
81+ dtype = torch.promote_types(dtype, tensor.dtype)
82+ return dtype
83+ 
84+ 
85+def _stat_matrix(tensor, n, c, name):
86+ expected = n * c
87+ if tensor.numel() != expected:
88+ raise ValueError(
89+ f"{name} must contain N*C={expected} elements, got {tensor.numel()}"
90+ )
91+ return torch.reshape(tensor, (n, c))
70 92 
71 93 
72def _compute(x, gamma, beta, mean, variance, epsilon):94def _compute(x, gamma, beta, mean, variance, epsilon):
73- """核心计算:返回 (y fp32计算后按输入 dtype 舍入, mean, variance)。"""95+ """Sole Torch true-value core; return outputs in def.cpp order."""
74- out_dtype = _out_dtype(x)96+ if mean is None or variance is None:
75- x_t = _to_torch_f32(x)97+ raise ValueError("mean and variance are required by INInferV2 tiling")
76- mean_t = _to_torch_f32(mean)98+ if (gamma is None) != (beta is None):
77- var_t = _to_torch_f32(variance)99+ raise ValueError("gamma and beta must be both present or both absent")
78- n, c = mean_t.shape[0], mean_t.shape[1]
79 100 
80- if _is_channels_last(tuple(x_t.shape), c):101+ x_tensor = _as_tensor(x)
81- bcast_shape = [n] + [1] * (x_t.dim() - 2) + [c]102+ gamma_tensor = _as_tensor(gamma) if gamma is not None else None
103+ beta_tensor = _as_tensor(beta) if beta is not None else None
104+ mean_tensor = _as_tensor(mean)
105+ variance_tensor = _as_tensor(variance)
106+ 
107+ if x_tensor.ndim < 2:
108+ raise ValueError(f"x rank must be at least 2, got {x_tensor.ndim}")
109+ n, c = x_tensor.shape[:2]
110+ compute_dtype = _reference_dtype(
111+ x_tensor, gamma_tensor, beta_tensor, mean_tensor, variance_tensor
112+ )
113+ x_compute = x_tensor.to(dtype=compute_dtype)
114+ mean_compute = mean_tensor.to(dtype=compute_dtype)
115+ variance_compute = variance_tensor.to(dtype=compute_dtype)
116+ 
117+ mean_matrix = _stat_matrix(mean_compute, n, c, "mean")
118+ variance_matrix = _stat_matrix(variance_compute, n, c, "variance")
119+ # The host reads a Float attribute, so preserve that fp32 quantization even
120+ # when Promote has lifted tensor inputs to fp64.
121+ epsilon_f32 = torch.tensor(float(epsilon), dtype=torch.float32)
122+ epsilon_tensor = epsilon_f32.to(dtype=compute_dtype)
123+ inverse_std = torch.rsqrt(torch.add(variance_matrix, epsilon_tensor))
124+ 
125+ broadcast_shape = (n, c) + (1,) * (x_tensor.ndim - 2)
126+ mean_broadcast = torch.reshape(mean_matrix, broadcast_shape)
127+ centered = torch.sub(x_compute, mean_broadcast)
128+ normalized = torch.mul(centered, torch.reshape(inverse_std, broadcast_shape))
129+ 
130+ if gamma_tensor is None:
131+ y = normalized
82 else:132 else:
83- bcast_shape = [n, c] + [1] * (x_t.dim() - 2)133+ gamma_compute = gamma_tensor.to(dtype=compute_dtype)
84- mean_b = mean_t.reshape(bcast_shape)134+ beta_compute = beta_tensor.to(dtype=compute_dtype)
85- var_b = var_t.reshape(bcast_shape)135+ gamma_matrix = _stat_matrix(gamma_compute, n, c, "gamma")
136+ beta_matrix = _stat_matrix(beta_compute, n, c, "beta")
137+ scaled = torch.mul(normalized, torch.reshape(gamma_matrix, broadcast_shape))
138+ y = torch.add(scaled, torch.reshape(beta_matrix, broadcast_shape))
86 139 
87- sqrt_var = torch.sqrt(var_b + float(np.float32(epsilon)))140+ # InferShape keeps the original mean/variance shapes for these two outputs.
88- gamma_t = _to_torch_f32(gamma)141+ return [y, torch.clone(mean_compute), torch.clone(variance_compute)]
89- if gamma_t is not None:
90- beta_t = _to_torch_f32(beta)
91- scale = (gamma_t / sqrt_var.reshape(gamma_t.shape)).reshape(bcast_shape)
92- beta_b = beta_t.reshape(bcast_shape)
93- y = (x_t - mean_b) * scale + beta_b
94- else:
95- y = (x_t - mean_b) / sqrt_var
96- return y.numpy().astype(out_dtype), mean_t.numpy(), var_t.numpy()
97 142 
98 143 
99-def in_infer_v2_golden(x, gamma, beta, mean, variance, epsilon=1e-5, **kwargs):144+def _normalize_dtype_name(dtype):
100- """Golden for in_infer_v2 kernel 模式。参数顺序同 def(不含输出)。"""145+ if isinstance(dtype, (list, tuple)):
101- del kwargs146+ dtype = dtype[0] if dtype else None
102- y, mean_np, var_np = _compute(x, gamma, beta, mean, variance, epsilon)147+ if dtype is None:
103- return [y, mean_np.copy(), var_np.copy()]148+ return None
149+ name = str(dtype).lower().replace("torch.", "").replace("numpy.", "")
150+ return {
151+ "fp16": "float16",
152+ "half": "float16",
153+ "fp32": "float32",
154+ "float": "float32",
155+ "fp64": "float64",
156+ "double": "float64",
157+ }.get(name, name)
104 158 
105 159 
106-def aclnn_in_infer_v2_golden(160+def _input_dtype_name(value):
161+ if isinstance(value, torch.Tensor):
162+ return _normalize_dtype_name(value.dtype)
163+ return np.asarray(value).dtype.name
164+ 
165+ 
166+def _numpy_outputs(outputs, output_dtypes):
167+ dtype_names = [_normalize_dtype_name(dtype) for dtype in (output_dtypes or ())]
168+ result = []
169+ for index, output in enumerate(outputs):
170+ array = output.detach().cpu().contiguous().numpy()
171+ if index < len(dtype_names) and dtype_names[index] is not None:
172+ array = array.astype(dtype_names[index], copy=False)
173+ result.append(np.ascontiguousarray(array))
174+ return result
175+ 
176+ 
177+def _kernel_golden(
178+ x,
179+ gamma,
180+ beta,
181+ mean,
182+ variance,
183+ epsilon=1e-5,
184+ **kwargs,
185+):
186+ """Kernel/GEIR adapter: NumPy inputs and a NumPy output list."""
187+ epsilon_value = _resolve_epsilon(epsilon, kwargs)
188+ outputs = _compute(x, gamma, beta, mean, variance, epsilon_value)
189+ output_dtypes = kwargs.get("output_dtypes")
190+ if not output_dtypes:
191+ output_dtypes = (
192+ _input_dtype_name(x),
193+ _input_dtype_name(mean),
194+ _input_dtype_name(variance),
195+ )
196+ return _numpy_outputs(outputs, output_dtypes)
197+ 
198+ 
199+class _INInferV2Compose:
200+ """Independent Torch composition matching the arch35 device arithmetic."""
201+ 
202+ def __init__(self, epsilon=1e-5, **kwargs):
203+ epsilon_value = _resolve_epsilon(epsilon, kwargs)
204+ # Tiling stores epsilon as float32 before kernel launch.
205+ self.epsilon = float(torch.tensor(epsilon_value, dtype=torch.float32).item())
206+ 
207+ def __call__(self, x, gamma, beta, mean, variance, **kwargs):
208+ del kwargs
209+ if mean is None or variance is None:
210+ raise ValueError("mean and variance are required by INInferV2 tiling")
211+ if (gamma is None) != (beta is None):
212+ raise ValueError("gamma and beta must be both present or both absent")
213+ if x.dtype not in (torch.float16, torch.float32):
214+ raise TypeError(f"INInferV2 supports only float16/float32 x, got {x.dtype}")
215+ 
216+ n, c = x.shape[:2]
217+ broadcast_shape = (n, c) + (1,) * (x.ndim - 2)
218+ 
219+ # INInferV2Kernel::Process converts every arithmetic operand to fp32.
220+ x_f32 = x.to(dtype=torch.float32)
221+ mean_f32 = _stat_matrix(mean.to(dtype=torch.float32), n, c, "mean")
222+ variance_f32 = _stat_matrix(variance.to(dtype=torch.float32), n, c, "variance")
223+ epsilon_f32 = variance_f32.new_tensor(self.epsilon)
224+ sqrt_variance = torch.sqrt(torch.add(variance_f32, epsilon_f32))
225+ centered = torch.sub(x_f32, torch.reshape(mean_f32, broadcast_shape))
226+ 
227+ if gamma is None:
228+ y_f32 = torch.div(centered, torch.reshape(sqrt_variance, broadcast_shape))
229+ else:
230+ gamma_f32 = _stat_matrix(gamma.to(dtype=torch.float32), n, c, "gamma")
231+ beta_f32 = _stat_matrix(beta.to(dtype=torch.float32), n, c, "beta")
232+ scale = torch.div(gamma_f32, sqrt_variance)
233+ scaled = torch.mul(centered, torch.reshape(scale, broadcast_shape))
234+ y_f32 = torch.add(scaled, torch.reshape(beta_f32, broadcast_shape))
235+ 
236+ # The first output is stored in x dtype; the two copy outputs are fp32.
237+ return [
238+ y_f32.to(dtype=x.dtype),
239+ mean.to(dtype=torch.float32).clone(),
240+ variance.to(dtype=torch.float32).clone(),
241+ ]
242+ 
243+ 
244+class INInferV2KernelSpec:
245+ """Shared kernel/GEIR TestSpec; parameters follow in_infer_v2_def.cpp."""
246+ 
247+ golden = _kernel_golden
248+ third_party = {"torch": _INInferV2Compose}
249+ tolerance = _TOL
250+ 
251+ 
252+def in_infer_v2_golden(
107 x,253 x,
108 gamma,254 gamma,
109 beta,255 beta,
110 mean,256 mean,
111 variance,257 variance,
112 epsilon=1e-5,258 epsilon=1e-5,
113- y=None,
114- batch_mean=None,
115- batch_variance=None,
116 *args,259 *args,
117 **kwargs,260 **kwargs,
118):261):
119- """Golden for aclnnINInferV2(ttk aclnn 模式)。262+ """Compatibility ``__golden__`` entry backed by the same compute core."""
263+ del args
264+ return _kernel_golden(
265+ x,
266+ gamma,
267+ beta,
268+ mean,
269+ variance,
270+ epsilon=epsilon,
271+ **kwargs,
272+ )
120 273 
121- 注意:ttk aclnn 模式按 C 头文件参数序位置传参(AclnnParamPlan.build_args)——274+ 
122- epsilonoutputs 之前,outputs 之后还有 workspaceSize/executor 占位,275+# 【不存】ACLNN 通路:op_host/CMakeLists.txt declares ``ACLNNTYPE aclnn_exclude``;
123- 故签名必须是 C + *args 吞掉尾部占位,不能按 csv tensor 序。276+# the repository provides neither op_api files nor an aclnn interface document.
124- batch_mean/batch_variance None(可选输出缺席)时仅返回 [y]。277+# 【不存在】e2e 通路:the supported surface is GE graph mode (README.md), and no
125- """278+# torch binding for this operator is provided in the repository.
126- del y, args, kwargs
127- has_batch_out = batch_mean is not None or batch_variance is not None
128- y_np, mean_np, var_np = _compute(x, gamma, beta, mean, variance, epsilon)
129- if not has_batch_out:
130- return [y_np]
131- return [y_np, mean_np.copy(), var_np.copy()]