import logging
import random
import math
from enum import Enum
from typing import Any, Optional, List
from typing import Union, Dict
from typing_extensions import Literal
import yaml
from pydantic import BaseModel, ConfigDict, model_validator, PrivateAttr
DEFAULT_TYPES = ["tensor", "tensors", "tensor_tuple",
"scalar", "scalars", "scalar_tuple",
"attr", "attrs", "attr_tuple"]
DEFAULT_DTYPES = [
"fp32",
"bf16",
"fp16",
"fp64",
"hf32",
"int8",
"uint8",
"int16",
'uint16',
"int32",
"uint32",
"int64",
"uint64",
"bool",
]
ATTR_DTYPES = ["float", "int", "string", "attr_bool"]
NEW_DTYPES = ["complex128", "complex64", "complex32"]
DEFAULT_DIM_NUMBERS = [1, 2, 3, 4, 5, 6, 7, 8]
DEFAULT_DIM_WEIGHTS = [1 / 8] * 8
DEFAULT_DIM_VALUES = [
1,
7,
8,
9,
15,
16,
17,
19,
20,
21,
[255, 257],
131073,
2147483648,
[1, 1024],
[1025, 10240],
[10241, 102400],
[102401, 1024000],
[1024001, 10240000],
[10240001, 102400000],
[102400001, 1024000000],
[1024000001, 2147483648],
]
DEFAULT_DIM_VALUES_WEIGHTS = [
0.05,
0.03,
0.04,
0.03,
0.03,
0.04,
0.03,
0.03,
0.04,
0.03,
0.05,
0.05,
0.001,
0.001,
0.001,
0.001,
0.001,
0.001,
0.001,
0.001,
0.001,
]
DEFAULT_RANGES_VALUES = [[-5, 5]]
DEFAULT_TENSOR_RANGES_INVALID_VALUES = [["-inf"], ["inf"], ["nan"], ["null"]]
DEFAULT_SCALAR_RANGES_INVALID_VALUES = [["-inf"], ["inf"], ["nan"]]
DEFAULT_ATTR_RANGES_INVALID_VALUES = [["-inf"], ["inf"]]
VALID_WEIGHTS = 0.95
MAX_NUMBER_OF_ELEMENTS = 2 ** 32
DEFAULT_DTYPE_NUMBERS = 50
class RandomTypes(Enum):
DEFAULT = "default"
ND = "nd"
class RandomTypesConfig(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
name: Optional[RandomTypes] = RandomTypes.DEFAULT
mean: Optional[List[float]] = [-100, 100]
std: Optional[List[float]] = [1, 25]
@model_validator(mode="after")
def check(self):
if self.name == RandomTypes.ND:
if not self.mean:
raise ValueError("not set nd mean, please check!")
if not self.std:
raise ValueError(" not set nd std, please check!")
return self
def model_dump(self):
data = super(RandomTypesConfig, self).model_dump()
if data.get("name"):
data["name"] = data["name"].value
return data
class RandomConfig(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
type: Optional[Literal["choices"]] = "choices"
values: List = None
weights: Optional[List[float]] = None
invalid_values: List[Union[str, int]] = []
random_types: Optional[List[RandomTypesConfig]] = None
custom_dist_ratio: Optional[Union[List[List[float]], str]] = None
@model_validator(mode="after")
def weight_len_must_same_values(self):
if self.weights and len(self.weights) != len(self.values):
raise ValueError("length of weights and values is not same.")
return self
def get_values(self, k=1, clc_interval=True):
fun = getattr(self, self.type)
if k == 1:
return fun(k, clc_interval)[0]
else:
return fun(k, clc_interval)
def choices(self, k, clc_interval):
ret = []
ret_nd = []
if self.random_types:
for random_type in self.random_types:
if random_type.name != RandomTypes.DEFAULT:
ret_nd.append(random_type.model_dump())
for _ in range(k):
index = random.choices(range(len(self.values)), weights=self.weights, k=k)[0]
value = self.values[index]
double_int = (
clc_interval
and isinstance(value, list)
and len(value) == 2
and isinstance(value[0], int)
and isinstance(value[1], int)
)
if double_int:
ret.append(random.randint(value[0], value[1]))
else:
ret.append(value)
if ret_nd:
return ret_nd if len(self.random_types) == 1 else random.choice([ret, ret_nd])
return ret
def get_actual_values(self):
ret = []
if not self.random_types:
return list(self.values)
for value in self.values:
for random_type in self.random_types:
if random_type.name != RandomTypes.DEFAULT:
ret.append(random_type.model_dump())
else:
ret.append(value)
return ret
def get_actual_values_with_custom_dist(self, custom_dist_ratio, len_cases: int):
"""
自定义分布比例
例如custom_dist_ratio为[[0.1, 0.3, 0.1], [0.4, 0.1]]时,其中第一个列表表示均匀分布相关比例,第二个列表表示正态分布相关比例
其中,[0.1, 0.3, 0.1]表示第一个均匀分布范围的比例为0.1,第二个均匀分布范围的比例为0.3,最后一个数固定表示离群分布比例,此时为0.1
[0.4, 0.1]表示正态分布的比例为0.4,离群分布比例为0.1
:param custom_dist_ratio:
:param len_cases:
:return:
"""
if isinstance(custom_dist_ratio, str) and custom_dist_ratio == "default":
custom_dist_ratio = DefaultCustomDistConfig().custom_dist_ratio
self.values = DefaultCustomDistConfig().default_range_values
logging.info("use default custom_dist_ratio, "
f"custom_dist_ratio is {custom_dist_ratio}, self.values is {self.values}")
ret = []
if not self.random_types or len(custom_dist_ratio) != 2:
logging.error(f"has no random_types: {self.random_types} or "
f"len of custom_dist_ratio is not 2: {custom_dist_ratio}, return default values!!!")
return list(self.values)
total_ratio = sum(sum(sublist) for sublist in custom_dist_ratio)
if abs(total_ratio - 1.0) > 1e-9:
logging.error(f"custom_dist_ratio is {custom_dist_ratio}, sum is not 1, return default values!!!")
return list(self.values)
if len(self.values) != len(custom_dist_ratio[0]) - 1:
logging.error(f"values length is {len(self.values)}, "
f"values_ratio in custom_dist_ratio length is {len(custom_dist_ratio[0]) - 1}")
return list(self.values)
nd_ratio = custom_dist_ratio[1]
nd_number = round(sum(nd_ratio) * len_cases)
if nd_number < 0 or nd_number > len_cases:
logging.error(f"nd_dist_number illegal, value is {nd_number}, "
f"len_cases is {len_cases}, return default values!!!")
return list(self.values)
for random_type in self.random_types:
if random_type.name != RandomTypes.DEFAULT:
ret.extend([random_type.model_dump()] * nd_number)
default_number = len_cases - nd_number
default_ratio_total = sum(custom_dist_ratio[0])
if default_number <= 0 or default_ratio_total <= 0:
return ret
for i, value in enumerate(self.values):
value_ratio = custom_dist_ratio[0][i] / default_ratio_total
if i == len(self.values) - 1:
value_ratio = (custom_dist_ratio[0][i] + custom_dist_ratio[0][-1]) / default_ratio_total
value_number = math.ceil(value_ratio * default_number)
ret.extend([value] * value_number)
return ret
class DefaultCustomDistConfig(BaseModel):
default_range_values: List[Union[List[float], float]] = [[-5, 5]]
custom_dist_ratio: Optional[List[List[float]]] = [[0.5, 0.0], [0.5, 0.0]]
default_outlier_values: List[float] = [0.001, 1000]
default_outlier_values_range_index: int = -1
class InputsShape(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
dim_numbers: RandomConfig = RandomConfig(
values=DEFAULT_DIM_NUMBERS, weights=DEFAULT_DIM_WEIGHTS
)
dim_values: Optional[Union[RandomConfig, List[RandomConfig]]] = RandomConfig(
values=DEFAULT_DIM_VALUES, weights=DEFAULT_DIM_VALUES_WEIGHTS
)
max_length: int = MAX_NUMBER_OF_ELEMENTS
@model_validator(mode="after")
def check_params(self):
if isinstance(self.dim_values, list):
dim_values_len = len(self.dim_values)
max_dim_numbers = max(self.dim_numbers.values)
if dim_values_len != max_dim_numbers:
raise ValueError(f"dim_values_length {dim_values_len} is not equal to "
f"max(dim_numbers) {max_dim_numbers}, please check")
return self
class InputSize(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
values: List[Union[List[float], float]]
ratio: float
@model_validator(mode='after')
def validate_values_format(self):
for element in self.values:
if isinstance(element, list):
if len(element) != 2:
raise ValueError(
"Unsupported data type. "
f"Use List[2] = [float, float] for range of value, instead of {element}."
)
elif element[0] > element[1]:
raise ValueError(
f"In range of value {element}, "
"the upper bound should be greater than the lower bound."
)
return self
class RangeConfig(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
valid: RandomConfig = RandomConfig(values=DEFAULT_RANGES_VALUES)
invalid: RandomConfig = RandomConfig()
valid_weights: float = VALID_WEIGHTS
def update(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
class BoundaryConfig(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
has_empty: Optional[bool] = True
has_infnan: Optional[bool] = True
has_scalar: Optional[bool] = True
has_upper_border: Optional[bool] = True
has_lower_border: Optional[bool] = True
_user_set_fields: set = PrivateAttr(default_factory=set)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._user_set_fields = set(kwargs.keys())
@property
def user_set_fields(self):
return self._user_set_fields
class InputDesignConfig(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
name: Optional[str] = None
aclnn_name: Optional[str] = None
type: Optional[str]
required: bool = True
backward: Optional[bool] = True
align_32B: Optional[bool] = None
outlier_values: Optional[List[float]] = None
dtypes: Optional[RandomConfig] = RandomConfig(values=DEFAULT_DTYPES)
ranges: Optional[RangeConfig] = RangeConfig()
shapes: Optional[InputsShape] = InputsShape()
tuple_numbers: Optional[RandomConfig] = RandomConfig(values=[1])
boundary: Optional[BoundaryConfig] = BoundaryConfig()
expected_error_msg: Optional[str] = None
@model_validator(mode="after")
def set_default_ranges_by_type(self):
tensor_like_types = ["tensor", "tensors", "tensor_tuple", "scalar", "scalars", "scalar_tuple"]
if self.type in tensor_like_types and "valid" not in self.ranges.model_fields_set:
self.ranges.valid.values = DefaultCustomDistConfig().default_range_values
self.ranges.valid.random_types = [RandomTypesConfig(name=RandomTypes.ND)]
self.ranges.valid.custom_dist_ratio = "default"
if not self.ranges.invalid.values:
self.ranges.invalid.values = self.ranges.valid.values
return self
@model_validator(mode="after")
def check_extra_params(self):
"""非tensor类的inputs的边界参数不允许设置, 且默认值为None"""
if self.type not in ["tensor", "tensors", "tensor_tuple"] and self.boundary:
for field_name in self.boundary.user_set_fields:
raise ValueError(f"type: '{self.type}' does not support parameters: {field_name}")
return self
@model_validator(mode="after")
def check_type_params(self):
"""检查 type 参数是否合法"""
if self.type not in DEFAULT_TYPES:
raise ValueError(f"type: '{self.type}' is not a valid type")
return self
@model_validator(mode="after")
def check_tuple_numbers_param(self):
"""检查 tuple_numbers 参数是否合法"""
if self.tuple_numbers.values != [1]:
if self.type not in ["tensors", "tensor_tuple", "scalars", "scalar_tuple", "attrs", "attr_tuple"]:
raise ValueError(f"type: '{self.type}' does not support tuple_numbers, please check.")
return self
@model_validator(mode="after")
def check_outlier_values_param(self):
"""检查 outlier_values 参数是否合法"""
if self.outlier_values and len(self.outlier_values) != 2:
raise ValueError(f"outlier_values: '{self.typoutlier_valuese}' should have two values, please check.")
return self
CATCCOS_SHAPE_COLUMNS = ("M", "K", "N", "Transpose A", "Transpose B")
class CatccosShapeInput(BaseModel):
model_config = ConfigDict(extra='forbid')
name: str
values: List[Union[int, str]]
@model_validator(mode="after")
def check_name(self):
if self.name not in CATCCOS_SHAPE_COLUMNS:
raise ValueError(
f"shape_inputs.name must be one of {CATCCOS_SHAPE_COLUMNS}, got {self.name!r}"
)
if not self.values:
raise ValueError(f"shape_inputs {self.name!r} values must not be empty")
return self
class StandardConfig(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
acc: Optional[Union[str, dict]] = "default"
perf: Optional[Union[str, List[float], Dict[str, List[float]]]] = "not_key"
mem: Optional[float] = 1.1
def is_acc_benchmark(self):
if isinstance(self.acc, str):
name = self.acc
else:
name = list(self.acc.keys())[0]
return "benchmark" in name
def is_bm_benchmark(self):
if isinstance(self.acc, str):
name = self.acc
else:
name = list(self.acc.keys())[0]
return "bm" in name
class DesignConfig(BaseModel):
model_config = ConfigDict(extra='forbid', ignored_types=(type(lambda x: x),))
def __init__(self, path):
with open(path, "r") as fout:
config = yaml.safe_load(fout)
super().__init__(**config)
self.adapt_shape_distribution()
name: Optional[str] = None
aclnn_name: Optional[str] = None
triton_name: Optional[str] = None
kernel_name: Optional[str] = None
version: Optional[str] = None
expected_error_msg: Optional[str] = None
api: str = "pytorch"
api_type: str = "function"
aclnn_api_type: str = "aclnn_function"
triton_api_type: str = "triton_function"
fusion_api_type: str = "fusion_function"
dist_api_type: str = "dist_function"
kernel_api_type: str = "kernel_function"
generate: str = "default"
standard: Optional[StandardConfig] = StandardConfig()
backward: bool = False
outputs: Optional[Union[str, int]] = None
inputs: Optional[List[InputDesignConfig]] = []
tensor_input: InputDesignConfig = None
method_inputs: List[InputDesignConfig] = None
dt_config: Optional[Dict[str, Any]] = None
shape_inputs: Optional[List[CatccosShapeInput]] = None
size_distributions: List[InputSize] = None
shape_distributions: Optional[List[List[Union[int, float]]]] = [
[10000000, 0.3],
[100000000, 0.1],
[1000000000, 0.01],
]
dtype_numbers: Optional[int] = DEFAULT_DTYPE_NUMBERS
extra_numbers: Optional[Union[str, int]] = "all"
compute_times: Optional[int] = None
_is_gen_extra: Optional[bool] = False
@property
def is_gen_extra(self):
return self._is_gen_extra
@model_validator(mode="after")
def check_catccos_fields(self):
if self.generate != "catccos":
return self
if not self.dt_config:
raise ValueError("generate=catccos requires dt_config")
if not self.shape_inputs:
raise ValueError("generate=catccos requires shape_inputs")
required_dt = ("kernel_name", "data_type", "device_list", "catccos_root")
missing = [k for k in required_dt if k not in self.dt_config or self.dt_config[k] in (None, "")]
if missing:
raise ValueError(f"dt_config missing required fields: {missing}")
if self.extra_numbers == "all" or (isinstance(self.extra_numbers, int) and self.extra_numbers > 0):
object.__setattr__(self, "extra_numbers", 0)
return self
@model_validator(mode="after")
def check_dtype_numbers_and_extra_numbers_is_valid(self):
if self.dtype_numbers < 0:
raise ValueError("dtype_numbers should be non-negative but is set to negative!")
if isinstance(self.extra_numbers, int) and self.extra_numbers < 0:
raise ValueError("extra_numbers should be a non-negative int or string 'all' but is set to negative!")
if isinstance(self.extra_numbers, str) and self.extra_numbers != 'all':
raise ValueError("extra_numbers should be a non-negative int or string 'all' but is set to other string!")
return self
@is_gen_extra.setter
def is_gen_extra(self, bool_value):
self._is_gen_extra = bool_value
def adapt_shape_distribution(self):
if self.generate == "catccos":
return
all_shapes = []
if self.inputs:
all_shapes.extend(
[
cur_input.shapes
for cur_input in self.inputs
if isinstance(cur_input, InputDesignConfig)
]
)
if isinstance(self.tensor_input, InputDesignConfig):
all_shapes.append(self.tensor_input.shapes)
if self.method_inputs:
all_shapes.extend(
[
cur_input.shapes
for cur_input in self.inputs
if isinstance(cur_input, InputDesignConfig)
]
)
if not all_shapes:
return
total_length = sum(cur_shape.max_length for cur_shape in all_shapes)
ratio = total_length / (MAX_NUMBER_OF_ELEMENTS * len(all_shapes))
for shape_distribution in self.shape_distributions:
shape_distribution[0] = round(shape_distribution[0] * ratio)