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.
Files changed (104) hide show
  1. inferencekit/__init__.py +9 -0
  2. inferencekit/__main__.py +5 -0
  3. inferencekit/analysis/__init__.py +1 -0
  4. inferencekit/analysis/ncu_csv.py +284 -0
  5. inferencekit/analysis/nsys_sqlite.py +518 -0
  6. inferencekit/canonical.py +104 -0
  7. inferencekit/checkpoint.py +396 -0
  8. inferencekit/cli.py +274 -0
  9. inferencekit/cuda/bw_peak.cu +252 -0
  10. inferencekit/cuda/torch_bw.py +60 -0
  11. inferencekit/engines/__init__.py +7 -0
  12. inferencekit/engines/adapters.py +190 -0
  13. inferencekit/engines/clip/__init__.py +13 -0
  14. inferencekit/engines/clip/comfyui.py +749 -0
  15. inferencekit/engines/clip/diffusers_ltx.py +601 -0
  16. inferencekit/engines/clip/noise.py +46 -0
  17. inferencekit/engines/clip/official_ltx.py +316 -0
  18. inferencekit/engines/clip/server.py +542 -0
  19. inferencekit/engines/clip/stepdump.py +182 -0
  20. inferencekit/engines/gates.py +94 -0
  21. inferencekit/engines/sglang_worker.py +86 -0
  22. inferencekit/engines/vllm_worker.py +129 -0
  23. inferencekit/evidence.py +427 -0
  24. inferencekit/gpuinfo.py +93 -0
  25. inferencekit/hardware.py +135 -0
  26. inferencekit/mcp_server.py +178 -0
  27. inferencekit/ops/__init__.py +30 -0
  28. inferencekit/ops/bench.py +184 -0
  29. inferencekit/ops/bench_audio.py +384 -0
  30. inferencekit/ops/bench_clip.py +819 -0
  31. inferencekit/ops/bench_image.py +393 -0
  32. inferencekit/ops/bench_rounds.py +297 -0
  33. inferencekit/ops/bench_serve.py +866 -0
  34. inferencekit/ops/ceiling.py +135 -0
  35. inferencekit/ops/compare.py +378 -0
  36. inferencekit/ops/correctness.py +153 -0
  37. inferencekit/ops/doctor.py +486 -0
  38. inferencekit/ops/gates.py +1772 -0
  39. inferencekit/ops/gates_clip.py +930 -0
  40. inferencekit/ops/intake.py +1321 -0
  41. inferencekit/ops/ledger.py +749 -0
  42. inferencekit/ops/manifest.py +309 -0
  43. inferencekit/ops/ncu.py +183 -0
  44. inferencekit/ops/nsys.py +224 -0
  45. inferencekit/ops/prompts.py +316 -0
  46. inferencekit/ops/quality.py +2161 -0
  47. inferencekit/ops/quality_clip.py +690 -0
  48. inferencekit/ops/quality_clip_steps.py +341 -0
  49. inferencekit/ops/report.py +317 -0
  50. inferencekit/ops/review.py +119 -0
  51. inferencekit/ops/rocprof.py +89 -0
  52. inferencekit/ops/roofline.py +392 -0
  53. inferencekit/ops/store.py +61 -0
  54. inferencekit/proc.py +172 -0
  55. inferencekit/prompts/ATTRIBUTION.md +56 -0
  56. inferencekit/prompts/__init__.py +167 -0
  57. inferencekit/prompts/build.py +518 -0
  58. inferencekit/prompts/dev-load.jsonl.gz +0 -0
  59. inferencekit/prompts/dev-load.manifest.json +212 -0
  60. inferencekit/prompts/dev.jsonl.gz +0 -0
  61. inferencekit/prompts/dev.manifest.json +214 -0
  62. inferencekit/prompts/exclusions.json +54 -0
  63. inferencekit/quality/__init__.py +23 -0
  64. inferencekit/quality/client.py +66 -0
  65. inferencekit/quality/data/gsm8k_LICENSE.txt +21 -0
  66. inferencekit/quality/fake.py +329 -0
  67. inferencekit/quality/gsm8k.py +224 -0
  68. inferencekit/quality/logprobs.py +106 -0
  69. inferencekit/quality/metrics.py +235 -0
  70. inferencekit/quality/niah.py +63 -0
  71. inferencekit/quality/parquet.py +296 -0
  72. inferencekit/quality/sampling.py +262 -0
  73. inferencekit/quality/step_replay.py +217 -0
  74. inferencekit/quality/video_metrics.py +304 -0
  75. inferencekit/registry.py +206 -0
  76. inferencekit/review/__init__.py +6 -0
  77. inferencekit/review/diff.py +163 -0
  78. inferencekit/review/media.py +181 -0
  79. inferencekit/review/page.py +920 -0
  80. inferencekit/review/rundir.py +844 -0
  81. inferencekit/roofline_headers.py +504 -0
  82. inferencekit/schemas/evidence.v1.json +130 -0
  83. inferencekit/schemas/evidence.v2.json +499 -0
  84. inferencekit/schemas/intake.v1.json +276 -0
  85. inferencekit/schemas/run-manifest.v1.json +118 -0
  86. inferencekit/serve/__init__.py +8 -0
  87. inferencekit/serve/audio_client.py +330 -0
  88. inferencekit/serve/client.py +446 -0
  89. inferencekit/serve/clip_client.py +351 -0
  90. inferencekit/serve/clip_rounds.py +429 -0
  91. inferencekit/serve/image_client.py +373 -0
  92. inferencekit/serve/loadgen.py +136 -0
  93. inferencekit/serve/metrics.py +203 -0
  94. inferencekit/serve/profiles.py +105 -0
  95. inferencekit/serve/prompts_io.py +125 -0
  96. inferencekit/serve/rounds.py +1111 -0
  97. inferencekit/serve/servers.py +897 -0
  98. inferencekit/stats.py +249 -0
  99. inferencekit_cli-0.1.0.dist-info/METADATA +233 -0
  100. inferencekit_cli-0.1.0.dist-info/RECORD +104 -0
  101. inferencekit_cli-0.1.0.dist-info/WHEEL +4 -0
  102. inferencekit_cli-0.1.0.dist-info/entry_points.txt +2 -0
  103. inferencekit_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
  104. 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