2 ''''export SCRIPT_PATH="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" # '''
3 ''''export PY_PATH=${SCRIPT_PATH}/venv/bin/python # '''
4 ''''test -f ${PY_PATH} && exec ${PY_PATH} "$0" "$@" # '''
5 ''''echo "Error: Virtual environment not found. Please run 'one-prepare-venv' command." # '''
8 # Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved
10 # Licensed under the Apache License, Version 2.0 (the "License");
11 # you may not use this file except in compliance with the License.
12 # You may obtain a copy of the License at
14 # http://www.apache.org/licenses/LICENSE-2.0
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS,
18 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 # See the License for the specific language governing permissions and
20 # limitations under the License.
33 from shutil import copyfile
34 from pathlib import Path
36 from visqlib.Palette import YLORRD9Palette
37 from visqlib.QErrorComputer import MPEIRComputer, MSEComputer, TAEComputer
38 from visqlib.Util import valid_attr, pretty_float
39 from visqlib.DotBuilder import DotBuilder
43 parser = argparse.ArgumentParser(
44 description='Command line tool to visualize layer-wise quantization errors')
49 help="Path to the fp32 circle model.",
55 help="Path to the quantized circle model.",
62 "Path to the data used for inference. Random data will be used if this option is not given.",
67 help="Path to the output json file (qerror metric = MPEIR).",
72 help="Path to the output json file (qerror metric = MSE).",
77 help="Path to the output json file (qerror metric = TAE).",
80 "--dump_dot_graph", action="store_true", help="Dump dot graph.", required=False)
85 help="Batch size to process large datasets.",
91 def _verify_args(args):
92 """Verify the given arguments"""
94 valid_outputs = ['mpeir_output', 'mse_output', 'tae_output']
96 # Check if at least one output option is given
98 for output_name in valid_outputs:
99 if valid_attr(args, output_name):
103 raise RuntimeError("At least one output should be given.")
106 def _run_dalgona(model, data, analysis, save_dir):
107 dir_path = Path(__file__).parent.resolve()
108 dalgona_path = os.path.join(dir_path, 'dalgona')
110 cmd += ['--input_model', str(model)]
111 cmd += ['--analysis', str(analysis)]
113 cmd += ['--input_data', str(data)]
114 cmd += ['--analysis_args', str(save_dir)]
117 subprocess.run(cmd, capture_output=True, check=True)
118 except subprocess.CalledProcessError as e:
119 print('Error raised while running the below command')
125 # Generate h5 file that contains a dataset of a single batch
126 # This is for batch execution of visq
127 def gen_batch_h5(inputs_data, inputs_path):
129 output_path = inputs_path + "/inputs.h5"
130 h5_file = h5.File(output_path, 'w')
131 group = h5_file.create_group("value")
132 group.attrs['desc'] = "Input data"
134 for i in range(len(inputs_data)):
135 sample = group.create_group(str(i))
136 for j in range(len(inputs_data[i])):
137 sample.create_dataset(str(j), data=inputs_data[i][j])
143 # Aggregate intermediate results for a given data
144 def advance_on_data(fp32_model, fq_model, data, computers):
146 curr_dir = Path(__file__).parent.resolve()
147 dump_fp32_py = curr_dir / 'visqlib' / 'DumpFP32FM.py'
148 dump_fq_py = curr_dir / 'visqlib' / 'DumpFakeQuantFM.py'
150 with tempfile.TemporaryDirectory() as fp32_dir, \
151 tempfile.TemporaryDirectory() as fq_dir:
153 _run_dalgona(fp32_model, data, dump_fp32_py, fp32_dir)
154 copyfile(fp32_dir + '/tensors.txt', fq_dir + '/tensors.txt')
155 _run_dalgona(fq_model, data, dump_fq_py, fq_dir)
157 for metric_key in computers:
158 computers[metric_key][0].advance_on(fp32_dir, fq_dir)
161 def _run_batch(fp32_model, fq_model, data, computers, batch_size):
162 with tempfile.TemporaryDirectory() as inputs_dir:
163 with h5.File(data, 'r') as f:
167 for data_index in dataset:
169 for input_index in dataset[data_index]:
170 d = dataset[data_index][input_index][:]
171 cur_inputs.append(np.array(d, np.float32))
173 inputs.append(cur_inputs)
174 if len(inputs) >= batch_size:
175 input_path = gen_batch_h5(inputs, inputs_dir)
176 advance_on_data(fp32_model, fq_model, input_path, computers)
180 input_path = gen_batch_h5(inputs, inputs_dir)
181 advance_on_data(fp32_model, fq_model, input_path, computers)
184 def _fake_quantize(input_model, output_model):
185 dir_path = Path(__file__).parent.resolve()
186 circle_quantizer_path = os.path.join(dir_path, 'circle-quantizer')
187 cmd = [circle_quantizer_path]
188 cmd += ['--fake_quantize']
189 cmd += [str(input_model)]
190 cmd += [str(output_model)]
193 subprocess.run(cmd, check=True)
194 except subprocess.CalledProcessError as e:
195 print('Error raised while running the below command')
201 # Recursively visit items and check if there is Infinity or NaN
202 def _check_float(item):
203 if isinstance(item, dict):
204 for v in item.values():
206 if isinstance(item, list):
209 if isinstance(item, float):
210 if item == -float('inf') or item == float('inf'):
211 raise RuntimeError('Infinite value detected. Value must be float')
213 raise RuntimeError('NaN value detected. Value must be float')
216 def _build_json(model, metric, colorscheme, error):
219 # colorscheme: list ['b': begin, 'e': end, 'c':color]
220 # error: dict {tensor_name:error}
223 meta["model"] = model
224 meta["metric"] = metric
225 meta["colorscheme"] = pretty_float(colorscheme)
227 result["meta"] = meta
228 # Why list? To support multiple subgraphs
229 result["error"] = [pretty_float(error)]
232 _check_float(meta["colorscheme"])
233 _check_float(result["error"])
237 def _save_dot(circle_path: str, dot_path: str, metric: str, colors: list, qerror: dict):
238 # circle_path: Path to the circle model (required to build graph)
239 # dot_path: Path to the output dot file
240 # metric: Metric name (ex: MPEIR, MSE)
241 # colors: list [{'b': begin, 'e': end, 'c':color}, ..]
242 # qerror: dict {tensor_name (str) -> qerror (float)}
243 builder = DotBuilder(
244 circle_path=circle_path, dot_path=dot_path, metric=metric, colors=colors)
249 def run_on_data_batchwise(fp32_model, q_model, data, dump_dot_graph, computers,
252 with tempfile.TemporaryDirectory() as model_dir:
253 fq_model = model_dir + '/fq_model.circle'
255 # Step 1. Fake quantize quantized circle model
256 _fake_quantize(q_model, fq_model)
258 # process the whole dataset batch by batch
259 _run_batch(fp32_model, fq_model, data, computers, batch_size)
261 #compute the final results
262 for metric_key in computers:
263 cur_computer = computers[metric_key][0]
264 output = computers[metric_key][1]
265 if metric_key == 'MPEIR':
266 qerror_map = cur_computer.get_final_result()
269 elif metric_key == 'MSE' or metric_key == 'TAE':
270 qerror_map, q_min, q_max = cur_computer.get_final_result()
272 palette = YLORRD9Palette(qerror_min=q_min, qerror_max=q_max)
273 result = _build_json(
275 model=Path(fp32_model).name,
276 colorscheme=palette.colorscheme(),
278 with open(output, "w") as f:
283 circle_path=fp32_model,
284 dot_path=output + '.dot',
286 colors=palette.colorscheme(),
290 def run_on_data(fp32_model, q_model, data, dump_dot_graph, computers):
291 curr_dir = Path(__file__).parent.resolve()
292 dump_fp32_py = curr_dir / 'visqlib' / 'DumpFP32FM.py'
293 dump_fq_py = curr_dir / 'visqlib' / 'DumpFakeQuantFM.py'
295 with tempfile.TemporaryDirectory() as model_dir, \
296 tempfile.TemporaryDirectory() as fp32_dir, \
297 tempfile.TemporaryDirectory() as fq_dir:
298 fq_model = model_dir + '/fq_model.circle'
300 # Step 1. Fake quantize quantized circle model
301 _fake_quantize(q_model, fq_model)
303 # Step 2. Run dalgona to dump intermediate FMs in FP32 model
304 _run_dalgona(fp32_model, data, dump_fp32_py, fp32_dir)
306 # Copy list of dumped tensors
307 copyfile(fp32_dir + '/tensors.txt', fq_dir + '/tensors.txt')
309 # Step 3. Run dalgona to dump intermediate FMs in fq model
310 _run_dalgona(fq_model, data, dump_fq_py, fq_dir)
312 # Step 4. Read results and compute qerror
313 for metric_key in computers:
314 cur_computer = computers[metric_key][0]
315 output = computers[metric_key][1]
316 cur_computer.advance_on(fp32_dir, fq_dir)
317 if metric_key == 'MPEIR':
318 qerror_map = cur_computer.get_final_result()
321 elif metric_key == 'MSE' or metric_key == 'TAE':
322 qerror_map, q_min, q_max = cur_computer.get_final_result()
324 palette = YLORRD9Palette(qerror_min=q_min, qerror_max=q_max)
325 result = _build_json(
327 model=Path(fp32_model).name,
328 colorscheme=palette.colorscheme(),
330 with open(output, "w") as f:
335 circle_path=fp32_model,
336 dot_path=output + '.dot',
338 colors=palette.colorscheme(),
344 parser = _get_parser()
345 args = parser.parse_args()
348 fp32_model = args.fp32_circle
349 q_model = args.q_circle
351 if valid_attr(args, 'data'):
353 dump_dot_graph = args.dump_dot_graph
355 if valid_attr(args, 'batch_size'):
356 batch_size = args.batch_size
359 if args.mpeir_output:
360 computers['MPEIR'] = (MPEIRComputer(None, None), args.mpeir_output)
363 computers['MSE'] = (MSEComputer(None, None), args.mse_output)
366 computers['TAE'] = (TAEComputer(None, None), args.tae_output)
368 if batch_size == None:
369 run_on_data(fp32_model, q_model, data, dump_dot_graph, computers)
371 run_on_data_batchwise(fp32_model, q_model, data, dump_dot_graph, computers,
375 if __name__ == '__main__':
378 except Exception as e:
379 prog_name = os.path.basename(__file__)
380 print(f"{prog_name}: {type(e).__name__}: " + str(e), file=sys.stderr)