quantizers 1.0.1__tar.gz → 1.2.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 (56) hide show
  1. {quantizers-1.0.1 → quantizers-1.2.0}/.pre-commit-config.yaml +5 -14
  2. {quantizers-1.0.1/src/quantizers.egg-info → quantizers-1.2.0}/PKG-INFO +3 -2
  3. {quantizers-1.0.1 → quantizers-1.2.0}/pyproject.toml +26 -1
  4. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers/__init__.py +2 -0
  5. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers/_version.py +9 -4
  6. quantizers-1.2.0/src/quantizers/binary/__init__.py +4 -0
  7. quantizers-1.0.1/src/quantizers/binary/binary_ops.py → quantizers-1.2.0/src/quantizers/binary/_binary_ops.py +6 -7
  8. quantizers-1.2.0/src/quantizers/binary/binary_ops.py +10 -0
  9. quantizers-1.2.0/src/quantizers/binary/binary_ops_np.py +9 -0
  10. quantizers-1.2.0/src/quantizers/fixed_point/__init__.py +4 -0
  11. quantizers-1.2.0/src/quantizers/fixed_point/_fixed_point_ops.py +215 -0
  12. quantizers-1.2.0/src/quantizers/fixed_point/fixed_point_ops.py +19 -0
  13. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers/fixed_point/fixed_point_ops_np.py +18 -18
  14. quantizers-1.2.0/src/quantizers/minifloat/__init__.py +4 -0
  15. quantizers-1.0.1/src/quantizers/minifloat/float_point_ops.py → quantizers-1.2.0/src/quantizers/minifloat/_float_point_ops.py +17 -17
  16. quantizers-1.2.0/src/quantizers/minifloat/float_point_ops.py +15 -0
  17. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers/minifloat/float_point_ops_np.py +12 -12
  18. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers/stateful_quantizers.py +14 -16
  19. {quantizers-1.0.1 → quantizers-1.2.0/src/quantizers.egg-info}/PKG-INFO +3 -2
  20. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers.egg-info/SOURCES.txt +3 -0
  21. {quantizers-1.0.1 → quantizers-1.2.0}/test/test_quantizer_match.py +35 -22
  22. {quantizers-1.0.1 → quantizers-1.2.0}/test/test_stateless_quantizer.py +56 -55
  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/fixed_point/fixed_point_ops.py +0 -279
  27. quantizers-1.0.1/src/quantizers/minifloat/__init__.py +0 -9
  28. {quantizers-1.0.1 → quantizers-1.2.0}/.clang-format +0 -0
  29. {quantizers-1.0.1 → quantizers-1.2.0}/.github/workflows/python-publish.yml +0 -0
  30. {quantizers-1.0.1 → quantizers-1.2.0}/.github/workflows/python-test.yml +0 -0
  31. {quantizers-1.0.1 → quantizers-1.2.0}/.gitignore +0 -0
  32. {quantizers-1.0.1 → quantizers-1.2.0}/LICENSE +0 -0
  33. {quantizers-1.0.1 → quantizers-1.2.0}/README.md +0 -0
  34. {quantizers-1.0.1 → quantizers-1.2.0}/setup.cfg +0 -0
  35. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers.egg-info/dependency_links.txt +0 -0
  36. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers.egg-info/requires.txt +0 -0
  37. {quantizers-1.0.1 → quantizers-1.2.0}/src/quantizers.egg-info/top_level.txt +0 -0
  38. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_binary.h +0 -0
  39. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_common.h +0 -0
  40. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_decl.h +0 -0
  41. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_fixed.h +0 -0
  42. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_fixed_base.h +0 -0
  43. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_fixed_ref.h +0 -0
  44. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_fixed_special.h +0 -0
  45. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_float.h +0 -0
  46. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_int.h +0 -0
  47. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_int_base.h +0 -0
  48. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_int_ref.h +0 -0
  49. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_int_special.h +0 -0
  50. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/ap_shift_reg.h +0 -0
  51. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/etc/ap_private.h +0 -0
  52. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/hls_math.h +0 -0
  53. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/hls_stream.h +0 -0
  54. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/test.cpp +0 -0
  55. {quantizers-1.0.1 → quantizers-1.2.0}/test/cpp_source/ap_types/utils/x_hls_utils.h +0 -0
  56. {quantizers-1.0.1 → quantizers-1.2.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
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: quantizers
3
- Version: 1.0.1
3
+ Version: 1.2.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
@@ -24,6 +24,7 @@ Requires-Dist: pytest-env; extra == "test"
24
24
  Requires-Dist: keras>=3.0; extra == "test"
25
25
  Requires-Dist: cppyy; extra == "test"
26
26
  Requires-Dist: jax; extra == "test"
27
+ Dynamic: license-file
27
28
 
28
29
  # Quantizers
29
30
  [![PyPI version](https://badge.fury.io/py/quantizers.svg)](https://badge.fury.io/py/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.2.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", "E741"]
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']
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '1.0.1'
16
- __version_tuple__ = version_tuple = (1, 0, 1)
20
+ __version__ = version = '1.2.0'
21
+ __version_tuple__ = version_tuple = (1, 2, 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']
@@ -0,0 +1,215 @@
1
+ from collections.abc import Callable
2
+ from functools import wraps
3
+ from typing import Any, TypeVar
4
+
5
+ from keras import ops
6
+ from keras.api.random import SeedGenerator
7
+ from numpy.typing import ArrayLike
8
+
9
+ round_mode_registry: dict[str, Callable[[Any], Any]] = {}
10
+ saturation_mode_registry: dict[str, Callable[[Any, Any, Any, Any], Any]] = {}
11
+
12
+ T = TypeVar('T', bound=ArrayLike)
13
+
14
+
15
+ def _clip(x, min_value, max_value):
16
+ mask_overflow = x > max_value
17
+ mask_underflow = x < min_value
18
+ return ops.where(mask_overflow, max_value, ops.where(mask_underflow, min_value, x))
19
+
20
+
21
+ def rnd_mode(name: str):
22
+ def inner(func):
23
+ assert name not in round_mode_registry, f"Round mode '{name}' already exists."
24
+
25
+ @wraps(func)
26
+ def wrapper(x):
27
+ xq = func(x)
28
+ return ops.stop_gradient(xq) + (x - ops.stop_gradient(x))
29
+
30
+ round_mode_registry[name] = wrapper
31
+ return wrapper
32
+
33
+ return inner
34
+
35
+
36
+ @rnd_mode('TRN')
37
+ def floor(x):
38
+ return ops.floor(x)
39
+
40
+
41
+ @rnd_mode('RND')
42
+ def round(x):
43
+ # Round to nearest, ties positive infinity.
44
+ return ops.floor(x + 0.5)
45
+
46
+
47
+ @rnd_mode('RND_CONV')
48
+ def round_conv(x):
49
+ # Round to nearest, ties to even.
50
+ return ops.round(x)
51
+
52
+
53
+ @rnd_mode('TRN_ZERO')
54
+ def floor_zero(x):
55
+ # Truncate towards zero.
56
+ sign = ops.sign(x)
57
+ return ops.floor(ops.abs(x)) * sign # type: ignore
58
+
59
+
60
+ @rnd_mode('RND_ZERO')
61
+ def round_zero(x):
62
+ # Round to nearest, ties towards zero.
63
+ sign = ops.sign(x)
64
+ return -ops.floor(-ops.abs(x) + 0.5) * sign # type:ignore
65
+
66
+
67
+ @rnd_mode('RND_MIN_INF')
68
+ def round_min_inf(x):
69
+ # Round to nearest, ties towards negative infinity.
70
+ return -ops.floor(-x + 0.5) # type:ignore
71
+
72
+
73
+ @rnd_mode('RND_INF')
74
+ def round_inf(x):
75
+ # Round to nearest, ties away from zero.
76
+ sign = ops.sign(x)
77
+ return ops.floor(ops.abs(x) + 0.5) * sign # type: ignore
78
+
79
+
80
+ def sat_mode(name: str | list | tuple):
81
+ names = (name,) if isinstance(name, str) else name
82
+
83
+ def inner(func):
84
+ for name in names:
85
+ assert name not in saturation_mode_registry, f"Saturation mode '{name}' already exists."
86
+ saturation_mode_registry[name] = func
87
+ saturation_mode_registry[func.__name__.upper()] = func
88
+ return func
89
+
90
+ return inner
91
+
92
+
93
+ @sat_mode('WRAP')
94
+ def wrap(x, k, i, f):
95
+ xs = x
96
+ bk = i + k
97
+ bias = k * 2.0 ** (bk - 1)
98
+ return (xs + bias) % (2.0**bk) - bias
99
+
100
+
101
+ @sat_mode('SAT')
102
+ def sat(x, k, i, f):
103
+ f_eps = 2.0 ** (-f)
104
+ __max = 2.0**i
105
+ _max = __max - f_eps
106
+ _min = -__max * k
107
+ r = _clip(x, _min, _max)
108
+ return r
109
+
110
+
111
+ @sat_mode('SAT_SYM')
112
+ def sat_sym(x, k, i, f):
113
+ f_eps = 2.0 ** (-f)
114
+ _max = 2.0**i - f_eps
115
+ _min = -_max * k
116
+ r = _clip(x, _min, _max)
117
+ return r
118
+
119
+
120
+ @sat_mode('WRAP_SM')
121
+ def wrap_sm_fn(x, k, i, f, training=None, quant_fn: Callable = lambda x: x):
122
+ # x=ops.round(x*2.**f)
123
+ # High and low bounds are reflective. When overflows, can be less trash than WARP but still more trash than SAT.
124
+ eps = 2.0**-f
125
+ high = 2.0**i - eps
126
+ low = -(high + eps) * k
127
+ interval = 2 ** (i + k)
128
+ c1 = ((x) / interval) % 2 >= 1 # type: ignore
129
+ c1 = c1 & (ops.abs(x) > eps / 2)
130
+ c2 = ((x + eps / 2) / (interval / 2)) % 2 >= 1 # type: ignore
131
+ qx = quant_fn(x)
132
+ mapped = ((qx - high - eps) % interval) + low
133
+
134
+ mapped = ops.where(c2, -mapped - eps, mapped) # type: ignore
135
+ mapped = ops.where(c1, -mapped - eps, mapped) # type: ignore
136
+
137
+ return mapped
138
+
139
+
140
+ class FixedPointQuantizer:
141
+ def round(self, x, f: Any = 1.0, stochastic: bool | None = None, seed_gen: SeedGenerator | None = None):
142
+ scale = 2.0**f
143
+ x = x * scale
144
+ xq = self.round_fn(x)
145
+ # if stochastic:
146
+ # noise = keras.random.uniform(ops.shape(x), -0.49, 0.49, seed=seed_gen)
147
+ # x = x + noise
148
+ # xq2 = self.round_fn(x)
149
+ # xq = xq + ops.stop_gradient(xq2 - xq)
150
+ xq = xq / scale
151
+ return xq
152
+
153
+ def saturate(self, x, k, i, f):
154
+ return self.sat_fn(x, k, i, f)
155
+
156
+ def __init__(self, round_mode: str = 'TRN', overflow_mode: str = 'WRAP'):
157
+ round_mode = round_mode.upper()
158
+ overflow_mode = overflow_mode.upper()
159
+ self.stochastic = False
160
+
161
+ if round_mode.startswith('S_'):
162
+ round_mode = round_mode[2:]
163
+ self.stochastic = True
164
+
165
+ if overflow_mode == 'WRAP_SM':
166
+ assert round_mode in (
167
+ 'RND',
168
+ 'RND_CONV',
169
+ ), 'WRAP_SM only supports RND and RND_CONV rounding modes in this implementation.'
170
+
171
+ self.round_mode = round_mode
172
+ self.overflow_mode = overflow_mode
173
+ round_fn = round_mode_registry[round_mode]
174
+ sat_fn = saturation_mode_registry[overflow_mode]
175
+ self.sat_fn = sat_fn
176
+ self.round_fn = round_fn
177
+
178
+ def forward(self, x, k, i, f, training=False, seed_gen=None):
179
+ # Workaround for gradient computation around 0.
180
+ # When have values outside boundary rounded to boundary, grad on f presents despite the value will be clipped off anyway.
181
+ # Thus have saturation before rounding, except for wrap mode, which doesn't round during training.
182
+
183
+ if self.overflow_mode != 'WRAP':
184
+ x = self.saturate(x, k, i, f)
185
+ x = self.round(x, f, self.stochastic and training, seed_gen)
186
+ if self.overflow_mode == 'WRAP' and not training:
187
+ x = self.saturate(x, k, i, f)
188
+ return x
189
+
190
+ def forward_wrap_sm(self, x, k, i, f, training=False, seed_gen: SeedGenerator | None = None):
191
+ def quant_fn(x):
192
+ return self.round(x, f, training and self.stochastic, seed_gen)
193
+
194
+ x = wrap_sm_fn(x, k, i, f, training, quant_fn)
195
+ return x
196
+
197
+ def __call__(self, x, k, i, f, training=False, seed_gen=None):
198
+ i = ops.stop_gradient(ops.maximum(i, -f)) + (i - ops.stop_gradient(i)) # type: ignore
199
+ if self.stochastic and training:
200
+ assert seed_gen is not None, 'Seed generator must be provided for stochastic rounding.'
201
+ if self.overflow_mode != 'WRAP_SM':
202
+ return self.forward(x, k, i, f, training, seed_gen)
203
+ else:
204
+ return self.forward_wrap_sm(x, k, i, f, training, seed_gen)
205
+
206
+
207
+ def _get_fixed_quantizer(round_mode: str = 'TRN', overflow_mode: str = 'WRAP'):
208
+ """Get a stateless fixed-point quantizer given the round and overflow mode.
209
+ The quantizer is differentiable w.r.t. to the input and f, also i if using saturation overflow mode.
210
+
211
+ Args:
212
+ round_mode: round mode, one of
213
+ """
214
+ quantizer = FixedPointQuantizer(round_mode, overflow_mode)
215
+ return quantizer # type: ignore
@@ -0,0 +1,19 @@
1
+ from typing import TYPE_CHECKING, TypeVar
2
+
3
+ from numpy.typing import ArrayLike
4
+
5
+ if TYPE_CHECKING:
6
+ pass
7
+ T = TypeVar('T', bound=ArrayLike)
8
+
9
+
10
+ def get_fixed_quantizer(round_mode: str = 'TRN', overflow_mode: str = 'WRAP'):
11
+ """Get a stateless fixed-point quantizer given the round and overflow mode.
12
+ The quantizer is differentiable w.r.t. to the input and f, also i if using saturation overflow mode.
13
+
14
+ Args:
15
+ round_mode: round mode, one of
16
+ """
17
+ from ._fixed_point_ops import _get_fixed_quantizer
18
+
19
+ 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
@@ -134,24 +135,22 @@ def get_fixed_quantizer_np(round_mode: str = 'TRN', overflow_mode: str = 'WRAP')
134
135
  overflow_mode = overflow_mode.upper()
135
136
  round_mode = round_mode[2:] if round_mode.startswith('S_') else round_mode
136
137
 
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
138
+ round_fn_scaled = round_mode_registry_scaled_np[round_mode]
139
+ sat_fn = saturation_mode_registry_np[overflow_mode]
143
140
 
144
141
  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."
142
+ assert round_mode in ('RND', 'RND_CONV'), 'WRAP_SM only supports RND and RND_CONV rounding modes in this implementation.'
146
143
 
147
144
  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."
145
+ assert not training, 'Training mode not supported in numpy implementation.'
149
146
 
150
147
  _i = np.maximum(i, -f)
151
148
 
152
149
  if overflow_mode == 'WRAP_SM':
150
+
153
151
  def rnd_fn_wrapped(x):
154
152
  return round_fn_scaled(x, f, training)
153
+
155
154
  return wrap_sm_fn(x, k, _i, f, training, rnd_fn_wrapped)
156
155
 
157
156
  if overflow_mode != 'WRAP':
@@ -160,4 +159,5 @@ def get_fixed_quantizer_np(round_mode: str = 'TRN', overflow_mode: str = 'WRAP')
160
159
  if overflow_mode == 'WRAP':
161
160
  x = sat_fn(x, k, _i, f, training)
162
161
  return x
162
+
163
163
  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,15 @@
1
+ from typing import Any
2
+
3
+
4
+ def float_quantize(x, m, e, e0: Any = 0.0):
5
+ """Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl."""
6
+ from ._float_point_ops import _float_quantize
7
+
8
+ return _float_quantize(x, m, e, e0)
9
+
10
+
11
+ def float_decompose(x, m, e, e0=0):
12
+ """Quantize an array to floatlet (m mantissa bits, excl. sign bit, e exponent bits) format. Tentative gradient impl."""
13
+ from ._float_point_ops import _float_decompose
14
+
15
+ return _float_decompose(x, m, e, e0)