torchdt 0.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 (53) hide show
  1. torchdt-0.1.0/LICENSE +21 -0
  2. torchdt-0.1.0/PKG-INFO +35 -0
  3. torchdt-0.1.0/README.md +1 -0
  4. torchdt-0.1.0/pyproject.toml +41 -0
  5. torchdt-0.1.0/setup.cfg +4 -0
  6. torchdt-0.1.0/torchdt/__init__.py +11 -0
  7. torchdt-0.1.0/torchdt/_build_ext.py +71 -0
  8. torchdt-0.1.0/torchdt/autograd.py +280 -0
  9. torchdt-0.1.0/torchdt/csrc/dispatcher.h +101 -0
  10. torchdt-0.1.0/torchdt/csrc/extension.cpp +273 -0
  11. torchdt-0.1.0/torchdt/csrc/lns16.cpp +137 -0
  12. torchdt-0.1.0/torchdt/csrc/lns16.h +8 -0
  13. torchdt-0.1.0/torchdt/csrc/ops_impl.cpp +1091 -0
  14. torchdt-0.1.0/torchdt/csrc/ops_impl.h +118 -0
  15. torchdt-0.1.0/torchdt/datatype.py +325 -0
  16. torchdt-0.1.0/torchdt/funcs/__init__.py +9 -0
  17. torchdt-0.1.0/torchdt/funcs/activation_funcs.py +105 -0
  18. torchdt-0.1.0/torchdt/funcs/arithmetic_funcs.py +193 -0
  19. torchdt-0.1.0/torchdt/funcs/comparison_funcs.py +194 -0
  20. torchdt-0.1.0/torchdt/funcs/creation_funcs.py +116 -0
  21. torchdt-0.1.0/torchdt/funcs/init_funcs.py +50 -0
  22. torchdt-0.1.0/torchdt/funcs/layer_funcs.py +56 -0
  23. torchdt-0.1.0/torchdt/funcs/loss_funcs.py +104 -0
  24. torchdt-0.1.0/torchdt/funcs/misc_funcs.py +132 -0
  25. torchdt-0.1.0/torchdt/funcs/unary_funcs.py +41 -0
  26. torchdt-0.1.0/torchdt/include/torchdt/registry.h +107 -0
  27. torchdt-0.1.0/torchdt/lns/__init__.py +9 -0
  28. torchdt-0.1.0/torchdt/lns/lns16.py +184 -0
  29. torchdt-0.1.0/torchdt/lns/lns32.py +184 -0
  30. torchdt-0.1.0/torchdt/lns/lns64.py +438 -0
  31. torchdt-0.1.0/torchdt/ops/__init__.py +20 -0
  32. torchdt-0.1.0/torchdt/ops/activation_ops.py +278 -0
  33. torchdt-0.1.0/torchdt/ops/arithmetic_ops.py +655 -0
  34. torchdt-0.1.0/torchdt/ops/base.py +525 -0
  35. torchdt-0.1.0/torchdt/ops/comparison_ops.py +462 -0
  36. torchdt-0.1.0/torchdt/ops/cpp_ops.py +153 -0
  37. torchdt-0.1.0/torchdt/ops/layer_ops.py +908 -0
  38. torchdt-0.1.0/torchdt/ops/loss_ops.py +954 -0
  39. torchdt-0.1.0/torchdt/ops/misc_ops.py +475 -0
  40. torchdt-0.1.0/torchdt/ops/triton_ops.py +2550 -0
  41. torchdt-0.1.0/torchdt/ops/unary_ops.py +61 -0
  42. torchdt-0.1.0/torchdt/optim/__init__.py +16 -0
  43. torchdt-0.1.0/torchdt/optim/adam.py +188 -0
  44. torchdt-0.1.0/torchdt/optim/lr_scheduler.py +224 -0
  45. torchdt-0.1.0/torchdt/optim/madam.py +175 -0
  46. torchdt-0.1.0/torchdt/optim/optimizer.py +33 -0
  47. torchdt-0.1.0/torchdt/optim/sgd.py +142 -0
  48. torchdt-0.1.0/torchdt/transforms.py +120 -0
  49. torchdt-0.1.0/torchdt.egg-info/PKG-INFO +35 -0
  50. torchdt-0.1.0/torchdt.egg-info/SOURCES.txt +51 -0
  51. torchdt-0.1.0/torchdt.egg-info/dependency_links.txt +1 -0
  52. torchdt-0.1.0/torchdt.egg-info/requires.txt +2 -0
  53. torchdt-0.1.0/torchdt.egg-info/top_level.txt +1 -0
