quantizers 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantizers/__init__.py +4 -0
- quantizers/_version.py +16 -0
- quantizers/binary/__init__.py +9 -0
- quantizers/binary/binary_ops.py +28 -0
- quantizers/binary/binary_ops_np.py +9 -0
- quantizers/fixed_point/__init__.py +8 -0
- quantizers/fixed_point/fixed_point_ops.py +279 -0
- quantizers/fixed_point/fixed_point_ops_np.py +163 -0
- quantizers/minifloat/__init__.py +9 -0
- quantizers/minifloat/float_point_ops.py +56 -0
- quantizers/minifloat/float_point_ops_np.py +42 -0
- quantizers/stateful_quantizers.py +142 -0
- quantizers-1.0.0.dist-info/LICENSE +165 -0
- quantizers-1.0.0.dist-info/METADATA +131 -0
- quantizers-1.0.0.dist-info/RECORD +17 -0
- quantizers-1.0.0.dist-info/WHEEL +5 -0
- quantizers-1.0.0.dist-info/top_level.txt +1 -0
quantizers/__init__.py
ADDED
quantizers/_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '1.0.0'
|
|
16
|
+
__version_tuple__ = version_tuple = (1, 0, 0)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .binary_ops_np import binary_quantize_np, ternary_quantize_np
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from .binary_ops import binary_quantize, ternary_quantize
|
|
5
|
+
except ImportError:
|
|
6
|
+
binary_quantize = binary_quantize_np
|
|
7
|
+
ternary_quantize = ternary_quantize_np
|
|
8
|
+
|
|
9
|
+
__all__ = ['binary_quantize', 'ternary_quantize', 'binary_quantize_np', 'ternary_quantize_np']
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from keras import ops
|
|
2
|
+
from keras.api.random import SeedGenerator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@ops.custom_gradient
|
|
6
|
+
def binary_quantize(x):
|
|
7
|
+
r = ops.where(x > 0, 1., -1.)
|
|
8
|
+
|
|
9
|
+
def grad(*args, upstream=None):
|
|
10
|
+
if upstream is None:
|
|
11
|
+
(upstream,) = args
|
|
12
|
+
dy = upstream
|
|
13
|
+
return dy * (1. - ops.tanh(x)**2) # type: ignore
|
|
14
|
+
|
|
15
|
+
return ops.stop_gradient(r), grad
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@ops.custom_gradient
|
|
19
|
+
def ternary_quantize(x):
|
|
20
|
+
r = ops.where(x > 0.5, 1., ops.where(x < -0.5, -1., 0.))
|
|
21
|
+
|
|
22
|
+
def grad(*args, upstream=None):
|
|
23
|
+
if upstream is None:
|
|
24
|
+
(upstream,) = args
|
|
25
|
+
dy = upstream
|
|
26
|
+
return dy * (1. - ops.tanh(x)**2) # type: ignore
|
|
27
|
+
|
|
28
|
+
return ops.stop_gradient(r), grad
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import singledispatch
|
|
3
|
+
from typing import Any, TypeVar
|
|
4
|
+
|
|
5
|
+
import keras
|
|
6
|
+
from keras import ops
|
|
7
|
+
from keras.api.random import SeedGenerator
|
|
8
|
+
|
|
9
|
+
round_mode_registry: dict[str, Callable[[Any, bool | None], Any]] = {}
|
|
10
|
+
round_mode_registry_scaled: dict[str, Callable[[Any, Any, bool | None, SeedGenerator | None], Any]] = {}
|
|
11
|
+
saturation_mode_registry: dict[str, Callable[[Any, Any, Any, Any, bool | None], Any]] = {}
|
|
12
|
+
from numpy.typing import ArrayLike
|
|
13
|
+
|
|
14
|
+
T = TypeVar('T', bound=ArrayLike)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@ops.custom_gradient
|
|
18
|
+
def _clip(x, min_value, max_value):
|
|
19
|
+
r = ops.clip(x, min_value, max_value)
|
|
20
|
+
|
|
21
|
+
def grad(*args, upstream=None):
|
|
22
|
+
mask1 = x > max_value
|
|
23
|
+
mask2 = x < min_value
|
|
24
|
+
if upstream is None:
|
|
25
|
+
(upstream,) = args
|
|
26
|
+
dy = upstream
|
|
27
|
+
dx = ops.where(~(mask1 | mask2), dy, 0.)
|
|
28
|
+
dmax = ops.where(mask1, dy, 0.)
|
|
29
|
+
dmin = ops.where(mask2, dy, 0.)
|
|
30
|
+
return dx, dmin, dmax
|
|
31
|
+
return r, grad
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def rnd_mode(names: str | list[str] | tuple[str, ...]):
|
|
35
|
+
names = (names,) if isinstance(names, str) else names
|
|
36
|
+
|
|
37
|
+
def inner(func):
|
|
38
|
+
@keras.ops.custom_gradient
|
|
39
|
+
def inner_wrapper(x, f):
|
|
40
|
+
scale = 2.**f
|
|
41
|
+
sx = x * scale
|
|
42
|
+
sxq = func(sx)
|
|
43
|
+
xq = sxq / scale
|
|
44
|
+
delta = xq - x
|
|
45
|
+
|
|
46
|
+
def grad(*args, upstream=None):
|
|
47
|
+
if upstream is None:
|
|
48
|
+
(upstream,) = args
|
|
49
|
+
dy = upstream
|
|
50
|
+
dx = dy
|
|
51
|
+
df = - ops.log(2.) * delta * dy # type: ignore
|
|
52
|
+
return dx, df
|
|
53
|
+
return ops.stop_gradient(xq), grad
|
|
54
|
+
|
|
55
|
+
@keras.ops.custom_gradient
|
|
56
|
+
def inner_ste_wrapper(x):
|
|
57
|
+
r = func(x)
|
|
58
|
+
|
|
59
|
+
def grad(*args, upstream=None):
|
|
60
|
+
if upstream is None:
|
|
61
|
+
(upstream,) = args
|
|
62
|
+
dy = upstream
|
|
63
|
+
return dy
|
|
64
|
+
return ops.stop_gradient(r), grad
|
|
65
|
+
|
|
66
|
+
def wrapper(x, f, training=None, seed_gen=None): # type: ignore
|
|
67
|
+
return inner_wrapper(x, f)
|
|
68
|
+
|
|
69
|
+
def ste_wrapper(x, training=None): # type: ignore
|
|
70
|
+
return inner_ste_wrapper(x)
|
|
71
|
+
|
|
72
|
+
for name in names:
|
|
73
|
+
assert name not in round_mode_registry_scaled, f"Rounding mode '{name}' already exists."
|
|
74
|
+
round_mode_registry_scaled[name] = wrapper
|
|
75
|
+
round_mode_registry_scaled[func.__name__.upper()] = wrapper
|
|
76
|
+
|
|
77
|
+
for name in names:
|
|
78
|
+
assert name not in round_mode_registry, f"Rounding mode '{name}' already exists."
|
|
79
|
+
round_mode_registry[name] = ste_wrapper
|
|
80
|
+
round_mode_registry[func.__name__.upper()] = ste_wrapper
|
|
81
|
+
|
|
82
|
+
return ste_wrapper
|
|
83
|
+
return inner
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@rnd_mode('TRN')
|
|
87
|
+
def floor(x):
|
|
88
|
+
return ops.floor(x)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@rnd_mode('RND')
|
|
92
|
+
def round(x):
|
|
93
|
+
# Round to nearest, ties positive infinity.
|
|
94
|
+
return ops.floor(x + 0.5)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@rnd_mode('RND_CONV')
|
|
98
|
+
def round_conv(x):
|
|
99
|
+
# Round to nearest, ties to even.
|
|
100
|
+
return ops.round(x)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@rnd_mode('TRN_ZERO')
|
|
104
|
+
def floor_zero(x):
|
|
105
|
+
# Truncate towards zero.
|
|
106
|
+
sign = ops.sign(x)
|
|
107
|
+
return ops.floor(ops.abs(x)) * sign # type: ignore
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@rnd_mode('RND_ZERO')
|
|
111
|
+
def round_zero(x):
|
|
112
|
+
# Round to nearest, ties towards zero.
|
|
113
|
+
sign = ops.sign(x)
|
|
114
|
+
return -ops.floor(-ops.abs(x) + 0.5) * sign # type:ignore
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@rnd_mode('RND_MIN_INF')
|
|
118
|
+
def round_min_inf(x):
|
|
119
|
+
# Round to nearest, ties towards negative infinity.
|
|
120
|
+
return -ops.floor(-x + 0.5) # type:ignore
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@rnd_mode('RND_INF')
|
|
124
|
+
def round_inf(x):
|
|
125
|
+
# Round to nearest, ties away from zero.
|
|
126
|
+
sign = ops.sign(x)
|
|
127
|
+
return ops.floor(ops.abs(x) + 0.5) * sign # type: ignore
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def sat_mode(name: str | list | tuple):
|
|
131
|
+
names = (name,) if isinstance(name, str) else name
|
|
132
|
+
|
|
133
|
+
def inner(func):
|
|
134
|
+
for name in names:
|
|
135
|
+
assert name not in saturation_mode_registry, f"Saturation mode '{name}' already exists."
|
|
136
|
+
saturation_mode_registry[name] = func
|
|
137
|
+
saturation_mode_registry[func.__name__.upper()] = func
|
|
138
|
+
return func
|
|
139
|
+
return inner
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@sat_mode('WRAP')
|
|
143
|
+
def wrap(x, k, i, f, training=None):
|
|
144
|
+
if training:
|
|
145
|
+
return x
|
|
146
|
+
|
|
147
|
+
xs = x
|
|
148
|
+
bk = i + k
|
|
149
|
+
bias = k * 2**(bk - 1)
|
|
150
|
+
return ((xs + bias) % (2**bk) - bias)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@sat_mode('SAT')
|
|
154
|
+
def sat(x, k, i, f, training=None):
|
|
155
|
+
f_eps = 2**(-f)
|
|
156
|
+
__max = 2**i
|
|
157
|
+
_max = __max - f_eps
|
|
158
|
+
_min = -__max * k
|
|
159
|
+
r = _clip(x, _min, _max)
|
|
160
|
+
return r
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@sat_mode('SAT_SYM')
|
|
164
|
+
def sat_sym(x, k, i, f, training=None):
|
|
165
|
+
f_eps = 2**(-f)
|
|
166
|
+
_max = 2**i - f_eps
|
|
167
|
+
_min = -_max * k
|
|
168
|
+
r = _clip(x, _min, _max)
|
|
169
|
+
return r
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@sat_mode('WRAP_SM')
|
|
173
|
+
def wrap_sm_fn(x, k, i, f, training=None, quant_fn: Callable = lambda x: x):
|
|
174
|
+
# x=ops.round(x*2**f)
|
|
175
|
+
# High and low bounds are reflective. When overflows, can be less trash than WARP but still more trash than SAT.
|
|
176
|
+
eps = 2**-f
|
|
177
|
+
high = 2**i - eps
|
|
178
|
+
low = -(high + eps) * k
|
|
179
|
+
interval = 2 ** (i + k)
|
|
180
|
+
c1 = ((x) / interval) % 2 >= 1 # type: ignore
|
|
181
|
+
c1 = c1 & (ops.abs(x) > eps / 2)
|
|
182
|
+
c2 = ((x + eps / 2) / (interval / 2)) % 2 >= 1 # type: ignore
|
|
183
|
+
qx = quant_fn(x)
|
|
184
|
+
mapped = ((qx - high - eps) % interval) + low
|
|
185
|
+
|
|
186
|
+
mapped = ops.where(c2, -mapped - eps, mapped) # type: ignore
|
|
187
|
+
mapped = ops.where(c1, -mapped - eps, mapped) # type: ignore
|
|
188
|
+
|
|
189
|
+
return mapped
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_fixed_quantizer(round_mode: str = 'TRN', overflow_mode: str = 'WRAP') -> Callable[[T, Any, Any, Any, bool | None, SeedGenerator | None], T]:
|
|
193
|
+
"""Get a stateless fixed-point quantizer given the round and overflow mode.
|
|
194
|
+
The quantizer is differentiable w.r.t. to the input and f, also i if using saturation overflow mode.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
round_mode: round mode, one of
|
|
198
|
+
"""
|
|
199
|
+
round_mode = round_mode.upper()
|
|
200
|
+
overflow_mode = overflow_mode.upper()
|
|
201
|
+
round_fn_scaled = round_mode_registry_scaled.get(round_mode, None)
|
|
202
|
+
if round_fn_scaled is None:
|
|
203
|
+
raise ValueError(f"Unknown rounding mode: {round_mode}") # pragma: no cover
|
|
204
|
+
sat_fn = saturation_mode_registry.get(overflow_mode, None)
|
|
205
|
+
if sat_fn is None:
|
|
206
|
+
raise ValueError(f"Unknown saturation mode: {overflow_mode}") # pragma: no cover
|
|
207
|
+
|
|
208
|
+
if overflow_mode == 'WRAP_SM':
|
|
209
|
+
assert round_mode in ('RND', 'RND_CONV'), "WRAP_SM only supports RND and RND_CONV rounding modes in this implementation."
|
|
210
|
+
|
|
211
|
+
def quantizer(x: T, k: T, i: T, f: T, training: bool | None = None, seed_gen: SeedGenerator | None = None) -> T:
|
|
212
|
+
"""Stateless fixed-point quantizer.
|
|
213
|
+
Args:
|
|
214
|
+
x: input tensor
|
|
215
|
+
k: number of fractional bits
|
|
216
|
+
i: number of integer bits
|
|
217
|
+
f: number of fractional bits
|
|
218
|
+
training: training mode
|
|
219
|
+
"""
|
|
220
|
+
i = ops.stop_gradient(ops.maximum(i, -f)) + (i - ops.stop_gradient(i)) # type: ignore
|
|
221
|
+
|
|
222
|
+
if overflow_mode == 'WRAP_SM':
|
|
223
|
+
def rnd_fn_wrapped(x):
|
|
224
|
+
return round_fn_scaled(x, f, training, seed_gen)
|
|
225
|
+
return wrap_sm_fn(x, k, i, f, training, rnd_fn_wrapped) # type: ignore
|
|
226
|
+
|
|
227
|
+
# Workaround for gradient computation around 0.
|
|
228
|
+
# When have small rounded to boundary, grad on f presents despite the value will be clipped off anyway.
|
|
229
|
+
# Thus have saturation before rounding, except for wrap mode, which doesn't round during training.
|
|
230
|
+
if overflow_mode != 'WRAP':
|
|
231
|
+
x = sat_fn(x, k, i, f, training)
|
|
232
|
+
x = round_fn_scaled(x, f, training, seed_gen)
|
|
233
|
+
if overflow_mode == 'WRAP':
|
|
234
|
+
x = sat_fn(x, k, i, f, training)
|
|
235
|
+
return x
|
|
236
|
+
return quantizer
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ======================= STOCHASTIC ROUNDING =======================
|
|
240
|
+
def register_stochastic_rounding():
|
|
241
|
+
@keras.ops.custom_gradient
|
|
242
|
+
def stochastic_scaled(x, f, noise):
|
|
243
|
+
scale = 2.**f
|
|
244
|
+
sx = x * scale
|
|
245
|
+
sxq = ops.floor(sx + noise)
|
|
246
|
+
xq = sxq / scale
|
|
247
|
+
delta = xq - x
|
|
248
|
+
|
|
249
|
+
def grad(*args, upstream=None):
|
|
250
|
+
if upstream is None:
|
|
251
|
+
(upstream,) = args
|
|
252
|
+
dy = upstream
|
|
253
|
+
dx = dy
|
|
254
|
+
df = - ops.log(2.) * delta * dy # type: ignore
|
|
255
|
+
return dx, df, None
|
|
256
|
+
return ops.stop_gradient(xq), grad
|
|
257
|
+
|
|
258
|
+
rnd_scaled = round_mode_registry_scaled['RND']
|
|
259
|
+
rnd_conv_scaled = round_mode_registry_scaled['RND_CONV']
|
|
260
|
+
|
|
261
|
+
def outer_rnd(x, f, training=None, seed_gen=None):
|
|
262
|
+
if training:
|
|
263
|
+
noise = keras.random.uniform(ops.shape(x), seed=seed_gen)
|
|
264
|
+
return stochastic_scaled(x, f, noise)
|
|
265
|
+
else:
|
|
266
|
+
return rnd_scaled(x, f, training, seed_gen)
|
|
267
|
+
|
|
268
|
+
def outer_rnd_conv(x, f, training=None, seed_gen=None):
|
|
269
|
+
if training:
|
|
270
|
+
noise = keras.random.uniform(ops.shape(x), seed=seed_gen)
|
|
271
|
+
return stochastic_scaled(x, f, noise)
|
|
272
|
+
else:
|
|
273
|
+
return rnd_conv_scaled(x, f, training, seed_gen)
|
|
274
|
+
|
|
275
|
+
round_mode_registry_scaled['S_RND'] = outer_rnd
|
|
276
|
+
round_mode_registry_scaled['S_RND_CONV'] = outer_rnd_conv
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
register_stochastic_rounding()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any, TypeVar
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import ArrayLike
|
|
6
|
+
|
|
7
|
+
round_mode_registry_np: dict[str, Callable[[Any, bool | None], Any]] = {}
|
|
8
|
+
round_mode_registry_scaled_np: dict[str, Callable[[Any, Any, bool | None], Any]] = {}
|
|
9
|
+
saturation_mode_registry_np: dict[str, Callable[[Any, Any, Any, Any, bool | None], Any]] = {}
|
|
10
|
+
|
|
11
|
+
T = TypeVar('T', bound=np.ndarray)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def rnd_mode_np(names: str | list[str] | tuple[str, ...]):
|
|
15
|
+
names = (names,) if isinstance(names, str) else names
|
|
16
|
+
|
|
17
|
+
def inner(func):
|
|
18
|
+
def wrapper(x, f, training=None, seed_gen=None):
|
|
19
|
+
scale = 2.**f
|
|
20
|
+
sx = x * scale
|
|
21
|
+
sxq = func(sx)
|
|
22
|
+
xq = sxq / scale
|
|
23
|
+
return xq
|
|
24
|
+
|
|
25
|
+
for name in names:
|
|
26
|
+
assert name not in round_mode_registry_scaled_np, f"Rounding mode '{name}' already exists."
|
|
27
|
+
round_mode_registry_scaled_np[name] = wrapper
|
|
28
|
+
round_mode_registry_scaled_np[func.__name__.upper()] = wrapper
|
|
29
|
+
|
|
30
|
+
for name in names:
|
|
31
|
+
assert name not in round_mode_registry_np, f"Rounding mode '{name}' already exists."
|
|
32
|
+
round_mode_registry_np[name] = func
|
|
33
|
+
round_mode_registry_np[func.__name__.upper()] = func
|
|
34
|
+
|
|
35
|
+
return func
|
|
36
|
+
return inner
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@rnd_mode_np('TRN')
|
|
40
|
+
def floor(x):
|
|
41
|
+
return np.floor(x)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@rnd_mode_np('RND')
|
|
45
|
+
def round(x):
|
|
46
|
+
return np.floor(x + 0.5)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@rnd_mode_np('RND_CONV')
|
|
50
|
+
def round_conv(x):
|
|
51
|
+
return np.round(x)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@rnd_mode_np('TRN_ZERO')
|
|
55
|
+
def floor_zero(x):
|
|
56
|
+
sign = np.sign(x)
|
|
57
|
+
return np.floor(np.abs(x)) * sign
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@rnd_mode_np('RND_ZERO')
|
|
61
|
+
def round_zero(x):
|
|
62
|
+
sign = np.sign(x)
|
|
63
|
+
return -np.floor(-np.abs(x) + 0.5) * sign
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@rnd_mode_np('RND_MIN_INF')
|
|
67
|
+
def round_min_inf(x):
|
|
68
|
+
return -np.floor(-x + 0.5)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@rnd_mode_np('RND_INF')
|
|
72
|
+
def round_inf(x):
|
|
73
|
+
sign = np.sign(x)
|
|
74
|
+
return np.floor(np.abs(x) + 0.5) * sign
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def sat_mode_np(name: str | list | tuple):
|
|
78
|
+
names = (name,) if isinstance(name, str) else name
|
|
79
|
+
|
|
80
|
+
def inner(func):
|
|
81
|
+
for name in names:
|
|
82
|
+
assert name not in saturation_mode_registry_np, f"Saturation mode '{name}' already exists."
|
|
83
|
+
saturation_mode_registry_np[name] = func
|
|
84
|
+
saturation_mode_registry_np[func.__name__.upper()] = func
|
|
85
|
+
return func
|
|
86
|
+
return inner
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@sat_mode_np('WRAP')
|
|
90
|
+
def wrap(x, k, i, f, training=None):
|
|
91
|
+
xs = x
|
|
92
|
+
bk = i + k
|
|
93
|
+
bias = k * 2**(bk - 1)
|
|
94
|
+
return ((xs + bias) % (2**bk) - bias)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@sat_mode_np('SAT')
|
|
98
|
+
def sat(x, k, i, f, training=None):
|
|
99
|
+
f_eps = 2**(-f)
|
|
100
|
+
__max = 2**i
|
|
101
|
+
_max = __max - f_eps
|
|
102
|
+
_min = -__max * k
|
|
103
|
+
return np.clip(x, _min, _max)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@sat_mode_np('SAT_SYM')
|
|
107
|
+
def sat_sym(x, k, i, f, training=None):
|
|
108
|
+
f_eps = 2**(-f)
|
|
109
|
+
_max = 2**i - f_eps
|
|
110
|
+
_min = -_max * k
|
|
111
|
+
return np.clip(x, _min, _max)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@sat_mode_np('WRAP_SM')
|
|
115
|
+
def wrap_sm_fn(x, k, i, f, training=None, quant_fn: Callable = lambda x: x):
|
|
116
|
+
eps = 2**-f
|
|
117
|
+
high = 2**i - eps
|
|
118
|
+
low = -(high + eps) * k
|
|
119
|
+
interval = 2 ** (i + k)
|
|
120
|
+
c1 = ((x) / interval) % 2 >= 1
|
|
121
|
+
c1 = c1 & (np.abs(x) > eps / 2)
|
|
122
|
+
c2 = ((x + eps / 2) / (interval / 2)) % 2 >= 1
|
|
123
|
+
qx = quant_fn(x)
|
|
124
|
+
mapped = ((qx - high - eps) % interval) + low
|
|
125
|
+
|
|
126
|
+
mapped = np.where(c2, -mapped - eps, mapped)
|
|
127
|
+
mapped = np.where(c1, -mapped - eps, mapped)
|
|
128
|
+
|
|
129
|
+
return mapped
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_fixed_quantizer_np(round_mode: str = 'TRN', overflow_mode: str = 'WRAP'):
|
|
133
|
+
round_mode = round_mode.upper()
|
|
134
|
+
overflow_mode = overflow_mode.upper()
|
|
135
|
+
round_mode = round_mode[2:] if round_mode.startswith('S_') else round_mode
|
|
136
|
+
|
|
137
|
+
round_fn_scaled = round_mode_registry_scaled_np.get(round_mode, None)
|
|
138
|
+
if round_fn_scaled is None:
|
|
139
|
+
raise ValueError(f"Unknown rounding mode: {round_mode}") # pragma: no cover
|
|
140
|
+
sat_fn = saturation_mode_registry_np.get(overflow_mode, None)
|
|
141
|
+
if sat_fn is None:
|
|
142
|
+
raise ValueError(f"Unknown saturation mode: {overflow_mode}") # pragma: no cover
|
|
143
|
+
|
|
144
|
+
if overflow_mode == 'WRAP_SM':
|
|
145
|
+
assert round_mode in ('RND', 'RND_CONV'), "WRAP_SM only supports RND and RND_CONV rounding modes in this implementation."
|
|
146
|
+
|
|
147
|
+
def quantizer(x: T, k: T, i: T, f: T, training: bool | None = False, seed_gen: None = None) -> np.ndarray:
|
|
148
|
+
assert not training, "Training mode not supported in numpy implementation."
|
|
149
|
+
|
|
150
|
+
_i = np.maximum(i, -f)
|
|
151
|
+
|
|
152
|
+
if overflow_mode == 'WRAP_SM':
|
|
153
|
+
def rnd_fn_wrapped(x):
|
|
154
|
+
return round_fn_scaled(x, f, training)
|
|
155
|
+
return wrap_sm_fn(x, k, _i, f, training, rnd_fn_wrapped)
|
|
156
|
+
|
|
157
|
+
if overflow_mode != 'WRAP':
|
|
158
|
+
x = sat_fn(x, k, _i, f, training)
|
|
159
|
+
x = round_fn_scaled(x, f, training)
|
|
160
|
+
if overflow_mode == 'WRAP':
|
|
161
|
+
x = sat_fn(x, k, _i, f, training)
|
|
162
|
+
return x
|
|
163
|
+
return quantizer
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .float_point_ops_np import float_decompose_np, float_quantize_np
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from .float_point_ops import float_decompose, float_quantize
|
|
5
|
+
except ImportError:
|
|
6
|
+
float_quantize = float_quantize_np
|
|
7
|
+
float_decompose = float_decompose_np
|
|
8
|
+
|
|
9
|
+
__all__ = ['float_quantize', 'float_decompose', 'float_quantize_np', 'float_decompose_np']
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from keras import ops
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@ops.custom_gradient
|
|
6
|
+
def float_quantize(x, m, e, e0=0.):
|
|
7
|
+
'''Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl.'''
|
|
8
|
+
m = m + 1
|
|
9
|
+
eps = 1e-7
|
|
10
|
+
|
|
11
|
+
e_req = ops.floor(ops.log2(ops.abs(x) + eps)) # type: ignore
|
|
12
|
+
_e_high = ops.maximum(2.**(e - 1), 1.)
|
|
13
|
+
e_low, e_high = -_e_high + e0 + 1, _e_high + e0 - 1 # type: ignore
|
|
14
|
+
e_act = ops.clip(e_req, e_low, e_high)
|
|
15
|
+
scale = 2.**(e_act - m + 1)
|
|
16
|
+
sig = x / scale
|
|
17
|
+
qsig = ops.round(sig)
|
|
18
|
+
_sig_high = 2.**m
|
|
19
|
+
_sig_high = ops.where(e_high != e_act, _sig_high, _sig_high - 1)
|
|
20
|
+
clip_sig = ops.clip(qsig, -_sig_high, _sig_high) # type: ignore
|
|
21
|
+
qx = clip_sig * scale
|
|
22
|
+
|
|
23
|
+
def grad(*args, upstream=None):
|
|
24
|
+
if upstream is None:
|
|
25
|
+
(upstream,) = args
|
|
26
|
+
dy = upstream
|
|
27
|
+
mask_e = e_req != e_act
|
|
28
|
+
mask_m = e_req <= e_high # type: ignore
|
|
29
|
+
dm = ops.where(mask_m, scale * (sig - qsig) * dy * ops.log(2.), 0.)
|
|
30
|
+
d_exp = ops.where(mask_e, (x - qx) * dy * ops.log(2.), 0.)
|
|
31
|
+
de = d_exp * ops.log(2.) * _e_high # type: ignore
|
|
32
|
+
de0 = ops.where(e_req > e_high, d_exp, -d_exp) # type: ignore
|
|
33
|
+
return dy, dm, de, de0
|
|
34
|
+
|
|
35
|
+
return ops.stop_gradient(qx), grad
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def float_decompose(x, m, e, e0=0):
|
|
39
|
+
'''Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl.'''
|
|
40
|
+
m = m + 1
|
|
41
|
+
eps = 1e-7
|
|
42
|
+
|
|
43
|
+
e_req = ops.floor(ops.log2(ops.abs(x) + eps)) # type: ignore
|
|
44
|
+
_e_high = 2.**(e - 1)
|
|
45
|
+
e_low, e_high = -_e_high + e0, _e_high + e0 - 1
|
|
46
|
+
e_act = ops.clip(e_req, e_low + 1, e_high)
|
|
47
|
+
scale = 2.**(e_act - m + 1)
|
|
48
|
+
sig = x / scale
|
|
49
|
+
qsig = ops.round(sig)
|
|
50
|
+
cond = (e_act != e_high) & (ops.abs(qsig) == 2**m)
|
|
51
|
+
e_act = ops.where(cond, e_act + 1, e_act) # type: ignore
|
|
52
|
+
qsig = ops.where(cond, qsig / 2., qsig) # type: ignore
|
|
53
|
+
r_mantissa_bound = 2 - 2.**(-m + 1)
|
|
54
|
+
r_mantissa = ops.clip(qsig * 2.**(-m + 1), -r_mantissa_bound, r_mantissa_bound)
|
|
55
|
+
r_exponent = e_act
|
|
56
|
+
return r_mantissa, r_exponent
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def float_quantize_np(x, m, e, e0=0.):
|
|
5
|
+
'''Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl.'''
|
|
6
|
+
m = m + 1
|
|
7
|
+
eps = 1e-7
|
|
8
|
+
|
|
9
|
+
e_req = np.floor(np.log2(np.abs(x) + eps)) # type: ignore
|
|
10
|
+
_e_high = np.maximum(2.**(e - 1), 1.)
|
|
11
|
+
e_low, e_high = -_e_high + e0 + 1, _e_high + e0 - 1 # type: ignore
|
|
12
|
+
e_act = np.clip(e_req, e_low, e_high)
|
|
13
|
+
scale = 2.**(e_act - m + 1)
|
|
14
|
+
sig = x / scale
|
|
15
|
+
qsig = np.round(sig)
|
|
16
|
+
_sig_high = 2.**m
|
|
17
|
+
_sig_high = np.where(e_high != e_act, _sig_high, _sig_high - 1)
|
|
18
|
+
clip_sig = np.clip(qsig, -_sig_high, _sig_high) # type: ignore
|
|
19
|
+
qx = clip_sig * scale
|
|
20
|
+
|
|
21
|
+
return qx
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def float_decompose_np(x, m, e, e0=0):
|
|
25
|
+
'''Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl.'''
|
|
26
|
+
m = m + 1
|
|
27
|
+
eps = 1e-7
|
|
28
|
+
|
|
29
|
+
e_req = np.floor(np.log2(np.abs(x) + eps)) # type: ignore
|
|
30
|
+
_e_high = 2.**(e - 1)
|
|
31
|
+
e_low, e_high = -_e_high + e0, _e_high + e0 - 1
|
|
32
|
+
e_act = np.clip(e_req, e_low + 1, e_high)
|
|
33
|
+
scale = 2.**(e_act - m + 1)
|
|
34
|
+
sig = x / scale
|
|
35
|
+
qsig = np.round(sig)
|
|
36
|
+
cond = (e_act != e_high) & (np.abs(qsig) == 2**m)
|
|
37
|
+
e_act = np.where(cond, e_act + 1, e_act) # type: ignore
|
|
38
|
+
qsig = np.where(cond, qsig / 2., qsig) # type: ignore
|
|
39
|
+
r_mantissa_bound = 2 - 2.**(-m + 1)
|
|
40
|
+
r_mantissa = np.clip(qsig * 2.**(-m + 1), -r_mantissa_bound, r_mantissa_bound)
|
|
41
|
+
r_exponent = e_act
|
|
42
|
+
return r_mantissa, r_exponent
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from typing import Any, TypeVar, overload
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from .binary import *
|
|
6
|
+
from .fixed_point import *
|
|
7
|
+
from .minifloat import *
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T', np.ndarray, float, int)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_tensor(x):
|
|
13
|
+
base_module = x.__class__.__module__.split('.', 1)[0]
|
|
14
|
+
return base_module in ('tensorflow', 'torch', 'jaxlib')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BinaryQ:
|
|
18
|
+
"""Stateful binary quantizer."""
|
|
19
|
+
|
|
20
|
+
@overload
|
|
21
|
+
def __call__(self, x: T) -> np.ndarray:
|
|
22
|
+
... # pragma: no cover
|
|
23
|
+
|
|
24
|
+
@overload
|
|
25
|
+
def __call__(self, x: Any) -> Any:
|
|
26
|
+
... # pragma: no cover
|
|
27
|
+
|
|
28
|
+
def __call__(self, x):
|
|
29
|
+
use_numpy = not is_tensor(x)
|
|
30
|
+
if use_numpy:
|
|
31
|
+
return binary_quantize_np(x)
|
|
32
|
+
else:
|
|
33
|
+
return binary_quantize(x)
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return 'Binary()'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TernaryQ:
|
|
40
|
+
"""Stateful ternary quantizer."""
|
|
41
|
+
|
|
42
|
+
@overload
|
|
43
|
+
def __call__(self, x: T) -> np.ndarray:
|
|
44
|
+
... # pragma: no cover
|
|
45
|
+
|
|
46
|
+
@overload
|
|
47
|
+
def __call__(self, x: Any) -> Any:
|
|
48
|
+
... # pragma: no cover
|
|
49
|
+
|
|
50
|
+
def __call__(self, x):
|
|
51
|
+
use_numpy = not is_tensor(x)
|
|
52
|
+
if use_numpy:
|
|
53
|
+
return ternary_quantize_np(x)
|
|
54
|
+
else:
|
|
55
|
+
return ternary_quantize(x)
|
|
56
|
+
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return 'Ternary()'
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class FixedQ:
|
|
62
|
+
"""Stateful fixed-point quantizer.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
width : int
|
|
67
|
+
The total bit-width of the quantized number.
|
|
68
|
+
integer : int
|
|
69
|
+
The number of bits used for the integer part. INCLUDES the sign bit.
|
|
70
|
+
signed : bool|int
|
|
71
|
+
Whether the quantized number is signed.
|
|
72
|
+
rounding : str, optional
|
|
73
|
+
The rounding method to use, by default 'TRN'. Available options are 'TRN', 'TRN_ZERO', 'RND', 'S_RND', 'S_RND_CONV', 'RND_CONV', 'RND_ZERO', 'RND_INF', 'RND_MIN_INF'.
|
|
74
|
+
overflow : str, optional
|
|
75
|
+
The overflow handling method to use, by default 'WRAP'. Available options are 'WRAP', 'SAT', 'SAT_SYM', 'WRAP_SM'.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, width: int, integer: int, signed: bool | int = True, rounding: str = 'TRN', overflow: str = 'WRAP'):
|
|
79
|
+
self.width = width
|
|
80
|
+
self.integer = integer
|
|
81
|
+
self.signed = signed
|
|
82
|
+
self.rounding = rounding
|
|
83
|
+
self.overflow = overflow
|
|
84
|
+
|
|
85
|
+
@overload
|
|
86
|
+
def __call__(self, x: T) -> np.ndarray:
|
|
87
|
+
... # pragma: no cover
|
|
88
|
+
|
|
89
|
+
@overload
|
|
90
|
+
def __call__(self, x: Any) -> Any:
|
|
91
|
+
... # pragma: no cover
|
|
92
|
+
|
|
93
|
+
def __call__(self, x):
|
|
94
|
+
use_numpy = not is_tensor(x)
|
|
95
|
+
k, i, f = self.signed, self.integer - self.signed, self.width - self.integer
|
|
96
|
+
k, i, f = float(k), float(i), float(f)
|
|
97
|
+
if use_numpy:
|
|
98
|
+
q = get_fixed_quantizer_np(self.rounding, self.overflow)
|
|
99
|
+
else:
|
|
100
|
+
q = get_fixed_quantizer(self.rounding, self.overflow)
|
|
101
|
+
return q(x, k, i, f, False, None) # type: ignore
|
|
102
|
+
|
|
103
|
+
def __repr__(self):
|
|
104
|
+
return f'Fixed(w={self.width}, i={self.integer}, s={self.signed}, r={self.rounding}, o={self.overflow})'
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class MinifloatQ:
|
|
108
|
+
"""Stateful minifloat quantizer.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
significant : int
|
|
113
|
+
The number of significant bits.
|
|
114
|
+
exponent : int
|
|
115
|
+
The number of exponent bits.
|
|
116
|
+
exponent_bias : int
|
|
117
|
+
The bias applied to the exponent.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self, significant: int, exponent: int, exponent_bias: int):
|
|
121
|
+
self.significant = significant
|
|
122
|
+
self.exponent = exponent
|
|
123
|
+
self.exponent_bias = exponent_bias
|
|
124
|
+
|
|
125
|
+
@overload
|
|
126
|
+
def __call__(self, x: T) -> np.ndarray:
|
|
127
|
+
... # pragma: no cover
|
|
128
|
+
|
|
129
|
+
@overload
|
|
130
|
+
def __call__(self, x: Any) -> Any:
|
|
131
|
+
... # pragma: no cover
|
|
132
|
+
|
|
133
|
+
def __call__(self, x):
|
|
134
|
+
s, e, e0 = float(self.significant), float(self.exponent), float(self.exponent_bias)
|
|
135
|
+
use_numpy = not is_tensor(x)
|
|
136
|
+
if use_numpy:
|
|
137
|
+
return float_quantize_np(x, s, e, e0)
|
|
138
|
+
else:
|
|
139
|
+
return float_quantize(x, s, e, e0)
|
|
140
|
+
|
|
141
|
+
def __repr__(self):
|
|
142
|
+
return f'Minifloat(s={self.significant}, e={self.exponent}, b={self.exponent_bias})'
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
6
|
+
of this license document, but changing it is not allowed.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
This version of the GNU Lesser General Public License incorporates
|
|
10
|
+
the terms and conditions of version 3 of the GNU General Public
|
|
11
|
+
License, supplemented by the additional permissions listed below.
|
|
12
|
+
|
|
13
|
+
0. Additional Definitions.
|
|
14
|
+
|
|
15
|
+
As used herein, "this License" refers to version 3 of the GNU Lesser
|
|
16
|
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
|
17
|
+
General Public License.
|
|
18
|
+
|
|
19
|
+
"The Library" refers to a covered work governed by this License,
|
|
20
|
+
other than an Application or a Combined Work as defined below.
|
|
21
|
+
|
|
22
|
+
An "Application" is any work that makes use of an interface provided
|
|
23
|
+
by the Library, but which is not otherwise based on the Library.
|
|
24
|
+
Defining a subclass of a class defined by the Library is deemed a mode
|
|
25
|
+
of using an interface provided by the Library.
|
|
26
|
+
|
|
27
|
+
A "Combined Work" is a work produced by combining or linking an
|
|
28
|
+
Application with the Library. The particular version of the Library
|
|
29
|
+
with which the Combined Work was made is also called the "Linked
|
|
30
|
+
Version".
|
|
31
|
+
|
|
32
|
+
The "Minimal Corresponding Source" for a Combined Work means the
|
|
33
|
+
Corresponding Source for the Combined Work, excluding any source code
|
|
34
|
+
for portions of the Combined Work that, considered in isolation, are
|
|
35
|
+
based on the Application, and not on the Linked Version.
|
|
36
|
+
|
|
37
|
+
The "Corresponding Application Code" for a Combined Work means the
|
|
38
|
+
object code and/or source code for the Application, including any data
|
|
39
|
+
and utility programs needed for reproducing the Combined Work from the
|
|
40
|
+
Application, but excluding the System Libraries of the Combined Work.
|
|
41
|
+
|
|
42
|
+
1. Exception to Section 3 of the GNU GPL.
|
|
43
|
+
|
|
44
|
+
You may convey a covered work under sections 3 and 4 of this License
|
|
45
|
+
without being bound by section 3 of the GNU GPL.
|
|
46
|
+
|
|
47
|
+
2. Conveying Modified Versions.
|
|
48
|
+
|
|
49
|
+
If you modify a copy of the Library, and, in your modifications, a
|
|
50
|
+
facility refers to a function or data to be supplied by an Application
|
|
51
|
+
that uses the facility (other than as an argument passed when the
|
|
52
|
+
facility is invoked), then you may convey a copy of the modified
|
|
53
|
+
version:
|
|
54
|
+
|
|
55
|
+
a) under this License, provided that you make a good faith effort to
|
|
56
|
+
ensure that, in the event an Application does not supply the
|
|
57
|
+
function or data, the facility still operates, and performs
|
|
58
|
+
whatever part of its purpose remains meaningful, or
|
|
59
|
+
|
|
60
|
+
b) under the GNU GPL, with none of the additional permissions of
|
|
61
|
+
this License applicable to that copy.
|
|
62
|
+
|
|
63
|
+
3. Object Code Incorporating Material from Library Header Files.
|
|
64
|
+
|
|
65
|
+
The object code form of an Application may incorporate material from
|
|
66
|
+
a header file that is part of the Library. You may convey such object
|
|
67
|
+
code under terms of your choice, provided that, if the incorporated
|
|
68
|
+
material is not limited to numerical parameters, data structure
|
|
69
|
+
layouts and accessors, or small macros, inline functions and templates
|
|
70
|
+
(ten or fewer lines in length), you do both of the following:
|
|
71
|
+
|
|
72
|
+
a) Give prominent notice with each copy of the object code that the
|
|
73
|
+
Library is used in it and that the Library and its use are
|
|
74
|
+
covered by this License.
|
|
75
|
+
|
|
76
|
+
b) Accompany the object code with a copy of the GNU GPL and this license
|
|
77
|
+
document.
|
|
78
|
+
|
|
79
|
+
4. Combined Works.
|
|
80
|
+
|
|
81
|
+
You may convey a Combined Work under terms of your choice that,
|
|
82
|
+
taken together, effectively do not restrict modification of the
|
|
83
|
+
portions of the Library contained in the Combined Work and reverse
|
|
84
|
+
engineering for debugging such modifications, if you also do each of
|
|
85
|
+
the following:
|
|
86
|
+
|
|
87
|
+
a) Give prominent notice with each copy of the Combined Work that
|
|
88
|
+
the Library is used in it and that the Library and its use are
|
|
89
|
+
covered by this License.
|
|
90
|
+
|
|
91
|
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
|
92
|
+
document.
|
|
93
|
+
|
|
94
|
+
c) For a Combined Work that displays copyright notices during
|
|
95
|
+
execution, include the copyright notice for the Library among
|
|
96
|
+
these notices, as well as a reference directing the user to the
|
|
97
|
+
copies of the GNU GPL and this license document.
|
|
98
|
+
|
|
99
|
+
d) Do one of the following:
|
|
100
|
+
|
|
101
|
+
0) Convey the Minimal Corresponding Source under the terms of this
|
|
102
|
+
License, and the Corresponding Application Code in a form
|
|
103
|
+
suitable for, and under terms that permit, the user to
|
|
104
|
+
recombine or relink the Application with a modified version of
|
|
105
|
+
the Linked Version to produce a modified Combined Work, in the
|
|
106
|
+
manner specified by section 6 of the GNU GPL for conveying
|
|
107
|
+
Corresponding Source.
|
|
108
|
+
|
|
109
|
+
1) Use a suitable shared library mechanism for linking with the
|
|
110
|
+
Library. A suitable mechanism is one that (a) uses at run time
|
|
111
|
+
a copy of the Library already present on the user's computer
|
|
112
|
+
system, and (b) will operate properly with a modified version
|
|
113
|
+
of the Library that is interface-compatible with the Linked
|
|
114
|
+
Version.
|
|
115
|
+
|
|
116
|
+
e) Provide Installation Information, but only if you would otherwise
|
|
117
|
+
be required to provide such information under section 6 of the
|
|
118
|
+
GNU GPL, and only to the extent that such information is
|
|
119
|
+
necessary to install and execute a modified version of the
|
|
120
|
+
Combined Work produced by recombining or relinking the
|
|
121
|
+
Application with a modified version of the Linked Version. (If
|
|
122
|
+
you use option 4d0, the Installation Information must accompany
|
|
123
|
+
the Minimal Corresponding Source and Corresponding Application
|
|
124
|
+
Code. If you use option 4d1, you must provide the Installation
|
|
125
|
+
Information in the manner specified by section 6 of the GNU GPL
|
|
126
|
+
for conveying Corresponding Source.)
|
|
127
|
+
|
|
128
|
+
5. Combined Libraries.
|
|
129
|
+
|
|
130
|
+
You may place library facilities that are a work based on the
|
|
131
|
+
Library side by side in a single library together with other library
|
|
132
|
+
facilities that are not Applications and are not covered by this
|
|
133
|
+
License, and convey such a combined library under terms of your
|
|
134
|
+
choice, if you do both of the following:
|
|
135
|
+
|
|
136
|
+
a) Accompany the combined library with a copy of the same work based
|
|
137
|
+
on the Library, uncombined with any other library facilities,
|
|
138
|
+
conveyed under the terms of this License.
|
|
139
|
+
|
|
140
|
+
b) Give prominent notice with the combined library that part of it
|
|
141
|
+
is a work based on the Library, and explaining where to find the
|
|
142
|
+
accompanying uncombined form of the same work.
|
|
143
|
+
|
|
144
|
+
6. Revised Versions of the GNU Lesser General Public License.
|
|
145
|
+
|
|
146
|
+
The Free Software Foundation may publish revised and/or new versions
|
|
147
|
+
of the GNU Lesser General Public License from time to time. Such new
|
|
148
|
+
versions will be similar in spirit to the present version, but may
|
|
149
|
+
differ in detail to address new problems or concerns.
|
|
150
|
+
|
|
151
|
+
Each version is given a distinguishing version number. If the
|
|
152
|
+
Library as you received it specifies that a certain numbered version
|
|
153
|
+
of the GNU Lesser General Public License "or any later version"
|
|
154
|
+
applies to it, you have the option of following the terms and
|
|
155
|
+
conditions either of that published version or of any later version
|
|
156
|
+
published by the Free Software Foundation. If the Library as you
|
|
157
|
+
received it does not specify a version number of the GNU Lesser
|
|
158
|
+
General Public License, you may choose any version of the GNU Lesser
|
|
159
|
+
General Public License ever published by the Free Software Foundation.
|
|
160
|
+
|
|
161
|
+
If the Library as you received it specifies that a proxy can decide
|
|
162
|
+
whether future versions of the GNU Lesser General Public License shall
|
|
163
|
+
apply, that proxy's public statement of acceptance of any version is
|
|
164
|
+
permanent authorization for you to choose that version for the
|
|
165
|
+
Library.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: quantizers
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Author-email: Chang Sun <chsun@cern.ch>
|
|
5
|
+
License: GNU Lesser General Public License v3 (LGPLv3)
|
|
6
|
+
Project-URL: repository, https://github.com/calad0i/quantizers
|
|
7
|
+
Keywords: quantization
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Provides-Extra: dependencies
|
|
16
|
+
Requires-Dist: numpy ; extra == 'dependencies'
|
|
17
|
+
Provides-Extra: grad
|
|
18
|
+
Requires-Dist: keras >=3 ; extra == 'grad'
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest ; extra == 'test'
|
|
21
|
+
Requires-Dist: pytest-cov ; extra == 'test'
|
|
22
|
+
Requires-Dist: pytest-xdist ; extra == 'test'
|
|
23
|
+
Requires-Dist: keras >=3 ; extra == 'test'
|
|
24
|
+
|
|
25
|
+
# Quantizers
|
|
26
|
+
[](https://badge.fury.io/py/quantizers)
|
|
27
|
+
[](LICENSE)
|
|
28
|
+
|
|
29
|
+
Hardware-oriented numerical quantizers for deep learning models, implemented in Keras v3 and NumPy. Provides bit-accurate precision matching with Vivado/Vitis HLS implementations.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Bit-accurate to the HLS implementation up to 32/64-bit floating point precision
|
|
34
|
+
- Support for fixed-point and minifloat number formats
|
|
35
|
+
- Differentiable Keras v3 implementations with gradients on inputs
|
|
36
|
+
- With surrogate gradients for bit-width optimization as described in *[Gradient-based Automatic Mixed Precision Quantization for Neural Networks On-Chip](https://arxiv.org/abs/2405.00645)*
|
|
37
|
+
- Supports stochastic rounding for training
|
|
38
|
+
|
|
39
|
+
## Supported Quantizers
|
|
40
|
+
|
|
41
|
+
### Fixed-Point Quantizer
|
|
42
|
+
|
|
43
|
+
Parameters:
|
|
44
|
+
- `k` (keep_negative): Enable negative numbers
|
|
45
|
+
- `i` (integer_bits): Number of bits before decimal point (excludes sign bit)
|
|
46
|
+
- `f` (fractional_bits): Number of bits after decimal point
|
|
47
|
+
- For C++: `W = k + i + f`, `I = k + i`, `S = k`
|
|
48
|
+
|
|
49
|
+
Supported modes:
|
|
50
|
+
- Rounding: `TRN`, `RND`, `RND_CONV`, `TRN_ZERO`, `RND_ZERO`, `RND_MIN_INF`, `RND_INF`
|
|
51
|
+
- `S_RND` and `S_RND_CONV` for stochastic rounding; Not available in NumPy implementation as it is for training only
|
|
52
|
+
- Overflow: `WRAP`, `SAT`, `SAT_SYM`, `WRAP_SM`
|
|
53
|
+
|
|
54
|
+
Limitations:
|
|
55
|
+
- `WRAP_SM` only works with `RND` or `RND_CONV` rounding
|
|
56
|
+
- `WRAP*` modes don't provide surrogate gradients for integer bits
|
|
57
|
+
- Saturation bit forced to zero for `WRAP` and `WRAP_SM`
|
|
58
|
+
|
|
59
|
+
### Minifloat Quantizer
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
- `m` (mantissa_bits): Mantissa width
|
|
63
|
+
- `e` (exponent_bits): Exponent width
|
|
64
|
+
- `e0` (exponent_zero): Exponent bias (default: 0)
|
|
65
|
+
- Range: `[-2^(e-1) + e0, 2^(e-1) - 1 + e0]`
|
|
66
|
+
|
|
67
|
+
Features:
|
|
68
|
+
- Supports subnormal numbers
|
|
69
|
+
- Uses `RND_CONV` rounding and `SAT` overflow
|
|
70
|
+
- HLS-synthesizable implementation in `test/cpp_source/ap_types/ap_float.h`
|
|
71
|
+
|
|
72
|
+
### Simplified Quantizers
|
|
73
|
+
|
|
74
|
+
- **Binary**: Maps to {-1,1} with 0 to -1. (preliminary implementation)
|
|
75
|
+
- **Ternary**: Shorthand for fixed-point `fixed<2, 1, RND_CONV, SAT_SYM>`
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
**requires python>=3.10**
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install quantizers
|
|
84
|
+
```
|
|
85
|
+
`keras>=3.0` and at least one compatible backend (`pytorch`, `jax`, or `tensorflow`) is required for training.
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
### Stateless Quantizers
|
|
90
|
+
```python
|
|
91
|
+
from quantizers import (
|
|
92
|
+
float_quantize(_np), # add _np for NumPy implementation
|
|
93
|
+
get_fixed_quantizer(_np),
|
|
94
|
+
binary_quantize(_np),
|
|
95
|
+
ternary_quantize(_np),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Fixed-point quantizer
|
|
99
|
+
fixed_quantizer = get_fixed_quantizer(round_mode, overflow_mode)
|
|
100
|
+
fixedp_qtensor = fixed_quantizer(
|
|
101
|
+
x,
|
|
102
|
+
integer_bits,
|
|
103
|
+
fractional_bits,
|
|
104
|
+
keep_negative,
|
|
105
|
+
training, # For stochastic rounding, and WRAP does not happen during training
|
|
106
|
+
seed, # For stochastic rounding only
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Minifloat quantizer
|
|
110
|
+
floatp_qtensor = float_quantize(x, mantissa_bits, exponent_bits, exponent_zero)
|
|
111
|
+
|
|
112
|
+
# Simplified quantizers
|
|
113
|
+
binary_qtensor = binary_quantize(x)
|
|
114
|
+
ternary_qtensor = ternary_quantize(x)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Stateful Quantizers
|
|
118
|
+
```python
|
|
119
|
+
# Can be used for, but not intended for training
|
|
120
|
+
fixed_q = FixedQ(
|
|
121
|
+
width,
|
|
122
|
+
integer_bits, # including the sign bit)
|
|
123
|
+
keep_negative,
|
|
124
|
+
fixed_round_mode, # No stochastic rounding
|
|
125
|
+
fixed_overflow_mode
|
|
126
|
+
)
|
|
127
|
+
quantized = fixed_q(x)
|
|
128
|
+
|
|
129
|
+
mfloat_q = MinifloatQ(mantissa_bits, exponent_bits, exponent_zero)
|
|
130
|
+
quantized = mfloat_q(x)
|
|
131
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
quantizers/__init__.py,sha256=o6yFBm_3iSB2Lz2oRTQHYA5o7s49i5La7Ib66P0bxXU,145
|
|
2
|
+
quantizers/_version.py,sha256=DGJ4pj32xs3_DRJhSzQwCiRNnAQrMgo09USYpyMZsKc,411
|
|
3
|
+
quantizers/stateful_quantizers.py,sha256=B7VuDJnpMR7Pp9bPGoq_FGyx8ykLx9B-_6dDKLnRdhA,4007
|
|
4
|
+
quantizers/binary/__init__.py,sha256=tguWtPkpGhAVzAo28fdh1SGc7Bk3Ww33FBU0K829MHo,335
|
|
5
|
+
quantizers/binary/binary_ops.py,sha256=xYFHN-3aENv3swwn7YQIsW9xsLbXXbJXZuCXPYEV07o,687
|
|
6
|
+
quantizers/binary/binary_ops_np.py,sha256=GMjT9BUmkN0ykkLvQGvyh8k6Lj5x-IQRmy92qm0phvo,176
|
|
7
|
+
quantizers/fixed_point/__init__.py,sha256=X7zxaHzTG7uFfW0warVxef2WMvVYtiqNH-Lo3HlcPPo,244
|
|
8
|
+
quantizers/fixed_point/fixed_point_ops.py,sha256=QDsyQYCCuk-FK9nk51Sa1GOuLEidQ8LFwKanLK4a9Xo,8900
|
|
9
|
+
quantizers/fixed_point/fixed_point_ops_np.py,sha256=3oHsEB_R_20u6VrvoPH3Ahx6xf234Xs9hX2S4jQha9A,4748
|
|
10
|
+
quantizers/minifloat/__init__.py,sha256=6AZVKSRESofllv0joQR6KAs4w9Eay8f6UAkE89ieW5s,333
|
|
11
|
+
quantizers/minifloat/float_point_ops.py,sha256=xOrqoHtPCBb2dJnbiMX3mMyf2JsPUKXGs96JGJeIezg,2105
|
|
12
|
+
quantizers/minifloat/float_point_ops_np.py,sha256=e2t_cOjuzwd_JGKEmHZj-POY-SLiXOtPKbswzJfkndc,1522
|
|
13
|
+
quantizers-1.0.0.dist-info/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
|
|
14
|
+
quantizers-1.0.0.dist-info/METADATA,sha256=gg-4sGI-3RtD53Tr1APALE774MXO1BtKJSK07KxEZyA,4306
|
|
15
|
+
quantizers-1.0.0.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
|
16
|
+
quantizers-1.0.0.dist-info/top_level.txt,sha256=K9nZlz267J8tCfY-mMGqqb8umqII1BxWSIXgC4J-vn0,11
|
|
17
|
+
quantizers-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
quantizers
|