| # 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) |