inferencekit-cli 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.
- inferencekit/__init__.py +9 -0
- inferencekit/__main__.py +5 -0
- inferencekit/analysis/__init__.py +1 -0
- inferencekit/analysis/ncu_csv.py +284 -0
- inferencekit/analysis/nsys_sqlite.py +518 -0
- inferencekit/canonical.py +104 -0
- inferencekit/checkpoint.py +396 -0
- inferencekit/cli.py +274 -0
- inferencekit/cuda/bw_peak.cu +252 -0
- inferencekit/cuda/torch_bw.py +60 -0
- inferencekit/engines/__init__.py +7 -0
- inferencekit/engines/adapters.py +190 -0
- inferencekit/engines/clip/__init__.py +13 -0
- inferencekit/engines/clip/comfyui.py +749 -0
- inferencekit/engines/clip/diffusers_ltx.py +601 -0
- inferencekit/engines/clip/noise.py +46 -0
- inferencekit/engines/clip/official_ltx.py +316 -0
- inferencekit/engines/clip/server.py +542 -0
- inferencekit/engines/clip/stepdump.py +182 -0
- inferencekit/engines/gates.py +94 -0
- inferencekit/engines/sglang_worker.py +86 -0
- inferencekit/engines/vllm_worker.py +129 -0
- inferencekit/evidence.py +427 -0
- inferencekit/gpuinfo.py +93 -0
- inferencekit/hardware.py +135 -0
- inferencekit/mcp_server.py +178 -0
- inferencekit/ops/__init__.py +30 -0
- inferencekit/ops/bench.py +184 -0
- inferencekit/ops/bench_audio.py +384 -0
- inferencekit/ops/bench_clip.py +819 -0
- inferencekit/ops/bench_image.py +393 -0
- inferencekit/ops/bench_rounds.py +297 -0
- inferencekit/ops/bench_serve.py +866 -0
- inferencekit/ops/ceiling.py +135 -0
- inferencekit/ops/compare.py +378 -0
- inferencekit/ops/correctness.py +153 -0
- inferencekit/ops/doctor.py +486 -0
- inferencekit/ops/gates.py +1772 -0
- inferencekit/ops/gates_clip.py +930 -0
- inferencekit/ops/intake.py +1321 -0
- inferencekit/ops/ledger.py +749 -0
- inferencekit/ops/manifest.py +309 -0
- inferencekit/ops/ncu.py +183 -0
- inferencekit/ops/nsys.py +224 -0
- inferencekit/ops/prompts.py +316 -0
- inferencekit/ops/quality.py +2161 -0
- inferencekit/ops/quality_clip.py +690 -0
- inferencekit/ops/quality_clip_steps.py +341 -0
- inferencekit/ops/report.py +317 -0
- inferencekit/ops/review.py +119 -0
- inferencekit/ops/rocprof.py +89 -0
- inferencekit/ops/roofline.py +392 -0
- inferencekit/ops/store.py +61 -0
- inferencekit/proc.py +172 -0
- inferencekit/prompts/ATTRIBUTION.md +56 -0
- inferencekit/prompts/__init__.py +167 -0
- inferencekit/prompts/build.py +518 -0
- inferencekit/prompts/dev-load.jsonl.gz +0 -0
- inferencekit/prompts/dev-load.manifest.json +212 -0
- inferencekit/prompts/dev.jsonl.gz +0 -0
- inferencekit/prompts/dev.manifest.json +214 -0
- inferencekit/prompts/exclusions.json +54 -0
- inferencekit/quality/__init__.py +23 -0
- inferencekit/quality/client.py +66 -0
- inferencekit/quality/data/gsm8k_LICENSE.txt +21 -0
- inferencekit/quality/fake.py +329 -0
- inferencekit/quality/gsm8k.py +224 -0
- inferencekit/quality/logprobs.py +106 -0
- inferencekit/quality/metrics.py +235 -0
- inferencekit/quality/niah.py +63 -0
- inferencekit/quality/parquet.py +296 -0
- inferencekit/quality/sampling.py +262 -0
- inferencekit/quality/step_replay.py +217 -0
- inferencekit/quality/video_metrics.py +304 -0
- inferencekit/registry.py +206 -0
- inferencekit/review/__init__.py +6 -0
- inferencekit/review/diff.py +163 -0
- inferencekit/review/media.py +181 -0
- inferencekit/review/page.py +920 -0
- inferencekit/review/rundir.py +844 -0
- inferencekit/roofline_headers.py +504 -0
- inferencekit/schemas/evidence.v1.json +130 -0
- inferencekit/schemas/evidence.v2.json +499 -0
- inferencekit/schemas/intake.v1.json +276 -0
- inferencekit/schemas/run-manifest.v1.json +118 -0
- inferencekit/serve/__init__.py +8 -0
- inferencekit/serve/audio_client.py +330 -0
- inferencekit/serve/client.py +446 -0
- inferencekit/serve/clip_client.py +351 -0
- inferencekit/serve/clip_rounds.py +429 -0
- inferencekit/serve/image_client.py +373 -0
- inferencekit/serve/loadgen.py +136 -0
- inferencekit/serve/metrics.py +203 -0
- inferencekit/serve/profiles.py +105 -0
- inferencekit/serve/prompts_io.py +125 -0
- inferencekit/serve/rounds.py +1111 -0
- inferencekit/serve/servers.py +897 -0
- inferencekit/stats.py +249 -0
- inferencekit_cli-0.1.0.dist-info/METADATA +233 -0
- inferencekit_cli-0.1.0.dist-info/RECORD +104 -0
- inferencekit_cli-0.1.0.dist-info/WHEEL +4 -0
- inferencekit_cli-0.1.0.dist-info/entry_points.txt +2 -0
- inferencekit_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
- inferencekit_cli-0.1.0.dist-info/licenses/NOTICE +9 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Display saved image and streamed PCM runs without re-measuring or judging quality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import tempfile
|
|
9
|
+
import wave
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from inferencekit.canonical import file_sha256
|
|
13
|
+
|
|
14
|
+
AUDIO_KEYS = ("first_pcm_ms", "first_signal_ms", "first_audio_ms", "complete_audio_ms", "rtf",
|
|
15
|
+
"audio_duration_s", "max_stream_gap_ms", "underruns", "underrun_ms")
|
|
16
|
+
DERIVED_AUDIO = {"first_audio_ms", "rtf", "audio_duration_s", "underruns", "underrun_ms"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def attach_verdicts(info, kind, data, evidence=None, problem=False):
|
|
20
|
+
"""Keep imported media screens visible even without a separate metrics summary."""
|
|
21
|
+
fields = [("semantic quality", None)]
|
|
22
|
+
if kind == "audio":
|
|
23
|
+
fields += [("PCM structure", "structural_verdict"), ("audio timing screen", "timing_verdict")]
|
|
24
|
+
for name, key in fields:
|
|
25
|
+
prior = [row for row in info.ev_quality if row.get("name") == name]
|
|
26
|
+
# Older records may omit these fields; they cannot replace an available summary.
|
|
27
|
+
if prior and (not evidence or (key and key not in data and not problem)):
|
|
28
|
+
continue
|
|
29
|
+
verdict = data.get(key, "UNPROVEN") if key and not problem else "UNPROVEN"
|
|
30
|
+
if verdict not in ("PASS", "FAIL", "UNPROVEN", "NOT RUN"):
|
|
31
|
+
verdict = "UNPROVEN"
|
|
32
|
+
row = {"name": name, "verdict": verdict}
|
|
33
|
+
if evidence:
|
|
34
|
+
row["evidence"] = evidence
|
|
35
|
+
info.ev_quality[:] = [old for old in info.ev_quality
|
|
36
|
+
if old.get("name") != name or old.get("evidence")]
|
|
37
|
+
info.ev_quality.append(row)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load_media_metrics(root, label, info, run, items):
|
|
41
|
+
from inferencekit.review.rundir import Item
|
|
42
|
+
|
|
43
|
+
for kind in ("image", "audio"):
|
|
44
|
+
path = root / "metrics" / f"{label}.{kind}.json"
|
|
45
|
+
if not path.is_file():
|
|
46
|
+
continue
|
|
47
|
+
try:
|
|
48
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
49
|
+
if not isinstance(data, dict) or data.get("source") != f"bench {kind}":
|
|
50
|
+
raise ValueError("not a media benchmark summary")
|
|
51
|
+
except (OSError, ValueError) as exc:
|
|
52
|
+
run.warnings.append(f"{path.name}: unreadable ({exc})")
|
|
53
|
+
continue
|
|
54
|
+
info.media_kind = kind
|
|
55
|
+
run.sources.append(path.relative_to(root).as_posix())
|
|
56
|
+
info.metrics_file = info.metrics_file or path.relative_to(root).as_posix()
|
|
57
|
+
attach_verdicts(info, kind, data)
|
|
58
|
+
if kind == "audio":
|
|
59
|
+
# This is the benchmark's structural/timing screen, never a semantic or full gates pass.
|
|
60
|
+
info.metric_classes.update({k: "derived" for k in DERIVED_AUDIO})
|
|
61
|
+
rounds = data.get("rounds")
|
|
62
|
+
for row in rounds if isinstance(rounds, list) else []:
|
|
63
|
+
if not isinstance(row, dict) or type(row.get("index")) is not int:
|
|
64
|
+
continue
|
|
65
|
+
bucket = f"r{row['index']}"
|
|
66
|
+
keys = ("image_ms", "images_per_hour") if kind == "image" else ("rtf",)
|
|
67
|
+
info.per_bucket.setdefault(bucket, {}).update({k: row[k] for k in keys if row.get(k) is not None})
|
|
68
|
+
if kind == "audio":
|
|
69
|
+
fixture_set = data.get("fixture_set")
|
|
70
|
+
fixtures = fixture_set.get("fixtures") if isinstance(fixture_set, dict) else None
|
|
71
|
+
for fixture in fixtures if isinstance(fixtures, list) else []:
|
|
72
|
+
if isinstance(fixture, dict) and isinstance(fixture.get("id"), str):
|
|
73
|
+
iid = fixture["id"]
|
|
74
|
+
items.setdefault(f"{bucket}/{iid}", Item(id=iid, bucket=bucket))
|
|
75
|
+
if kind == "image":
|
|
76
|
+
info.metric_classes["images_per_hour"] = "derived"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def attach_rows(run, info, rec, source):
|
|
80
|
+
from inferencekit.review.rundir import artifact_lines
|
|
81
|
+
|
|
82
|
+
data = rec.get("data") or {}
|
|
83
|
+
kind = "image" if rec.get("kind") == "image_benchmark" else "audio"
|
|
84
|
+
if data.get("source") != f"bench {kind}":
|
|
85
|
+
return
|
|
86
|
+
info.media_kind = kind
|
|
87
|
+
if kind == "audio":
|
|
88
|
+
info.metric_classes.update({k: "derived" for k in DERIVED_AUDIO})
|
|
89
|
+
keys = ("e2e_ms",) if kind == "image" else AUDIO_KEYS
|
|
90
|
+
for row in artifact_lines(run, rec, source, "requests", "per-request media metrics") or []:
|
|
91
|
+
if not isinstance(row.get("id"), str) or type(row.get("round")) is not int:
|
|
92
|
+
continue
|
|
93
|
+
key = f"r{row['round']}/{row['id']}"
|
|
94
|
+
filtered = {k: row[k] for k in ("ok", "error", *keys) if k in row}
|
|
95
|
+
old = info.media_rows.get(key)
|
|
96
|
+
if old is not None and old != filtered:
|
|
97
|
+
info.media_rows[key] = {"ok": False, "error": "conflicting benchmark rows for this output"}
|
|
98
|
+
else:
|
|
99
|
+
info.media_rows[key] = filtered
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def merge_rows(run):
|
|
103
|
+
for info in run.engines:
|
|
104
|
+
for item in run.items:
|
|
105
|
+
cell = run.cells[(info.label, run.key(item))]
|
|
106
|
+
row = info.media_rows.get(run.key(item), {})
|
|
107
|
+
cell.metrics.update({k: row[k] for k in ("e2e_ms", *AUDIO_KEYS) if k in row})
|
|
108
|
+
if row.get("ok") is False:
|
|
109
|
+
cell.status, cell.reason = "failed", str(row.get("error") or "media request failed")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def prepare_pcm(run):
|
|
113
|
+
"""Wrap verified pcm_s16le bytes in WAV under review/media; never edit source outputs.
|
|
114
|
+
|
|
115
|
+
A WAV is a playback copy, not a measurement or quality proof. The original index and
|
|
116
|
+
PCM are included in the page's source manifest. Missing or mismatched metadata fails
|
|
117
|
+
the cell rather than guessing a sample rate and playing incorrect audio.
|
|
118
|
+
"""
|
|
119
|
+
derived = []
|
|
120
|
+
indexes = {}
|
|
121
|
+
for cell in run.cells.values():
|
|
122
|
+
if not cell.pcm or cell.status == "failed":
|
|
123
|
+
continue
|
|
124
|
+
try:
|
|
125
|
+
index = run.root / "outputs" / cell.engine / "index.json"
|
|
126
|
+
if cell.engine not in indexes:
|
|
127
|
+
obj = json.loads(index.read_text(encoding="utf-8"))
|
|
128
|
+
if not isinstance(obj, list):
|
|
129
|
+
raise ValueError("PCM index must be a list")
|
|
130
|
+
indexes[cell.engine] = obj
|
|
131
|
+
run.sources.append(index.relative_to(run.root).as_posix())
|
|
132
|
+
matches = [r for r in indexes[cell.engine] if isinstance(r, dict) and r.get("path") == cell.pcm]
|
|
133
|
+
if len(matches) != 1:
|
|
134
|
+
raise ValueError("PCM output needs exactly one index entry")
|
|
135
|
+
meta = matches[0]
|
|
136
|
+
rate, channels = meta.get("sample_rate"), meta.get("channels")
|
|
137
|
+
if (meta.get("encoding") != "pcm_s16le" or type(rate) is not int or not 8000 <= rate <= 192000
|
|
138
|
+
or type(channels) is not int or not 1 <= channels <= 8):
|
|
139
|
+
raise ValueError("invalid PCM encoding, sample rate or channel count")
|
|
140
|
+
source = run.root / cell.pcm
|
|
141
|
+
if not source.resolve().is_relative_to(run.root.resolve()):
|
|
142
|
+
raise ValueError("PCM output escapes the run directory")
|
|
143
|
+
sha, size = file_sha256(source)
|
|
144
|
+
if (sha != meta.get("sha256") or type(meta.get("bytes")) is not int or size != meta["bytes"]
|
|
145
|
+
or size == 0 or size % (2 * channels) or size > 0xffffffff - 36):
|
|
146
|
+
raise ValueError("PCM bytes do not match the index or are not frame-aligned")
|
|
147
|
+
identity = hashlib.sha256(f"{sha}:{rate}:{channels}".encode()).hexdigest()
|
|
148
|
+
relative = f"review/media/{identity}.wav"
|
|
149
|
+
dest = run.root / relative
|
|
150
|
+
if not dest.parent.resolve().is_relative_to(run.root.resolve()):
|
|
151
|
+
raise ValueError("playback directory escapes the run directory")
|
|
152
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
temp = None
|
|
154
|
+
try:
|
|
155
|
+
digest = hashlib.sha256()
|
|
156
|
+
# Exclusive creation prevents an existing .tmp symlink from redirecting writes.
|
|
157
|
+
with tempfile.NamedTemporaryFile(dir=dest.parent, prefix=".pcm-", suffix=".wav.tmp",
|
|
158
|
+
delete=False) as temporary:
|
|
159
|
+
temp = Path(temporary.name)
|
|
160
|
+
with source.open("rb") as src, wave.open(temporary, "wb") as wav:
|
|
161
|
+
wav.setnchannels(channels)
|
|
162
|
+
wav.setsampwidth(2)
|
|
163
|
+
wav.setframerate(rate)
|
|
164
|
+
while block := src.read(1024 * 1024):
|
|
165
|
+
digest.update(block)
|
|
166
|
+
wav.writeframesraw(block)
|
|
167
|
+
if digest.hexdigest() != sha:
|
|
168
|
+
raise ValueError("PCM changed while preparing playback")
|
|
169
|
+
os.replace(temp, dest)
|
|
170
|
+
finally:
|
|
171
|
+
if temp is not None:
|
|
172
|
+
temp.unlink(missing_ok=True)
|
|
173
|
+
cell.audio = relative
|
|
174
|
+
out_sha, out_bytes = file_sha256(dest)
|
|
175
|
+
derived.append({"path": relative, "sha256": out_sha, "bytes": out_bytes,
|
|
176
|
+
"source": cell.pcm, "source_sha256": sha,
|
|
177
|
+
"transformation": "WAV header around unchanged pcm_s16le bytes"})
|
|
178
|
+
except (OSError, ValueError, wave.Error) as exc:
|
|
179
|
+
cell.status, cell.reason = "failed", f"PCM playback unavailable: {exc}"
|
|
180
|
+
run.sources = sorted(set(run.sources))
|
|
181
|
+
return derived
|