| Files | Last commit | Last update |
|---|---|---|
| 1 month ago | ||
| 18 days ago | ||
| 19 days ago | ||
| 1 month ago | ||
| 30 days ago | ||
| 19 days ago | ||
| 19 days ago | ||
| 1 month ago | ||
| 1 month ago | ||
| 1 month ago | ||
| 19 days ago |
TestSpec — 算子测试规范
不依赖任何测试框架或工具,Ascend 生态统一的算子测试规范。
快速上手
以 abs 算子为例,四种测试路径的 spec 注册汇总:
| 测试路径 | 注册名 | 参数名来源 | Golden 参数类型 | 无 spec 时默认行为 |
|---|---|---|---|---|
| Kernel | abs(CSV op_name) |
算子 def.cpp 形参 |
numpy.ndarray | 回退内置映射(KERNEL_GOLDEN → numpy 同名 API,如 abs),无命中才报 UNSUPPORTED |
| GEIR | abs(CSV op_name) |
算子 def.cpp 形参 |
numpy.ndarray | 回退内置映射(KERNEL_GOLDEN → numpy 同名 API,如 abs),无命中才报 UNSUPPORTED |
| ACLNN | aclnnAbs(CSV api_name) |
aclnn_abs.h 形参 |
torch.Tensor | 自动从 torch 同名 API 映射 |
| E2E | torch.abs(CSV api_name) |
torch API 签名 | torch.Tensor | 框架自动在 CPU 跑同 API 作 golden |
| E2E onnx 域 | onnx.Abs(CSV api_name) |
onnx schema(onnx.defs,环境注册表即权威) |
numpy.ndarray | onnx ReferenceEvaluator 跑同一组模产物(同构参照) |
注册名即
get_spec_attr的查找 key:Kernel/GEIR 用op_name,ACLNN/E2E 用api_name。GEIR 复用 Kernel 的 spec,无需额外编写。类名按 PascalCase+TestSpec 约定或通过__spec__dict 显式注册。
Kernel
__spec__ = {"abs": "AbsTestSpec"}
import torch
class AbsTestSpec:
"""Abs 算子测试规范(kernel 流程)"""
def golden(x, **kwargs):
return [torch.abs(torch.from_numpy(x)).numpy()]
tolerance = {"float32": {"standard": "binary_equal"}}
ACLNN
__spec__ = {"aclnnAbs": "AclnnAbsTestSpec"}
import torch
class AclnnAbsTestSpec:
"""Abs 算子测试规范(aclnn 流程)"""
def golden(x, **kwargs):
return [torch.abs(x)]
third_party = {"torch": "torch.abs"}
tolerance = {"float32": {"standard": "binary_equal"}}
E2E
__spec__ = {"torch.abs": "TorchAbsTestSpec"}
import torch
class TorchAbsTestSpec:
"""Abs 算子测试规范(e2e 流程)"""
def golden(x, **kwargs):
return [torch.abs(x)]
tolerance = {"float32": {"standard": "binary_equal"}}
Spec 字段一览
所有字段可选,按需声明:
| 字段 | 用途 | 合法类型 | 适用路径 |
|---|---|---|---|
golden |
CPU 真值 | str / 函数 / 类 |
Kernel / GEIR / ACLNN / E2E |
third_party |
三方标杆 | str / dict / 类 |
Kernel / GEIR / ACLNN / E2E |
compare |
自定义比对 | 函数 | Kernel / GEIR / ACLNN / E2E |
pre_compare |
比对前处理 | 函数 | Kernel / GEIR / ACLNN / E2E |
customize_inputs |
自定义输入 | 函数 | Kernel / GEIR / ACLNN / E2E |
tolerance |
精度标准 | dict(dtype→{standard, ...}) |
Kernel / GEIR / ACLNN / E2E |
torch_graph |
Graph 模式 | torch.nn.Module 子类 |
E2E(仅 torch API) |
npu_preprocess |
NPU 执行前设备侧预处理钩子 | 函数 | ACLNN / E2E |
describe |
算子描述信息(保留字段,当前框架暂未消费) | 函数 | 全路径 |
所有属性可选。golden 三种形式:
| 形式 | 示例 | 适用场景 |
|---|---|---|
| 字符串 | golden = "numpy.abs" |
numpy 有直接对应 API |
| 函数 | def golden(x, *, axis=-1): |
中等复杂度,* 前=输入,* 后=属性 |
| 类 | class Golden: def __call__(self, x): |
需要状态管理,__init__(可选)+ __call__(必须) |
golden 在 CPU 计算,Kernel/GEIR 与 E2E onnx 域传入 numpy.ndarray,ACLNN/E2E(torch/tf)传入 CPU(host)侧 torch.Tensor(
torch.from_numpy构造,先于设备执行生成;设备侧张量仅npu_preprocesshook 可见)。
third_party 三种形式:
| 形式 | 示例 | 适用场景 |
|---|---|---|
| 字符串 | third_party = "torch.nn.functional.softmax" |
单 API 对标 |
| dict | third_party = {"torch": "...", "tf": "..."} |
多 vendor / 多框架 |
| 类 | third_party = MyImplClass |
小算子拼接,需要 __call__ |
third_party 调用三方 API(如 torch),Kernel/GEIR 会将 numpy 转 torch 后调用,ACLNN/E2E 直接传入 torch.Tensor(三方执行侧再上设备)。
类形式参数绑定(Impl class)
golden/third_party用类形式时适用。框架按参数名从算子输入/属性池中按名匹配,分别传给__init__和__call__。
- 严格按名匹配,未命中快速失败:
__init__与__call__的参数(除self、*args、**kwargs)按名从算子输入/属性池匹配;按名未命中且无默认值的参数直接抛UnknownParamError(参数名拼错/改名时快速失败,不做位置兜底)。未被具名消费的池条目只能经*args(按池插入顺序,即算子定义参数顺序)或**kwargs进入。 - 按方法声明分发:每个输入/属性只传给声明了该参数名的方法;若
__init__和__call__同时声明同名参数,则两者都收到。 - 带默认值的参数可不传:算子定义中未出现的参数,若声明了默认值则取默认值(如
def __call__(self, x, axis=-1)中axis未传时取 -1)。 *args收集未消费条目:当算子有与 Python 保留名冲突的参数(如 ACLNN 接口入参名self),可用*args按算子定义顺序接收所有未被具名消费的输入/属性,无需逐个声明参数名。self条目不会注入**kwargs(避免multiple values for argument 'self'),只能通过*args或具名参数(非self)获取。device为third_party实现类的框架保留参数:bind_params仅在三方实现(本地/远端 XPU 执行)中注入device;golden类形式不注入——声明device参数须自带默认值或经**kwargs获取。有输入 tensor 时设备信息也可直接取tensor.device。
class AddImpl:
def __init__(self, x): # x 可进 __init__
self.x = x
def __call__(self, x, y): # 同名 x 两边都喂;y 只喂 __call__
return [self.x + x + y]
class EyeImpl: # third_party 实现类(golden 类形式不注入 device)
def __init__(self, n, device): # 无输入 tensor: device 框架注入
self.n, self.device = n, device
def __call__(self):
import torch
return [torch.eye(self.n, device=self.device)]
# ACLNN 入参名含 self 时,用 *args 按位置接收
class AclnnSelfOpImpl:
def __call__(self, *args, **kwargs):
# args[0] 对应 aclnn 接口的 self 张量, args[1] 为 other, ...
import torch
return [torch.add(args[0], args[1])]
compare / pre_compare 消费规则:
- ACLNN 必须用 CSV 中的精确
aclnn*api_name注册;Kernel 必须用 CSV 中的精确 rawop_name注册。 - Kernel 会对每个已启用的 dynamic、const 和 binary 模式分别调用 hook,关闭模式哨兵不进入 hook。
- 需要读取当前或 replay 恢复输入时,compare 必须显式声明仅关键字参数
compare_context;仅声明**kwargs不会收到该参数。
customize_inputs 自定义输入生成:
- 参数名与算子定义一致(按算子/接口签名顺序传入,含标量与输出张量)。
- 若只需调整随机数据范围,可不写此字段,直接在 CSV 中设置
input_data_ranges。 - 返回值契约按通路不同:
| 通路 | 入参类型 | 返回值处理 |
|---|---|---|
| Kernel / GEIR | numpy.ndarray | 必须返回修改后的输入序列(TensorList 可嵌套可平铺,展平后数量须与输入槽一致;只原地改而不返回会报错) |
| ACLNN | torch.Tensor(非 torch 原生 dtype 时为 numpy 视图) | 返回序列按输入张量槽回写(纯输出槽除外,None 槽保持原值);整体返回 None 表示原地写法 |
| E2E | torch.Tensor(numpy 回退场景为 ndarray;onnx 域恒为 ndarray) | 返回值被忽略,仅原地写法(x[:] = value / x.copy_(...)) |
def customize_inputs(x, min_val, max_val, **kwargs):
if min_val[0] == max_val[0]:
min_val[0] = numpy.min(x)
max_val[0] = numpy.max(x)
return (x, min_val, max_val) # Kernel/GEIR:返回修改后的输入
tolerance 精度标准:
按 dtype 声明每个输出的精度比对标准。standard 字段只接受官方标准:
| standard 值 | 含义 | --compare 传参 |
|---|---|---|
mix_tolerance |
混合容差(默认,生态算子开源精度标准) | mixed |
stat_rel_err |
统计相对误差 | stat_rel_err |
binary_equal |
逐 bit 相等 | binary(bin 为内部判据别名) |
cross_check |
交叉比对(需配合 third_party) |
cross_check |
quant |
量化比对(int4/int8 量化输出,绝对误差 ≤ 1) | —(仅 Spec.tolerance 声明) |
isclose |
numpy.isclose 容差比对 | close |
cosine |
余弦相似度 | cosine |
前六行可写入
Spec.tolerance;cosine为 CLI 框架增强,仅通过--compare指定。CLI 可用值以--compare的 choices 为准(bin仅为内部判据别名,不能直接传入)。mix_tolerance可用rtol/atol/required_matched_ratio/max_abs_error_limit覆盖默认阈值表。max_abs_error_limit默认按max(兜底值, 32×ULP(最大误差点))动态计算;显式配置将完全替代动态计算(配置值即最终上限,不再叠加 ULP——大值域将失去值域自适应放行)。isclose可用rtol/atol/ptol覆盖 dtype 分档默认(ptol为允许超差元素占比;CSVprecision_tolerances/absolute_precision的 legacy 容差仍优先于 Spec 顶层值)。
torch_graph Graph 模式(仅 E2E):
声明一个 torch.nn.Module 子类,用于 E2E 的 Graph 模式构图测试。仅适用于 torch API。
import torch.nn as nn
class SoftmaxGraph(nn.Module):
def forward(self, x):
return torch.softmax(x, dim=-1)
class TorchSoftmaxTestSpec:
torch_graph = SoftmaxGraph
describe 参数化用例组装(简化 CSV 列 → shapes/dtypes):
多输入算子可用抽象参数列替代逐张量形状声明(如 B/S1/N1/D)。CSV 行的
通路形状/类型字段(kernel/geir 为 input_shapes/input_dtypes,aclmn/e2e
为 tensor_view_shapes/tensor_dtypes)任一缺省时,框架把该行所有
TTK 未识别列收集为 params(数字/容器经字面量求值,裸词如 BSND 退回
字符串),调用 describe(params)。
契约:describe 只返回 {"shapes": ..., "dtypes": ...} 两个 key,
各通路自行映射:kernel/geir → input_shapes/input_dtypes;aclmn/e2e
→ tensor_view_shapes/tensor_dtypes。aclnn 的 shapes 按 API 参数列表
含输出槽位;attributes/output_tensor_indexes/输出形状等其余字段
仍由 CSV 列显式提供(describe 不回填)。shapes 与 dtypes 不强绑定:
两者任一缺省即触发,缺啥补啥——CSV 已显式声明的字段不回填("不缺的
不读")。describe 未声明时未识别列照旧忽略,行为不变;抛异常/非 dict/
缺 shapes → 用例置无效(DESCRIBE_FAILED / DESCRIBE_RESULT_NOT_DICT /
DESCRIBE_MISSING_SHAPES)。
class GemmTestSpec:
@staticmethod
def describe(params):
m, k, n = params["M"], params["K"], params["N"]
return {
"shapes": ((m, k), (k, n)),
"dtypes": ("float16", "float16"),
}
# CSV: testcase_name,api_name,M,K,N
# gemm_tc,torch.mm,128,4096,4096
命名与注册
类命名约定
op_name → 类名 = PascalCase + TestSpec:
| op_name | 类名 |
|---|---|
abs |
AbsTestSpec |
softmax_v2 |
SoftmaxV2TestSpec |
layer_norm |
LayerNormTestSpec |
仅处理纯 snake_case(如
softmax_v2→SoftmaxV2)。对于已是 PascalCase 的算子名(如BatchMatMul→Batchmatmul),或期望类名与机械转换结果不一致时(如想要Matmul3DTestSpec而非matmul_3d机械转换出的Matmul3dTestSpec),需用__spec__显式注册。
__spec__ 显式注册
# 值为类名字符串(非类对象);可写在文件顶部
__spec__ = {"abs": "AbsTestSpec"}
文件与发现规则
- 文件命名:spec 文件名不以
_开头,否则 loader 跳过该文件(__init__.py例外,不被跳过)。 - 静态索引:首次加载时,loader 遍历目录下所有
.py文件,通过ast.parse解析模块级__spec__赋值,构建op → (file, classname)索引。此过程仅做语法解析,不执行文件代码,无副作用。 - 惰性执行:静态扫描
__spec__只解析 AST,不执行代码。查询命中__spec__映射时,loader 只导入对应文件;未命中时会按搜索顺序导入.py候选文件,通过类名约定查找<PascalCase>TestSpec,未找到时会尝试所有候选文件(每个文件至多 exec 一次)。因此类名回退可能执行未命中文件的顶层代码;希望常规查询只导入对应文件时,应为算子显式声明__spec__。 __spec__位置不限:因索引阶段只读 AST 不执行代码,__spec__写在文件顶部或底部效果相同;建议写在顶部,使 op→class 映射一目了然。
消费方式
from ttk.core_modules.common.plugin import SpecResolver, get_spec_class_meta
resolver = SpecResolver("softmax_v2", "kernel", ("path/to/specs",))
golden_fn = resolver.resolve_golden()
tp = resolver.resolve_third_party()
meta = get_spec_class_meta("softmax_v2", ("path/to/specs",)) # {spec_file, class_name}
更多示例
见 examples/ 目录:
| 文件 | 场景 |
|---|---|
01_minimal_golden.py |
最简 golden(单个函数) |
02_golden_three_forms.py |
golden 三种形式:字符串/函数/类 |
03_third_party_three_forms.py |
third_party 三种形式:字符串/dict/类 |
04_golden_tolerance_compare.py |
golden + tolerance + compare 组合 |
05_pre_compare_customize_inputs.py |
pre_compare 两种模式、customize_inputs、多输出 compare |
06_golden_multi_path.py |
同一算子在 Kernel/ACLNN/E2E 不同路径下的 golden 编写 |