Coverage for mlprodict/testing/test_utils/quantized_tensor.py: 94%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2@file
3@brief Initializes a quantized tensor from float values.
4"""
5import numpy
6from ...npy.xop import loadop
7from ...onnxrt import OnnxInference
10class QuantizedTensor:
11 """
12 Instantiates a quantized tensor (uint8) from a float tensor.
14 :param data: array
15 :param scale: scale if data.dtype is float32, None otherwise
16 :param zero_point: zero_point if data.dtype is float32, None otherwise
17 """
19 def __init__(self, data, scale=None, zero_point=None):
20 "constructor"
21 if data.dtype == numpy.float32:
22 if scale is not None or zero_point is not None:
23 raise ValueError( # pragma: no cover
24 "scale and zero_point are ignored.")
25 self._init(data)
26 elif data.dtype == numpy.uint8:
27 if scale is None or zero_point is None:
28 raise ValueError( # pragma: no cover
29 "scale and zero_point must be specified.")
30 self.quantized_ = data
31 self.scale_ = numpy.float32(scale)
32 self.zero_point_ = numpy.uint8(zero_point)
34 def _init(self, data):
35 "Initialization when dtype is float32."
36 rav = data.flatten().astype(numpy.float32)
37 mini = min(rav.min(), numpy.float32(0))
38 maxi = max(rav.max(), numpy.float32(0))
40 info = numpy.iinfo(numpy.uint8)
41 qmin = numpy.float32(info.min)
42 qmax = numpy.float32(info.max)
44 self.scale_ = (maxi - mini) / (qmax - qmin)
45 initial_zero_point = qmin - mini / self.scale_
46 self.zero_point_ = numpy.uint8(numpy.round(
47 max(qmin, min(qmax, initial_zero_point))))
49 self.quantized_ = numpy.empty(data.size, dtype=numpy.uint8)
50 for i in range(0, data.size):
51 clamped_val = numpy.float32(
52 max(qmin, min(qmax, numpy.round(data[i] / self.scale_) + self.zero_point_)))
53 self.quantized_[i] = numpy.uint8(clamped_val)
55 if self.quantized_.dtype != numpy.uint8:
56 raise TypeError( # pragma: no cover
57 "dtype={} not uint8".format(self.quantized_.dtype))
60class QuantizedBiasTensor:
61 """
62 Instantiates a quantized tensor (uint8) with bias
63 from a float tensor.
65 :param data: array
66 :param X_or_scale: a @see cl QuantizedTensor or a float
67 :param zero_point: a @see cl QuantizedTensor or or None
68 """
70 def __init__(self, data, X_or_scale, W: QuantizedTensor = None):
71 if W is None:
72 self.quantized_ = data
73 self.scale_ = numpy.float32(X_or_scale)
74 else:
75 self.scale_ = X_or_scale.scale_ * W.scale_
77 self.quantized_ = numpy.empty(data.size(), dtype=numpy.int32)
78 for i in range(0, data.size()):
79 self.quantized_[i] = numpy.int32(
80 numpy.floor(data[i] / (X_or_scale.scale_ * W.scale_)))
81 if self.quantized_.dtype != numpy.int32:
82 raise TypeError( # pragma: no cover
83 "dtype={} not int32".format(self.quantized_.dtype))
86def test_qlinear_conv(x: QuantizedTensor, x_shape,
87 w: QuantizedTensor, w_shape,
88 b: QuantizedBiasTensor,
89 y: QuantizedTensor, y_shape,
90 opset=None, runtime='python',
91 pads=None, strides=None, group=None):
92 """
93 Checks a runtime for operator `QLinearConv`.
95 :param x: @see cl QuantizedTensor
96 :param x_shape: shape of X
97 :param w: @see cl QuantizedTensor
98 :param w_shape: shape of W
99 :param b: @see cl QuantizedBiasTensor or None
100 :param y: expected output, @see cl QuantizedTensor or None
101 :param y_shape: shape of Y
102 :param opset: desired onnx opset
103 :param runtime: runtime for @see cl OnnxInference
104 :param pads: optional parameter for operator `QLinearConv`
105 :param strides: optional parameter for operator `QLinearConv`
106 :param group: optional paramerer for operator `QLinearConv`
107 """
108 OnnxQLinearConv = loadop('QLinearConv')
110 if opset is None:
111 from ... import __max_supported_opset__
112 opset = __max_supported_opset__
114 kwargs = {}
115 if pads is not None:
116 kwargs['pads'] = pads
117 if strides is not None:
118 kwargs['strides'] = strides
119 if group is not None:
120 kwargs['group'] = group
122 if b is None:
123 inputs_list = [
124 'x', 'x_scale', 'x_zero_point', 'w', 'w_scale', 'w_zero_point',
125 'y_scale', 'y_zero_point']
126 inputs = {'x': x.quantized_.reshape(x_shape),
127 'x_scale': x.scale_, 'x_zero_point': x.zero_point_,
128 'w': w.quantized_.reshape(w_shape),
129 'w_scale': w.scale_, 'w_zero_point': w.zero_point_,
130 'y_scale': y.scale_, 'y_zero_point': y.zero_point_}
131 else:
132 inputs_list = [
133 'x', 'x_scale', 'x_zero_point', 'w', 'w_scale', 'w_zero_point',
134 'y_scale', 'y_zero_point', 'b']
135 inputs = {'x': x.quantized_.reshape(x_shape),
136 'x_scale': x.scale_, 'x_zero_point': x.zero_point_,
137 'w': w.quantized_.reshape(w_shape),
138 'w_scale': w.scale_, 'w_zero_point': w.zero_point_,
139 'y_scale': y.scale_, 'y_zero_point': y.zero_point_,
140 'b': b.quantized_}
142 updated = {}
143 for k in inputs: # pylint: disable=C0206
144 v = inputs[k]
145 if len(v.shape) == 0:
146 updated[k] = numpy.array([v], dtype=v.dtype)
147 inputs.update(updated)
149 node = OnnxQLinearConv(*inputs_list, output_names=['y'],
150 op_version=opset, **kwargs)
151 model_def = node.to_onnx(inputs, target_opset=opset)
153 oinf = OnnxInference(
154 model_def, runtime=runtime, runtime_options=dict(
155 log_severity_level=3))
156 got = oinf.run(inputs)['y']
157 expected = y.quantized_.reshape(y_shape)
158 if got.dtype != expected.dtype:
159 raise TypeError( # pragma: no cover
160 "Unexpected output dtype:\nEXPECTED\n{}\nGOT\n{}"
161 "".format(expected, got))
162 diff = numpy.abs(got.ravel().astype(numpy.float32) -
163 expected.ravel().astype(numpy.float32))
164 mdiff = diff.max()
165 if mdiff > 1e-5:
166 raise ValueError( # pragma: no cover
167 "Unexpected output maximum difference={}:\nEXPECTED\n{}\nGOT\n{}"
168 "".format(mdiff, expected, got))