torchdt-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 XLNS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
torchdt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: torchdt
3
+ Version: 0.1.0
4
+ Summary: torchdt is a python package that provides custom datatypes for training and inference in PyTorch.
5
+ Author-email: Alexander Krentz <alexkrentz2@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 XLNS
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ Project-URL: Homepage, https://github.com/akrentz6/torchdt
28
+ Keywords: python,pytorch,machine learning
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: torch>=2.0
32
+ Requires-Dist: numpy
33
+ Dynamic: license-file
34
+
35
+ A Python addon package to PyTorch that provides experimentation with custom tensor datatypes for training and inference.
@@ -0,0 +1 @@
1
+ A Python addon package to PyTorch that provides experimentation with custom tensor datatypes for training and inference.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=69.0",
4
+ "wheel",
5
+ "torch>=2.0",
6
+ "ninja",
7
+ ]
8
+ build-backend = "setuptools.build_meta"
9
+
10
+ [project]
11
+ name = "torchdt"
12
+ version = "0.1.0"
13
+ description = "torchdt is a python package that provides custom datatypes for training and inference in PyTorch."
14
+ readme = "README.md"
15
+ license = {file = "LICENSE"}
16
+ authors = [{name = "Alexander Krentz", email = "alexkrentz2@gmail.com"}]
17
+ keywords = ["python", "pytorch", "machine learning"]
18
+ dependencies = ["torch>=2.0", "numpy"]
19
+
20
+ [project.urls]
21
+ "Homepage" = "https://github.com/akrentz6/torchdt"
22
+
23
+ [tool.setuptools]
24
+ include-package-data = true
25
+
26
+ [tool.setuptools.package-data]
27
+ "torchdt" = [
28
+ "include/torchdt/**/*.h",
29
+ "csrc/**/*.cpp",
30
+ "csrc/**/*.h",
31
+ ]
32
+
33
+ [[tool.setuptools.ext-modules]]
34
+ name = "torchdt._C"
35
+ sources = [] # Filled dynamically
36
+ language = "c++"
37
+ extra-compile-args = ["-O3", "-std=c++17"]
38
+ optional = true
39
+
40
+ [tool.setuptools.cmdclass]
41
+ build_ext = "torchdt._build_ext.BuildCxxExtension"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ from . import transforms
2
+ from .datatype import DType
3
+ from . import lns
4
+ from . import ops
5
+ from . import autograd
6
+ from . import funcs
7
+ from . import optim
8
+
9
+ __all__ = [
10
+ "DType",
11
+ ]
@@ -0,0 +1,71 @@
1
+ from torch.utils.cpp_extension import BuildExtension, include_paths
2
+ from pathlib import Path
3
+ import warnings
4
+ import os
5
+
6
+ # The exact exception raised by a compiler/linker failure can vary
7
+ # between setuptools versions, so we catch the whole family.
8
+ try:
9
+ # setuptools ≥ 68
10
+ from setuptools.errors import (
11
+ CompileError,
12
+ LinkError,
13
+ PlatformError,
14
+ )
15
+ except ImportError:
16
+ # setuptools < 68
17
+ from distutils.errors import ( # type: ignore
18
+ CompileError,
19
+ LinkError,
20
+ DistutilsPlatformError as PlatformError,
21
+ )
22
+
23
+ class BuildCxxExtension(BuildExtension):
24
+ """
25
+ Build all .cpp files in csrc/ and include Torch headers.
26
+ Fails hard if compilation fails.
27
+ """
28
+
29
+ user_options = BuildExtension.user_options + [
30
+ ("no-cpp", None, "Do not build the C++ extension"),
31
+ ]
32
+ boolean_options = getattr(BuildExtension, "boolean_options", []) + ["no-cpp"]
33
+
34
+ def initialize_options(self):
35
+ super().initialize_options()
36
+ self.no_cpp = False
37
+
38
+ def finalize_options(self):
39
+ super().finalize_options()
40
+ if os.environ.get("TORCHDT_NO_CPP", "").lower() in {"1", "true", "yes", "on"}:
41
+ self.no_cpp = True
42
+
43
+ def run(self):
44
+ try:
45
+ super().run()
46
+ except (CompileError, LinkError, PlatformError) as exc:
47
+ warnings.warn(
48
+ "C++ backend for xlnstorch was not built; "
49
+ "installation will succeed but will run in pure-python mode.",
50
+ RuntimeWarning,
51
+ )
52
+
53
+ def build_extensions(self):
54
+ if self.no_cpp:
55
+ self.extensions = []
56
+ return
57
+
58
+ src_dir = Path(__file__).resolve().parent / "csrc"
59
+ include_dir = Path(__file__).resolve().parent / "include"
60
+ cpp_files = [str(p) for p in src_dir.glob("*.cpp")]
61
+
62
+ torch_includes = include_paths()
63
+
64
+ for ext in self.extensions:
65
+ if ext.name == "torchdt._C":
66
+ ext.sources = cpp_files
67
+ ext.include_dirs = list(ext.include_dirs or [])
68
+ ext.include_dirs += torch_includes
69
+ ext.include_dirs += [str(include_dir)]
70
+
71
+ super().build_extensions()
@@ -0,0 +1,280 @@
1
+ import torch
2
+ from torch.autograd.graph import get_gradient_edge
3
+ import functools
4
+ from typing import Optional, Tuple
5
+
6
+ __all__ = [
7
+ "DTFunction",
8
+ "DTNonDifferentiableFunction",
9
+ ]
10
+
11
+ def _find_first_grad_tensor(args):
12
+ if not isinstance(args, (list, tuple)):
13
+ args = (args,)
14
+ for arg in args:
15
+ if isinstance(arg, torch.Tensor) and arg.requires_grad:
16
+ return arg
17
+ return None
18
+
19
+ def _cast_int(x, dtype):
20
+ return x.view(dtype.int_dtype) if isinstance(x, torch.Tensor) and x.dtype == dtype.float_dtype else x
21
+
22
+ def _cast_float(x, dtype):
23
+ return x.view(dtype.float_dtype) if isinstance(x, torch.Tensor) and x.dtype == dtype.int_dtype else x
24
+
25
+ def _cast_values(values, indices, cast_fn, dtype):
26
+ all_indices = indices is None
27
+
28
+ if isinstance(values, tuple):
29
+ return tuple(
30
+ cast_fn(values[i], dtype) if all_indices or i in indices else values[i]
31
+ for i in range(len(values))
32
+ )
33
+
34
+ elif isinstance(values, list):
35
+ return [
36
+ cast_fn(values[i], dtype) if all_indices or i in indices else values[i]
37
+ for i in range(len(values))
38
+ ]
39
+
40
+ else:
41
+ return cast_fn(values, dtype) if all_indices or 0 in indices else values
42
+
43
+ # added to forward only if setup_context is not defined
44
+ def forward_ctx_decorator(func, cls):
45
+
46
+ @functools.wraps(func)
47
+ def wrapper(*args, **kwargs):
48
+ ctx = args[0]
49
+ ops = args[1]
50
+ inputs = args[2:]
51
+
52
+ ctx._dtype = cls.dtype
53
+ ctx._input_indices = cls.input_indices
54
+
55
+ cast_inputs = _cast_values(inputs, cls.input_indices, _cast_int, cls.dtype)
56
+ output = func(ctx, ops, *cast_inputs, **kwargs)
57
+ return _cast_values(output, cls.output_indices, _cast_float, cls.dtype)
58
+
59
+ return wrapper
60
+
61
+ def forward_no_ctx_decorator(func, cls):
62
+
63
+ @functools.wraps(func)
64
+ def wrapper(*args, **kwargs):
65
+ ops = args[0]
66
+ inputs = args[1:]
67
+
68
+ cast_inputs = _cast_values(inputs, cls.input_indices, _cast_int, cls.dtype)
69
+ output = func(ops, *cast_inputs, **kwargs)
70
+ return _cast_values(output, cls.output_indices, _cast_float, cls.dtype)
71
+
72
+ return wrapper
73
+
74
+ def setup_context_decorator(func, cls):
75
+
76
+ @functools.wraps(func)
77
+ def wrapper(*args, **kwargs):
78
+ ctx = args[0]
79
+ ops = args[1][0]
80
+ inputs = args[1][1:]
81
+ output = args[2]
82
+
83
+ ctx._dtype = cls.dtype
84
+ ctx._input_indices = cls.input_indices
85
+
86
+ cast_inputs = _cast_values(inputs, cls.input_indices, _cast_int, cls.dtype)
87
+ cast_output = _cast_values(output, cls.output_indices, _cast_int, cls.dtype)
88
+ return func(ctx, ops, cast_inputs, cast_output, **kwargs)
89
+
90
+ return wrapper
91
+
92
+ def backward_decorator(func, cls):
93
+
94
+ @functools.wraps(func)
95
+ def wrapper(*args, **kwargs):
96
+ ctx = args[0]
97
+ grads = args[1:]
98
+
99
+ dtype = ctx._dtype
100
+ ops = ctx._dtype.ops
101
+ input_indices = ctx._input_indices
102
+
103
+ cast_grads = _cast_values(grads, cls.output_indices, _cast_int, dtype)
104
+ output = func(ctx, ops, *cast_grads, **kwargs)
105
+ cast_output = _cast_values(output, input_indices, _cast_float, dtype)
106
+
107
+ if isinstance(output, tuple):
108
+ return (None,) + cast_output
109
+ return None, cast_output
110
+
111
+ return wrapper
112
+
113
+ class DTFunction(torch.autograd.Function):
114
+ """
115
+ Parent class for custom autograd Functions that work with DType tensors.
116
+ Subclasses should implement static methods `forward` and `backward` (and
117
+ optionally `setup_context`).
118
+ """
119
+
120
+ output_indices: Optional[Tuple[int, ...]] = None
121
+
122
+ @classmethod
123
+ def _register_ops_decorator(cls, func_name, decorator):
124
+ func = getattr(cls, func_name)
125
+
126
+ if not getattr(func, "_is_decorated", False):
127
+ decorated = decorator(func, cls)
128
+ decorated._is_lns_decorated = True
129
+ setattr(cls, func_name, decorated)
130
+
131
+ def __init_subclass__(cls, **kwargs):
132
+ super().__init_subclass__(**kwargs)
133
+
134
+ # check if setup_context is defined in subclass or inherited
135
+ # from torch.autograd.function._SingleLevelFunction. If it is
136
+ # inherited, we only need to decorate forward and backward.
137
+ # If it is defined, we need to decorate setup_context instead
138
+ # of forward.
139
+ if cls.setup_context is torch.autograd.function._SingleLevelFunction.setup_context:
140
+ cls._register_ops_decorator("forward", forward_ctx_decorator)
141
+ else:
142
+ cls._register_ops_decorator("forward", forward_no_ctx_decorator)
143
+ cls._register_ops_decorator("setup_context", setup_context_decorator)
144
+
145
+ cls._register_ops_decorator("backward", backward_decorator)
146
+
147
+ @classmethod
148
+ def apply(cls, *args, **kwargs):
149
+ from torchdt import DType # avoid circular import
150
+
151
+ if kwargs:
152
+ raise ValueError(
153
+ "torch.autograd.Function does not support keyword arguments. "
154
+ "Please use positional arguments only."
155
+ )
156
+
157
+ input_indices = []
158
+ subtypes = []
159
+ prepped_inputs = []
160
+
161
+ for i, arg in enumerate(args):
162
+ if isinstance(arg, DType):
163
+ subtypes.append(arg.__class__)
164
+ input_indices.append(i)
165
+ prepped_inputs.append(arg._float)
166
+ else:
167
+ prepped_inputs.append(arg)
168
+
169
+ if not subtypes:
170
+ raise ValueError("DTFunction.apply() requires at least one DType tensor argument.")
171
+
172
+ dtype = subtypes[0]
173
+ if any(st != dtype for st in subtypes):
174
+ raise ValueError("All DType arguments to DTFunction.apply() must be of the same type.")
175
+
176
+ # perform the operation using the Ops class for this DType
177
+ cls.dtype = dtype
178
+ cls.input_indices = input_indices
179
+ result = super().apply(dtype.ops, *prepped_inputs)
180
+ del cls.input_indices
181
+ del cls.dtype
182
+
183
+ # get gradient edge to correctly handle grads for DType tensors
184
+ first_tensor = _find_first_grad_tensor(result)
185
+ if first_tensor is not None:
186
+ edge = get_gradient_edge(first_tensor)
187
+
188
+ j = 0
189
+ for arg in args:
190
+
191
+ # ignore non-tensor arguments
192
+ if not isinstance(arg, torch.Tensor):
193
+ continue
194
+ j += 1
195
+
196
+ # only register hooks for DType inputs with gradients
197
+ if not (isinstance(arg, dtype) and arg.requires_grad):
198
+ continue
199
+
200
+ arg._track_operation(edge, j - 1)
201
+
202
+ if isinstance(result, torch.Tensor):
203
+ return dtype(
204
+ result, internal=True
205
+ ) if cls.output_indices is None or 0 in cls.output_indices else result
206
+
207
+ elif isinstance(result, list):
208
+ return [
209
+ dtype(result[i], internal=True)
210
+ if cls.output_indices is None or i in cls.output_indices else result[i]
211
+ for i in range(len(result))
212
+ ]
213
+
214
+ elif isinstance(result, tuple):
215
+ return tuple(
216
+ dtype(result[i], internal=True)
217
+ if cls.output_indices is None or i in cls.output_indices else result[i]
218
+ for i in range(len(result))
219
+ )
220
+
221
+ return result
222
+
223
+ class DTNonDifferentiableFunction:
224
+
225
+ output_indices: Optional[Tuple[int, ...]] = None
226
+
227
+ @staticmethod
228
+ def forward(ops, *args, **kwargs):
229
+ """Forward pass for non-differentiable function."""
230
+ raise NotImplementedError
231
+
232
+ @classmethod
233
+ def apply(cls, *args, **kwargs):
234
+ from torchdt import DType # avoid circular import
235
+
236
+ if kwargs:
237
+ raise ValueError(
238
+ "DTNonDifferentiableFunction does not support keyword arguments. "
239
+ "Please use positional arguments only."
240
+ )
241
+
242
+ subtypes = []
243
+ prepped_inputs = []
244
+
245
+ for i, arg in enumerate(args):
246
+ if isinstance(arg, DType):
247
+ subtypes.append(arg.__class__)
248
+ prepped_inputs.append(arg._int)
249
+ else:
250
+ prepped_inputs.append(arg)
251
+
252
+ if not subtypes:
253
+ raise ValueError("DTFunction.apply() requires at least one DType tensor argument.")
254
+
255
+ dtype = subtypes[0]
256
+ if any(st != dtype for st in subtypes):
257
+ raise ValueError("All DType arguments to DTFunction.apply() must be of the same type.")
258
+
259
+ result = cls.forward(dtype.ops, *prepped_inputs)
260
+
261
+ if isinstance(result, torch.Tensor):
262
+ return dtype(
263
+ result, internal=True
264
+ ) if cls.output_indices is None or 0 in cls.output_indices else result
265
+
266
+ elif isinstance(result, list):
267
+ return [
268
+ dtype(result[i], internal=True)
269
+ if cls.output_indices is None or i in cls.output_indices else result[i]
270
+ for i in range(len(result))
271
+ ]
272
+
273
+ elif isinstance(result, tuple):
274
+ return tuple(
275
+ dtype(result[i], internal=True)
276
+ if cls.output_indices is None or i in cls.output_indices else result[i]
277
+ for i in range(len(result))
278
+ )
279
+
280
+ return result
@@ -0,0 +1,101 @@
1
+ #ifndef DISPATCHER_H
2
+ #define DISPATCHER_H
3
+
4
+ #include <torchdt/registry.h>
5
+ #include "ops_impl.h"
6
+
7
+ class Dispatcher {
8
+ public:
9
+ static Dispatcher& instance() {
10
+ static Dispatcher d;
11
+ return d;
12
+ }
13
+
14
+ // Get cached OpsBase* for name and bitwidth.
15
+ // If not cached and the public registry contains an Ops<bitwidth> for this name,
16
+ // lazily construct an OpsImpl<bitwidth> from the stored Ops and cache it.
17
+ // Returns nullptr if no Ops registered for (name,bitwidth).
18
+ OpsBase* get(const std::string& name, size_t bitwidth) {
19
+ const std::string key = make_key(name, bitwidth);
20
+
21
+ // fast path: check cached map under lock
22
+ {
23
+ std::lock_guard<std::mutex> guard(mutex_);
24
+ auto it = impl_map_.find(key);
25
+ if (it != impl_map_.end()) return it->second.get();
26
+ }
27
+
28
+ // not cached: try to construct based on bitwidth
29
+ std::unique_ptr<OpsBase> created = make_impl_from_registry(name, bitwidth);
30
+ if (!created) return nullptr;
31
+
32
+ // store & return
33
+ {
34
+ std::lock_guard<std::mutex> guard(mutex_);
35
+ // double-check another thread didn't create it
36
+ auto it = impl_map_.find(key);
37
+ if (it == impl_map_.end()) {
38
+ OpsBase* raw = created.get();
39
+ impl_map_.emplace(key, std::move(created));
40
+ return raw;
41
+ } else {
42
+ return it->second.get();
43
+ }
44
+ }
45
+ }
46
+
47
+ // Convenience template: typed get that returns pointer to OpsImpl<bitwidth>
48
+ template <size_t bitwidth>
49
+ OpsImpl<bitwidth>* get_typed(const std::string& name) {
50
+ OpsBase* base = get(name, bitwidth);
51
+ return static_cast<OpsImpl<bitwidth>*>(base);
52
+ }
53
+
54
+ private:
55
+ Dispatcher() = default;
56
+
57
+ // Create OpsImpl for supported bitwidths by fetching the registered Ops<bitwidth> from public registry
58
+ std::unique_ptr<OpsBase> make_impl_from_registry(const std::string& name, size_t bitwidth) {
59
+ switch (bitwidth) {
60
+ case 8: {
61
+ auto p = Registry::instance().get_ops_typed<8>(name);
62
+ if (!p) return nullptr;
63
+ return std::make_unique<OpsImpl<8>>(*p);
64
+ }
65
+ case 16: {
66
+ auto p = Registry::instance().get_ops_typed<16>(name);
67
+ if (!p) return nullptr;
68
+ return std::make_unique<OpsImpl<16>>(*p);
69
+ }
70
+ case 32: {
71
+ auto p = Registry::instance().get_ops_typed<32>(name);
72
+ if (!p) return nullptr;
73
+ return std::make_unique<OpsImpl<32>>(*p);
74
+ }
75
+ case 64: {
76
+ auto p = Registry::instance().get_ops_typed<64>(name);
77
+ if (!p) return nullptr;
78
+ return std::make_unique<OpsImpl<64>>(*p);
79
+ }
80
+ default:
81
+ return nullptr;
82
+ }
83
+ }
84
+
85
+ std::map<std::string, std::unique_ptr<OpsBase>> impl_map_;
86
+ std::mutex mutex_;
87
+ };
88
+
89
+ // -----------------------------
90
+ // Helper functions (optional)
91
+ // -----------------------------
92
+ inline OpsBase* get_ops_impl(const std::string& name, size_t bitwidth) {
93
+ return Dispatcher::instance().get(name, bitwidth);
94
+ }
95
+
96
+ template <size_t bitwidth>
97
+ inline OpsImpl<bitwidth>* get_ops_impl_typed(const std::string& name) {
98
+ return Dispatcher::instance().get_typed<bitwidth>(name);
99
+ }
100
+
101
+ #endif // DISPATCHER_H