1 # Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
19 from pathlib import Path
20 from visqlib.Util import to_filename
24 def __init__(self, fp32_dir, fq_dir):
25 self._fp32_dir = fp32_dir
27 self.qerror_map = dict()
28 self._num_processed_data = 0
30 def collect_data_path(self, fp32_dir, fq_dir):
31 # Assumption: FM data are saved as follows
42 self._num_data = len(list(filter(os.path.isdir, glob.glob(fp32_dir + '/*'))))
43 if self._num_data != len(list(filter(os.path.isdir, glob.glob(fq_dir + '/*')))):
44 raise RuntimeError("Number of data mistmatches")
46 self._num_processed_data += self._num_data
48 self._filename_to_tensor = dict()
49 with open(Path(fp32_dir) / 'tensors.txt') as f:
50 tensors = set([line.rstrip() for line in f])
51 for tensor in tensors:
52 # Check if filename is unique
53 # Fix name finding logic unless
54 assert to_filename(tensor) not in self._filename_to_tensor
55 self._filename_to_tensor[to_filename(tensor)] = tensor
57 # Save paths to fp32 data and fq data for each tensor
60 # <tensor_name>: (fp32_path, fq_path),
61 # <tensor_name>: (fp32_path, fq_path),
65 for data_idx in range(self._num_data):
66 fp32_results = glob.glob(fp32_dir + '/' + str(data_idx) + '/*.npy')
67 for fp32_data_path in fp32_results:
68 fp32_path = Path(fp32_data_path)
69 fq_data_path = fq_dir + '/' + str(data_idx) + '/' + fp32_path.with_suffix(
71 fq_path = Path(fq_data_path)
72 filename = fp32_path.stem
73 tensor_name = self._filename_to_tensor[filename]
75 # Only save the tensors which have both fp32 data and fq data
76 if fq_path.is_file() and fp32_path.is_file():
77 if tensor_name in data_paths:
78 data_paths[tensor_name].append((fp32_data_path, fq_data_path))
80 data_paths[tensor_name] = [(fp32_data_path, fq_data_path)]
85 '''Return qerror map (dict: tensor_name(string) -> qerror(float)).'''
86 raise NotImplementedError # Child must implement this
89 class MPEIRComputer(QErrorComputer):
90 def __init__(self, fp32_dir, fq_dir):
91 super().__init__(fp32_dir, fq_dir)
93 # Incrementally compute Qerror while traversing all data in fp32_dir and fq_dir
94 def advance_on(self, fp32_dir, fq_dir):
95 data_paths = self.collect_data_path(fp32_dir, fq_dir)
96 for tensor_name, data_path in data_paths.items():
97 for (fp32_data_path, fq_data_path) in data_path:
98 fp32_data = np.load(fp32_data_path)
99 fq_data = np.load(fq_data_path)
101 diff = np.absolute(fp32_data - fq_data).reshape(-1)
103 fp32_min = np.min(fp32_data.reshape(-1))
104 fp32_max = np.max(fp32_data.reshape(-1))
106 # Peak Error-to-Interval Ratio (PEIR)
107 # NOTE: PEIR is an analogue of PSNR (Peak Signal to Noise Ratio)
108 PEAK_ERROR = np.max(diff)
109 INTERVAL = fp32_max - fp32_min
111 # If INTERVAL is 0, PEIR becomes NaN.
112 # To prevent this, relaxed PEIR with epsilon(10^(-6)) is used.
113 rPEIR = PEAK_ERROR / (INTERVAL + 0.000001)
115 if tensor_name in self.qerror_map:
116 self.qerror_map[tensor_name] += rPEIR
118 self.qerror_map[tensor_name] = rPEIR
120 def get_final_result(self):
122 for tensor_name, acc in self.qerror_map.items():
123 qerror_map[tensor_name] = acc / self._num_processed_data
128 self.advance_on(self._fp32_dir, self._fq_dir)
129 return self.get_final_result()
132 class MSEComputer(QErrorComputer):
133 def __init__(self, fp32_dir, fq_dir):
134 super().__init__(fp32_dir, fq_dir)
135 self.qerror_min = float('inf')
136 self.qerror_max = -self.qerror_min
138 # Incrementally compute Qerror while traversing all data in fp32_dir and fq_dir
139 def advance_on(self, fp32_dir, fq_dir):
140 data_paths = self.collect_data_path(fp32_dir, fq_dir)
141 for tensor_name, data_path in data_paths.items():
142 for (fp32_data_path, fq_data_path) in data_path:
143 fp32_data = np.load(fp32_data_path)
144 fq_data = np.load(fq_data_path)
146 MSE = np.square(fp32_data - fq_data).mean()
148 if tensor_name in self.qerror_map:
149 self.qerror_map[tensor_name] += MSE
151 self.qerror_map[tensor_name] = MSE
153 self.qerror_min = min(MSE, self.qerror_min)
154 self.qerror_max = max(MSE, self.qerror_max)
156 def get_final_result(self):
158 for tensor_name, acc in self.qerror_map.items():
159 qerror_map[tensor_name] = acc / self._num_processed_data
161 return qerror_map, self.qerror_min, self.qerror_max
164 self.advance_on(self._fp32_dir, self._fq_dir)
165 return self.get_final_result()
168 class TAEComputer(QErrorComputer): #total absolute error
169 def __init__(self, fp32_dir, fq_dir):
170 super().__init__(fp32_dir, fq_dir)
172 self.qerror_min = float('inf')
173 self.qerror_max = -self.qerror_min
175 def advance_on(self, fp32_dir, fq_dir):
176 data_paths = self.collect_data_path(fp32_dir, fq_dir)
177 for tensor_name, data_path in data_paths.items():
178 for (fp32_data_path, fq_data_path) in data_path:
179 fp32_data = np.load(fp32_data_path)
180 fq_data = np.load(fq_data_path)
182 total_error = np.sum(np.abs(fp32_data - fq_data))
184 if tensor_name in self.qerror_map:
185 self.qerror_map[tensor_name] += total_error
187 self.qerror_map[tensor_name] = total_error
189 self.qerror_min = min(total_error, self.qerror_min)
190 self.qerror_max = max(total_error, self.qerror_max)
192 def get_final_result(self):
194 for tensor_name, acc in self.qerror_map.items():
195 qerror_map[tensor_name] = acc / self._num_processed_data
196 return qerror_map, self.qerror_min, self.qerror_max
199 self.advance_on(self._fp32_dir, self._fq_dir)
200 return self.get_final_result()