blob: ae8ae5c57bad14c64f2a8834cbda19a9eddbe9c3 [file] [log] [blame]
# Copyright (c) 2024, ARM Limited.
# SPDX-License-Identifier: Apache-2.0
import hashlib
import logging
import generator.tosa_utils as gtu
import numpy as np
from tosa.DType import DType
logging.basicConfig()
logger = logging.getLogger("tosa_verif_build_tests")
class TosaRandomGenerator(np.random.Generator):
"""Equivalent to numpy.default_rng, with support for TOSA data types"""
def __init__(self, seed, restrict_range_by_type={}):
"""Create random generator with TOSA type support.
seed: integer seed
restrict_range_by_type: see TosaHashRandomGenerator.__init__()
"""
self._restrict_range_by_type = restrict_range_by_type
self._seed = int(seed)
self._bitgen = np.random.PCG64(self._seed)
super().__init__(self._bitgen)
@property
def seed(self):
return self._seed
@property
def hexSeed(self):
return hex(self._seed)
def dTypeRange(self, dtype, high_inclusive=False):
"""Returns range tuple for given dtype.
dtype: DType
high_inclusive: True for inclusive high values
Returns: dtype value range boundaries tuple (low, high)
The high boundary is excluded in the range unless high_inclusive is True
"""
if dtype in self._restrict_range_by_type:
rng = self._restrict_range_by_type[dtype]
elif dtype == DType.BOOL:
rng = (0, 2)
elif dtype == DType.UINT8:
rng = (0, 256)
elif dtype == DType.UINT16:
rng = (0, 65536)
elif dtype == DType.INT4:
# TOSA specific INT4 weight range from -7 to 7
rng = (-7, 8)
elif dtype == DType.INT8:
rng = (-128, 128)
elif dtype == DType.INT16:
rng = (-32768, 32768)
elif dtype == DType.INT32:
rng = (-(1 << 31), (1 << 31))
elif dtype == DType.INT48:
rng = (-(1 << 47), (1 << 47))
else:
# Float types and SHAPE should be in _restrict_range_by_type dict
raise Exception("Unknown supported dtype: {}".format(dtype))
if dtype in (DType.FP16, DType.BF16, DType.FP32, DType.FP8E4M3, DType.FP8E5M2):
# Floating point - range is always inclusive
return rng
else:
# Integer
if not high_inclusive:
# Exclusive high: low <= range < high
return rng
else:
# Inclusive range: low <= range <= high
return (rng[0], rng[1] - 1)
def randInt(self, low=0, high=256):
return np.int32(self.integers(low=low, high=high, size=1))[0]
def randNumberDType(self, dtype):
low, high = self.dTypeRange(dtype)
if dtype == DType.FP32:
return np.float32(self.uniform(low=low, high=high))
elif dtype == DType.FP16:
return np.float16(self.uniform(low=low, high=high))
elif dtype == DType.BF16:
rand_f32 = np.float32(self.uniform(low=low, high=high))
return gtu.vect_f32_to_bf16(rand_f32)
elif dtype == DType.FP8E4M3:
rand_f32 = np.float32(self.uniform(low=low, high=high))
return gtu.vect_f32_to_fp8e4m3(rand_f32)
elif dtype == DType.FP8E5M2:
rand_f32 = np.float32(self.uniform(low=low, high=high))
return gtu.vect_f32_to_fp8e5m2(rand_f32)
elif dtype == DType.BOOL:
return self.choice([False, True])
elif dtype == DType.INT48 or dtype == DType.SHAPE:
# Special size
return np.int64(self.integers(low, high, size=1))[0]
return np.int32(self.integers(low, high, size=1))[0]
def randTensor(self, shape, dtype, data_range=None):
if data_range is None:
low, high = self.dTypeRange(dtype)
else:
low, high = data_range
if dtype == DType.BOOL:
return np.bool_(self.choice(a=[False, True], size=shape))
elif dtype == DType.INT4:
return np.int8(self.integers(low=low, high=high, size=shape))
elif dtype == DType.INT8:
return np.int8(self.integers(low=low, high=high, size=shape))
elif dtype == DType.UINT8:
return np.uint8(self.integers(low=low, high=high, size=shape))
elif dtype == DType.INT16:
return np.int16(self.integers(low=low, high=high, size=shape))
elif dtype == DType.UINT16:
return np.uint16(self.integers(low=low, high=high, size=shape))
elif dtype in (DType.INT48, DType.SHAPE):
return np.int64(self.integers(low=low, high=high, size=shape))
elif dtype in (
DType.FP16,
DType.BF16,
DType.FP32,
DType.FP8E4M3,
DType.FP8E5M2,
):
f_tensor = self.uniform(low=low, high=high, size=shape)
if dtype == DType.FP16:
return np.float16(f_tensor)
else:
f32_tensor = np.float32(f_tensor)
if dtype == DType.BF16:
# Floor the last 16 bits of each f32 value
return np.float32(gtu.vect_f32_to_bf16(f32_tensor))
elif dtype == DType.FP8E4M3:
return np.float32(gtu.vect_f32_to_fp8e4m3(f32_tensor))
elif dtype == DType.FP8E5M2:
return np.float32(gtu.vect_f32_to_fp8e5m2(f32_tensor))
else:
return f32_tensor
else:
# All other integer types
return np.int32(self.integers(low=low, high=high, size=shape))
class TosaHashRandomGenerator(TosaRandomGenerator):
"""Hash seeded TOSA random number generator."""
def __init__(self, seed, seed_list, restrict_range_by_type={}):
"""Create TOSA random generator seeding it with a hashable list.
seed: integer starting seed
seed_list: list of hashable items to add to starting seed
restrict_range_by_type: dictionary of DTypes with (low, high) range tuples
This must contain entries for SHAPE and all Floating Point data types.
NOTE: For integers, the high value must be the exclusive value
"""
# Convert seed_list to strings
seed_strings_list = [str(s) for s in seed_list]
# Create a single string and create hash
self._seed_string = "__".join(seed_strings_list)
self._hash = hashlib.md5(bytes(self._seed_string, "utf-8"))
# Add the hash value to the given seed
seed += int(self._hash.hexdigest(), 16)
logger.debug(f"Seed={seed} Seed string={self._seed_string}")
super().__init__(seed, restrict_range_by_type)