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.
Files changed (55) hide show
  1. {quantizers-1.0.1 → quantizers-1.1.0}/.pre-commit-config.yaml +5 -14
  2. {quantizers-1.0.1/src/quantizers.egg-info → quantizers-1.1.0}/PKG-INFO +1 -1
  3. {quantizers-1.0.1 → quantizers-1.1.0}/pyproject.toml +26 -1
  4. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/__init__.py +2 -0
  5. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/_version.py +2 -2
  6. quantizers-1.1.0/src/quantizers/binary/__init__.py +4 -0
  7. quantizers-1.0.1/src/quantizers/binary/binary_ops.py → quantizers-1.1.0/src/quantizers/binary/_binary_ops.py +6 -7
  8. quantizers-1.1.0/src/quantizers/binary/binary_ops.py +10 -0
  9. quantizers-1.1.0/src/quantizers/binary/binary_ops_np.py +9 -0
  10. quantizers-1.1.0/src/quantizers/fixed_point/__init__.py +4 -0
  11. 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
  12. quantizers-1.1.0/src/quantizers/fixed_point/fixed_point_ops.py +22 -0
  13. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/fixed_point/fixed_point_ops_np.py +18 -14
  14. quantizers-1.1.0/src/quantizers/minifloat/__init__.py +4 -0
  15. quantizers-1.0.1/src/quantizers/minifloat/float_point_ops.py → quantizers-1.1.0/src/quantizers/minifloat/_float_point_ops.py +17 -17
  16. quantizers-1.1.0/src/quantizers/minifloat/float_point_ops.py +12 -0
  17. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/minifloat/float_point_ops_np.py +12 -12
  18. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers/stateful_quantizers.py +8 -16
  19. {quantizers-1.0.1 → quantizers-1.1.0/src/quantizers.egg-info}/PKG-INFO +1 -1
  20. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers.egg-info/SOURCES.txt +3 -0
  21. {quantizers-1.0.1 → quantizers-1.1.0}/test/test_quantizer_match.py +14 -7
  22. {quantizers-1.0.1 → quantizers-1.1.0}/test/test_stateless_quantizer.py +1 -1
  23. quantizers-1.0.1/src/quantizers/binary/__init__.py +0 -9
  24. quantizers-1.0.1/src/quantizers/binary/binary_ops_np.py +0 -9
  25. quantizers-1.0.1/src/quantizers/fixed_point/__init__.py +0 -8
  26. quantizers-1.0.1/src/quantizers/minifloat/__init__.py +0 -9
  27. {quantizers-1.0.1 → quantizers-1.1.0}/.clang-format +0 -0
  28. {quantizers-1.0.1 → quantizers-1.1.0}/.github/workflows/python-publish.yml +0 -0
  29. {quantizers-1.0.1 → quantizers-1.1.0}/.github/workflows/python-test.yml +0 -0
  30. {quantizers-1.0.1 → quantizers-1.1.0}/.gitignore +0 -0
  31. {quantizers-1.0.1 → quantizers-1.1.0}/LICENSE +0 -0
  32. {quantizers-1.0.1 → quantizers-1.1.0}/README.md +0 -0
  33. {quantizers-1.0.1 → quantizers-1.1.0}/setup.cfg +0 -0
  34. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers.egg-info/dependency_links.txt +0 -0
  35. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers.egg-info/requires.txt +0 -0
  36. {quantizers-1.0.1 → quantizers-1.1.0}/src/quantizers.egg-info/top_level.txt +0 -0
  37. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_binary.h +0 -0
  38. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_common.h +0 -0
  39. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_decl.h +0 -0
  40. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_fixed.h +0 -0
  41. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_fixed_base.h +0 -0
  42. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_fixed_ref.h +0 -0
  43. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_fixed_special.h +0 -0
  44. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_float.h +0 -0
  45. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_int.h +0 -0
  46. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_int_base.h +0 -0
  47. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_int_ref.h +0 -0
  48. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_int_special.h +0 -0
  49. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/ap_shift_reg.h +0 -0
  50. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/etc/ap_private.h +0 -0
  51. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/hls_math.h +0 -0
  52. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/hls_stream.h +0 -0
  53. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/test.cpp +0 -0
  54. {quantizers-1.0.1 → quantizers-1.1.0}/test/cpp_source/ap_types/utils/x_hls_utils.h +0 -0
  55. {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/hhatto/autopep8
5
- rev: v2.3.1
4
+ - repo: https://github.com/astral-sh/ruff-pre-commit
5
+ rev: v0.8.2
6
6
  hooks:
7
- - id: autopep8
8
- language_version: python3
9
- args: ['--max-line-length=65535',
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantizers
3
- Version: 1.0.1
3
+ Version: 1.1.0
4
4
  Author-email: Chang Sun <chsun@cern.ch>
5
5
  License: GNU Lesser General Public License v3 (LGPLv3)
6
6
  Project-URL: repository, https://github.com/calad0i/quantizers
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "quantizers"
8
- version = "1.0.1"
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 = []
@@ -2,3 +2,5 @@ from .binary import *
2
2
  from .fixed_point import *
3
3
  from .minifloat import *
4
4
  from .stateful_quantizers import BinaryQ, FixedQ, MinifloatQ, TernaryQ
5
+
6
+ __all__ = ['BinaryQ', 'FixedQ', 'MinifloatQ', 'TernaryQ']
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.0.1'
16
- __version_tuple__ = version_tuple = (1, 0, 1)
15
+ __version__ = version = '1.1.0'
16
+ __version_tuple__ = version_tuple = (1, 1, 0)
@@ -0,0 +1,4 @@
1
+ from .binary_ops import binary_quantize, ternary_quantize
2
+ from .binary_ops_np import binary_quantize_np, ternary_quantize_np
3
+
4
+ __all__ = ['binary_quantize', 'ternary_quantize', 'binary_quantize_np', 'ternary_quantize_np']
@@ -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 binary_quantize(x):
7
- r = ops.where(x > 0, 1., -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 ternary_quantize(x):
20
- r = ops.where(x > 0.5, 1., ops.where(x < -0.5, -1., 0.))
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
@@ -0,0 +1,10 @@
1
+ def binary_quantize(x):
2
+ from ._binary_ops import _binary_quantize
3
+
4
+ return _binary_quantize(x)
5
+
6
+
7
+ def ternary_quantize(x):
8
+ from ._binary_ops import _ternary_quantize
9
+
10
+ return _ternary_quantize(x)
@@ -0,0 +1,9 @@
1
+ import numpy as np
2
+
3
+
4
+ def binary_quantize_np(x):
5
+ return np.where(x > 0, 1.0, -1.0)
6
+
7
+
8
+ def ternary_quantize_np(x):
9
+ return np.where(x > 0.5, 1.0, np.where(x < -0.5, -1.0, 0.0))
@@ -0,0 +1,4 @@
1
+ from .fixed_point_ops import get_fixed_quantizer
2
+ from .fixed_point_ops_np import get_fixed_quantizer_np
3
+
4
+ __all__ = ['get_fixed_quantizer', 'get_fixed_quantizer_np']
@@ -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.**f
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 = - ops.log(2.) * delta * dy # type: ignore
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.**(bk - 1)
150
- return ((xs + bias) % (2.**bk) - bias)
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.**(-f)
156
- __max = 2.**i
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.**(-f)
166
- _max = 2.**i - f_eps
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.**-f
177
- high = 2.**i - eps
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 get_fixed_quantizer(round_mode: str = 'TRN', overflow_mode: str = 'WRAP') -> Callable[[T, Any, Any, Any, bool | None, SeedGenerator | None], T]:
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"Unknown rounding mode: {round_mode}") # pragma: no cover
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"Unknown saturation mode: {overflow_mode}") # pragma: no cover
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'), "WRAP_SM only supports RND and RND_CONV rounding modes in this implementation."
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.**f
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 = - ops.log(2.) * delta * dy # type: ignore
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.**f
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.**(bk - 1)
94
- return ((xs + bias) % (2.**bk) - bias)
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.**(-f)
100
- __max = 2.**i
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.**(-f)
109
- _max = 2.**i - f_eps
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.**-f
117
- high = 2.**i - eps
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"Unknown rounding mode: {round_mode}") # pragma: no cover
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"Unknown saturation mode: {overflow_mode}") # pragma: no cover
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'), "WRAP_SM only supports RND and RND_CONV rounding modes in this implementation."
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, "Training mode not supported in numpy implementation."
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
@@ -0,0 +1,4 @@
1
+ from .float_point_ops import float_decompose, float_quantize
2
+ from .float_point_ops_np import float_decompose_np, float_quantize_np
3
+
4
+ __all__ = ['float_quantize', 'float_decompose', 'float_quantize_np', 'float_decompose_np']
@@ -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 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.'''
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.**(e - 1), 1.)
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.**(e_act - m + 1)
14
+ scale = 2.0 ** (e_act - m + 1)
16
15
  sig = x / scale
17
16
  qsig = ops.round(sig)
18
- _sig_high = 2.**m
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 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.'''
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.**(e - 1)
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.**(e_act - m + 1)
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.**m)
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., 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)
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
- '''Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl.'''
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.**(e - 1), 1.)
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.**(e_act - m + 1)
13
+ scale = 2.0 ** (e_act - m + 1)
14
14
  sig = x / scale
