klongpy 0.6.9__py3-none-any.whl → 0.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- klongpy/__init__.py +17 -1
- klongpy/adverbs.py +84 -82
- klongpy/autograd.py +299 -0
- klongpy/backend.py +38 -103
- klongpy/backends/__init__.py +26 -0
- klongpy/backends/base.py +469 -0
- klongpy/backends/numpy_backend.py +123 -0
- klongpy/backends/registry.py +76 -0
- klongpy/backends/torch_backend.py +1047 -0
- klongpy-0.6.9.data/scripts/kgpy → klongpy/cli.py +110 -90
- klongpy/core.py +113 -974
- klongpy/db/sys_fn_db.py +7 -6
- klongpy/db/sys_fn_kvs.py +2 -4
- klongpy/dyads.py +332 -160
- klongpy/interpreter.py +60 -15
- klongpy/monads.py +121 -75
- klongpy/parser.py +328 -0
- klongpy/repl.py +23 -5
- klongpy/sys_fn.py +170 -21
- klongpy/sys_fn_autograd.py +290 -0
- klongpy/sys_fn_ipc.py +22 -15
- klongpy/sys_fn_timer.py +13 -3
- klongpy/types.py +503 -0
- klongpy/web/sys_fn_web.py +14 -4
- klongpy/writer.py +122 -0
- klongpy/ws/sys_fn_ws.py +5 -8
- klongpy-0.7.1.dist-info/METADATA +544 -0
- klongpy-0.7.1.dist-info/RECORD +52 -0
- {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/WHEEL +1 -1
- klongpy-0.7.1.dist-info/entry_points.txt +2 -0
- {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/top_level.txt +0 -1
- klongpy-0.6.9.dist-info/METADATA +0 -448
- klongpy-0.6.9.dist-info/RECORD +0 -77
- tests/__init__.py +0 -6
- tests/gen_join_over.py +0 -119
- tests/gen_py_suite.py +0 -77
- tests/gen_test_fn.py +0 -259
- tests/perf_async.py +0 -25
- tests/perf_avg.py +0 -18
- tests/perf_duckdb.py +0 -32
- tests/perf_gen.py +0 -38
- tests/perf_ipc_overhead.py +0 -34
- tests/perf_join.py +0 -53
- tests/perf_load.py +0 -17
- tests/perf_prog.py +0 -18
- tests/perf_serdes.py +0 -52
- tests/perf_sys_fn_db.py +0 -263
- tests/perf_vector.py +0 -40
- tests/test_accel.py +0 -227
- tests/test_df_cache.py +0 -85
- tests/test_eval_monad_list.py +0 -34
- tests/test_examples.py +0 -64
- tests/test_extra_suite.py +0 -382
- tests/test_file_cache.py +0 -185
- tests/test_interop.py +0 -180
- tests/test_kg_asarray.py +0 -94
- tests/test_kgtests.py +0 -65
- tests/test_known_bugs.py +0 -206
- tests/test_prog.py +0 -107
- tests/test_reshape_strings.py +0 -33
- tests/test_suite.py +0 -1480
- tests/test_suite_file.py +0 -153
- tests/test_sys_fn.py +0 -420
- tests/test_sys_fn_db.py +0 -88
- tests/test_sys_fn_ipc.py +0 -587
- tests/test_sys_fn_timer.py +0 -133
- tests/test_sys_fn_web.py +0 -50
- tests/test_util.py +0 -233
- tests/utils.py +0 -126
- {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/licenses/LICENSE +0 -0
klongpy/backend.py
CHANGED
|
@@ -1,103 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class CuPyReductionKernelWrapper:
|
|
41
|
-
def __init__(self, fn, reduce_fn_1, reduce_fn_2):
|
|
42
|
-
self.fn = fn
|
|
43
|
-
self.reduce_fn_1 = reduce_fn_1
|
|
44
|
-
self.reduce_fn_2 = reduce_fn_2
|
|
45
|
-
|
|
46
|
-
def __call__(self, *args, **kwargs):
|
|
47
|
-
return self.fn(*args, **kwargs)
|
|
48
|
-
|
|
49
|
-
def reduce(self, x):
|
|
50
|
-
return self.reduce_fn_1(x) if x.ndim == 1 else self.reduce_fn_2(x[0], x[1])
|
|
51
|
-
|
|
52
|
-
add_reduce_2 = cupy.ElementwiseKernel(
|
|
53
|
-
'T x, T y',
|
|
54
|
-
'T z',
|
|
55
|
-
'z = (x + y)',
|
|
56
|
-
'add_reduce_2')
|
|
57
|
-
np.add = CuPyReductionKernelWrapper(cupy.add, cupy.sum, add_reduce_2)
|
|
58
|
-
|
|
59
|
-
def subtract_reduce_1(x):
|
|
60
|
-
return 2*x[0] - cupy.sum(x)
|
|
61
|
-
|
|
62
|
-
subtract_reduce_2 = cupy.ElementwiseKernel(
|
|
63
|
-
'T x, T y',
|
|
64
|
-
'T z',
|
|
65
|
-
'z = (x - y)',
|
|
66
|
-
'subtract_reduce_2')
|
|
67
|
-
np.subtract = CuPyReductionKernelWrapper(cupy.subtract, subtract_reduce_1, subtract_reduce_2)
|
|
68
|
-
|
|
69
|
-
multiply_reduce_1 = cupy.ReductionKernel(
|
|
70
|
-
'T x',
|
|
71
|
-
'T y',
|
|
72
|
-
'x',
|
|
73
|
-
'a * b',
|
|
74
|
-
'y = a',
|
|
75
|
-
'1',
|
|
76
|
-
'multiply_reduce_1'
|
|
77
|
-
)
|
|
78
|
-
multiply_reduce_2 = cupy.ElementwiseKernel(
|
|
79
|
-
'T x, T y',
|
|
80
|
-
'T z',
|
|
81
|
-
'z = (x * y)',
|
|
82
|
-
'multiply_reduce_2')
|
|
83
|
-
np.multiply = CuPyReductionKernelWrapper(cupy.multiply, multiply_reduce_1, multiply_reduce_2)
|
|
84
|
-
|
|
85
|
-
def divide_reduce_1(x):
|
|
86
|
-
raise NotImplementedError()
|
|
87
|
-
|
|
88
|
-
divide_reduce_2 = cupy.ElementwiseKernel(
|
|
89
|
-
'T x, T y',
|
|
90
|
-
'T z',
|
|
91
|
-
'z = (x / y)',
|
|
92
|
-
'divide_reduce_2')
|
|
93
|
-
np.divide = CuPyReductionKernelWrapper(cupy.divide, divide_reduce_1, divide_reduce_2)
|
|
94
|
-
|
|
95
|
-
np.isarray = lambda x: isinstance(x, (numpy.ndarray, cupy.ndarray))
|
|
96
|
-
|
|
97
|
-
# np.hstack = lambda x: cupy.hstack(x) if use_gpu and is_supported_type(x) else numpy.hstack(x)
|
|
98
|
-
else:
|
|
99
|
-
np.seterr(divide='ignore')
|
|
100
|
-
warnings.filterwarnings("error", category=np.VisibleDeprecationWarning)
|
|
101
|
-
np.isarray = lambda x: isinstance(x, np.ndarray)
|
|
102
|
-
|
|
103
|
-
np
|
|
1
|
+
"""
|
|
2
|
+
Backend module for KlongPy.
|
|
3
|
+
|
|
4
|
+
Prefer using the backends package directly:
|
|
5
|
+
|
|
6
|
+
from klongpy.backends import get_backend, BackendProvider
|
|
7
|
+
|
|
8
|
+
For per-interpreter backends, use:
|
|
9
|
+
|
|
10
|
+
klong = KlongInterpreter(backend='torch')
|
|
11
|
+
"""
|
|
12
|
+
from .backends.base import (
|
|
13
|
+
BackendProvider,
|
|
14
|
+
UnsupportedDtypeError,
|
|
15
|
+
is_jagged_array,
|
|
16
|
+
is_supported_type,
|
|
17
|
+
)
|
|
18
|
+
from .backends.numpy_backend import KGChar, NumpyBackendProvider
|
|
19
|
+
from .backends.registry import get_backend, list_backends, register_backend, TorchBackendProvider
|
|
20
|
+
|
|
21
|
+
_default_np_backend = get_backend('numpy')
|
|
22
|
+
np = _default_np_backend.np
|
|
23
|
+
bknp = np
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'np',
|
|
27
|
+
'bknp',
|
|
28
|
+
'get_backend',
|
|
29
|
+
'register_backend',
|
|
30
|
+
'list_backends',
|
|
31
|
+
'BackendProvider',
|
|
32
|
+
'UnsupportedDtypeError',
|
|
33
|
+
'NumpyBackendProvider',
|
|
34
|
+
'TorchBackendProvider',
|
|
35
|
+
'KGChar',
|
|
36
|
+
'is_supported_type',
|
|
37
|
+
'is_jagged_array',
|
|
38
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend public API for KlongPy.
|
|
3
|
+
|
|
4
|
+
Re-exports registry helpers and core backend types.
|
|
5
|
+
"""
|
|
6
|
+
from .base import (
|
|
7
|
+
BackendProvider,
|
|
8
|
+
UnsupportedDtypeError,
|
|
9
|
+
is_jagged_array,
|
|
10
|
+
is_supported_type,
|
|
11
|
+
)
|
|
12
|
+
from .numpy_backend import NumpyBackendProvider, KGChar
|
|
13
|
+
from .registry import get_backend, list_backends, register_backend, TorchBackendProvider
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'BackendProvider',
|
|
17
|
+
'UnsupportedDtypeError',
|
|
18
|
+
'NumpyBackendProvider',
|
|
19
|
+
'TorchBackendProvider',
|
|
20
|
+
'KGChar',
|
|
21
|
+
'get_backend',
|
|
22
|
+
'register_backend',
|
|
23
|
+
'list_backends',
|
|
24
|
+
'is_jagged_array',
|
|
25
|
+
'is_supported_type',
|
|
26
|
+
]
|
klongpy/backends/base.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base interface for array backends.
|
|
3
|
+
|
|
4
|
+
All backends must implement the BackendProvider interface to ensure
|
|
5
|
+
consistent behavior across numpy, torch, and any future backends.
|
|
6
|
+
"""
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_jagged_array(x):
|
|
12
|
+
"""Check if x is a jagged (ragged) array - a list of lists with different lengths."""
|
|
13
|
+
if isinstance(x, list) and len(x) > 0:
|
|
14
|
+
if all(isinstance(item, (list, tuple)) for item in x):
|
|
15
|
+
return len(set(map(len, x))) > 1
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_supported_type(x):
|
|
20
|
+
"""Check if x can be converted to a tensor/array by the current backend.
|
|
21
|
+
|
|
22
|
+
Default implementation returns True for everything except strings and jagged arrays.
|
|
23
|
+
"""
|
|
24
|
+
return not (isinstance(x, str) or is_jagged_array(x))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BackendProvider(ABC):
|
|
28
|
+
"""Abstract interface for array backends."""
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def name(self) -> str:
|
|
33
|
+
"""Return the backend name."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def np(self):
|
|
39
|
+
"""Return numpy-compatible array module."""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def supports_object_dtype(self) -> bool:
|
|
44
|
+
"""Whether this backend supports object dtype."""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def supports_strings(self) -> bool:
|
|
49
|
+
"""Whether this backend supports string operations."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def supports_float64(self) -> bool:
|
|
54
|
+
"""Whether this backend supports float64 (double precision)."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def str_to_char_array(self, s):
|
|
59
|
+
"""Convert string to character array."""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def kg_asarray(self, a):
|
|
64
|
+
"""
|
|
65
|
+
Klong-specific array conversion.
|
|
66
|
+
|
|
67
|
+
Converts input data into an array suitable for Klong operations.
|
|
68
|
+
For backends that don't support object dtype, this should raise
|
|
69
|
+
an appropriate exception for unsupported data types.
|
|
70
|
+
"""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def is_array(self, x) -> bool:
|
|
75
|
+
"""Check if x is an array type for this backend."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def is_backend_array(self, x) -> bool:
|
|
80
|
+
"""Check if x is specifically this backend's array type (not numpy)."""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def get_dtype_kind(self, arr) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Get the dtype 'kind' character for an array.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
'O' for object dtype
|
|
90
|
+
'i' for integer types
|
|
91
|
+
'f' for float types
|
|
92
|
+
'u' for unsigned integer
|
|
93
|
+
'b' for boolean
|
|
94
|
+
'c' for complex
|
|
95
|
+
None if not an array
|
|
96
|
+
"""
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def to_numpy(self, x):
|
|
101
|
+
"""
|
|
102
|
+
Convert backend array to numpy array.
|
|
103
|
+
|
|
104
|
+
Handles device transfers (e.g., GPU to CPU) and gradient detachment.
|
|
105
|
+
"""
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def to_display(self, x):
|
|
109
|
+
"""
|
|
110
|
+
Convert backend array to display-friendly format.
|
|
111
|
+
|
|
112
|
+
For display purposes, converts arrays to numpy for consistent formatting.
|
|
113
|
+
0-dim arrays are converted to Python scalars.
|
|
114
|
+
Override in subclasses if different behavior is needed.
|
|
115
|
+
"""
|
|
116
|
+
if self.is_backend_array(x):
|
|
117
|
+
x = self.to_numpy(x)
|
|
118
|
+
# Convert 0-dim arrays to Python scalars
|
|
119
|
+
if hasattr(x, 'item') and hasattr(x, 'ndim') and x.ndim == 0:
|
|
120
|
+
return x.item()
|
|
121
|
+
return x
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def is_scalar_integer(self, x) -> bool:
|
|
125
|
+
"""Check if x is a 0-dim integer array/tensor."""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
@abstractmethod
|
|
129
|
+
def is_scalar_float(self, x) -> bool:
|
|
130
|
+
"""Check if x is a 0-dim float array/tensor."""
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
def scalar_to_python(self, x):
|
|
134
|
+
"""Convert a 0-dim array/tensor to Python scalar."""
|
|
135
|
+
if hasattr(x, 'item'):
|
|
136
|
+
return x.item()
|
|
137
|
+
return x
|
|
138
|
+
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def argsort(self, a, descending=False):
|
|
141
|
+
"""Return indices that would sort the array."""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
def is_integer(self, x) -> bool:
|
|
145
|
+
"""Check if x is an integer type (scalar, numpy integer, or 0-dim integer tensor)."""
|
|
146
|
+
if issubclass(type(x), (int, np.integer)):
|
|
147
|
+
return True
|
|
148
|
+
return self.is_scalar_integer(x)
|
|
149
|
+
|
|
150
|
+
def is_float(self, x) -> bool:
|
|
151
|
+
"""Check if x is a float type (scalar, numpy float, int, or 0-dim float tensor)."""
|
|
152
|
+
if issubclass(type(x), (float, np.floating, int)):
|
|
153
|
+
return True
|
|
154
|
+
return self.is_scalar_float(x) or self.is_scalar_integer(x)
|
|
155
|
+
|
|
156
|
+
def is_number(self, a) -> bool:
|
|
157
|
+
"""Check if a is a number (integer or float)."""
|
|
158
|
+
return self.is_float(a) or self.is_integer(a)
|
|
159
|
+
|
|
160
|
+
def str_to_chr_arr(self, s):
|
|
161
|
+
"""Convert string to character array (alias for str_to_char_array)."""
|
|
162
|
+
return self.str_to_char_array(s)
|
|
163
|
+
|
|
164
|
+
@abstractmethod
|
|
165
|
+
def array_size(self, a):
|
|
166
|
+
"""
|
|
167
|
+
Get the total number of elements in an array/tensor.
|
|
168
|
+
|
|
169
|
+
Works with both numpy arrays and torch tensors.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
int: Total element count (product of all dimensions)
|
|
173
|
+
"""
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
def safe_equal(self, x, y):
|
|
177
|
+
"""
|
|
178
|
+
Compare two values for equality, handling backend-specific array types.
|
|
179
|
+
|
|
180
|
+
Returns a truth value (0 or 1) suitable for Klong.
|
|
181
|
+
"""
|
|
182
|
+
return np.asarray(x, dtype=object) == np.asarray(y, dtype=object)
|
|
183
|
+
|
|
184
|
+
def detach_if_needed(self, x):
|
|
185
|
+
"""
|
|
186
|
+
Detach array from computation graph if needed.
|
|
187
|
+
|
|
188
|
+
For backends without autograd, this is a no-op.
|
|
189
|
+
"""
|
|
190
|
+
return x
|
|
191
|
+
|
|
192
|
+
def to_int_array(self, a):
|
|
193
|
+
"""
|
|
194
|
+
Convert array to integer type.
|
|
195
|
+
"""
|
|
196
|
+
return np.asarray(a, dtype=int) if self.is_array(a) else int(a)
|
|
197
|
+
|
|
198
|
+
def floor_to_int(self, a):
|
|
199
|
+
"""
|
|
200
|
+
Floor a value and convert to integer.
|
|
201
|
+
"""
|
|
202
|
+
result = np.floor(np.asarray(a, dtype=float))
|
|
203
|
+
return result.astype(int) if hasattr(result, 'astype') else int(result)
|
|
204
|
+
|
|
205
|
+
def power(self, a, b):
|
|
206
|
+
"""
|
|
207
|
+
Compute a^b, handling gradient tracking if applicable.
|
|
208
|
+
|
|
209
|
+
Returns integer result if the result is a whole number.
|
|
210
|
+
"""
|
|
211
|
+
r = np.power(float(a) if isinstance(a, (int, np.integer)) else a, b)
|
|
212
|
+
return r
|
|
213
|
+
|
|
214
|
+
def has_gradient(self, x) -> bool:
|
|
215
|
+
"""Check if x is tracking gradients (for autograd)."""
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
def supports_autograd(self) -> bool:
|
|
219
|
+
"""Whether this backend supports automatic differentiation."""
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
def array_equal(self, a, b) -> bool:
|
|
223
|
+
"""Backend-native exact equality for arrays/tensors."""
|
|
224
|
+
return bool(np.array_equal(a, b))
|
|
225
|
+
|
|
226
|
+
def create_grad_tensor(self, x):
|
|
227
|
+
"""Create a tensor that tracks gradients. Raises if not supported."""
|
|
228
|
+
raise NotImplementedError("This backend does not support autograd")
|
|
229
|
+
|
|
230
|
+
def compute_autograd(self, func, x):
|
|
231
|
+
"""Compute gradient using automatic differentiation. Raises if not supported."""
|
|
232
|
+
raise NotImplementedError("This backend does not support autograd")
|
|
233
|
+
|
|
234
|
+
def compute_multi_autograd(self, func, params):
|
|
235
|
+
"""
|
|
236
|
+
Compute gradients for multiple parameters in one backward pass.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
func: Callable that takes a list of tensors and returns a scalar loss
|
|
240
|
+
params: List of parameter values to compute gradients for
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
List of gradients, one per parameter
|
|
244
|
+
"""
|
|
245
|
+
raise NotImplementedError("This backend does not support multi-parameter autograd")
|
|
246
|
+
|
|
247
|
+
def compute_jacobian(self, func, x):
|
|
248
|
+
"""
|
|
249
|
+
Compute Jacobian matrix of func at point x.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
func: Callable that takes x and returns a vector
|
|
253
|
+
x: Input point (tensor/array)
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Jacobian matrix J where J[i,j] = df_i/dx_j
|
|
257
|
+
"""
|
|
258
|
+
raise NotImplementedError("This backend does not support Jacobian computation")
|
|
259
|
+
|
|
260
|
+
def compile_function(self, func, example_input, output_path=None, mode="default",
|
|
261
|
+
backend="inductor", fullgraph=False, dynamic=None):
|
|
262
|
+
"""
|
|
263
|
+
Compile a function for optimized execution and optionally export for inspection.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
func: Callable to compile
|
|
267
|
+
example_input: Example input for tracing the function
|
|
268
|
+
output_path: Optional path to export the compiled graph
|
|
269
|
+
mode: Compilation mode ("default", "reduce-overhead", "max-autotune")
|
|
270
|
+
backend: Compilation backend ("inductor", "eager", "cudagraphs")
|
|
271
|
+
fullgraph: If True, requires entire function to compile as one graph
|
|
272
|
+
dynamic: If True, enables dynamic shapes
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Compiled function or export info dict
|
|
276
|
+
"""
|
|
277
|
+
raise NotImplementedError("This backend does not support function compilation")
|
|
278
|
+
|
|
279
|
+
def get_compile_modes(self):
|
|
280
|
+
"""
|
|
281
|
+
Return information about available compilation modes.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Dict with mode descriptions and recommendations
|
|
285
|
+
"""
|
|
286
|
+
raise NotImplementedError("This backend does not support function compilation")
|
|
287
|
+
|
|
288
|
+
def gradcheck(self, func, inputs, eps=1e-6, atol=1e-5, rtol=1e-3):
|
|
289
|
+
"""
|
|
290
|
+
Check gradients computed by autograd against numeric gradients.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
func: Function to check
|
|
294
|
+
inputs: Tuple of input tensors
|
|
295
|
+
eps: Step size for numeric differentiation
|
|
296
|
+
atol: Absolute tolerance
|
|
297
|
+
rtol: Relative tolerance
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
True if gradients match, raises error otherwise
|
|
301
|
+
"""
|
|
302
|
+
raise NotImplementedError("This backend does not support gradcheck")
|
|
303
|
+
|
|
304
|
+
def klong_gradcheck(self, klong, fn, inputs):
|
|
305
|
+
"""
|
|
306
|
+
Check gradients for a Klong function.
|
|
307
|
+
|
|
308
|
+
This is a higher-level interface that handles wrapping the Klong function
|
|
309
|
+
and converting inputs appropriately for the backend.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
klong: KlongInterpreter instance
|
|
313
|
+
fn: Klong function to check
|
|
314
|
+
inputs: Input value or list of inputs
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
1 if gradients are correct, raises error otherwise
|
|
318
|
+
"""
|
|
319
|
+
raise RuntimeError(
|
|
320
|
+
".gradcheck() requires PyTorch backend. "
|
|
321
|
+
"Run with USE_TORCH=1 environment variable."
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def kg_equal(self, a, b):
|
|
326
|
+
"""Compare two values or arrays for equality, handling nested arrays and tensors."""
|
|
327
|
+
if a is b:
|
|
328
|
+
return True
|
|
329
|
+
|
|
330
|
+
# Backend-native comparison for backend arrays
|
|
331
|
+
if self.is_backend_array(a) and self.is_backend_array(b):
|
|
332
|
+
return self.array_equal(a, b)
|
|
333
|
+
|
|
334
|
+
# Fast path for numpy arrays (non-object)
|
|
335
|
+
if isinstance(a, np.ndarray) and isinstance(b, np.ndarray):
|
|
336
|
+
if a.dtype != object and b.dtype != object:
|
|
337
|
+
return bool(np.array_equal(a, b))
|
|
338
|
+
|
|
339
|
+
# Convert backend arrays to numpy for mixed comparisons
|
|
340
|
+
if self.is_backend_array(a):
|
|
341
|
+
a = self.to_numpy(a)
|
|
342
|
+
if self.is_backend_array(b):
|
|
343
|
+
b = self.to_numpy(b)
|
|
344
|
+
|
|
345
|
+
# Fast path for numpy arrays (after any conversion)
|
|
346
|
+
if isinstance(a, np.ndarray) and isinstance(b, np.ndarray):
|
|
347
|
+
if a.dtype != object and b.dtype != object:
|
|
348
|
+
return bool(np.array_equal(a, b))
|
|
349
|
+
|
|
350
|
+
# Normalize 0-d numpy arrays to scalars for mixed comparisons
|
|
351
|
+
if isinstance(a, np.ndarray) and a.ndim == 0:
|
|
352
|
+
a = a.item()
|
|
353
|
+
if isinstance(b, np.ndarray) and b.ndim == 0:
|
|
354
|
+
b = b.item()
|
|
355
|
+
|
|
356
|
+
# List/sequence comparison
|
|
357
|
+
a_is_seq = isinstance(a, (list, tuple)) or (isinstance(a, np.ndarray) and a.ndim > 0)
|
|
358
|
+
b_is_seq = isinstance(b, (list, tuple)) or (isinstance(b, np.ndarray) and b.ndim > 0)
|
|
359
|
+
if a_is_seq or b_is_seq:
|
|
360
|
+
if not (a_is_seq and b_is_seq):
|
|
361
|
+
return False
|
|
362
|
+
if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
|
|
363
|
+
def _is_int_scalar(x):
|
|
364
|
+
return isinstance(x, (int, bool, np.integer))
|
|
365
|
+
if len(a) == len(b) and len(a) >= 32 and all(_is_int_scalar(x) for x in a) and all(_is_int_scalar(y) for y in b):
|
|
366
|
+
return a == b
|
|
367
|
+
# Fast path for object numpy arrays when possible
|
|
368
|
+
if isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and a.dtype == object and b.dtype == object:
|
|
369
|
+
if a.size >= 128:
|
|
370
|
+
try:
|
|
371
|
+
return bool(np.array_equal(a, b))
|
|
372
|
+
except Exception:
|
|
373
|
+
pass
|
|
374
|
+
if len(a) != len(b):
|
|
375
|
+
return False
|
|
376
|
+
return all(self.kg_equal(x, y) for x, y in zip(a, b))
|
|
377
|
+
|
|
378
|
+
# Numeric scalars: tolerant comparison
|
|
379
|
+
if self.is_number(a) and self.is_number(b):
|
|
380
|
+
result = np.isclose(a, b)
|
|
381
|
+
if hasattr(result, 'item'):
|
|
382
|
+
return bool(result.item())
|
|
383
|
+
return bool(result)
|
|
384
|
+
|
|
385
|
+
# Fallback: direct equality
|
|
386
|
+
result = a == b
|
|
387
|
+
if hasattr(result, 'all'):
|
|
388
|
+
return bool(result.all())
|
|
389
|
+
if hasattr(result, 'item'):
|
|
390
|
+
return bool(result.item())
|
|
391
|
+
return bool(result)
|
|
392
|
+
|
|
393
|
+
def vec_fn(self, a, f):
|
|
394
|
+
"""
|
|
395
|
+
Apply function f to array a, with support for nested object arrays.
|
|
396
|
+
"""
|
|
397
|
+
if self.np.isarray(a) and a.dtype == 'O':
|
|
398
|
+
result = [self.vec_fn(x, f) if self._is_list(x) else f(x) for x in a]
|
|
399
|
+
return np.asarray(result, dtype=object)
|
|
400
|
+
return f(a)
|
|
401
|
+
|
|
402
|
+
def vec_fn2(self, a, b, f):
|
|
403
|
+
"""
|
|
404
|
+
Apply function f to elements of a and b, handling nested structures.
|
|
405
|
+
"""
|
|
406
|
+
if self.np.isarray(a):
|
|
407
|
+
if a.dtype == 'O':
|
|
408
|
+
if self.np.isarray(b):
|
|
409
|
+
assert len(a) == len(b)
|
|
410
|
+
return self.kg_asarray([self.vec_fn2(x, y, f) for x, y in zip(a, b)])
|
|
411
|
+
else:
|
|
412
|
+
return self.kg_asarray([self.vec_fn2(x, b, f) for x in a])
|
|
413
|
+
elif self.np.isarray(b) and b.dtype == 'O':
|
|
414
|
+
assert len(a) == len(b)
|
|
415
|
+
return self.kg_asarray([self.vec_fn2(x, y, f) for x, y in zip(a, b)])
|
|
416
|
+
elif self.np.isarray(b) and b.dtype == 'O':
|
|
417
|
+
return self.kg_asarray([self.vec_fn2(a, x, f) for x in b])
|
|
418
|
+
return f(a, b)
|
|
419
|
+
|
|
420
|
+
def rec_fn(self, a, f):
|
|
421
|
+
"""
|
|
422
|
+
Recursively apply function f to all elements of a nested structure.
|
|
423
|
+
"""
|
|
424
|
+
return self.kg_asarray([self.rec_fn(x, f) for x in a]) if self._is_list(a) else f(a)
|
|
425
|
+
|
|
426
|
+
def _is_list(self, x):
|
|
427
|
+
"""Check if x is a list-like structure (array or list, non-empty)."""
|
|
428
|
+
if isinstance(x, np.ndarray):
|
|
429
|
+
return x.size > 0
|
|
430
|
+
if isinstance(x, (list, tuple)):
|
|
431
|
+
return len(x) > 0
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def device(self):
|
|
436
|
+
"""Return the current device for this backend (e.g., 'cpu', 'cuda:0', 'mps')."""
|
|
437
|
+
return 'cpu'
|
|
438
|
+
|
|
439
|
+
def list_devices(self):
|
|
440
|
+
"""
|
|
441
|
+
List available devices for this backend.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
list: List of available device names (e.g., ['cpu'], ['cpu', 'cuda:0', 'mps'])
|
|
445
|
+
"""
|
|
446
|
+
return ['cpu']
|
|
447
|
+
|
|
448
|
+
def get_info(self):
|
|
449
|
+
"""
|
|
450
|
+
Get comprehensive information about this backend.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
dict: Dictionary with backend name, current device, available devices,
|
|
454
|
+
and feature support flags.
|
|
455
|
+
"""
|
|
456
|
+
return {
|
|
457
|
+
'name': self.name,
|
|
458
|
+
'device': self.device,
|
|
459
|
+
'devices': self.list_devices(),
|
|
460
|
+
'supports_float64': self.supports_float64(),
|
|
461
|
+
'supports_strings': self.supports_strings(),
|
|
462
|
+
'supports_object_dtype': self.supports_object_dtype(),
|
|
463
|
+
'supports_autograd': self.supports_autograd(),
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class UnsupportedDtypeError(Exception):
|
|
468
|
+
"""Raised when an operation requires a dtype not supported by the backend."""
|
|
469
|
+
pass
|