MNN/tools/script/analyzeQuantErrors.py

174 lines
6.0 KiB
Python

import json
import os
import re
import math
import shutil
_SPACE_PATTERN = re.compile(r'\s+')
def _load_tensor(tensor_file):
text = open(tensor_file).read().strip()
parts = [v for v in _SPACE_PATTERN.split(text)]
numbers_per_line = len(parts)
index = text.find('\n')
if index != -1:
numbers_per_line = len(_SPACE_PATTERN.split(text[:index].strip()))
return [float(v) for v in parts], numbers_per_line
def _normalize(a):
sum = 0
for v in a:
sum += v * v
sum = math.sqrt(sum)
if not sum:
return a
return [v/sum for v in a]
def norm(a):
sum = 0
for v in a:
sum += v * v
return math.sqrt(sum)
def _normalized_distance(a, b):
assert len(a) == len(b)
a = _normalize(a)
b = _normalize(b)
sum = 0
for i, va in enumerate(a):
vb = b[i]
diff = va - vb
sum += diff * diff
return math.sqrt(sum)
def _distance(a, b):
assert len(a) == len(b)
# a = _normalize(a)
# b = _normalize(b)
sum = 0
for i, va in enumerate(a):
vb = b[i]
diff = va - vb
sum += diff * diff
return math.sqrt(sum)
def _cos_distance(a, b):
norm_a = norm(a)
norm_b = norm(b)
sum = 0
for i, va in enumerate(a):
vb = b[i]
sum += va * vb
return 1 - sum / (norm_a * norm_b)
class Analyzer(object):
# @param: scale_file - 量化时生成的scale_file
# @param: normal_output - 运行正常模型时生成的tensor dump目录
# @param: quant_output - 运行量化模型时生成的tensor dump目录
def __init__(self, scale_file, normal_output, quant_output):
self.scale_file = scale_file
self.normal_output = normal_output
self.quant_output = quant_output
self.scales = self._load_scale()
def _load_scale(self):
return json.loads(open(self.scale_file).read())
def execute(self):
dequant_output = self.quant_output + '-dequant'
shutil.rmtree(dequant_output, ignore_errors=True)
os.makedirs(dequant_output, exist_ok=True)
for op in self.scales:
if not op['outputs']:
continue
outputs = op['outputs']
for i, output in enumerate(outputs):
if not output['scales']:
continue
file_name = '%s_%d' % (op['name'].replace('/', '_'), i)
normal_tensor_file = os.path.join(
self.normal_output, file_name)
quant_tensor_file = os.path.join(self.quant_output, file_name)
if not os.path.exists(normal_tensor_file):
continue
if not os.path.exists(quant_tensor_file):
continue
normal_tensor, numbers_per_line = _load_tensor(normal_tensor_file)
quant_tensor, _ = _load_tensor(quant_tensor_file)
if len(normal_tensor) != len(quant_tensor):
print('error: normal tensor file: %s count: %d' % (normal_tensor_file, len(normal_tensor)))
print('error: quant tensor file: %s count: %d' % (quant_tensor_file, len(quant_tensor)))
sys.exit(1)
scales = output['scales']
assert len(normal_tensor) % len(scales) == 0
plane_size = len(normal_tensor) // len(scales)
dequant_tensor = []
non_zero_count = 0
max_value_count = 0
min_value_count = 0
max_value = 127
min_value = -127
for i, scale in enumerate(scales):
plane = quant_tensor[i*plane_size:(i+1)*plane_size]
for v in plane:
dequant_tensor.append(v * scale)
if v:
non_zero_count += 1
if v == max_value:
max_value_count += 1
if v == min_value:
min_value_count += 1
print(file_name)
d = _distance(normal_tensor, dequant_tensor)
normalized_d = _normalized_distance(normal_tensor, dequant_tensor)
cos_d = _cos_distance(normal_tensor, dequant_tensor)
print('max rate: %.06f%%' % (max_value_count / non_zero_count * 100))
print('min rate: %.06f%%' % (min_value_count / non_zero_count * 100))
print('norm of normal: %.06f' % (norm(normal_tensor)))
print('norm of quant: %.06f' % (norm(dequant_tensor)))
print('cos distance: %.08f' % (cos_d))
print('normalized distance: %.08f' % (normalized_d))
print('distance: %.08f\n' % (d))
# Output dequant tensor
lines = []
col = numbers_per_line
row = len(dequant_tensor) // col
assert len(dequant_tensor) % col == 0
for i in range(row):
parts = []
for j in range(col):
parts.append(('%f' % dequant_tensor[col*i+j]).rstrip('0').rstrip('.'))
lines.append('\t'.join(parts))
dequant_tensor_file = os.path.join(dequant_output, file_name)
open(dequant_tensor_file, 'w').write('\n'.join(lines))
if __name__ == '__main__':
import sys
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--scale-file",
required=True, help="量化时输出的scale文件")
parser.add_argument("-n", "--normal-output",
required=True, help="运行正常模型时生成的tensor dump目录")
parser.add_argument("-q", "--quant-output", required=True,
help="运行量化模型时生成的tensor dump目录")
args = parser.parse_args()
Analyzer(args.scale_file, args.normal_output, args.quant_output).execute()