15
15
  qsig = np.round(sig)
16
- _sig_high = 2.**m
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
- '''Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl.'''
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.**(e - 1)
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.**(e_act - m + 1)
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.**m)
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., 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)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantizers
3
- Version: 1.0.1
3
+ Version: 1.1.0
4
4
  Author-email: Chang Sun <chsun@cern.ch>
5
5
  License: GNU Lesser General Public License v3 (LGPLv3)
6
6
  Project-URL: repository, https://github.com/calad0i/quantizers
@@ -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.**15, 2.**15, 1000) * 2.**-8
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
- arr_py_float = np.array(MinifloatQ(M, E, E0)(data))
95
- arr_py_float_np = np.array(MinifloatQ(M, E, E0)(np.array(data)))
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
- arr_py_binary = BinaryQ()(data)
148
- arr_py_binary_np = np.array(BinaryQ()(np.array(data)))
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
- arr_py_ternary = TernaryQ()(data)
165
- arr_py_ternary_np = np.array(TernaryQ()(np.array(data)))
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.fixed_point_ops import round_mode_registry
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
- import numpy as np
2
-
3
-
4
- def binary_quantize_np(x):
5
- return np.where(x > 0, 1., -1.)
6
-
7
-
8
- def ternary_quantize_np(x):
9
- return np.where(x > 0.5, 1., np.where(x < -0.5, -1., 0.))
@@ -1,8 +0,0 @@
1
- from .fixed_point_ops_np import get_fixed_quantizer_np
2
-
3
- try:
4
- from .fixed_point_ops import get_fixed_quantizer
5
- except ImportError:
6
- get_fixed_quantizer = get_fixed_quantizer_np
7
-
8
- __all__ = ['get_fixed_quantizer', 'get_fixed_quantizer_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