graphspace 0.1.0__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.
- graphspace/__init__.py +16 -0
- graphspace/_version.py +1 -0
- graphspace/backends/__init__.py +28 -0
- graphspace/backends/numpy.py +127 -0
- graphspace/backends/python.py +184 -0
- graphspace/claims.py +28 -0
- graphspace/cli.py +24 -0
- graphspace/core.py +697 -0
- graphspace/failures.py +64 -0
- graphspace/integrations/__init__.py +23 -0
- graphspace/integrations/numpy.py +14 -0
- graphspace/integrations/torch.py +14 -0
- graphspace/py.typed +0 -0
- graphspace/uncertainty.py +34 -0
- graphspace-0.1.0.dist-info/METADATA +101 -0
- graphspace-0.1.0.dist-info/RECORD +20 -0
- graphspace-0.1.0.dist-info/WHEEL +5 -0
- graphspace-0.1.0.dist-info/entry_points.txt +2 -0
- graphspace-0.1.0.dist-info/licenses/LICENSE +202 -0
- graphspace-0.1.0.dist-info/top_level.txt +1 -0
graphspace/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from ._version import __version__
|
|
2
|
+
from .claims import Basis, Claim
|
|
3
|
+
from .core import Analysis, Graph, Node, TensorSpec, ResourceContract, ExecutionRecord, MemoryPlan, MemoryValue
|
|
4
|
+
from .uncertainty import Uncertain
|
|
5
|
+
from .failures import (
|
|
6
|
+
GraphspaceError, ShapeMismatch, DTypeMismatch, ResourceLimitExceeded, UnknownValue, ContractViolation,
|
|
7
|
+
BackendUnavailable, LowConfidence,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"__version__",
|
|
12
|
+
"Analysis", "Basis", "Claim",
|
|
13
|
+
"Graph", "Node", "TensorSpec", "ResourceContract", "ExecutionRecord", "MemoryPlan", "MemoryValue", "Uncertain",
|
|
14
|
+
"GraphspaceError", "ShapeMismatch", "DTypeMismatch", "ResourceLimitExceeded", "UnknownValue",
|
|
15
|
+
"ContractViolation", "BackendUnavailable", "LowConfidence",
|
|
16
|
+
]
|
graphspace/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from ..failures import BackendUnavailable
|
|
6
|
+
|
|
7
|
+
BACKENDS = ("python", "numpy")
|
|
8
|
+
_INSTANCES: dict = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_backend(name: str):
|
|
12
|
+
if name in _INSTANCES:
|
|
13
|
+
return _INSTANCES[name]
|
|
14
|
+
if name == "python":
|
|
15
|
+
from .python import PythonBackend
|
|
16
|
+
return _INSTANCES.setdefault(name, PythonBackend())
|
|
17
|
+
if name == "numpy":
|
|
18
|
+
from .numpy import NumpyBackend
|
|
19
|
+
return _INSTANCES.setdefault(name, NumpyBackend())
|
|
20
|
+
raise BackendUnavailable(f"unknown backend {name!r}", expected=list(BACKENDS), actual=name)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def frame_digest(entries: Iterable[tuple[str, str, int, bytes]]) -> str:
|
|
24
|
+
digest = hashlib.sha256()
|
|
25
|
+
for name, dtype, length, payload in sorted(entries, key=lambda entry: entry[0]):
|
|
26
|
+
header = json.dumps([name, dtype, length]).encode()
|
|
27
|
+
digest.update(len(header).to_bytes(8, "little") + header + payload)
|
|
28
|
+
return digest.hexdigest()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from ..core import FLOAT_DTYPES, INT_RANGES, Dims, Node, TensorSpec, _product
|
|
5
|
+
from ..failures import BackendUnavailable, ContractViolation, DTypeMismatch, ShapeMismatch
|
|
6
|
+
from . import frame_digest
|
|
7
|
+
from .python import PythonBackend
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NumpyBackend:
|
|
11
|
+
name = "numpy"
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
try:
|
|
15
|
+
import numpy
|
|
16
|
+
except ImportError as error:
|
|
17
|
+
raise BackendUnavailable(
|
|
18
|
+
"backend 'numpy' requires NumPy",
|
|
19
|
+
actual="numpy not installed", remediation="pip install 'graphspace[numpy]'",
|
|
20
|
+
) from error
|
|
21
|
+
self.np = numpy
|
|
22
|
+
self.version = numpy.__version__
|
|
23
|
+
self._python = PythonBackend()
|
|
24
|
+
|
|
25
|
+
def session(self):
|
|
26
|
+
return self.np.errstate(all="ignore")
|
|
27
|
+
|
|
28
|
+
def length(self, name: str, data: Any) -> int:
|
|
29
|
+
if isinstance(data, self.np.ndarray):
|
|
30
|
+
return int(data.size)
|
|
31
|
+
return self._python.length(name, data)
|
|
32
|
+
|
|
33
|
+
def coerce(self, name: str, data: Any, spec: TensorSpec, dims: Dims):
|
|
34
|
+
np = self.np
|
|
35
|
+
shape = spec.concrete_shape(dims)
|
|
36
|
+
if not isinstance(data, np.ndarray):
|
|
37
|
+
return np.array(self._python.coerce(name, data, spec, dims), dtype=spec.dtype).reshape(shape)
|
|
38
|
+
if isinstance(data, np.ma.MaskedArray):
|
|
39
|
+
raise DTypeMismatch(
|
|
40
|
+
f"input {name} is a masked array",
|
|
41
|
+
node=name, expected="ndarray", actual="MaskedArray", remediation="pass data.filled(value) or data.compressed()",
|
|
42
|
+
)
|
|
43
|
+
data = np.asarray(data)
|
|
44
|
+
if data.size != _product(shape):
|
|
45
|
+
raise ShapeMismatch(
|
|
46
|
+
f"input {name} has {data.size} elements, shape {spec.shape} needs {_product(shape)}",
|
|
47
|
+
node=name, expected=_product(shape), actual=int(data.size),
|
|
48
|
+
)
|
|
49
|
+
if data.ndim > 1 and data.shape != shape:
|
|
50
|
+
raise ShapeMismatch(f"input {name} has shape {data.shape}, expected {shape}", node=name, expected=shape, actual=data.shape)
|
|
51
|
+
allowed = "fiu" if spec.dtype in FLOAT_DTYPES else "iu"
|
|
52
|
+
if data.dtype.kind not in allowed:
|
|
53
|
+
raise DTypeMismatch(
|
|
54
|
+
f"input {name} has dtype {data.dtype}, not usable as {spec.dtype}",
|
|
55
|
+
node=name, expected=spec.dtype, actual=str(data.dtype),
|
|
56
|
+
)
|
|
57
|
+
if spec.dtype in INT_RANGES and data.size:
|
|
58
|
+
low, high = INT_RANGES[spec.dtype]
|
|
59
|
+
if int(data.min()) < low or int(data.max()) > high:
|
|
60
|
+
raise DTypeMismatch(
|
|
61
|
+
f"input {name}: value outside {spec.dtype} range",
|
|
62
|
+
node=name, expected=[low, high], actual=f"{int(((data < low) | (data > high)).sum())} values out of range",
|
|
63
|
+
)
|
|
64
|
+
array = data if data.dtype == spec.dtype else data.astype(spec.dtype)
|
|
65
|
+
return array.reshape(shape)
|
|
66
|
+
|
|
67
|
+
def run(self, node: Node, args: list, in_shapes: list[tuple[int, ...]], out_shape: tuple[int, ...], out: Any = None):
|
|
68
|
+
np = self.np
|
|
69
|
+
dtype = node.output_spec.dtype
|
|
70
|
+
if dtype in INT_RANGES and node.operation in {"add", "multiply", "subtract", "matmul"}:
|
|
71
|
+
wide = np.int64 if dtype == "int32" and node.operation != "matmul" else object
|
|
72
|
+
result = self._apply(node, [arg.astype(wide) for arg in args], out_shape, None)
|
|
73
|
+
low, high = INT_RANGES[dtype]
|
|
74
|
+
if result.size and (result.min() < low or result.max() > high):
|
|
75
|
+
raise DTypeMismatch(
|
|
76
|
+
f"{node.output}: value outside {dtype} range",
|
|
77
|
+
node=node.output, expected=[low, high], actual=f"{int(((result < low) | (result > high)).sum())} values out of range",
|
|
78
|
+
)
|
|
79
|
+
return result.astype(dtype)
|
|
80
|
+
return self._apply(node, args, out_shape, out)
|
|
81
|
+
|
|
82
|
+
def _apply(self, node: Node, args: list, out_shape: tuple[int, ...], out: Any):
|
|
83
|
+
np = self.np
|
|
84
|
+
if node.operation == "add":
|
|
85
|
+
return np.add(*args, out=out, order="C")
|
|
86
|
+
if node.operation == "multiply":
|
|
87
|
+
return np.multiply(*args, out=out, order="C")
|
|
88
|
+
if node.operation == "subtract":
|
|
89
|
+
return np.subtract(*args, out=out, order="C")
|
|
90
|
+
if node.operation == "divide":
|
|
91
|
+
return np.divide(*args, out=out, order="C")
|
|
92
|
+
if node.operation == "relu":
|
|
93
|
+
return np.maximum(args[0], args[0].dtype.type(0), out=out, order="C")
|
|
94
|
+
if node.operation == "scale":
|
|
95
|
+
return np.multiply(args[0], args[0].dtype.type(node.attribute("factor")), out=out, order="C")
|
|
96
|
+
if node.operation == "reshape":
|
|
97
|
+
return args[0].reshape(out_shape)
|
|
98
|
+
if node.operation == "transpose":
|
|
99
|
+
return args[0].transpose(node.attribute("axes"))
|
|
100
|
+
if node.operation == "matmul":
|
|
101
|
+
return np.matmul(*args, out=out)
|
|
102
|
+
if node.operation == "softmax":
|
|
103
|
+
values = args[0]
|
|
104
|
+
result = np.subtract(values, values.max(axis=-1, keepdims=True, initial=-np.inf), out=out, order="C")
|
|
105
|
+
np.exp(result, out=result)
|
|
106
|
+
return np.divide(result, result.sum(axis=-1, keepdims=True), out=result)
|
|
107
|
+
if node.operation == "layer_norm":
|
|
108
|
+
values, gamma, beta = args
|
|
109
|
+
width = values.shape[-1]
|
|
110
|
+
result = np.subtract(values, values.mean(axis=-1, keepdims=True), out=out, order="C")
|
|
111
|
+
rows = result.reshape(-1, width)
|
|
112
|
+
variance = np.einsum("ij,ij->i", rows, rows).reshape(result.shape[:-1] + (1,))
|
|
113
|
+
np.divide(variance, width, out=variance)
|
|
114
|
+
np.add(variance, node.attribute("eps"), out=variance)
|
|
115
|
+
np.sqrt(variance, out=variance)
|
|
116
|
+
np.divide(result, variance, out=result)
|
|
117
|
+
np.multiply(result, gamma, out=result)
|
|
118
|
+
return np.add(result, beta, out=result)
|
|
119
|
+
raise ContractViolation(f"{node.name}: unsupported operation {node.operation}", node=node.name, actual=node.operation)
|
|
120
|
+
|
|
121
|
+
def digest(self, values: Mapping[str, tuple[str, Any]]) -> str:
|
|
122
|
+
np = self.np
|
|
123
|
+
entries = []
|
|
124
|
+
for name, (dtype, data) in values.items():
|
|
125
|
+
wide = np.ascontiguousarray(data, dtype="<f8" if dtype in FLOAT_DTYPES else "<i8")
|
|
126
|
+
entries.append((name, dtype, int(wide.size), wide.tobytes()))
|
|
127
|
+
return frame_digest(entries)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from collections.abc import Mapping, Sequence
|
|
2
|
+
from contextlib import nullcontext
|
|
3
|
+
from typing import Any
|
|
4
|
+
import array
|
|
5
|
+
import math
|
|
6
|
+
import platform
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from ..core import FLOAT_DTYPES, INT_RANGES, Dims, Node, TensorSpec, _product
|
|
10
|
+
from ..failures import ContractViolation, DTypeMismatch, ShapeMismatch
|
|
11
|
+
from . import frame_digest
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PythonBackend:
|
|
15
|
+
name = "python"
|
|
16
|
+
version = platform.python_version()
|
|
17
|
+
|
|
18
|
+
def session(self):
|
|
19
|
+
return nullcontext()
|
|
20
|
+
|
|
21
|
+
def length(self, name: str, data: Any) -> int:
|
|
22
|
+
if isinstance(data, (str, bytes)) or not isinstance(data, Sequence):
|
|
23
|
+
raise DTypeMismatch(
|
|
24
|
+
f"input {name} must be a flat sequence of numbers, got {type(data).__name__}",
|
|
25
|
+
node=name, expected="sequence", actual=type(data).__name__,
|
|
26
|
+
)
|
|
27
|
+
return len(data)
|
|
28
|
+
|
|
29
|
+
def coerce(self, name: str, data: Any, spec: TensorSpec, dims: Dims) -> list:
|
|
30
|
+
expected = _product(spec.concrete_shape(dims))
|
|
31
|
+
if self.length(name, data) != expected:
|
|
32
|
+
raise ShapeMismatch(
|
|
33
|
+
f"input {name} has {len(data)} elements, shape {spec.shape} needs {expected}",
|
|
34
|
+
node=name, expected=expected, actual=len(data),
|
|
35
|
+
)
|
|
36
|
+
if spec.dtype in FLOAT_DTYPES:
|
|
37
|
+
if all(type(value) is float for value in data):
|
|
38
|
+
return data if isinstance(data, list) else list(data)
|
|
39
|
+
if any(isinstance(value, bool) or not isinstance(value, (int, float)) for value in data):
|
|
40
|
+
raise DTypeMismatch(
|
|
41
|
+
f"input {name} must contain only numbers for {spec.dtype}",
|
|
42
|
+
node=name, expected=spec.dtype, actual=_first_invalid(data, (int, float)),
|
|
43
|
+
)
|
|
44
|
+
return [float(value) for value in data]
|
|
45
|
+
if not all(type(value) is int for value in data) and any(
|
|
46
|
+
isinstance(value, bool) or not isinstance(value, int) for value in data
|
|
47
|
+
):
|
|
48
|
+
raise DTypeMismatch(
|
|
49
|
+
f"input {name} must contain only ints for {spec.dtype}",
|
|
50
|
+
node=name, expected=spec.dtype, actual=_first_invalid(data, (int,)),
|
|
51
|
+
)
|
|
52
|
+
values = [int(value) for value in data]
|
|
53
|
+
check_range(name, values, spec.dtype)
|
|
54
|
+
return values
|
|
55
|
+
|
|
56
|
+
def run(
|
|
57
|
+
self, node: Node, args: list[list], in_shapes: list[tuple[int, ...]], out_shape: tuple[int, ...], out: Any = None,
|
|
58
|
+
) -> list:
|
|
59
|
+
result = self._apply(node, args, in_shapes, out_shape)
|
|
60
|
+
check_range(node.output, result, node.output_spec.dtype)
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
def _apply(self, node: Node, args: list[list], in_shapes: list[tuple[int, ...]], out_shape: tuple[int, ...]) -> list:
|
|
64
|
+
operation = node.operation
|
|
65
|
+
if operation in BINARY:
|
|
66
|
+
left, right = args
|
|
67
|
+
if in_shapes[0] != out_shape:
|
|
68
|
+
left = [left[index] for index in broadcast_indices(in_shapes[0], out_shape)]
|
|
69
|
+
if in_shapes[1] != out_shape:
|
|
70
|
+
right = [right[index] for index in broadcast_indices(in_shapes[1], out_shape)]
|
|
71
|
+
function = BINARY[operation]
|
|
72
|
+
return [function(a, b) for a, b in zip(left, right)]
|
|
73
|
+
if operation == "relu":
|
|
74
|
+
zero = 0.0 if node.output_spec.dtype in FLOAT_DTYPES else 0
|
|
75
|
+
return [zero if value < zero else value for value in args[0]]
|
|
76
|
+
if operation == "scale":
|
|
77
|
+
factor = node.attribute("factor")
|
|
78
|
+
return [value * factor for value in args[0]]
|
|
79
|
+
if operation == "reshape":
|
|
80
|
+
return args[0]
|
|
81
|
+
if operation == "transpose":
|
|
82
|
+
return [args[0][index] for index in transpose_indices(in_shapes[0], node.attribute("axes"))]
|
|
83
|
+
if operation == "matmul":
|
|
84
|
+
left, right = args
|
|
85
|
+
(rows, inner), (_, columns) = in_shapes
|
|
86
|
+
return [sum(left[row * inner + k] * right[k * columns + col] for k in range(inner)) for row in range(rows) for col in range(columns)]
|
|
87
|
+
if operation == "softmax":
|
|
88
|
+
return [value for row in _rows(args[0], out_shape) for value in _softmax(row)]
|
|
89
|
+
if operation == "layer_norm":
|
|
90
|
+
values, gamma, beta = args
|
|
91
|
+
eps = node.attribute("eps")
|
|
92
|
+
return [value for row in _rows(values, out_shape) for value in _layer_norm(row, gamma, beta, eps)]
|
|
93
|
+
raise ContractViolation(f"{node.name}: unsupported operation {operation}", node=node.name, actual=operation)
|
|
94
|
+
|
|
95
|
+
def digest(self, values: Mapping[str, tuple[str, list]]) -> str:
|
|
96
|
+
entries = []
|
|
97
|
+
for name, (dtype, data) in values.items():
|
|
98
|
+
packed = array.array("d" if dtype in FLOAT_DTYPES else "q", data)
|
|
99
|
+
if sys.byteorder == "big":
|
|
100
|
+
packed.byteswap()
|
|
101
|
+
entries.append((name, dtype, len(data), packed.tobytes()))
|
|
102
|
+
return frame_digest(entries)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _divide(a: float, b: float) -> float:
|
|
106
|
+
if b != 0:
|
|
107
|
+
return a / b
|
|
108
|
+
if a != a or a == 0:
|
|
109
|
+
return math.nan
|
|
110
|
+
return math.copysign(math.inf, a) * math.copysign(1.0, b)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
BINARY = {
|
|
114
|
+
"add": lambda a, b: a + b,
|
|
115
|
+
"subtract": lambda a, b: a - b,
|
|
116
|
+
"multiply": lambda a, b: a * b,
|
|
117
|
+
"divide": _divide,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _strides(shape: tuple[int, ...]) -> list[int]:
|
|
122
|
+
strides = [1] * len(shape)
|
|
123
|
+
for axis in range(len(shape) - 2, -1, -1):
|
|
124
|
+
strides[axis] = strides[axis + 1] * shape[axis + 1]
|
|
125
|
+
return strides
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _expand(sizes: tuple[int, ...], strides: list[int]) -> list[int]:
|
|
129
|
+
indices = [0]
|
|
130
|
+
for size, stride in zip(sizes, strides):
|
|
131
|
+
indices = [base + step * stride for base in indices for step in range(size)]
|
|
132
|
+
return indices
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def broadcast_indices(in_shape: tuple[int, ...], out_shape: tuple[int, ...]) -> list[int]:
|
|
136
|
+
padded = (1,) * (len(out_shape) - len(in_shape)) + tuple(in_shape)
|
|
137
|
+
strides = [0 if size == 1 else stride for size, stride in zip(padded, _strides(padded))]
|
|
138
|
+
return _expand(out_shape, strides)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def transpose_indices(in_shape: tuple[int, ...], axes: tuple[int, ...]) -> list[int]:
|
|
142
|
+
strides = _strides(in_shape)
|
|
143
|
+
return _expand(tuple(in_shape[axis] for axis in axes), [strides[axis] for axis in axes])
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _rows(values: list, shape: tuple[int, ...]):
|
|
147
|
+
width = shape[-1]
|
|
148
|
+
for start in range(0, len(values), width or 1):
|
|
149
|
+
yield values[start:start + width]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _softmax(row: list) -> list:
|
|
153
|
+
if any(value != value for value in row):
|
|
154
|
+
return [math.nan] * len(row)
|
|
155
|
+
peak = max(row, default=0.0)
|
|
156
|
+
exponents = [math.exp(value - peak) if value - peak == value - peak else math.nan for value in row]
|
|
157
|
+
total = sum(exponents)
|
|
158
|
+
return [_divide(value, total) for value in exponents]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _layer_norm(row: list, gamma: list, beta: list, eps: float) -> list:
|
|
162
|
+
width = len(row)
|
|
163
|
+
mean = sum(row) / width if width else math.nan
|
|
164
|
+
centered = [value - mean for value in row]
|
|
165
|
+
deviation = math.sqrt(sum(value * value for value in centered) / width + eps) if width else math.nan
|
|
166
|
+
return [value / deviation * g + b for value, g, b in zip(centered, gamma, beta)]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def check_range(name: str, values: list, dtype: str) -> None:
|
|
170
|
+
if dtype not in INT_RANGES:
|
|
171
|
+
return
|
|
172
|
+
low, high = INT_RANGES[dtype]
|
|
173
|
+
if values and (min(values) < low or max(values) > high):
|
|
174
|
+
raise DTypeMismatch(
|
|
175
|
+
f"{name}: value outside {dtype} range",
|
|
176
|
+
node=name, expected=[low, high], actual=f"{sum(not low <= value <= high for value in values)} values out of range",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _first_invalid(data: Sequence, allowed: tuple[type, ...]) -> str:
|
|
181
|
+
for value in data:
|
|
182
|
+
if isinstance(value, bool) or not isinstance(value, allowed):
|
|
183
|
+
return type(value).__name__
|
|
184
|
+
return ""
|
graphspace/claims.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Basis(str, Enum):
|
|
8
|
+
DECLARED = "declared"
|
|
9
|
+
PROVEN = "proven"
|
|
10
|
+
INFERRED = "inferred"
|
|
11
|
+
ESTIMATED = "estimated"
|
|
12
|
+
RUNTIME_CHECKED = "runtime_checked"
|
|
13
|
+
MEASURED = "measured"
|
|
14
|
+
BACKEND_REPORTED = "backend_reported"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class Claim:
|
|
19
|
+
name: str
|
|
20
|
+
value: Any
|
|
21
|
+
basis: Basis
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def find_claim(claims: Iterable[Claim], name: str) -> Claim:
|
|
25
|
+
for claim in claims:
|
|
26
|
+
if claim.name == name:
|
|
27
|
+
return claim
|
|
28
|
+
raise KeyError(name)
|
graphspace/cli.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .core import Graph, ResourceContract, TensorSpec
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def demo() -> int:
|
|
5
|
+
graph = Graph("demo_add", ResourceContract.max_memory(65536, deterministic=True))
|
|
6
|
+
spec = TensorSpec((2, 4), "float32")
|
|
7
|
+
graph.input("a", spec)
|
|
8
|
+
graph.input("b", spec)
|
|
9
|
+
output = graph.add("a", "b")
|
|
10
|
+
graph.output(output)
|
|
11
|
+
result, record = graph.execute({"a": [1.0] * 8, "b": [2.0] * 8})
|
|
12
|
+
print(f"graph={record.graph_name}")
|
|
13
|
+
print(f"result_elements={len(result)}")
|
|
14
|
+
print(f"peak_memory_bytes={record.peak_memory_bytes}")
|
|
15
|
+
print(f"deterministic={record.deterministic}")
|
|
16
|
+
return 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
return demo()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
raise SystemExit(main())
|