tensorcode 0.1.0a1__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.
- tensorcode/__init__.py +84 -0
- tensorcode/actions.py +137 -0
- tensorcode/answer_type.py +222 -0
- tensorcode/awareness.py +344 -0
- tensorcode/backends/__init__.py +0 -0
- tensorcode/backends/builtin.py +167 -0
- tensorcode/backends/hf_local.py +89 -0
- tensorcode/backends/linear.py +133 -0
- tensorcode/backends/neural.py +361 -0
- tensorcode/causal.py +262 -0
- tensorcode/change.py +566 -0
- tensorcode/chunking.py +195 -0
- tensorcode/cognition.py +311 -0
- tensorcode/context.py +97 -0
- tensorcode/control.py +291 -0
- tensorcode/cues.py +192 -0
- tensorcode/expectation.py +270 -0
- tensorcode/frames.py +232 -0
- tensorcode/language/__init__.py +36 -0
- tensorcode/language/chart.py +558 -0
- tensorcode/language/discourse.py +132 -0
- tensorcode/language/domains/__init__.py +0 -0
- tensorcode/language/domains/desktop.py +552 -0
- tensorcode/language/english.py +459 -0
- tensorcode/language/features.py +112 -0
- tensorcode/language/generate.py +574 -0
- tensorcode/language/grammar.py +893 -0
- tensorcode/language/semantics.py +349 -0
- tensorcode/learning/__init__.py +30 -0
- tensorcode/learning/certificate.py +148 -0
- tensorcode/learning/induce.py +304 -0
- tensorcode/learning/library.py +217 -0
- tensorcode/learning/literals.py +126 -0
- tensorcode/learning/verify.py +253 -0
- tensorcode/memory.py +303 -0
- tensorcode/metacognition.py +351 -0
- tensorcode/ops.py +207 -0
- tensorcode/outcomes.py +99 -0
- tensorcode/permanence.py +376 -0
- tensorcode/priming.py +191 -0
- tensorcode/py.typed +0 -0
- tensorcode/quantity.py +311 -0
- tensorcode/records.py +728 -0
- tensorcode/relation.py +771 -0
- tensorcode/runtime.py +471 -0
- tensorcode/semantics_bridge.py +308 -0
- tensorcode/social.py +380 -0
- tensorcode/temporal.py +189 -0
- tensorcode/wants.py +185 -0
- tensorcode-0.1.0a1.dist-info/METADATA +196 -0
- tensorcode-0.1.0a1.dist-info/RECORD +53 -0
- tensorcode-0.1.0a1.dist-info/WHEEL +4 -0
- tensorcode-0.1.0a1.dist-info/licenses/LICENSE +21 -0
tensorcode/runtime.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
"""Binding operations to implementations, and recording what happened.
|
|
2
|
+
|
|
3
|
+
An operation call is a ``Request``. Policy decides which implementations are
|
|
4
|
+
acceptable (hard constraints) and in what order to try them (cascade). Each
|
|
5
|
+
implementation may answer, abstain (``Unknown``), or fail. Every attempt is
|
|
6
|
+
recorded in the active ``Trace``.
|
|
7
|
+
|
|
8
|
+
Unknown quantities stay unknown: a missing cost is never treated as zero, a
|
|
9
|
+
missing quality estimate is never treated as good, and a cost cap excludes
|
|
10
|
+
implementations whose cost is unknown.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import contextlib
|
|
16
|
+
import contextvars
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import time
|
|
20
|
+
import uuid
|
|
21
|
+
from dataclasses import asdict, dataclass, field, is_dataclass
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from typing import Any, Callable, Iterator, Literal, Mapping, Protocol, Sequence, runtime_checkable
|
|
24
|
+
|
|
25
|
+
from .outcomes import Score, Unknown
|
|
26
|
+
|
|
27
|
+
# ----------------------------------------------------------------- contracts
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class Request:
|
|
32
|
+
op: str # "parse", "classify", "choose", "rank", "check", "propose"
|
|
33
|
+
subject: Any # the primary input
|
|
34
|
+
target: Any = None # output type, label set, objective, ...
|
|
35
|
+
params: Mapping[str, Any] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class Output:
|
|
40
|
+
value: Any # an answer or an Unknown (abstention)
|
|
41
|
+
score: Score | None = None
|
|
42
|
+
usd: float | None = None # metered cost of producing this output, if the backend knows it
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class Failure:
|
|
47
|
+
error: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class Traits:
|
|
52
|
+
"""Hard, declared properties used for filtering, never for ranking."""
|
|
53
|
+
|
|
54
|
+
locality: Literal["in_process", "local_service", "remote"] = "in_process"
|
|
55
|
+
egress: bool = False # request data leaves this machine
|
|
56
|
+
deterministic: bool = True # same request, same output (enables caching)
|
|
57
|
+
requires: frozenset[str] = frozenset() # e.g. {"cuda"}, {"sklearn"}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class Profile:
|
|
62
|
+
"""Measured properties. ``None`` means unmeasured, not zero and not good."""
|
|
63
|
+
|
|
64
|
+
source: str | None = None # the evaluation artifact these numbers came from
|
|
65
|
+
quality: Mapping[str, float] = field(default_factory=dict)
|
|
66
|
+
latency_ms_p50: float | None = None
|
|
67
|
+
latency_ms_p95: float | None = None
|
|
68
|
+
usd_per_call: float | None = None
|
|
69
|
+
peak_memory_mb: float | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@runtime_checkable
|
|
73
|
+
class Implementation(Protocol):
|
|
74
|
+
name: str
|
|
75
|
+
version: str
|
|
76
|
+
op: str
|
|
77
|
+
traits: Traits
|
|
78
|
+
profile: Profile
|
|
79
|
+
|
|
80
|
+
def accepts(self, request: Request) -> bool: ...
|
|
81
|
+
|
|
82
|
+
def run(self, requests: Sequence[Request]) -> Sequence[Output | Failure]: ...
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class FunctionImplementation:
|
|
87
|
+
"""Adapts a plain function ``request -> value | Unknown | Output`` (or a batch function)."""
|
|
88
|
+
|
|
89
|
+
op: str
|
|
90
|
+
name: str
|
|
91
|
+
version: str
|
|
92
|
+
fn: Callable[..., Any]
|
|
93
|
+
accepts_fn: Callable[[Request], bool] = lambda request: True
|
|
94
|
+
traits: Traits = Traits()
|
|
95
|
+
profile: Profile = Profile()
|
|
96
|
+
batched: bool = False
|
|
97
|
+
|
|
98
|
+
def accepts(self, request: Request) -> bool:
|
|
99
|
+
return request.op == self.op and self.accepts_fn(request)
|
|
100
|
+
|
|
101
|
+
def run(self, requests: Sequence[Request]) -> list[Output | Failure]:
|
|
102
|
+
raw = self.fn(list(requests)) if self.batched else [self.fn(r) for r in requests]
|
|
103
|
+
return [r if isinstance(r, (Output, Failure)) else Output(r) for r in raw]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def implementation(
|
|
107
|
+
op: str,
|
|
108
|
+
*,
|
|
109
|
+
name: str,
|
|
110
|
+
version: str,
|
|
111
|
+
accepts: Callable[[Request], bool] = lambda request: True,
|
|
112
|
+
traits: Traits = Traits(),
|
|
113
|
+
profile: Profile = Profile(),
|
|
114
|
+
batched: bool = False,
|
|
115
|
+
) -> Callable[[Callable[..., Any]], FunctionImplementation]:
|
|
116
|
+
def wrap(fn: Callable[..., Any]) -> FunctionImplementation:
|
|
117
|
+
return FunctionImplementation(op, name, version, fn, accepts, traits, profile, batched)
|
|
118
|
+
|
|
119
|
+
return wrap
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# -------------------------------------------------------------------- policy
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class Policy:
|
|
127
|
+
localities: frozenset[str] = frozenset({"in_process", "local_service"})
|
|
128
|
+
allow_egress: bool = False
|
|
129
|
+
available: frozenset[str] = frozenset() # capabilities present on this host
|
|
130
|
+
max_attempts: int = 3 # implementations that may *run* per item
|
|
131
|
+
deadline_ms: float | None = None # per operation call
|
|
132
|
+
max_usd_per_call: float | None = None
|
|
133
|
+
order: Literal["declared", "cheapest", "fastest"] = "declared"
|
|
134
|
+
cache: bool = True
|
|
135
|
+
record_inputs: Literal["full", "digest"] = "full"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class Budget:
|
|
139
|
+
"""A shared allowance across many calls (e.g. one agent episode)."""
|
|
140
|
+
|
|
141
|
+
def __init__(self, *, usd: float | None = None, attempts: int | None = None, seconds: float | None = None):
|
|
142
|
+
self.usd, self.attempts = usd, attempts
|
|
143
|
+
self.deadline = time.monotonic() + seconds if seconds is not None else None
|
|
144
|
+
self.spent_usd = 0.0
|
|
145
|
+
self.spent_attempts = 0
|
|
146
|
+
self.unmetered_calls = 0 # calls whose cost is unknown; reported, never summed as zero
|
|
147
|
+
|
|
148
|
+
def refusal(self, profile: Profile) -> str | None:
|
|
149
|
+
if self.deadline is not None and time.monotonic() >= self.deadline:
|
|
150
|
+
return "budget deadline passed"
|
|
151
|
+
if self.attempts is not None and self.spent_attempts >= self.attempts:
|
|
152
|
+
return "budget attempts exhausted"
|
|
153
|
+
if self.usd is not None:
|
|
154
|
+
if profile.usd_per_call is None:
|
|
155
|
+
return "cost unknown under a cost cap"
|
|
156
|
+
if self.spent_usd + profile.usd_per_call > self.usd:
|
|
157
|
+
return "cost would exceed budget"
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _hard_constraint_violation(impl: Implementation, policy: Policy) -> str | None:
|
|
162
|
+
t = impl.traits
|
|
163
|
+
if t.locality not in policy.localities:
|
|
164
|
+
return f"locality {t.locality} not permitted"
|
|
165
|
+
if t.egress and not policy.allow_egress:
|
|
166
|
+
return "data egress not permitted"
|
|
167
|
+
if missing := t.requires - policy.available:
|
|
168
|
+
return f"missing capability {sorted(missing)}"
|
|
169
|
+
if policy.max_usd_per_call is not None:
|
|
170
|
+
if impl.profile.usd_per_call is None:
|
|
171
|
+
return "cost unknown under a per-call cost cap"
|
|
172
|
+
if impl.profile.usd_per_call > policy.max_usd_per_call:
|
|
173
|
+
return "per-call cost above cap"
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _ordered(candidates: list[Implementation], policy: Policy) -> list[Implementation]:
|
|
178
|
+
if policy.order == "declared":
|
|
179
|
+
return candidates
|
|
180
|
+
key = "usd_per_call" if policy.order == "cheapest" else "latency_ms_p50"
|
|
181
|
+
# unknown values sort last; they are not assumed cheap or fast
|
|
182
|
+
return sorted(candidates, key=lambda i: (getattr(i.profile, key) is None, getattr(i.profile, key) or 0.0))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# --------------------------------------------------------------------- trace
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass
|
|
189
|
+
class Attempt:
|
|
190
|
+
implementation: str
|
|
191
|
+
version: str
|
|
192
|
+
outcome: Literal["answer", "abstain", "invalid", "error", "skipped", "cache_hit"]
|
|
193
|
+
reason: str = ""
|
|
194
|
+
backend_ms: float | None = None
|
|
195
|
+
usd: float | None = None
|
|
196
|
+
usd_basis: Literal["metered", "profile", "unknown", "none"] = "none"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
class Span:
|
|
201
|
+
id: str
|
|
202
|
+
op: str
|
|
203
|
+
target: str
|
|
204
|
+
input: Any
|
|
205
|
+
input_digest: str
|
|
206
|
+
output: Any = None
|
|
207
|
+
outcome: Literal["answer", "unknown", "event"] = "event"
|
|
208
|
+
attempts: list[Attempt] = field(default_factory=list)
|
|
209
|
+
notes: list[str] = field(default_factory=list)
|
|
210
|
+
started_at: str = ""
|
|
211
|
+
total_ms: float = 0.0
|
|
212
|
+
backend_ms: float = 0.0
|
|
213
|
+
parent: str | None = None
|
|
214
|
+
labels: dict[str, Any] = field(default_factory=dict)
|
|
215
|
+
t0: float = field(default_factory=time.perf_counter, repr=False)
|
|
216
|
+
|
|
217
|
+
def close(self, output: Any, outcome: Literal["answer", "unknown", "event"]) -> Any:
|
|
218
|
+
self.output, self.outcome = output, outcome
|
|
219
|
+
self.total_ms = (time.perf_counter() - self.t0) * 1e3
|
|
220
|
+
return output
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def answered_by(self) -> str | None:
|
|
224
|
+
for a in self.attempts:
|
|
225
|
+
if a.outcome in ("answer", "cache_hit"):
|
|
226
|
+
return f"{a.implementation}@{a.version}"
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class Trace:
|
|
231
|
+
def __init__(self) -> None:
|
|
232
|
+
self.spans: list[Span] = []
|
|
233
|
+
self._parent: contextvars.ContextVar[str | None] = contextvars.ContextVar("parent", default=None)
|
|
234
|
+
|
|
235
|
+
@contextlib.contextmanager
|
|
236
|
+
def section(self, name: str, **labels: Any) -> Iterator[Span]:
|
|
237
|
+
span = self.open(name, target="", input=None, labels=labels)
|
|
238
|
+
token = self._parent.set(span.id)
|
|
239
|
+
t0 = time.perf_counter()
|
|
240
|
+
try:
|
|
241
|
+
yield span
|
|
242
|
+
finally:
|
|
243
|
+
span.total_ms = (time.perf_counter() - t0) * 1e3
|
|
244
|
+
self._parent.reset(token)
|
|
245
|
+
|
|
246
|
+
def open(self, op: str, *, target: str, input: Any, labels: Mapping[str, Any] | None = None, digest: str = "") -> Span:
|
|
247
|
+
span = Span(
|
|
248
|
+
id=uuid.uuid4().hex[:12],
|
|
249
|
+
op=op,
|
|
250
|
+
target=target,
|
|
251
|
+
input=input,
|
|
252
|
+
input_digest=digest,
|
|
253
|
+
started_at=datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
|
|
254
|
+
parent=self._parent.get(),
|
|
255
|
+
labels=dict(labels or {}),
|
|
256
|
+
)
|
|
257
|
+
self.spans.append(span)
|
|
258
|
+
return span
|
|
259
|
+
|
|
260
|
+
def of(self, op: str) -> list[Span]:
|
|
261
|
+
return [s for s in self.spans if s.op == op]
|
|
262
|
+
|
|
263
|
+
def to_jsonl(self) -> str:
|
|
264
|
+
return "\n".join(json.dumps(asdict(s), default=_preview) for s in self.spans)
|
|
265
|
+
|
|
266
|
+
def render(self, *, since: int = 0) -> str:
|
|
267
|
+
"""One line per span, indented under its section, attempts in order."""
|
|
268
|
+
depth: dict[str | None, int] = {None: 0}
|
|
269
|
+
lines = []
|
|
270
|
+
for s in self.spans[since:]:
|
|
271
|
+
d = depth.get(s.parent, 0)
|
|
272
|
+
depth[s.id] = d + 1
|
|
273
|
+
pad = " " * d
|
|
274
|
+
if s.outcome == "event" and not s.attempts and s.op not in ("invoke", "verify", "choose"):
|
|
275
|
+
labels = " ".join(f"{k}={v}" for k, v in s.labels.items())
|
|
276
|
+
lines.append(f"{pad}{s.op} {labels}".rstrip())
|
|
277
|
+
continue
|
|
278
|
+
out = _preview(s.output, 70)
|
|
279
|
+
lines.append(f"{pad}{s.op:<8} -> {out} [{s.total_ms:.2f} ms total, {s.backend_ms:.2f} ms backend]")
|
|
280
|
+
if s.labels:
|
|
281
|
+
lines.append(f"{pad} · " + " ".join(f"{k}={v}" for k, v in s.labels.items()))
|
|
282
|
+
for a in s.attempts:
|
|
283
|
+
cost = "" if a.usd_basis in ("none", "profile") and not a.usd else f", usd={'?' if a.usd is None else a.usd} ({a.usd_basis})"
|
|
284
|
+
reason = f": {a.reason}" if a.reason else ""
|
|
285
|
+
lines.append(f"{pad} - {a.implementation}@{a.version} {a.outcome}{reason}{cost}")
|
|
286
|
+
for n in s.notes:
|
|
287
|
+
lines.append(f"{pad} · {n}")
|
|
288
|
+
return "\n".join(lines)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _preview(value: Any, limit: int = 200) -> Any:
|
|
292
|
+
if isinstance(value, Unknown):
|
|
293
|
+
return {"unknown": value.reason, "detail": value.detail}
|
|
294
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
295
|
+
text = repr(value)
|
|
296
|
+
elif isinstance(value, type):
|
|
297
|
+
text = value.__qualname__
|
|
298
|
+
else:
|
|
299
|
+
text = str(value)
|
|
300
|
+
return text if len(text) <= limit else text[: limit - 1] + "…"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def digest(value: Any) -> str:
|
|
304
|
+
try:
|
|
305
|
+
from .records import _canonical
|
|
306
|
+
|
|
307
|
+
data = json.dumps(_canonical(value), sort_keys=True)
|
|
308
|
+
except Exception: # noqa: BLE001 - unhashable inputs are digested by repr, and flagged
|
|
309
|
+
data = "repr:" + repr(value)
|
|
310
|
+
return hashlib.sha256(data.encode()).hexdigest()[:16]
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# ------------------------------------------------------------------- runtime
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class Runtime:
|
|
317
|
+
def __init__(
|
|
318
|
+
self,
|
|
319
|
+
implementations: Sequence[Implementation] = (),
|
|
320
|
+
*,
|
|
321
|
+
policy: Policy = Policy(),
|
|
322
|
+
budget: Budget | None = None,
|
|
323
|
+
trace: Trace | None = None,
|
|
324
|
+
) -> None:
|
|
325
|
+
self.implementations = list(implementations)
|
|
326
|
+
self.policy = policy
|
|
327
|
+
self.budget = budget
|
|
328
|
+
self.trace = trace or Trace()
|
|
329
|
+
self._cache: dict[tuple[str, str, str], Output] = {}
|
|
330
|
+
|
|
331
|
+
def call(self, request: Request, *, validate: Callable[[Any], bool] = lambda v: True, notes: Sequence[str] = ()) -> Output:
|
|
332
|
+
return self.call_many([request], validate=validate, notes=notes)[0]
|
|
333
|
+
|
|
334
|
+
def call_many(
|
|
335
|
+
self, requests: Sequence[Request], *, validate: Callable[[Any], bool] = lambda v: True, notes: Sequence[str] = ()
|
|
336
|
+
) -> list[Output]:
|
|
337
|
+
t_start = time.perf_counter()
|
|
338
|
+
n = len(requests)
|
|
339
|
+
digests = [digest((r.op, r.subject, _preview(r.target), r.params)) for r in requests]
|
|
340
|
+
spans = [
|
|
341
|
+
self.trace.open(
|
|
342
|
+
r.op,
|
|
343
|
+
target=_preview(r.target),
|
|
344
|
+
input=r.subject if self.policy.record_inputs == "full" else None,
|
|
345
|
+
digest=d,
|
|
346
|
+
)
|
|
347
|
+
for r, d in zip(requests, digests)
|
|
348
|
+
]
|
|
349
|
+
for s in spans:
|
|
350
|
+
s.notes.extend(notes)
|
|
351
|
+
results: list[Output | None] = [None] * n
|
|
352
|
+
abstentions: list[list[tuple[Any, Score]]] = [[] for _ in range(n)]
|
|
353
|
+
ran = [0] * n
|
|
354
|
+
backend_ms = [0.0] * n
|
|
355
|
+
|
|
356
|
+
candidates = [impl for impl in self.implementations if all(impl.accepts(r) for r in requests)]
|
|
357
|
+
if not candidates:
|
|
358
|
+
for s in spans:
|
|
359
|
+
s.notes.append(f"no implementation accepts op={requests[0].op} target={_preview(requests[0].target)}")
|
|
360
|
+
|
|
361
|
+
for impl in _ordered(candidates, self.policy):
|
|
362
|
+
pending = [i for i in range(n) if results[i] is None and ran[i] < self.policy.max_attempts]
|
|
363
|
+
if not pending:
|
|
364
|
+
break
|
|
365
|
+
refusal = _hard_constraint_violation(impl, self.policy)
|
|
366
|
+
if refusal is None and self.policy.deadline_ms is not None:
|
|
367
|
+
if (time.perf_counter() - t_start) * 1e3 >= self.policy.deadline_ms:
|
|
368
|
+
refusal = "deadline passed"
|
|
369
|
+
if refusal is None and self.budget is not None:
|
|
370
|
+
refusal = self.budget.refusal(impl.profile)
|
|
371
|
+
if refusal:
|
|
372
|
+
for i in pending:
|
|
373
|
+
spans[i].attempts.append(Attempt(impl.name, impl.version, "skipped", refusal))
|
|
374
|
+
continue
|
|
375
|
+
|
|
376
|
+
to_run = []
|
|
377
|
+
for i in pending:
|
|
378
|
+
key = (impl.name, impl.version, digests[i])
|
|
379
|
+
if self.policy.cache and impl.traits.deterministic and key in self._cache:
|
|
380
|
+
cached = self._cache[key]
|
|
381
|
+
if isinstance(cached.value, Unknown):
|
|
382
|
+
spans[i].attempts.append(Attempt(impl.name, impl.version, "abstain", f"cached: {cached.value.reason}"))
|
|
383
|
+
else:
|
|
384
|
+
results[i] = cached
|
|
385
|
+
spans[i].attempts.append(Attempt(impl.name, impl.version, "cache_hit"))
|
|
386
|
+
else:
|
|
387
|
+
to_run.append(i)
|
|
388
|
+
if not to_run:
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
t0 = time.perf_counter()
|
|
392
|
+
try:
|
|
393
|
+
outs = list(impl.run([requests[i] for i in to_run]))
|
|
394
|
+
if len(outs) != len(to_run):
|
|
395
|
+
raise RuntimeError(f"{impl.name} returned {len(outs)} results for {len(to_run)} requests")
|
|
396
|
+
except Exception as exc: # noqa: BLE001 - a crashing backend is an attempt outcome, not a program crash
|
|
397
|
+
outs = [Failure(f"{type(exc).__name__}: {exc}")] * len(to_run)
|
|
398
|
+
per_item_ms = (time.perf_counter() - t0) * 1e3 / len(to_run)
|
|
399
|
+
if self.budget is not None:
|
|
400
|
+
self.budget.spent_attempts += 1
|
|
401
|
+
|
|
402
|
+
for i, out in zip(to_run, outs):
|
|
403
|
+
ran[i] += 1
|
|
404
|
+
backend_ms[i] += per_item_ms
|
|
405
|
+
usd, basis = self._charge(impl, out)
|
|
406
|
+
attempt = Attempt(impl.name, impl.version, "error", backend_ms=per_item_ms, usd=usd, usd_basis=basis)
|
|
407
|
+
spans[i].attempts.append(attempt)
|
|
408
|
+
if isinstance(out, Failure):
|
|
409
|
+
attempt.reason = out.error
|
|
410
|
+
continue
|
|
411
|
+
if isinstance(out.value, Unknown):
|
|
412
|
+
attempt.outcome, attempt.reason = "abstain", out.value.reason
|
|
413
|
+
abstentions[i].extend(out.value.candidates)
|
|
414
|
+
elif not validate(out.value):
|
|
415
|
+
attempt.outcome, attempt.reason = "invalid", f"output violates contract: {_preview(out.value, 80)}"
|
|
416
|
+
continue # invalid outputs are never cached
|
|
417
|
+
else:
|
|
418
|
+
attempt.outcome = "answer"
|
|
419
|
+
results[i] = out
|
|
420
|
+
if self.policy.cache and impl.traits.deterministic:
|
|
421
|
+
self._cache[(impl.name, impl.version, digests[i])] = out
|
|
422
|
+
|
|
423
|
+
total_ms = (time.perf_counter() - t_start) * 1e3 / n
|
|
424
|
+
final: list[Output] = []
|
|
425
|
+
for i in range(n):
|
|
426
|
+
out = results[i]
|
|
427
|
+
if out is None:
|
|
428
|
+
reasons = [f"{a.implementation}: {a.outcome} ({a.reason})" for a in spans[i].attempts] or spans[i].notes
|
|
429
|
+
ran = [a for a in spans[i].attempts if a.outcome != "skipped"]
|
|
430
|
+
if ran: # the last real attempt's reason is the most specific explanation
|
|
431
|
+
reason = ran[-1].reason.removeprefix("cached: ") if ran[-1].outcome == "abstain" else f"{ran[-1].outcome}"
|
|
432
|
+
else:
|
|
433
|
+
reason = "not_permitted" if spans[i].attempts else "no_implementation"
|
|
434
|
+
out = Output(Unknown(reason, "; ".join(reasons), tuple(abstentions[i])))
|
|
435
|
+
spans[i].output = out.value
|
|
436
|
+
spans[i].outcome = "unknown" if isinstance(out.value, Unknown) else "answer"
|
|
437
|
+
spans[i].total_ms, spans[i].backend_ms = total_ms, backend_ms[i]
|
|
438
|
+
final.append(out)
|
|
439
|
+
return final
|
|
440
|
+
|
|
441
|
+
def _charge(self, impl: Implementation, out: Output | Failure) -> tuple[float | None, Literal["metered", "profile", "unknown"]]:
|
|
442
|
+
if isinstance(out, Output) and out.usd is not None:
|
|
443
|
+
usd, basis = out.usd, "metered"
|
|
444
|
+
elif impl.profile.usd_per_call is not None:
|
|
445
|
+
usd, basis = impl.profile.usd_per_call, "profile"
|
|
446
|
+
else:
|
|
447
|
+
usd, basis = None, "unknown"
|
|
448
|
+
if self.budget is not None:
|
|
449
|
+
if usd is None:
|
|
450
|
+
self.budget.unmetered_calls += 1
|
|
451
|
+
else:
|
|
452
|
+
self.budget.spent_usd += usd
|
|
453
|
+
return usd, basis # type: ignore[return-value]
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
_current: contextvars.ContextVar[Runtime | None] = contextvars.ContextVar("tensorcode_runtime", default=None)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def current() -> Runtime:
|
|
460
|
+
"""The bound runtime, or an empty one: with nothing bound, inferential ops return Unknown."""
|
|
461
|
+
return _current.get() or Runtime()
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@contextlib.contextmanager
|
|
465
|
+
def use(runtime: Runtime) -> Iterator[Runtime]:
|
|
466
|
+
"""Bind a runtime for the enclosed code. Programs never construct one themselves."""
|
|
467
|
+
token = _current.set(runtime)
|
|
468
|
+
try:
|
|
469
|
+
yield runtime
|
|
470
|
+
finally:
|
|
471
|
+
_current.reset(token)
|