quantizers 1.0.1__tar.gz → 1.1.0__tar.gz
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-1.0.1 → quantizers-1.1.0}/.pre-commit-config.yaml +5 -14
- {quantizers-1.0.1/src/quantizers.egg-info → quantizers-1.1.0}/PKG-INFO +1 -1
- {quantizers-1.0.1 → quantizers-1.1.0}/pyproject.toml +26 -1
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/__init__.py +2 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/_version.py +2 -2
- quantizers-1.1.0/src/quantizers/binary/__init__.py +4 -0
- quantizers-1.0.1/src/quantizers/binary/binary_ops.py → quantizers-1.1.0/src/quantizers/binary/_binary_ops.py +6 -7
- quantizers-1.1.0/src/quantizers/binary/binary_ops.py +10 -0
- quantizers-1.1.0/src/quantizers/binary/binary_ops_np.py +9 -0
- quantizers-1.1.0/src/quantizers/fixed_point/__init__.py +4 -0
- quantizers-1.0.1/src/quantizers/fixed_point/fixed_point_ops.py → quantizers-1.1.0/src/quantizers/fixed_point/_fixed_point_ops.py +31 -21
- quantizers-1.1.0/src/quantizers/fixed_point/fixed_point_ops.py +22 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/fixed_point/fixed_point_ops_np.py +18 -14
- quantizers-1.1.0/src/quantizers/minifloat/__init__.py +4 -0
- quantizers-1.0.1/src/quantizers/minifloat/float_point_ops.py → quantizers-1.1.0/src/quantizers/minifloat/_float_point_ops.py +17 -17
- quantizers-1.1.0/src/quantizers/minifloat/float_point_ops.py +12 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/minifloat/float_point_ops_np.py +12 -12
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/stateful_quantizers.py +8 -16
- {quantizers-1.0.1 → quantizers-1.1.0/src/quantizers.egg-info}/PKG-INFO +1 -1
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers.egg-info/SOURCES.txt +3 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/test_quantizer_match.py +14 -7
- {quantizers-1.0.1 → quantizers-1.1.0}/test/test_stateless_quantizer.py +1 -1
- quantizers-1.0.1/src/quantizers/binary/__init__.py +0 -9
- quantizers-1.0.1/src/quantizers/binary/binary_ops_np.py +0 -9
- quantizers-1.0.1/src/quantizers/fixed_point/__init__.py +0 -8
- quantizers-1.0.1/src/quantizers/minifloat/__init__.py +0 -9
- {quantizers-1.0.1 → quantizers-1.1.0}/.clang-format +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/.github/workflows/python-publish.yml +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/.github/workflows/python-test.yml +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/.gitignore +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/LICENSE +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/README.md +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/setup.cfg +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers.egg-info/dependency_links.txt +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers.egg-info/requires.txt +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers.egg-info/top_level.txt +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_binary.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_common.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_decl.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_fixed.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_fixed_base.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_fixed_ref.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_fixed_special.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_float.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_int.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_int_base.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_int_ref.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_int_special.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_shift_reg.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/etc/ap_private.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/hls_math.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/hls_stream.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/test.cpp +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/utils/x_hls_utils.h +0 -0
- {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/quantizers.h +0 -0
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
exclude: '\.svg$|ap_types/|_version.py'
|
|
2
2
|
|
|
3
3
|
repos:
|
|
4
|
-
- repo: https://github.com/
|
|
5
|
-
rev:
|
|
4
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
5
|
+
rev: v0.8.2
|
|
6
6
|
hooks:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'--ignore=E731,E402',
|
|
11
|
-
'--in-place',
|
|
12
|
-
]
|
|
7
|
+
- id: ruff
|
|
8
|
+
args: [--fix]
|
|
9
|
+
- id: ruff-format
|
|
13
10
|
|
|
14
11
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
15
12
|
rev: v4.6.0
|
|
@@ -36,12 +33,6 @@ repos:
|
|
|
36
33
|
- id: pyupgrade
|
|
37
34
|
args: ["--py310-plus"]
|
|
38
35
|
|
|
39
|
-
- repo: https://github.com/PyCQA/isort
|
|
40
|
-
rev: 5.13.2
|
|
41
|
-
hooks:
|
|
42
|
-
- id: isort
|
|
43
|
-
args: ['--line-length=65535']
|
|
44
|
-
|
|
45
36
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
|
46
37
|
rev: v18.1.8
|
|
47
38
|
hooks:
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "quantizers"
|
|
8
|
-
version = "1.0
|
|
8
|
+
version = "1.1.0"
|
|
9
9
|
authors = [{ name = "Chang Sun", email = "chsun@cern.ch" }]
|
|
10
10
|
description = ""
|
|
11
11
|
readme = "README.md"
|
|
@@ -70,3 +70,28 @@ addopts = "--import-mode=importlib --cov=src/quantizers --cov-report=xml --cov-r
|
|
|
70
70
|
|
|
71
71
|
[tool.coverage.run]
|
|
72
72
|
omit = ["*/_version.py"]
|
|
73
|
+
|
|
74
|
+
[tool.coverage.report]
|
|
75
|
+
exclude_lines = ['pragma: no cover', 'if TYPE_CHECKING:']
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
include = ["src/**/*.py"]
|
|
79
|
+
|
|
80
|
+
line-length = 130
|
|
81
|
+
indent-width = 4
|
|
82
|
+
|
|
83
|
+
target-version = "py310"
|
|
84
|
+
|
|
85
|
+
[tool.ruff.format]
|
|
86
|
+
quote-style = "single"
|
|
87
|
+
indent-style = "space"
|
|
88
|
+
skip-magic-trailing-comma = false
|
|
89
|
+
docstring-code-format = true
|
|
90
|
+
docstring-code-line-length = 130
|
|
91
|
+
|
|
92
|
+
[tool.ruff.lint]
|
|
93
|
+
select = ["E", "W", "F", "I", "F401"]
|
|
94
|
+
ignore = ["E501", "F403", "F405"]
|
|
95
|
+
explicit-preview-rules = true
|
|
96
|
+
fixable = ["ALL"]
|
|
97
|
+
unfixable = []
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
from keras import ops
|
|
2
|
-
from keras.api.random import SeedGenerator
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
@ops.custom_gradient
|
|
6
|
-
def
|
|
7
|
-
r = ops.where(x > 0, 1
|
|
5
|
+
def _binary_quantize(x):
|
|
6
|
+
r = ops.where(x > 0, 1.0, -1.0)
|
|
8
7
|
|
|
9
8
|
def grad(*args, upstream=None):
|
|
10
9
|
if upstream is None:
|
|
11
10
|
(upstream,) = args
|
|
12
11
|
dy = upstream
|
|
13
|
-
return dy * (1. - ops.tanh(x)**2) # type: ignore
|
|
12
|
+
return dy * (1.0 - ops.tanh(x) ** 2) # type: ignore
|
|
14
13
|
|
|
15
14
|
return ops.stop_gradient(r), grad
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
@ops.custom_gradient
|
|
19
|
-
def
|
|
20
|
-
r = ops.where(x > 0.5, 1
|
|
18
|
+
def _ternary_quantize(x):
|
|
19
|
+
r = ops.where(x > 0.5, 1.0, ops.where(x < -0.5, -1.0, 0.0))
|
|
21
20
|
|
|
22
21
|
def grad(*args, upstream=None):
|
|
23
22
|
if upstream is None:
|
|
24
23
|
(upstream,) = args
|
|
25
24
|
dy = upstream
|
|
26
|
-
return dy * (1. - ops.tanh(x)**2) # type: ignore
|
|
25
|
+
return dy * (1.0 - ops.tanh(x) ** 2) # type: ignore
|
|
27
26
|
|
|
28
27
|
return ops.stop_gradient(r), grad
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
|
-
from functools import singledispatch
|
|
3
2
|
from typing import Any, TypeVar
|
|
4
3
|
|
|
5
4
|
import keras
|
|
6
5
|
from keras import ops
|
|
7
6
|
from keras.api.random import SeedGenerator
|
|
7
|
+
from numpy.typing import ArrayLike
|
|
8
8
|
|
|
9
9
|
round_mode_registry: dict[str, Callable[[Any, bool | None], Any]] = {}
|
|
10
10
|
round_mode_registry_scaled: dict[str, Callable[[Any, Any, bool | None, SeedGenerator | None], Any]] = {}
|
|
11
11
|
saturation_mode_registry: dict[str, Callable[[Any, Any, Any, Any, bool | None], Any]] = {}
|
|
12
|
-
from numpy.typing import ArrayLike
|
|
13
12
|
|
|
14
13
|
T = TypeVar('T', bound=ArrayLike)
|
|
15
14
|
|
|
@@ -24,10 +23,11 @@ def _clip(x, min_value, max_value):
|
|
|
24
23
|
if upstream is None:
|
|
25
24
|
(upstream,) = args
|
|
26
25
|
dy = upstream
|
|
27
|
-
dx = ops.where(~(mask1 | mask2), dy, 0.)
|
|
28
|
-
dmax = ops.where(mask1, dy, 0.)
|
|
29
|
-
dmin = ops.where(mask2, dy, 0.)
|
|
26
|
+
dx = ops.where(~(mask1 | mask2), dy, 0.0)
|
|
27
|
+
dmax = ops.where(mask1, dy, 0.0)
|
|
28
|
+
dmin = ops.where(mask2, dy, 0.0)
|
|
30
29
|
return dx, dmin, dmax
|
|
30
|
+
|
|
31
31
|
return r, grad
|
|
32
32
|
|
|
33
33
|
|
|
@@ -37,7 +37,7 @@ def rnd_mode(names: str | list[str] | tuple[str, ...]):
|
|
|
37
37
|
def inner(func):
|
|
38
38
|
@keras.ops.custom_gradient
|
|
39
39
|
def inner_wrapper(x, f):
|
|
40
|
-
scale = 2
|
|
40
|
+
scale = 2.0**f
|
|
41
41
|
sx = x * scale
|
|
42
42
|
sxq = func(sx)
|
|
43
43
|
xq = sxq / scale
|
|
@@ -48,8 +48,9 @@ def rnd_mode(names: str | list[str] | tuple[str, ...]):
|
|
|
48
48
|
(upstream,) = args
|
|
49
49
|
dy = upstream
|
|
50
50
|
dx = dy
|
|
51
|
-
df = -
|
|
51
|
+
df = -ops.log(2.0) * delta * dy # type: ignore
|
|
52
52
|
return dx, df
|
|
53
|
+
|
|
53
54
|
return ops.stop_gradient(xq), grad
|
|
54
55
|
|
|
55
56
|
@keras.ops.custom_gradient
|
|
@@ -61,6 +62,7 @@ def rnd_mode(names: str | list[str] | tuple[str, ...]):
|
|
|
61
62
|
(upstream,) = args
|
|
62
63
|
dy = upstream
|
|
63
64
|
return dy
|
|
65
|
+
|
|
64
66
|
return ops.stop_gradient(r), grad
|
|
65
67
|
|
|
66
68
|
def wrapper(x, f, training=None, seed_gen=None): # type: ignore
|
|
@@ -80,6 +82,7 @@ def rnd_mode(names: str | list[str] | tuple[str, ...]):
|
|
|
80
82
|
round_mode_registry[func.__name__.upper()] = ste_wrapper
|
|
81
83
|
|
|
82
84
|
return ste_wrapper
|
|
85
|
+
|
|
83
86
|
return inner
|
|
84
87
|
|
|
85
88
|
|
|
@@ -136,6 +139,7 @@ def sat_mode(name: str | list | tuple):
|
|
|
136
139
|
saturation_mode_registry[name] = func
|
|
137
140
|
saturation_mode_registry[func.__name__.upper()] = func
|
|
138
141
|
return func
|
|
142
|
+
|
|
139
143
|
return inner
|
|
140
144
|
|
|
141
145
|
|
|
@@ -146,14 +150,14 @@ def wrap(x, k, i, f, training=None):
|
|
|
146
150
|
|
|
147
151
|
xs = x
|
|
148
152
|
bk = i + k
|
|
149
|
-
bias = k * 2
|
|
150
|
-
return (
|
|
153
|
+
bias = k * 2.0 ** (bk - 1)
|
|
154
|
+
return (xs + bias) % (2.0**bk) - bias
|
|
151
155
|
|
|
152
156
|
|
|
153
157
|
@sat_mode('SAT')
|
|
154
158
|
def sat(x, k, i, f, training=None):
|
|
155
|
-
f_eps = 2
|
|
156
|
-
__max = 2
|
|
159
|
+
f_eps = 2.0 ** (-f)
|
|
160
|
+
__max = 2.0**i
|
|
157
161
|
_max = __max - f_eps
|
|
158
162
|
_min = -__max * k
|
|
159
163
|
r = _clip(x, _min, _max)
|
|
@@ -162,8 +166,8 @@ def sat(x, k, i, f, training=None):
|
|
|
162
166
|
|
|
163
167
|
@sat_mode('SAT_SYM')
|
|
164
168
|
def sat_sym(x, k, i, f, training=None):
|
|
165
|
-
f_eps = 2
|
|
166
|
-
_max = 2
|
|
169
|
+
f_eps = 2.0 ** (-f)
|
|
170
|
+
_max = 2.0**i - f_eps
|
|
167
171
|
_min = -_max * k
|
|
168
172
|
r = _clip(x, _min, _max)
|
|
169
173
|
return r
|
|
@@ -173,8 +177,8 @@ def sat_sym(x, k, i, f, training=None):
|
|
|
173
177
|
def wrap_sm_fn(x, k, i, f, training=None, quant_fn: Callable = lambda x: x):
|
|
174
178
|
# x=ops.round(x*2.**f)
|
|
175
179
|
# High and low bounds are reflective. When overflows, can be less trash than WARP but still more trash than SAT.
|
|
176
|
-
eps = 2
|
|
177
|
-
high = 2
|
|
180
|
+
eps = 2.0**-f
|
|
181
|
+
high = 2.0**i - eps
|
|
178
182
|
low = -(high + eps) * k
|
|
179
183
|
interval = 2 ** (i + k)
|
|
180
184
|
c1 = ((x) / interval) % 2 >= 1 # type: ignore
|
|
@@ -189,7 +193,9 @@ def wrap_sm_fn(x, k, i, f, training=None, quant_fn: Callable = lambda x: x):
|
|
|
189
193
|
return mapped
|
|
190
194
|
|
|
191
195
|
|
|
192
|
-
def
|
|
196
|
+
def _get_fixed_quantizer(
|
|
197
|
+
round_mode: str = 'TRN', overflow_mode: str = 'WRAP'
|
|
198
|
+
) -> Callable[[T, Any, Any, Any, bool | None, SeedGenerator | None], T]:
|
|
193
199
|
"""Get a stateless fixed-point quantizer given the round and overflow mode.
|
|
194
200
|
The quantizer is differentiable w.r.t. to the input and f, also i if using saturation overflow mode.
|
|
195
201
|
|
|
@@ -200,13 +206,13 @@ def get_fixed_quantizer(round_mode: str = 'TRN', overflow_mode: str = 'WRAP') ->
|
|
|
200
206
|
overflow_mode = overflow_mode.upper()
|
|
201
207
|
round_fn_scaled = round_mode_registry_scaled.get(round_mode, None)
|
|
202
208
|
if round_fn_scaled is None:
|
|
203
|
-
raise ValueError(f
|
|
209
|
+
raise ValueError(f'Unknown rounding mode: {round_mode}')
|
|
204
210
|
sat_fn = saturation_mode_registry.get(overflow_mode, None)
|
|
205
211
|
if sat_fn is None:
|
|
206
|
-
raise ValueError(f
|
|
212
|
+
raise ValueError(f'Unknown saturation mode: {overflow_mode}')
|
|
207
213
|
|
|
208
214
|
if overflow_mode == 'WRAP_SM':
|
|
209
|
-
assert round_mode in ('RND', 'RND_CONV'),
|
|
215
|
+
assert round_mode in ('RND', 'RND_CONV'), 'WRAP_SM only supports RND and RND_CONV rounding modes in this implementation.'
|
|
210
216
|
|
|
211
217
|
def quantizer(x: T, k: T, i: T, f: T, training: bool | None = None, seed_gen: SeedGenerator | None = None) -> T:
|
|
212
218
|
"""Stateless fixed-point quantizer.
|
|
@@ -220,8 +226,10 @@ def get_fixed_quantizer(round_mode: str = 'TRN', overflow_mode: str = 'WRAP') ->
|
|
|
220
226
|
i = ops.stop_gradient(ops.maximum(i, -f)) + (i - ops.stop_gradient(i)) # type: ignore
|
|
221
227
|
|
|
222
228
|
if overflow_mode == 'WRAP_SM':
|
|
229
|
+
|
|
223
230
|
def rnd_fn_wrapped(x):
|
|
224
231
|
return round_fn_scaled(x, f, training, seed_gen)
|
|
232
|
+
|
|
225
233
|
return wrap_sm_fn(x, k, i, f, training, rnd_fn_wrapped) # type: ignore
|
|
226
234
|
|
|
227
235
|
# Workaround for gradient computation around 0.
|
|
@@ -233,6 +241,7 @@ def get_fixed_quantizer(round_mode: str = 'TRN', overflow_mode: str = 'WRAP') ->
|
|
|
233
241
|
if overflow_mode == 'WRAP':
|
|
234
242
|
x = sat_fn(x, k, i, f, training)
|
|
235
243
|
return x
|
|
244
|
+
|
|
236
245
|
return quantizer
|
|
237
246
|
|
|
238
247
|
|
|
@@ -240,7 +249,7 @@ def get_fixed_quantizer(round_mode: str = 'TRN', overflow_mode: str = 'WRAP') ->
|
|
|
240
249
|
def register_stochastic_rounding():
|
|
241
250
|
@keras.ops.custom_gradient
|
|
242
251
|
def stochastic_scaled(x, f, noise):
|
|
243
|
-
scale = 2
|
|
252
|
+
scale = 2.0**f
|
|
244
253
|
sx = x * scale
|
|
245
254
|
sxq = ops.floor(sx + noise)
|
|
246
255
|
xq = sxq / scale
|
|
@@ -251,8 +260,9 @@ def register_stochastic_rounding():
|
|
|
251
260
|
(upstream,) = args
|
|
252
261
|
dy = upstream
|
|
253
262
|
dx = dy
|
|
254
|
-
df = -
|
|
263
|
+
df = -ops.log(2.0) * delta * dy # type: ignore
|
|
255
264
|
return dx, df, None
|
|
265
|
+
|
|
256
266
|
return ops.stop_gradient(xq), grad
|
|
257
267
|
|
|
258
268
|
rnd_scaled = round_mode_registry_scaled['RND']
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
3
|
+
|
|
4
|
+
from numpy.typing import ArrayLike
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from keras.api.random import SeedGenerator
|
|
8
|
+
T = TypeVar('T', bound=ArrayLike)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_fixed_quantizer(
|
|
12
|
+
round_mode: str = 'TRN', overflow_mode: str = 'WRAP'
|
|
13
|
+
) -> Callable[[T, Any, Any, Any, bool | None, 'SeedGenerator | None'], T]:
|
|
14
|
+
"""Get a stateless fixed-point quantizer given the round and overflow mode.
|
|
15
|
+
The quantizer is differentiable w.r.t. to the input and f, also i if using saturation overflow mode.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
round_mode: round mode, one of
|
|
19
|
+
"""
|
|
20
|
+
from ._fixed_point_ops import _get_fixed_quantizer
|
|
21
|
+
|
|
22
|
+
return _get_fixed_quantizer(round_mode, overflow_mode)
|
|
@@ -2,7 +2,6 @@ from collections.abc import Callable
|
|
|
2
2
|
from typing import Any, TypeVar
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
|
-
from numpy.typing import ArrayLike
|
|
6
5
|
|
|
7
6
|
round_mode_registry_np: dict[str, Callable[[Any, bool | None], Any]] = {}
|
|
8
7
|
round_mode_registry_scaled_np: dict[str, Callable[[Any, Any, bool | None], Any]] = {}
|
|
@@ -16,7 +15,7 @@ def rnd_mode_np(names: str | list[str] | tuple[str, ...]):
|
|
|
16
15
|
|
|
17
16
|
def inner(func):
|
|
18
17
|
def wrapper(x, f, training=None, seed_gen=None):
|
|
19
|
-
scale = 2
|
|
18
|
+
scale = 2.0**f
|
|
20
19
|
sx = x * scale
|
|
21
20
|
sxq = func(sx)
|
|
22
21
|
xq = sxq / scale
|
|
@@ -33,6 +32,7 @@ def rnd_mode_np(names: str | list[str] | tuple[str, ...]):
|
|
|
33
32
|
round_mode_registry_np[func.__name__.upper()] = func
|
|
34
33
|
|
|
35
34
|
return func
|
|
35
|
+
|
|
36
36
|
return inner
|
|
37
37
|
|
|
38
38
|
|
|
@@ -83,6 +83,7 @@ def sat_mode_np(name: str | list | tuple):
|
|
|
83
83
|
saturation_mode_registry_np[name] = func
|
|
84
84
|
saturation_mode_registry_np[func.__name__.upper()] = func
|
|
85
85
|
return func
|
|
86
|
+
|
|
86
87
|
return inner
|
|
87
88
|
|
|
88
89
|
|
|
@@ -90,14 +91,14 @@ def sat_mode_np(name: str | list | tuple):
|
|
|
90
91
|
def wrap(x, k, i, f, training=None):
|
|
91
92
|
xs = x
|
|
92
93
|
bk = i + k
|
|
93
|
-
bias = k * 2
|
|
94
|
-
return (
|
|
94
|
+
bias = k * 2.0 ** (bk - 1)
|
|
95
|
+
return (xs + bias) % (2.0**bk) - bias
|
|
95
96
|
|
|
96
97
|
|
|
97
98
|
@sat_mode_np('SAT')
|
|
98
99
|
def sat(x, k, i, f, training=None):
|
|
99
|
-
f_eps = 2
|
|
100
|
-
__max = 2
|
|
100
|
+
f_eps = 2.0 ** (-f)
|
|
101
|
+
__max = 2.0**i
|
|
101
102
|
_max = __max - f_eps
|
|
102
103
|
_min = -__max * k
|
|
103
104
|
return np.clip(x, _min, _max)
|
|
@@ -105,16 +106,16 @@ def sat(x, k, i, f, training=None):
|
|
|
105
106
|
|
|
106
107
|
@sat_mode_np('SAT_SYM')
|
|
107
108
|
def sat_sym(x, k, i, f, training=None):
|
|
108
|
-
f_eps = 2
|
|
109
|
-
_max = 2
|
|
109
|
+
f_eps = 2.0 ** (-f)
|
|
110
|
+
_max = 2.0**i - f_eps
|
|
110
111
|
_min = -_max * k
|
|
111
112
|
return np.clip(x, _min, _max)
|
|
112
113
|
|
|
113
114
|
|
|
114
115
|
@sat_mode_np('WRAP_SM')
|
|
115
116
|
def wrap_sm_fn(x, k, i, f, training=None, quant_fn: Callable = lambda x: x):
|
|
116
|
-
eps = 2
|
|
117
|
-
high = 2
|
|
117
|
+
eps = 2.0**-f
|
|
118
|
+
high = 2.0**i - eps
|
|
118
119
|
low = -(high + eps) * k
|
|
119
120
|
interval = 2 ** (i + k)
|
|
120
121
|
c1 = ((x) / interval) % 2 >= 1
|
|
@@ -136,22 +137,24 @@ def get_fixed_quantizer_np(round_mode: str = 'TRN', overflow_mode: str = 'WRAP')
|
|
|
136
137
|
|
|
137
138
|
round_fn_scaled = round_mode_registry_scaled_np.get(round_mode, None)
|
|
138
139
|
if round_fn_scaled is None:
|
|
139
|
-
raise ValueError(f
|
|
140
|
+
raise ValueError(f'Unknown rounding mode: {round_mode}')
|
|
140
141
|
sat_fn = saturation_mode_registry_np.get(overflow_mode, None)
|
|
141
142
|
if sat_fn is None:
|
|
142
|
-
raise ValueError(f
|
|
143
|
+
raise ValueError(f'Unknown saturation mode: {overflow_mode}')
|
|
143
144
|
|
|
144
145
|
if overflow_mode == 'WRAP_SM':
|
|
145
|
-
assert round_mode in ('RND', 'RND_CONV'),
|
|
146
|
+
assert round_mode in ('RND', 'RND_CONV'), 'WRAP_SM only supports RND and RND_CONV rounding modes in this implementation.'
|
|
146
147
|
|
|
147
148
|
def quantizer(x: T, k: T, i: T, f: T, training: bool | None = False, seed_gen: None = None) -> np.ndarray:
|
|
148
|
-
assert not training,
|
|
149
|
+
assert not training, 'Training mode not supported in numpy implementation.'
|
|
149
150
|
|
|
150
151
|
_i = np.maximum(i, -f)
|
|
151
152
|
|
|
152
153
|
if overflow_mode == 'WRAP_SM':
|
|
154
|
+
|
|
153
155
|
def rnd_fn_wrapped(x):
|
|
154
156
|
return round_fn_scaled(x, f, training)
|
|
157
|
+
|
|
155
158
|
return wrap_sm_fn(x, k, _i, f, training, rnd_fn_wrapped)
|
|
156
159
|
|
|
157
160
|
if overflow_mode != 'WRAP':
|
|
@@ -160,4 +163,5 @@ def get_fixed_quantizer_np(round_mode: str = 'TRN', overflow_mode: str = 'WRAP')
|
|
|
160
163
|
if overflow_mode == 'WRAP':
|
|
161
164
|
x = sat_fn(x, k, _i, f, training)
|
|
162
165
|
return x
|
|
166
|
+
|
|
163
167
|
return quantizer
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import numpy as np
|
|
2
1
|
from keras import ops
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
@ops.custom_gradient
|
|
6
|
-
def
|
|
7
|
-
|
|
5
|
+
def _float_quantize(x, m, e, e0=0.0):
|
|
6
|
+
"""Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl."""
|
|
8
7
|
m = m + 1
|
|
9
8
|
eps = 1e-7
|
|
10
9
|
|
|
11
10
|
e_req = ops.floor(ops.log2(ops.abs(x) + eps)) # type: ignore
|
|
12
|
-
_e_high = ops.maximum(2
|
|
11
|
+
_e_high = ops.maximum(2.0 ** (e - 1), 1.0)
|
|
13
12
|
e_low, e_high = -_e_high + e0 + 1, _e_high + e0 - 1 # type: ignore
|
|
14
13
|
e_act = ops.clip(e_req, e_low, e_high)
|
|
15
|
-
scale = 2
|
|
14
|
+
scale = 2.0 ** (e_act - m + 1)
|
|
16
15
|
sig = x / scale
|
|
17
16
|
qsig = ops.round(sig)
|
|
18
|
-
_sig_high = 2
|
|
17
|
+
_sig_high = 2.0**m
|
|
19
18
|
_sig_high = ops.where(e_high != e_act, _sig_high, _sig_high - 1)
|
|
20
19
|
clip_sig = ops.clip(qsig, -_sig_high, _sig_high) # type: ignore
|
|
21
20
|
qx = clip_sig * scale
|
|
@@ -26,31 +25,32 @@ def float_quantize(x, m, e, e0=0.):
|
|
|
26
25
|
dy = upstream
|
|
27
26
|
mask_e = e_req != e_act
|
|
28
27
|
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
|
|
28
|
+
dm = ops.where(mask_m, scale * (sig - qsig) * dy * ops.log(2.0), 0.0)
|
|
29
|
+
d_exp = ops.where(mask_e, (x - qx) * dy * ops.log(2.0), 0.0)
|
|
30
|
+
de = d_exp * ops.log(2.0) * _e_high # type: ignore
|
|
32
31
|
de0 = ops.where(e_req > e_high, d_exp, -d_exp) # type: ignore
|
|
33
32
|
return dy, dm, de, de0
|
|
34
33
|
|
|
35
34
|
return ops.stop_gradient(qx), grad
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
def
|
|
39
|
-
|
|
37
|
+
def _float_decompose(x, m, e, e0=0):
|
|
38
|
+
"""Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl."""
|
|
39
|
+
|
|
40
40
|
m = m + 1
|
|
41
41
|
eps = 1e-7
|
|
42
42
|
|
|
43
43
|
e_req = ops.floor(ops.log2(ops.abs(x) + eps)) # type: ignore
|
|
44
|
-
_e_high = 2
|
|
44
|
+
_e_high = 2.0 ** (e - 1)
|
|
45
45
|
e_low, e_high = -_e_high + e0, _e_high + e0 - 1
|
|
46
46
|
e_act = ops.clip(e_req, e_low + 1, e_high)
|
|
47
|
-
scale = 2
|
|
47
|
+
scale = 2.0 ** (e_act - m + 1)
|
|
48
48
|
sig = x / scale
|
|
49
49
|
qsig = ops.round(sig)
|
|
50
|
-
cond = (e_act != e_high) & (ops.abs(qsig) == 2
|
|
50
|
+
cond = (e_act != e_high) & (ops.abs(qsig) == 2.0**m)
|
|
51
51
|
e_act = ops.where(cond, e_act + 1, e_act) # type: ignore
|
|
52
|
-
qsig = ops.where(cond, qsig / 2
|
|
53
|
-
r_mantissa_bound = 2 - 2
|
|
54
|
-
r_mantissa = ops.clip(qsig * 2
|
|
52
|
+
qsig = ops.where(cond, qsig / 2.0, qsig) # type: ignore
|
|
53
|
+
r_mantissa_bound = 2 - 2.0 ** (-m + 1)
|
|
54
|
+
r_mantissa = ops.clip(qsig * 2.0 ** (-m + 1), -r_mantissa_bound, r_mantissa_bound)
|
|
55
55
|
r_exponent = e_act
|
|
56
56
|
return r_mantissa, r_exponent
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
def float_quantize(x, m, e, e0=0.0):
|
|
2
|
+
"""Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl."""
|
|
3
|
+
from ._float_point_ops import _float_quantize
|
|
4
|
+
|
|
5
|
+
return _float_quantize(x, m, e, e0)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def float_decompose(x, m, e, e0=0):
|
|
9
|
+
"""Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl."""
|
|
10
|
+
from ._float_point_ops import _float_decompose
|
|
11
|
+
|
|
12
|
+
return _float_decompose(x, m, e, e0)
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
def float_quantize_np(x, m, e, e0=0.):
|
|
5
|
-
|
|
4
|
+
def float_quantize_np(x, m, e, e0=0.0):
|
|
5
|
+
"""Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl."""
|
|
6
6
|
m = m + 1
|
|
7
7
|
eps = 1e-7
|
|
8
8
|
|
|
9
9
|
e_req = np.floor(np.log2(np.abs(x) + eps)) # type: ignore
|
|
10
|
-
_e_high = np.maximum(2
|
|
10
|
+
_e_high = np.maximum(2.0 ** (e - 1), 1.0)
|
|
11
11
|
e_low, e_high = -_e_high + e0 + 1, _e_high + e0 - 1 # type: ignore
|
|
12
12
|
e_act = np.clip(e_req, e_low, e_high)
|
|
13
|
-
scale = 2
|
|
13
|
+
scale = 2.0 ** (e_act - m + 1)
|
|
14
14
|
sig = x / scale
|
|
15
15
|
qsig = np.round(sig)
|
|
16
|
-
_sig_high = 2
|
|
16
|
+
_sig_high = 2.0**m
|
|
17
17
|
_sig_high = np.where(e_high != e_act, _sig_high, _sig_high - 1)
|
|
18
18
|
clip_sig = np.clip(qsig, -_sig_high, _sig_high) # type: ignore
|
|
19
19
|
qx = clip_sig * scale
|
|
@@ -22,21 +22,21 @@ def float_quantize_np(x, m, e, e0=0.):
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def float_decompose_np(x, m, e, e0=0):
|
|
25
|
-
|
|
25
|
+
"""Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl."""
|
|
26
26
|
m = m + 1
|
|
27
27
|
eps = 1e-7
|
|
28
28
|
|
|
29
29
|
e_req = np.floor(np.log2(np.abs(x) + eps)) # type: ignore
|
|
30
|
-
_e_high = 2
|
|
30
|
+
_e_high = 2.0 ** (e - 1)
|
|
31
31
|
e_low, e_high = -_e_high + e0, _e_high + e0 - 1
|
|
32
32
|
e_act = np.clip(e_req, e_low + 1, e_high)
|
|
33
|
-
scale = 2
|
|
33
|
+
scale = 2.0 ** (e_act - m + 1)
|
|
34
34
|
sig = x / scale
|
|
35
35
|
qsig = np.round(sig)
|
|
36
|
-
cond = (e_act != e_high) & (np.abs(qsig) == 2
|
|
36
|
+
cond = (e_act != e_high) & (np.abs(qsig) == 2.0**m)
|
|
37
37
|
e_act = np.where(cond, e_act + 1, e_act) # type: ignore
|
|
38
|
-
qsig = np.where(cond, qsig / 2
|
|
39
|
-
r_mantissa_bound = 2 - 2
|
|
40
|
-
r_mantissa = np.clip(qsig * 2
|
|
38
|
+
qsig = np.where(cond, qsig / 2.0, qsig) # type: ignore
|
|
39
|
+
r_mantissa_bound = 2 - 2.0 ** (-m + 1)
|
|
40
|
+
r_mantissa = np.clip(qsig * 2.0 ** (-m + 1), -r_mantissa_bound, r_mantissa_bound)
|
|
41
41
|
r_exponent = e_act
|
|
42
42
|
return r_mantissa, r_exponent
|
|
@@ -18,12 +18,10 @@ class BinaryQ:
|
|
|
18
18
|
"""Stateful binary quantizer."""
|
|
19
19
|
|
|
20
20
|
@overload
|
|
21
|
-
def __call__(self, x: T) -> np.ndarray:
|
|
22
|
-
... # pragma: no cover
|
|
21
|
+
def __call__(self, x: T) -> np.ndarray: ...
|
|
23
22
|
|
|
24
23
|
@overload
|
|
25
|
-
def __call__(self, x: Any) -> Any:
|
|
26
|
-
... # pragma: no cover
|
|
24
|
+
def __call__(self, x: Any) -> Any: ...
|
|
27
25
|
|
|
28
26
|
def __call__(self, x):
|
|
29
27
|
use_numpy = not is_tensor(x)
|
|
@@ -40,12 +38,10 @@ class TernaryQ:
|
|
|
40
38
|
"""Stateful ternary quantizer."""
|
|
41
39
|
|
|
42
40
|
@overload
|
|
43
|
-
def __call__(self, x: T) -> np.ndarray:
|
|
44
|
-
... # pragma: no cover
|
|
41
|
+
def __call__(self, x: T) -> np.ndarray: ...
|
|
45
42
|
|
|
46
43
|
@overload
|
|
47
|
-
def __call__(self, x: Any) -> Any:
|
|
48
|
-
... # pragma: no cover
|
|
44
|
+
def __call__(self, x: Any) -> Any: ...
|
|
49
45
|
|
|
50
46
|
def __call__(self, x):
|
|
51
47
|
use_numpy = not is_tensor(x)
|
|
@@ -83,12 +79,10 @@ class FixedQ:
|
|
|
83
79
|
self.overflow = overflow
|
|
84
80
|
|
|
85
81
|
@overload
|
|
86
|
-
def __call__(self, x: T) -> np.ndarray:
|
|
87
|
-
... # pragma: no cover
|
|
82
|
+
def __call__(self, x: T) -> np.ndarray: ...
|
|
88
83
|
|
|
89
84
|
@overload
|
|
90
|
-
def __call__(self, x: Any) -> Any:
|
|
91
|
-
... # pragma: no cover
|
|
85
|
+
def __call__(self, x: Any) -> Any: ...
|
|
92
86
|
|
|
93
87
|
def __call__(self, x):
|
|
94
88
|
use_numpy = not is_tensor(x)
|
|
@@ -123,12 +117,10 @@ class MinifloatQ:
|
|
|
123
117
|
self.exponent_bias = exponent_bias
|
|
124
118
|
|
|
125
119
|
@overload
|
|
126
|
-
def __call__(self, x: T) -> np.ndarray:
|
|
127
|
-
... # pragma: no cover
|
|
120
|
+
def __call__(self, x: T) -> np.ndarray: ...
|
|
128
121
|
|
|
129
122
|
@overload
|
|
130
|
-
def __call__(self, x: Any) -> Any:
|
|
131
|
-
... # pragma: no cover
|
|
123
|
+
def __call__(self, x: Any) -> Any: ...
|
|
132
124
|
|
|
133
125
|
def __call__(self, x):
|
|
134
126
|
s, e, e0 = float(self.significant), float(self.exponent), float(self.exponent_bias)
|
|
@@ -15,12 +15,15 @@ src/quantizers.egg-info/dependency_links.txt
|
|
|
15
15
|
src/quantizers.egg-info/requires.txt
|
|
16
16
|
src/quantizers.egg-info/top_level.txt
|
|
17
17
|
src/quantizers/binary/__init__.py
|
|
18
|
+
src/quantizers/binary/_binary_ops.py
|
|
18
19
|
src/quantizers/binary/binary_ops.py
|
|
19
20
|
src/quantizers/binary/binary_ops_np.py
|
|
20
21
|
src/quantizers/fixed_point/__init__.py
|
|
22
|
+
src/quantizers/fixed_point/_fixed_point_ops.py
|
|
21
23
|
src/quantizers/fixed_point/fixed_point_ops.py
|
|
22
24
|
src/quantizers/fixed_point/fixed_point_ops_np.py
|
|
23
25
|
src/quantizers/minifloat/__init__.py
|
|
26
|
+
src/quantizers/minifloat/_float_point_ops.py
|
|
24
27
|
src/quantizers/minifloat/float_point_ops.py
|
|
25
28
|
src/quantizers/minifloat/float_point_ops_np.py
|
|
26
29
|
test/test_quantizer_match.py
|
|
@@ -18,7 +18,7 @@ def register_cpp():
|
|
|
18
18
|
|
|
19
19
|
@pytest.fixture(scope='session')
|
|
20
20
|
def data():
|
|
21
|
-
arr = np.random.randint(-2
|
|
21
|
+
arr = np.random.randint(-2**15, 2**15, 1000) * 2.**-8
|
|
22
22
|
return ops.array(arr.astype(np.float32))
|
|
23
23
|
|
|
24
24
|
|
|
@@ -69,6 +69,7 @@ def test_fixed_quantizer_forward(fixed_round_mode, fixed_overflow_mode, k, b, i,
|
|
|
69
69
|
pytest.skip('WRAP_SM only supports RND-like rounding')
|
|
70
70
|
|
|
71
71
|
fixed_q = FixedQ(W, I, k, fixed_round_mode, fixed_overflow_mode)
|
|
72
|
+
print(fixed_q)
|
|
72
73
|
|
|
73
74
|
arr_py_fixed_np = np.array(fixed_q(np.array(data)))
|
|
74
75
|
arr_py_fixed = np.array(fixed_q(data))
|
|
@@ -91,8 +92,10 @@ def test_fixed_quantizer_forward(fixed_round_mode, fixed_overflow_mode, k, b, i,
|
|
|
91
92
|
@pytest.mark.parametrize('E0', [0, 3, 5])
|
|
92
93
|
def test_minifloat_quantizer_forward(M, E, E0, data, register_cpp):
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
q = MinifloatQ(M, E, E0)
|
|
96
|
+
print(q)
|
|
97
|
+
arr_py_float = np.array(q(data))
|
|
98
|
+
arr_py_float_np = np.array(q(np.array(data)))
|
|
96
99
|
assert np.all(arr_py_float_np == arr_py_float), 'numpy / keras implementation inconsistent'
|
|
97
100
|
|
|
98
101
|
arr_c_float = c_quantize_float(data, M, E, E0)
|
|
@@ -144,8 +147,10 @@ def test_fixed_float_mult(fixed_round_mode, fixed_overflow_mode, k, i, f, M, E,
|
|
|
144
147
|
|
|
145
148
|
|
|
146
149
|
def test_binary(data, register_cpp):
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
q = BinaryQ()
|
|
151
|
+
print(q)
|
|
152
|
+
arr_py_binary = q(data)
|
|
153
|
+
arr_py_binary_np = np.array(q(np.array(data)))
|
|
149
154
|
assert np.all(arr_py_binary_np == arr_py_binary), 'numpy / keras implementation inconsistent'
|
|
150
155
|
|
|
151
156
|
arr_c_binary = c_quantize_binary(data)
|
|
@@ -161,8 +166,10 @@ def test_binary(data, register_cpp):
|
|
|
161
166
|
|
|
162
167
|
|
|
163
168
|
def test_ternary(data, register_cpp):
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
q = TernaryQ()
|
|
170
|
+
print(q)
|
|
171
|
+
arr_py_ternary = q(data)
|
|
172
|
+
arr_py_ternary_np = np.array(q(np.array(data)))
|
|
166
173
|
assert np.all(arr_py_ternary_np == arr_py_ternary), 'numpy / keras implementation inconsistent'
|
|
167
174
|
|
|
168
175
|
arr_c_ternary = c_quantize_ternary(data)
|
|
@@ -6,7 +6,7 @@ import numpy as np
|
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
8
|
from quantizers import binary_quantize, float_decompose, float_decompose_np, float_quantize, get_fixed_quantizer, ternary_quantize
|
|
9
|
-
from quantizers.fixed_point.
|
|
9
|
+
from quantizers.fixed_point._fixed_point_ops import round_mode_registry
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@pytest.fixture(scope='module')
|
|
@@ -1,9 +0,0 @@
|
|
|
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']
|
|
@@ -1,9 +0,0 @@
|
|
|
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']
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|