import numpy as np
__golden__ = {"kernel": {"dynamic_block_quant": "dynamic_block_quant_golden"}}
DATA_TYPE_INT_TO_STR = {
0: "float32",
1: "float16",
2: "int8",
3: "int32",
4: "uint8",
6: "int16",
7: "uint16",
8: "uint32",
9: "int64",
10: "uint64",
11: "double",
12: "bool",
16: "complex64",
17: "complex128",
18: "qint8",
19: "qint16",
20: "qint32",
21: "quint8",
22: "quint16",
23: "resource",
25: "dual",
26: "variant",
27: "bfloat16",
29: "int4",
30: "uint1",
31: "int2",
32: "uint2",
33: "complex32",
34: "hifloat8",
35: "float8_e5m2",
36: "float8_e4m3fn",
37: "float8_e8m0",
40: "float4_e2m1",
41: "float4_e1m2",
42: "hifloat4",
}
def _get_dtype_range(dt):
if "bfloat16" in str(dt):
return -float.fromhex("0x1.FEp127"), float.fromhex("0x1.FEp127")
if "uint4" in str(dt):
return 0, 15
if "int4" in str(dt):
return -8, 7
if "bool" in str(dt):
return 0, 1
if "float4_e2m1" in str(dt):
return -float.fromhex("0x1.8p2"), float.fromhex("0x1.8p2")
if "float4_e1m2" in str(dt):
return -float.fromhex("0x1.Cp0"), float.fromhex("0x1.Cp0")
if "float8_e8m0" in str(dt):
return float.fromhex("0x1.p-127"), float.fromhex("0x1.p127")
if "float8_e5m2" in str(dt):
return -float.fromhex("0x1.Cp15"), float.fromhex("0x1.Cp15")
if "float8_e4m3fn" in str(dt):
return -float.fromhex("0x1.Cp8"), float.fromhex("0x1.Cp8")
if "hifloat8" in str(dt):
return -float.fromhex("0x1.p15"), float.fromhex("0x1.p15")
if "complex32" in str(dt):
dt = "float16"
numpy_dtype = np.dtype(dt)
if numpy_dtype.kind in "iu":
numpy_info = np.iinfo(numpy_dtype)
else:
numpy_info = np.finfo(numpy_dtype)
return numpy_info.min, numpy_info.max
def dynamic_block_quant_golden(
x,
*,
min_scale=0.0,
round_mode="rint",
dst_type=35,
row_block_size=1,
col_block_size=128,
dst_type_max=0.0,
**kwargs,
):
"""
Golden function for dynamic_block_quant.
All the parameters (names and order) follow @dynamic_block_quant_def.cpp without outputs.
All the input Tensors are numpy.ndarray.
Args:
**kwargs: {input,output}_{dtypes,ori_shapes,formats,ori_formats},
full_soc_version, short_soc_version, testcase_name
Returns:
Output tensor
"""
def replace_inf_nan_with_zero(x):
x_cleaned = x.copy()
mask = np.isinf(x) | np.isnan(x)
x_cleaned[mask] = 0
mask_tensor = np.zeros_like(x, dtype=int)
mask_tensor[mask] = 1
return x_cleaned, mask_tensor
def block_max_with_padding(x, row_block_size, col_block_size):
rows, cols = x.shape
pad_rows = (row_block_size - rows % row_block_size) % row_block_size
pad_cols = (col_block_size - cols % col_block_size) % col_block_size
x_padded = np.pad(
x, ((0, pad_rows), (0, pad_cols)), mode="constant", constant_values=0
)
padded_rows, padded_cols = x_padded.shape
row_blocks = padded_rows // row_block_size
col_blocks = padded_cols // col_block_size
result = np.zeros((row_blocks, col_blocks))
for i in range(row_blocks):
for j in range(col_blocks):
block = x_padded[
i * row_block_size : (i + 1) * row_block_size,
j * col_block_size : (j + 1) * col_block_size,
]
result[i, j] = np.max(block)
return result
dst_type_str = DATA_TYPE_INT_TO_STR[dst_type]
max_value = 0
if dst_type_str == "hifloat8":
max_value = pow(2, 15)
elif dst_type_str == "float8_e5m2":
max_value = (2 - pow(2, -2)) * pow(2, 15)
elif dst_type_str == "float8_e4m3fn":
max_value = (2 - pow(2, -2)) * pow(2, 8)
x_cleaned, mask_tensor = replace_inf_nan_with_zero(x)
x_abs = np.abs(x_cleaned)
block_max = block_max_with_padding(x_abs, row_block_size, col_block_size)
block_max_f32 = block_max.astype(np.float32)
if min_scale != 0:
scale = np.minimum(block_max_f32 / max_value, 1 / min_scale)
else:
scale = block_max_f32 / max_value
scale_expanded = np.zeros_like(x_cleaned).astype(np.float32)
for i in range(scale.shape[0]):
for j in range(scale.shape[1]):
scale_expanded[
i * row_block_size : (i + 1) * row_block_size,
j * col_block_size : (j + 1) * col_block_size,
] = scale[i, j]
x_f32 = x_cleaned.astype(np.float32)
out_f32 = x_f32 / scale_expanded
out_f32[mask_tensor == 1] = x[mask_tensor == 1]
max_norm = _get_dtype_range(dst_type_str)[1]
np.clip(out_f32, a_min=-max_norm, a_max=max_norm, out=out_f32)
output_scale = scale.astype("float32")
round_data = np.round(out_f32, 8)
round_data = np.nan_to_num(round_data, nan=0.0, copy=False)
if dst_type == 35:
from ml_dtypes import float8_e5m2
round_data = round_data.astype(float8_e5m2, copy=False)
elif dst_type == 36:
from ml_dtypes import float8_e4m3fn
round_data = round_data.astype(float8_e4m3fn, copy=False)
elif dst_type == 34:
from en_dtypes import hifloat8
round_data = round_data.astype(hifloat8, copy=False)
return round_data, output_scale