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,1321 @@
1
+ """intake template / intake validate / intake budget: the input of the optimizing-inference flow.
2
+
3
+ An intake file (``intake.yaml``) says what to make fast (model + pinned revision +
4
+ allowed precisions), on what hardware and how to reach it, for which goal
5
+ (single-user speed or capacity, with optional targets), with which input/output
6
+ modes and buckets, and inside which approved budget. The orchestrator skill reads it
7
+ once at stage 1 and never changes it afterwards: the targets in it are the
8
+ pre-registered claims the final report is judged against.
9
+
10
+ * ``intake template`` prints an annotated YAML file to start from.
11
+ * ``intake validate <file>`` checks it against ``schemas/intake.v1.json`` plus the
12
+ rules a schema cannot express (an unattended run needs an approved budget, a
13
+ capacity goal needs an SLO, a lossy run needs a quality budget, ...), fills defaults and
14
+ returns the normalized intake with warnings and the derived plan (stop rule, fidelity rule
15
+ and quality budget, speculation and route, budget cap, per-stage time boxes).
16
+ With ``--run-dir`` it also starts (or resumes) ``runs/<run-id>/``: a redacted copy of
17
+ the intake (access details removed), ``intake.normalized.json``, ``state.json`` and
18
+ ``QUESTIONS.md``. A run whose recorded intake_sha256 differs is refused: the targets
19
+ are pre-registered, so a changed intake needs a new run id.
20
+ * ``intake budget <run-dir>`` reads ``state.json`` and says how much of the budget and
21
+ of the current stage's time box is spent. Read-only; the skill runs it before every
22
+ long step.
23
+
24
+ No numbers here are measurements: the budget and time-box figures are arithmetic on
25
+ the intake and the clock (claim class ``derived``), so no evidence record is written.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import datetime as _dt
31
+ import json
32
+ import math
33
+ import os
34
+ import re
35
+ from importlib import resources
36
+ from pathlib import Path
37
+
38
+ from inferencekit.canonical import sha256_hex
39
+ from inferencekit.registry import Arg, OpError, operation, utc_now
40
+
41
+ BUCKETS = ("very_short", "short", "medium", "long")
42
+
43
+ # Stage keys of state.json, in order, with the share of the run's hour cap each one gets.
44
+ # The report keeps a fixed reserve so a run that hits its budget still ends with a report.
45
+ STAGES = (
46
+ ("1_intake", 0.02),
47
+ ("2_hardware", 0.05),
48
+ ("3_candidates", 0.18),
49
+ ("4_tuning", 0.22),
50
+ ("5_benchmark", 0.18),
51
+ ("6_stop_point", 0.02),
52
+ ("7_custom", 0.23),
53
+ ("8_report", 0.10),
54
+ )
55
+ REPORT_RESERVE = dict(STAGES)["8_report"]
56
+
57
+ # name -> (better direction, unit)
58
+ TARGETS = {
59
+ "decode_tok_s": ("higher", "tok/s"),
60
+ "ttft_ms": ("lower", "ms"),
61
+ "n_star": ("higher", "streams/GPU"),
62
+ "decode_ceiling_fraction": ("higher", "fraction of roofline ceiling"),
63
+ "ttfa_ms": ("lower", "ms"),
64
+ "ttfa_p95_ms": ("lower", "ms"),
65
+ "image_ms": ("lower", "ms per image"),
66
+ "images_per_hour": ("higher", "images/h"),
67
+ "rtf": ("lower", "x real time"),
68
+ "usd_per_1m": ("lower", "USD per 1M units"),
69
+ "clip_ms": ("lower", "ms per clip"),
70
+ "clips_per_hour": ("higher", "clips/h"),
71
+ }
72
+ MODALITY_TARGETS = {
73
+ "llm": {"decode_tok_s", "ttft_ms", "n_star", "decode_ceiling_fraction", "usd_per_1m"},
74
+ "tts": {"ttfa_ms", "ttfa_p95_ms", "rtf", "n_star", "usd_per_1m"},
75
+ "stt": {"rtf", "n_star", "usd_per_1m"},
76
+ "video": {"clip_ms", "clips_per_hour"},
77
+ "image": {"image_ms", "images_per_hour"},
78
+ }
79
+ DEFAULT_CONCURRENCY = [1, 2, 4, 8, 16, 32, 64]
80
+ ROOFLINE_DTYPES = {"bf16", "fp16", "fp8", "q8_0"}
81
+ # Run rules a constraint string can set ("after_target: keep_climbing"): name -> allowed values. after_target and
82
+ # fidelity are also schema fields (goals.after_target, fidelity); a field and a constraint must agree.
83
+ RULES = {"after_target": ("stop", "keep_climbing"), "fidelity": ("lossless", "lossy"),
84
+ "speculation": ("off", "auto"), "route": ("build", "patch")}
85
+ _RULE_NAME = r"(after[\s_-]?target|fidelity|speculation|route)"
86
+ _RULE = re.compile(rf"^\s*{_RULE_NAME}\s*:\s*(.*)$", re.IGNORECASE | re.DOTALL)
87
+ _RULE_INSIDE = re.compile(rf"(?<![\w-]){_RULE_NAME}\s*:", re.IGNORECASE)
88
+ # Budget keys that fix how long a run may go (with keep_climbing they are where it ends).
89
+ CAP_KEYS = ("gpu_hours", "usd", "wall_clock_h")
90
+ QUALITY_BUDGET_KEYS = ("gsm8k_drop_pts_max", "top1_agreement_min", "kl_max", "nll_increase_max", "tv_max")
91
+ # Video (quality clip --fidelity lossy --budget): full-reference bounds on the engine's mean vs the BF16 reference.
92
+ VIDEO_BUDGET_KEYS = ("psnr_db_min", "ssim_min", "lpips_max")
93
+ SPEECH_BUDGET_KEYS = ("wer_max", "cer_max", "speaker_similarity_min")
94
+ # Degradation limits evaluated by a hash-bound external modality protocol, not by
95
+ # the built-in absolute scorers. Positive increase/drop is always worse.
96
+ RELATIVE_BUDGET_KEYS = {
97
+ "image": ("lpips_excess_max", "ssim_loss_max", "ocr_cer_increase_max",
98
+ "semantic_score_drop_max", "new_severe_regressions_max"),
99
+ "tts": ("wer_increase_max", "cer_increase_max", "completion_failure_increase_max",
100
+ "speaker_similarity_drop_max", "perceptual_score_drop_max", "new_severe_regressions_max"),
101
+ "stt": ("wer_increase_max", "cer_increase_max", "completion_failure_increase_max",
102
+ "new_severe_regressions_max"),
103
+ }
104
+ PERCEPTUAL_ACCEPTANCE = "perceptual_and_task_noninferiority"
105
+ PERCEPTUAL_BUDGET_KEYS = {
106
+ "image": ("ocr_cer_increase_max", "semantic_score_drop_max", "new_severe_regressions_max"),
107
+ "tts": RELATIVE_BUDGET_KEYS["tts"],
108
+ }
109
+ _DRIFT_DIAGNOSTICS = ("cross_engine_byte_identity", "cross_engine_tensor_equality", "rounding_drift",
110
+ "eager_compiled_drift", "repeated_run_variation")
111
+ DIAGNOSTIC_QUALITY_METRICS = {
112
+ "image": ("lpips", "ssim", "psnr", *_DRIFT_DIAGNOSTICS),
113
+ "tts": ("cross_engine_pcm_equality", "cross_engine_generated_code_equality", *_DRIFT_DIAGNOSTICS),
114
+ }
115
+ # KV cache dtypes below 16 bits. Under fidelity lossless they change numerics, unless the checkpoint itself specifies
116
+ # one (then the intake says checkpoint_default).
117
+ _LOSSY_KV = re.compile(r"^(u?int[248]|fp[468]|float[48]|f8|e[2-5]m[0-9]|nvfp4|mxfp[468]|i?q[2-8])(?![0-9])",
118
+ re.IGNORECASE)
119
+ # <angle>, CHANGEME, TODO, and __UPPER_SNAKE__ fill-in markers (case-sensitive, so __init__ is not one).
120
+ _PLACEHOLDER = re.compile(r"<[^<>]+>|\bCHANGE[_-]?ME\b|\bTODO\b|(?-i:(?<![A-Za-z0-9])__[A-Z][A-Z0-9_]*__(?![A-Za-z0-9]))",
121
+ re.IGNORECASE)
122
+ REDACTED = "<redacted>"
123
+ _SHA = re.compile(r"^[0-9a-f]{40}$")
124
+
125
+
126
+ # --------------------------------------------------------------------------- loading
127
+
128
+ def schema_path() -> Path:
129
+ """The schema shipped in the wheel, or the repo copy in a source checkout."""
130
+ try:
131
+ packaged = resources.files("inferencekit").joinpath("schemas/intake.v1.json")
132
+ if packaged.is_file():
133
+ return Path(str(packaged))
134
+ except (ModuleNotFoundError, FileNotFoundError):
135
+ pass
136
+ return Path(__file__).resolve().parents[3] / "schemas" / "intake.v1.json"
137
+
138
+
139
+ def load_schema() -> dict:
140
+ p = schema_path()
141
+ if not p.is_file():
142
+ raise OpError(f"intake schema not found at {p}: the wheel must ship schemas/intake.v1.json")
143
+ return json.loads(p.read_text())
144
+
145
+
146
+ def parse_text(text: str, name: str = "intake") -> dict:
147
+ """YAML (or JSON, which is valid YAML) to a dict."""
148
+ stripped = text.lstrip()
149
+ if stripped.startswith("{"):
150
+ try:
151
+ data = json.loads(text)
152
+ except ValueError as e:
153
+ raise OpError(f"{name}: invalid JSON: {e}") from None
154
+ else:
155
+ try:
156
+ import yaml
157
+ except ImportError:
158
+ raise OpError("reading YAML needs PyYAML (pip install pyyaml); a JSON intake works without it") from None
159
+ try:
160
+ data = yaml.safe_load(text)
161
+ except yaml.YAMLError as e:
162
+ raise OpError(f"{name}: invalid YAML: {e}") from None
163
+ if not isinstance(data, dict):
164
+ raise OpError(f"{name}: the top level must be a mapping (key: value), got {type(data).__name__}")
165
+ return data
166
+
167
+
168
+ def load_file(path: str | os.PathLike) -> tuple[dict, bytes]:
169
+ p = Path(path)
170
+ if not p.is_file():
171
+ raise OpError(f"intake file not found: {p}")
172
+ raw = p.read_bytes()
173
+ try:
174
+ text = raw.decode("utf-8")
175
+ except UnicodeDecodeError:
176
+ raise OpError(f"{p}: not UTF-8 text") from None
177
+ return parse_text(text, p.name), raw
178
+
179
+
180
+ # --------------------------------------------------------------------------- checking
181
+
182
+ def _dotted(path) -> str:
183
+ return ".".join(str(p) for p in path) or "(top level)"
184
+
185
+
186
+ def schema_problems(data: dict) -> tuple[list[str], list[str]]:
187
+ """(errors, warnings). Unknown keys are warnings: probably a typo, never silently used."""
188
+ import jsonschema
189
+
190
+ errors, warnings = [], []
191
+ validator = jsonschema.Draft202012Validator(load_schema())
192
+ for err in sorted(validator.iter_errors(data), key=lambda e: list(e.absolute_path)):
193
+ where = _dotted(err.absolute_path)
194
+ if err.validator == "additionalProperties":
195
+ known = sorted((err.schema.get("properties") or {}).keys())
196
+ extra = sorted(set(err.instance) - set(known)) if isinstance(err.instance, dict) else []
197
+ if list(err.absolute_path)[:3] == ["quality", "budget", "relative"]:
198
+ errors.append(f"{where}: unknown relative budget key(s) {extra}; known keys: {known}")
199
+ else:
200
+ warnings.append(f"{where}: unknown key(s) {extra} ignored (typo?); known keys: {known}")
201
+ elif err.validator == "oneOf":
202
+ errors.append(f"{where}: {err.instance!r} is not a valid value "
203
+ f"({err.schema.get('description') or 'see schemas/intake.v1.json'})")
204
+ else:
205
+ errors.append(f"{where}: {err.message}")
206
+ return errors, warnings
207
+
208
+
209
+ def _drop_unknown(data, schema):
210
+ """Remove keys the schema does not define (they were reported as warnings)."""
211
+ if not isinstance(data, dict) or not isinstance(schema, dict):
212
+ return data
213
+ props = schema.get("properties")
214
+ if props is None or schema.get("additionalProperties", True) is not False:
215
+ return data
216
+ return {k: _drop_unknown(v, props[k]) for k, v in data.items() if k in props}
217
+
218
+
219
+ def placeholders(node, path=()) -> list[str]:
220
+ """Dotted paths of string values that still look like template placeholders."""
221
+ out = []
222
+ if isinstance(node, dict):
223
+ for k, v in node.items():
224
+ out += placeholders(v, path + (k,))
225
+ elif isinstance(node, list):
226
+ for i, v in enumerate(node):
227
+ out += placeholders(v, path + (i,))
228
+ elif isinstance(node, str) and _PLACEHOLDER.search(node):
229
+ out.append(_dotted(path))
230
+ return out
231
+
232
+
233
+ def _slug(text: str) -> str:
234
+ return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")[:40] or "x"
235
+
236
+
237
+ def constraint_rules(constraints) -> tuple[dict, list[str]]:
238
+ """Run rules set by constraint strings, e.g. ``after_target: keep_climbing`` or ``speculation: off``.
239
+
240
+ Returns ({name: value}, errors). An entry may hold several rules separated by ``;``. The name may be written
241
+ with ``_``, ``-``, a space or nothing between its words ("after target"); the value is read at the start of
242
+ the text after the colon, the same way ("keep climbing"), so a reason may follow it ("speculation: off, the
243
+ task forbids it"). A recognised name with an unknown value, a second rule inside a reason, or two entries that
244
+ disagree, is an error: a typo here would otherwise be ignored silently.
245
+ """
246
+ found, errors = {}, []
247
+ for text in constraints or []:
248
+ for part in text.split(";") if isinstance(text, str) else []:
249
+ m = _RULE.match(part)
250
+ if not m:
251
+ continue
252
+ name = "after_target" if m.group(1).lower().startswith("after") else m.group(1).lower()
253
+ rest = m.group(2).strip()
254
+ if _RULE_INSIDE.search(rest):
255
+ errors.append(f"constraints: {part.strip()!r} holds more than one rule: give each rule its own "
256
+ "entry, or separate them with ';'")
257
+ continue
258
+ value = next((v for v in RULES[name]
259
+ if re.match(v.replace("_", r"[\s_-]+") + r"(?![\w-])", rest, re.IGNORECASE)), "")
260
+ if value not in RULES[name]:
261
+ errors.append(f"constraints: {part.strip()!r}: {name} must be one of {list(RULES[name])}")
262
+ elif found.setdefault(name, value) != value:
263
+ errors.append(f"constraints: {name} is given twice with different values "
264
+ f"({found[name]!r} and {value!r}); keep one")
265
+ return found, errors
266
+
267
+
268
+ def normalize(data: dict) -> tuple[dict, list[str], list[str]]:
269
+ """Fill defaults and apply the cross-field rules. Returns (intake, errors, warnings)."""
270
+ errors, warnings = [], []
271
+ d = json.loads(json.dumps(data)) # deep copy, JSON types only
272
+
273
+ model = d["model"]
274
+ if "precision_allowed" not in model:
275
+ if "precision" in model:
276
+ model["precision_allowed"] = [model["precision"]]
277
+ else:
278
+ errors.append("model: give precision (the checkpoint's own) and/or precision_allowed")
279
+ model["precision_allowed"] = []
280
+ if "precision" in model and model["precision_allowed"] and model["precision"] not in model["precision_allowed"]:
281
+ warnings.append(f"model.precision {model['precision']!r} is not in model.precision_allowed "
282
+ f"{model['precision_allowed']}: every ranked engine converts the checkpoint; the "
283
+ "conversion itself must pass the quality gates")
284
+ if len(model.get("precision_allowed", [])) > 1:
285
+ warnings.append(f"model.precision_allowed has {len(model['precision_allowed'])} entries: each precision "
286
+ "is its own track; rows are never ranked across precisions")
287
+ model.setdefault("kv_cache_dtype_allowed", ["checkpoint_default"])
288
+ model.setdefault("tokenizer", model["id"])
289
+ rev = model.get("revision")
290
+ if rev is None:
291
+ warnings.append("model.revision missing: stage 1 resolves the current commit sha and records it; "
292
+ "every engine must load that exact revision")
293
+ elif not _SHA.match(rev) and not _PLACEHOLDER.search(rev):
294
+ warnings.append(f"model.revision {rev!r} is not a 40-hex commit sha: stage 1 resolves it to one and "
295
+ "records it")
296
+
297
+ hw = d["hardware"]
298
+ hw.setdefault("count", 1)
299
+ access = hw.setdefault("access", {"kind": "local"})
300
+ if access.get("kind") == "ssh":
301
+ access.setdefault("port", 22)
302
+ key = access.get("key_path")
303
+ if key and not _PLACEHOLDER.search(key) and not Path(os.path.expanduser(key)).exists():
304
+ warnings.append("hardware.access.key_path does not exist on this machine")
305
+
306
+ modality = d["modality"]
307
+ goals = d.setdefault("goals", {})
308
+ goals.setdefault("lead", "single_user")
309
+ affordability = goals.get("affordability")
310
+ if goals["lead"] == "affordability" and not affordability:
311
+ errors.append("goals.lead affordability requires goals.affordability memory limits")
312
+ if affordability:
313
+ affordability.setdefault("offload_allowed", False)
314
+ stretch = affordability.get("stretch_memory_gb_max")
315
+ if stretch is not None and stretch >= affordability["memory_gb_max"]:
316
+ errors.append("goals.affordability.stretch_memory_gb_max must be smaller than memory_gb_max")
317
+ targets = goals.setdefault("targets", {})
318
+ slo = goals.setdefault("slo", {})
319
+ slo.setdefault("max_fail", 0)
320
+ goals.setdefault("traffic", {})
321
+ bad = sorted(set(targets) - {"buckets"} - MODALITY_TARGETS[modality])
322
+ if bad:
323
+ warnings.append(f"goals.targets {bad} do not apply to modality {modality} "
324
+ f"(use {sorted(MODALITY_TARGETS[modality])}); they are reported NOT RUN")
325
+ needs_load = goals["lead"] == "capacity" or "n_star" in targets
326
+ if needs_load and "ttft_p95_ms" not in slo:
327
+ errors.append("goals.slo.ttft_p95_ms is required when the lead goal is capacity or a target is n_star "
328
+ "(N* is defined by the SLO)")
329
+ if needs_load and modality == "llm" and "tpot_p95_ms" not in slo:
330
+ warnings.append("goals.slo.tpot_p95_ms missing: N* will hold only the first-token SLO and failures")
331
+ if "n_star" in targets and goals["lead"] != "capacity":
332
+ warnings.append("goals.targets.n_star is set but lead is single_user: the capacity (load) path runs too")
333
+ frac = targets.get("decode_ceiling_fraction")
334
+ if frac is not None and frac > 0.9:
335
+ warnings.append(f"goals.targets.decode_ceiling_fraction {frac} is above 0.9 of a measured-bandwidth "
336
+ "ceiling; few engines get there at batch 1")
337
+ if frac is not None and not set(model["precision_allowed"]) <= ROOFLINE_DTYPES:
338
+ warnings.append("goals.targets.decode_ceiling_fraction: 'inferencekit roofline' covers dense "
339
+ f"{sorted(ROOFLINE_DTYPES)} full-attention models only; for {model['precision_allowed']} (or MoE, "
340
+ "sliding-window or hybrid attention) stage 2 derives bytes/token from the checkpoint with the "
341
+ "skill's scripts/bytes_per_token.py and records the formula")
342
+
343
+ io = d.setdefault("io_modes", {})
344
+ io.setdefault("input", "complete")
345
+ io.setdefault("output", "streaming")
346
+ if io["input"] == "streaming":
347
+ warnings.append("io_modes.input is streaming: engines without incremental input are NOT SUPPORTED for "
348
+ "it; any client-side emulation is labelled as emulation")
349
+ if "input_rate" not in io:
350
+ warnings.append("io_modes.input_rate missing: the replay pace of streaming input is unknown")
351
+
352
+ d.setdefault("buckets", list(BUCKETS))
353
+ d["buckets"] = [b for b in BUCKETS if b in d["buckets"]]
354
+ if modality in ("image", "video"): # modality workload set, not text length buckets
355
+ if targets.pop("buckets", None) is not None:
356
+ warnings.append(f"goals.targets.buckets do not apply to modality {modality}: a target holds over the "
357
+ "whole modality workload set")
358
+ elif "buckets" in targets:
359
+ outside = sorted(set(targets["buckets"]) - set(d["buckets"]))
360
+ if outside:
361
+ errors.append(f"goals.targets.buckets {outside} are not in buckets {d['buckets']}")
362
+ elif any(k != "buckets" for k in targets):
363
+ targets["buckets"] = list(d["buckets"])
364
+
365
+ wl = d.setdefault("workload", {})
366
+ wl.setdefault("max_tokens", 256)
367
+ wl.setdefault("rounds", 5)
368
+ wl.setdefault("prompt_set", "dev")
369
+ if needs_load:
370
+ wl.setdefault("concurrency", list(DEFAULT_CONCURRENCY))
371
+ if "concurrency" in wl:
372
+ wl["concurrency"] = sorted(set(wl["concurrency"]))
373
+
374
+ budget = d.setdefault("budget", {})
375
+ budget.setdefault("approved", False)
376
+ uncapped = budget.get("spend_cap") == "none"
377
+ if uncapped:
378
+ if budget.get("usd") is not None or budget.get("gpu_hours") is not None:
379
+ errors.append("budget.spend_cap: none conflicts with budget.usd or budget.gpu_hours")
380
+ if not budget["approved"]:
381
+ errors.append("budget.spend_cap: none requires budget.approved: true")
382
+ if not budget.get("deadline") and not budget.get("wall_clock_h"):
383
+ errors.append("budget.spend_cap: none requires a finite budget.deadline or budget.wall_clock_h")
384
+ if budget.get("deadline"):
385
+ try:
386
+ deadline = _dt.datetime.fromisoformat(budget["deadline"].replace("Z", "+00:00"))
387
+ if deadline.tzinfo is None:
388
+ raise ValueError("timezone required")
389
+ except ValueError:
390
+ errors.append("budget.deadline must be an ISO8601 time with a timezone")
391
+ price = hw.get("price_usd_h")
392
+ has_cap = "gpu_hours" in budget or ("usd" in budget and price)
393
+ if "usd" in budget and not price:
394
+ (errors if d.get("non_interactive") else warnings).append(
395
+ "budget.usd is set but hardware.price_usd_h is not: dollars cannot be turned into hours")
396
+ if d.get("non_interactive"):
397
+ if not budget["approved"]:
398
+ errors.append("non_interactive: true needs budget.approved: true (an unattended run may only spend an "
399
+ "approved budget)")
400
+ if not has_cap and not uncapped:
401
+ errors.append("non_interactive: true needs a spend cap: budget.gpu_hours, or budget.usd with "
402
+ "hardware.price_usd_h")
403
+ elif not budget["approved"]:
404
+ warnings.append("budget.approved is false: every GPU-occupying step asks first")
405
+
406
+ q = d.setdefault("quality", {})
407
+ ref = q.get("reference", "none")
408
+ if ref == "none" or (isinstance(ref, dict) and ref.get("kind") == "none"):
409
+ ref = {"kind": "none"}
410
+ warnings.append("quality.reference is none: agreement is measured against the baseline engine at the same "
411
+ "precision, not against a higher-precision reference")
412
+ q["reference"] = ref
413
+ if goals["lead"] == "affordability":
414
+ if not q.get("protocol"):
415
+ errors.append("goals.lead affordability requires quality.protocol with modality quality gates")
416
+ if not any(TARGETS[k][0] == "lower" and k != "usd_per_1m" for k in targets if k in MODALITY_TARGETS[modality]):
417
+ errors.append("goals.lead affordability requires an explicit latency target in goals.targets")
418
+ q.setdefault("noise_floor", "measure")
419
+
420
+ repo = d.get("repo")
421
+ if repo:
422
+ if not repo.get("path"):
423
+ warnings.append("repo.path missing: interactive runs ask which repo to use; non_interactive runs skip "
424
+ "the ledger (NOT RUN)")
425
+ repo.setdefault("push", "ask")
426
+ repo.setdefault("branch", f"perf/{_slug(model['id'].split('/')[-1])}-{_slug(hw['gpu'])}")
427
+ else:
428
+ d["repo"] = None
429
+ warnings.append("repo missing: kept changes are not committed; the commit ledger is NOT RUN")
430
+
431
+ cons = d.get("constraints")
432
+ d["constraints"] = [cons] if isinstance(cons, str) else list(cons or [])
433
+ d.setdefault("non_interactive", False)
434
+
435
+ # Run rules: a schema field wins over a constraint string, and the two must agree when both are given. The
436
+ # defaults are written into the intake; rule_sources records where each rule came from (see rule_values).
437
+ rules, errs = constraint_rules(d["constraints"])
438
+ errors += errs
439
+ resolved = rule_values(d)
440
+ preference = q.get("preference")
441
+ if preference is not None:
442
+ expected = "lossless" if preference == "exact" else "lossy"
443
+ if resolved["fidelity"]["value"] != expected:
444
+ errors.append(f"quality.preference {preference!r} requires fidelity {expected!r}; "
445
+ "resolve the conflicting field or constraint")
446
+ if preference == "exact":
447
+ warnings.append("quality.preference exact requires equality of the declared deterministic/seeded "
448
+ "outputs; legacy lossless distribution checks alone do not prove identical outputs")
449
+ interaction = d.get("interaction", {})
450
+ if d["non_interactive"] and interaction.get("checkpoints", "required") != "required":
451
+ errors.append("non_interactive: true cannot promise human quality or milestone checkpoints")
452
+ for name, where in (("fidelity", "fidelity"), ("after_target", "goals.after_target")):
453
+ given = resolved[name]["value"]
454
+ if resolved[name]["source"] == where and name in rules and rules[name] != given:
455
+ errors.append(f"{where} is {given!r} but constraints say '{name}: {rules[name]}': keep one of them")
456
+ after = goals["after_target"] = resolved["after_target"]["value"]
457
+ fidelity = d["fidelity"] = resolved["fidelity"]["value"]
458
+ d["rule_sources"] = {name: r["source"] for name, r in resolved.items()}
459
+ if after == "keep_climbing" and hour_cap(d)["host_hours_cap"] is None and not budget.get("deadline"):
460
+ warnings.append("goals.after_target is keep_climbing but no budget cap is set"
461
+ + (" (budget.usd needs hardware.price_usd_h to become one)" if budget.get("usd") else "")
462
+ + ": the run climbs until you stop it")
463
+ qbudget = q.get("budget")
464
+ relative = (qbudget or {}).get("relative")
465
+ acceptance = q.get("acceptance")
466
+ if relative is not None:
467
+ if fidelity != "lossy":
468
+ errors.append("quality.budget.relative requires fidelity: lossy")
469
+ if relative["modality"] != modality or modality not in RELATIVE_BUDGET_KEYS:
470
+ errors.append("quality.budget.relative.modality must match the intake modality (image, tts or stt)")
471
+ if not q.get("protocol", "").strip() or not re.fullmatch(r"[0-9a-f]{64}", q.get("protocol_sha256", "")):
472
+ errors.append("quality.budget.relative requires quality.protocol and quality.protocol_sha256 "
473
+ "(64 lowercase hexadecimal characters)")
474
+ allowed = RELATIVE_BUDGET_KEYS[relative["modality"]]
475
+ wrong = sorted(set(relative) - {"modality", *allowed})
476
+ if wrong:
477
+ errors.append(f"quality.budget.relative {wrong} do not apply to modality {relative['modality']}")
478
+ for key, value in relative.items():
479
+ if key != "modality" and isinstance(value, float) and not math.isfinite(value):
480
+ errors.append(f"quality.budget.relative.{key} must be finite")
481
+ if acceptance == PERCEPTUAL_ACCEPTANCE:
482
+ if modality not in PERCEPTUAL_BUDGET_KEYS or relative is None:
483
+ errors.append("quality.acceptance perceptual_and_task_noninferiority requires a typed relative "
484
+ "image or tts budget and hash-bound quality.protocol")
485
+ else:
486
+ missing = sorted(set(PERCEPTUAL_BUDGET_KEYS[modality]) - set(relative))
487
+ if missing:
488
+ errors.append(f"perceptual/task acceptance requires relative bounds {missing}")
489
+ if modality == "image" and ({"lpips_excess_max", "ssim_loss_max"} & set(relative)
490
+ or {"lpips_max", "ssim_min", "psnr_db_min"} & set(qbudget)):
491
+ errors.append("perceptual/task acceptance treats image numerical similarity as diagnostic only; "
492
+ "remove LPIPS/SSIM/PSNR acceptance thresholds")
493
+ wrong = sorted(set(q.get("diagnostic_only", [])) - set(DIAGNOSTIC_QUALITY_METRICS.get(modality, ())))
494
+ if wrong:
495
+ errors.append(f"quality.diagnostic_only {wrong} do not apply to modality {modality}")
496
+ elif "diagnostic_only" in q:
497
+ errors.append("quality.diagnostic_only requires explicit quality.acceptance "
498
+ "perceptual_and_task_noninferiority; it cannot disable existing quality gates")
499
+ if fidelity == "lossless":
500
+ prec, allowed = model.get("precision"), model["precision_allowed"]
501
+ if prec and allowed and allowed != [prec]:
502
+ warnings.append(f"fidelity is lossless but model.precision_allowed {allowed} is not [{prec!r}]: rows at "
503
+ "another precision change numerics; drop them, or set fidelity: lossy with a "
504
+ "quality.budget")
505
+ lossy_kv = [k for k in model["kv_cache_dtype_allowed"] if _LOSSY_KV.match(k)]
506
+ if lossy_kv:
507
+ warnings.append(f"fidelity is lossless but model.kv_cache_dtype_allowed has {lossy_kv}: a low-precision "
508
+ "KV cache changes numerics unless the checkpoint itself specifies it (then write "
509
+ "checkpoint_default); otherwise set fidelity: lossy with a quality.budget")
510
+ if qbudget:
511
+ warnings.append("quality.budget is set but fidelity is lossless: the budget is ignored; set "
512
+ "fidelity: lossy if speed-ups may change numerics")
513
+ elif not qbudget:
514
+ msg = ("fidelity is lossy but quality.budget is missing: declare its bounds "
515
+ f"({', '.join(budget_keys(modality))}) before any measurement")
516
+ if d["non_interactive"]:
517
+ errors.append(msg)
518
+ else:
519
+ warnings.append(msg + "; the agent agrees them with you before ranking a lossy change")
520
+ elif _unbounded(qbudget, modality, acceptance):
521
+ warnings.append(f"fidelity is lossy but quality.budget does not bound {_unbounded(qbudget, modality, acceptance)}: those "
522
+ "metrics have no limit; agree them (add them to quality.budget) before ranking a lossy change")
523
+ other = sorted(set(qbudget or {}) - set(budget_keys(modality)) - {"relative"})
524
+ if fidelity == "lossy" and other:
525
+ warnings.append(f"quality.budget {other} do not apply to modality {modality} (use "
526
+ f"{list(budget_keys(modality))}); they bound nothing")
527
+ d.setdefault("harness", {})
528
+ if access.get("kind") == "ssh" and not d["harness"].get("wheel") and not d["harness"].get("requirements"):
529
+ warnings.append("harness: no wheel or requirements given for the ssh host: install the same inferencekit "
530
+ "version there as here, and record its sha256")
531
+ cands = d.setdefault("candidates", {})
532
+ cands.setdefault("include", [])
533
+ cands.setdefault("exclude", [])
534
+ cands.setdefault("max_engines", 5)
535
+ if modality in ("tts", "stt"):
536
+ warnings.append(f"modality {modality}: bench serve times OpenAI-compatible text endpoints; audio "
537
+ "latencies need a modality client, and claims without one are UNPROVEN")
538
+ if modality == "image":
539
+ warnings.append("modality image: bench serve token metrics and bench clip video metrics do not prove image "
540
+ "latency or quality; a client-timed image adapter and modality quality protocol are required; "
541
+ "unsupported checks are NOT RUN, and claims without them are UNPROVEN")
542
+ if modality == "video":
543
+ warnings.append("modality video: time clips with bench clip (clip HTTP contract or ComfyUI) and check them "
544
+ "with gates clip and quality clip; bench serve's token metrics and buckets do not apply")
545
+ d["intake_version"] = 1
546
+ if not d.get("run_id"):
547
+ stamp = _dt.datetime.now(_dt.timezone.utc).strftime("%Y%m%d-%H%M")
548
+ d["run_id"] = f"{_slug(model['id'].split('/')[-1])}-{_slug(hw['gpu'])}-{stamp}"
549
+ return d, errors, warnings
550
+
551
+
552
+ # --------------------------------------------------------------------------- the derived plan
553
+
554
+ def hour_cap(intake: dict) -> dict:
555
+ """The binding cap on host hours: the smallest of gpu_hours/count, usd/price, wall_clock_h."""
556
+ b, hw = intake["budget"], intake["hardware"]
557
+ caps = {}
558
+ if b.get("gpu_hours"):
559
+ caps["gpu_hours"] = b["gpu_hours"] / hw["count"]
560
+ if b.get("usd") and hw.get("price_usd_h"):
561
+ caps["usd"] = b["usd"] / hw["price_usd_h"]
562
+ if b.get("wall_clock_h"):
563
+ caps["wall_clock_h"] = b["wall_clock_h"]
564
+ if not caps:
565
+ return {"host_hours_cap": None, "binding": None, "caps_h": {}, "usd_cap": b.get("usd"),
566
+ "formula": "no cap given", "spend_cap": b.get("spend_cap", "bounded"),
567
+ "deadline": b.get("deadline")}
568
+ binding = min(caps, key=caps.get)
569
+ usd_cap = b.get("usd")
570
+ if usd_cap is None and b.get("spend_cap") != "none" and hw.get("price_usd_h"):
571
+ usd_cap = round(caps[binding] * hw["price_usd_h"], 2)
572
+ return {"host_hours_cap": round(caps[binding], 3), "binding": binding,
573
+ "caps_h": {k: round(v, 3) for k, v in caps.items()}, "usd_cap": usd_cap,
574
+ "formula": "min(gpu_hours / count, usd / price_usd_h, wall_clock_h)",
575
+ "spend_cap": b.get("spend_cap", "bounded"), "deadline": b.get("deadline")}
576
+
577
+
578
+ def rule_values(intake: dict) -> dict:
579
+ """{name: {"value", "source"}} for every run rule in RULES: its schema field when written, else a constraint
580
+ string, else the default. ``source`` is the field name, ``constraints`` or ``default: ...``.
581
+
582
+ normalize writes the defaults into the intake and records each source in ``rule_sources``. A field value that
583
+ ``rule_sources`` says came from a constraint or a default, and still equals what that would give, keeps that
584
+ source, so checking a normalized intake again gives the same plan.
585
+ """
586
+ found, _ = constraint_rules(intake.get("constraints"))
587
+ goals = intake.get("goals") if isinstance(intake.get("goals"), dict) else {}
588
+ budget = intake.get("budget") if isinstance(intake.get("budget"), dict) else {}
589
+ prior = intake.get("rule_sources") if isinstance(intake.get("rule_sources"), dict) else {}
590
+ lead = goals.get("lead", "single_user")
591
+ if intake.get("non_interactive"):
592
+ after_default = ("keep_climbing", "default: non_interactive")
593
+ elif budget.get("deadline"):
594
+ after_default = ("keep_climbing", "default: budget.deadline is set")
595
+ elif any(budget.get(k) for k in CAP_KEYS):
596
+ after_default = ("keep_climbing", "default: budget.gpu_hours, usd or wall_clock_h is set")
597
+ else:
598
+ after_default = ("stop", "default: interactive, no budget cap")
599
+ preference = (intake.get("quality") or {}).get("preference")
600
+ fidelity_default = (("lossless" if preference == "exact" else "lossy", "quality.preference")
601
+ if preference is not None else ("lossless", "default"))
602
+ rules = {"after_target": ("goals.after_target", goals.get("after_target"), after_default),
603
+ "fidelity": ("fidelity", intake.get("fidelity"), fidelity_default),
604
+ "speculation": (None, None, ("auto", "default")),
605
+ "route": (None, None, ("build" if lead == "single_user" else "patch", f"default: goals.lead {lead}"))}
606
+ out = {}
607
+ for name, (field, written, (default, default_source)) in rules.items():
608
+ fallback = ({"value": found[name], "source": "constraints"} if name in found
609
+ else {"value": default, "source": default_source})
610
+ filled_in = prior.get(name, field) != field and written == fallback["value"]
611
+ out[name] = {"value": written, "source": field} if written is not None and not filled_in else fallback
612
+ return out
613
+
614
+
615
+ def _stop_rule(named: bool, after: str, ni: bool, capped: bool, explicit: bool, set_keys: list[str],
616
+ where: str = "in every listed bucket") -> dict:
617
+ """``capped``: the budget gives an hour cap (hour_cap), so a climbing run has an end. ``explicit``: after_target
618
+ was written (field or constraint), not defaulted. ``set_keys``: the budget keys that made keep_climbing the
619
+ default. ``where``: where a target must hold (video: over the whole clip set). Without a target, an
620
+ interactive run still asks at the stop point unless the person wrote keep_climbing."""
621
+ until = "until the budget ends" if capped else "until you stop it (no budget cap is set)"
622
+ if named and after == "keep_climbing":
623
+ return {"kind": "target", "text": f"keep climbing {until}; record the best result over time (screened or verified). The "
624
+ f"targets are milestones: record when each is first met by its conservative CI bound {where}, "
625
+ "then continue (the report reserve is kept)"}
626
+ if named:
627
+ return {"kind": "target", "text": f"stop when every target is met by its conservative CI bound {where}; "
628
+ "when one is missed, continue to stage 7 within the approved budget"}
629
+ if after == "keep_climbing" and (ni or explicit):
630
+ return {"kind": "budget", "text": f"no target: keep climbing {until}, stage 7 included, keeping the report "
631
+ "reserve; record the best result over time (screened or verified)"}
632
+ if ni:
633
+ return {"kind": "stop_point", "text": "no target and after_target stop: stop after the stage 6 ranking and "
634
+ "write the report (stage 7 NOT RUN)"}
635
+ text = "no target: show the ranked table at stage 6 and ask whether to stop or continue"
636
+ if after == "keep_climbing":
637
+ text += (f"; the default to offer is keep climbing {until}, recording the best result over time (screened or verified) "
638
+ f"(after_target defaults to keep_climbing because the intake sets "
639
+ f"{', '.join('budget.' + k for k in set_keys)})")
640
+ return {"kind": "ask", "text": text}
641
+
642
+
643
+ def budget_keys(modality: str) -> tuple[str, ...]:
644
+ """The quality-budget bounds applicable to a modality."""
645
+ if modality in ("image", "video"):
646
+ return VIDEO_BUDGET_KEYS
647
+ if modality in ("tts", "stt"):
648
+ return SPEECH_BUDGET_KEYS
649
+ return QUALITY_BUDGET_KEYS
650
+
651
+
652
+ def _unbounded(qbudget, modality: str = "llm", acceptance: str | None = None) -> list[str]:
653
+ """The quality-budget bounds a lossy run leaves out."""
654
+ if (qbudget or {}).get("relative"):
655
+ expected = PERCEPTUAL_BUDGET_KEYS if acceptance == PERCEPTUAL_ACCEPTANCE else RELATIVE_BUDGET_KEYS
656
+ return ["relative." + k for k in expected.get(modality, ())
657
+ if k not in qbudget["relative"]]
658
+ return [k for k in budget_keys(modality) if k not in (qbudget or {})]
659
+
660
+
661
+ def _fidelity_text(fidelity: str, qbudget, modality: str = "llm") -> str:
662
+ if modality in ("image", "tts", "stt"):
663
+ return (f"{fidelity}: validate the exact speed configuration using quality.protocol and the declared "
664
+ "modality quality budget against the reference and A/A noise floor; no LLM token/GSM8K proxy. "
665
+ "Numerics changes are declared; missing modality evidence is UNPROVEN")
666
+ if fidelity == "lossless" and modality == "video":
667
+ return ("lossless: numerics stay as shipped (the checkpoint's own precision, the same steps, shape, guidance "
668
+ "and initial noise). Only changes that leave the clips unchanged count: refactors, equivalent "
669
+ "kernels, compilation, overlapped stages. Proof: quality clip (PSNR, SSIM, LPIPS per frame against "
670
+ "the BF16 reference, within the run-to-run and alternative-backend floors); without it a change is "
671
+ "UNPROVEN")
672
+ if fidelity == "lossless":
673
+ return ("lossless: numerics stay as shipped (weights and activations in the checkpoint's own precision; no "
674
+ "KV cache below 16 bits unless the checkpoint ships one). Only changes that leave the output "
675
+ "distribution unchanged count: refactors, equivalent kernels, re-encodings that decode to identical "
676
+ "values, exact speculative decoding. Proof: next-token agreement and KL against the reference within "
677
+ "the noise floor; without it a change is UNPROVEN")
678
+ text = ("lossy: changes that alter numerics are allowed when every bound in quality_budget holds against the "
679
+ "reference; they are a separate track, reported as speed at a stated quality cost and never ranked "
680
+ "against lossless results")
681
+ if not qbudget:
682
+ return text + ". quality_budget is not set yet: agree it before ranking a lossy change"
683
+ missing = _unbounded(qbudget, modality)
684
+ return text + (f". Not bounded: {missing}; agree them before ranking a lossy change" if missing else "")
685
+
686
+
687
+ def _precision_rule(model: dict, fidelity: str, modality: str = "llm") -> str:
688
+ """Which numerics a ranked engine may use. Under lossless only the as-shipped ones: model.precision (when given)
689
+ and KV dtypes of 16 bits or more; the other listed entries are named as levers that are not run or ranked.
690
+ A video model has no KV cache: only the precision part applies."""
691
+ allowed, kv = model["precision_allowed"], model["kv_cache_dtype_allowed"]
692
+ if modality in ("image", "video"):
693
+ kv = []
694
+ prec, kv_ranked = allowed, kv
695
+ if fidelity == "lossless":
696
+ prec = [model["precision"]] if model.get("precision") else allowed
697
+ kv_ranked = [k for k in kv if not _LOSSY_KV.match(k)] or ([] if modality in ("image", "video") else ["checkpoint_default"])
698
+ text = (f"every ranked engine runs weights/activations in one of {prec}"
699
+ + (f" and KV cache in one of {kv_ranked}" if modality not in ("image", "video") else "")
700
+ + "; different precisions are separate tracks")
701
+ extra = [f"{what} {left}" for what, left in (("precision", [p for p in allowed if p not in prec]),
702
+ ("KV cache", [k for k in kv if k not in kv_ranked])) if left]
703
+ if extra:
704
+ text += (f". Listed but outside fidelity lossless (they change numerics): {', '.join(extra)}; they are "
705
+ "reported as levers, not run or ranked, unless fidelity is lossy with a quality.budget")
706
+ return text
707
+
708
+
709
+ def _speculation_text(value: str, fidelity: str, modality: str = "llm") -> str:
710
+ if modality in ("image", "video"):
711
+ return f"does not apply to modality {modality} (no token decoding)"
712
+ if value == "off":
713
+ return "off: no speculative decoding in any ranked engine (no draft model, MTP head or n-gram lookup)"
714
+ return ("auto: speculative decoding is an ordinary lever where it pays (single user, low concurrency), kept only "
715
+ "if faster on this workload; " + ("only exact speculation counts: greedy output identical and the "
716
+ "sampling distribution unchanged" if fidelity == "lossless" else
717
+ "inexact acceptance counts only within quality_budget"))
718
+
719
+
720
+ ROUTE_TEXT = {"build": "build: write a single-purpose engine for this model and workload; the best tuned existing "
721
+ "engine stays the baseline",
722
+ "patch": "patch: tune and patch the best existing engine"}
723
+
724
+
725
+ def derive(intake: dict) -> dict:
726
+ """The derived plan (claim class ``derived``) of a normalized intake. Where each run rule came from is read
727
+ from the intake's ``rule_sources`` (see rule_values), so deriving twice gives the same plan."""
728
+ goals, targets = intake["goals"], intake["goals"]["targets"]
729
+ named = [k for k in targets if k != "buckets"]
730
+ video = intake["modality"] in ("image", "video")
731
+ target_rows = [{"name": k, "value": targets[k], "better": TARGETS[k][0], "unit": TARGETS[k][1],
732
+ "buckets": ["all"] if video else targets.get("buckets", intake["buckets"]),
733
+ "applies": k in MODALITY_TARGETS[intake["modality"]]} for k in named]
734
+ ni = intake["non_interactive"]
735
+ rules = rule_values(intake)
736
+ after = rules["after_target"]["value"]
737
+ after_source = rules["after_target"]["source"]
738
+ cap = hour_cap(intake)
739
+ hours = cap["host_hours_cap"]
740
+ set_keys = [k for k in CAP_KEYS if intake["budget"].get(k)]
741
+ target_scope = ("over the whole clip set" if intake["modality"] == "video" else "over the whole image set") if video else "in every listed bucket"
742
+ stop = {**_stop_rule(bool(named), after, ni, hours is not None or bool(intake["budget"].get("deadline")),
743
+ not after_source.startswith("default"), set_keys, target_scope),
744
+ "after_target": after, "after_target_source": after_source}
745
+ if intake["budget"].get("report_reserve_h") == 0:
746
+ stop["text"] = stop["text"].replace("the report reserve is kept", "no early reserve; validate/report continuously")
747
+ stop["text"] = stop["text"].replace("keeping the report reserve", "validating/reporting continuously")
748
+ interaction = intake.get("interaction") or {}
749
+ if interaction.get("checkpoints") == "required" and not named and stop["kind"] == "ask":
750
+ if after == "keep_climbing" and (hours is not None or intake["budget"].get("deadline")):
751
+ stop.update(kind="budget", text="no target: continue within the authorized budget; pause only "
752
+ "for a new required decision")
753
+ else:
754
+ stop.update(kind="stop_point", text="no target: finish the ranking and report; further scope "
755
+ "needs a new decision")
756
+ fidelity = rules["fidelity"]["value"]
757
+ qbudget = (intake.get("quality") or {}).get("budget") if fidelity == "lossy" else None
758
+ boxes = {k: (round(hours * share, 3) if hours and intake["budget"].get("stage_timeboxes", True) else None)
759
+ for k, share in STAGES}
760
+ modes = ["single"]
761
+ if goals["lead"] == "capacity" or "n_star" in targets:
762
+ modes.append("load")
763
+ if intake["modality"] == "image":
764
+ modes = ["image"] # requires an external image adapter; no built-in command implied
765
+ if intake["modality"] == "video":
766
+ modes = ["clip"] # bench rounds --mode clip: one clip at a time
767
+ questions = []
768
+ if ni:
769
+ if not named:
770
+ questions.append({"question": "No target was given. Stop after stage 6 or continue?",
771
+ "choice": ("keep climbing while budget remains (report reserve kept)"
772
+ if after == "keep_climbing" else "stop after stage 6 (after_target: stop)")})
773
+ elif rules["after_target"]["source"].startswith("default"):
774
+ questions.append({"question": "Once every target is met: stop, or keep climbing?",
775
+ "choice": f"{after} ({rules['after_target']['source']})"})
776
+ if rules["fidelity"]["source"] == "default":
777
+ questions.append({"question": "May speed-ups change numerics (lossy), and inside which quality budget?",
778
+ "choice": "no: fidelity lossless (the default), numerics stay as shipped"})
779
+ if rules["route"]["source"].startswith("default"):
780
+ questions.append({"question": "Build a new engine, or patch the best existing one?",
781
+ "choice": f"{rules['route']['value']} ({rules['route']['source']})"})
782
+ if intake["repo"] and intake["repo"]["push"] == "ask":
783
+ questions.append({"question": "Push the perf branch?", "choice": "no push (non_interactive never "
784
+ "pushes)"})
785
+ if intake["quality"]["reference"]["kind"] == "none":
786
+ questions.append({"question": "Which reference should outputs be compared with?",
787
+ "choice": "the baseline engine at the checkpoint's precision"})
788
+ if not intake["model"].get("revision"):
789
+ questions.append({"question": "Which model revision?", "choice": "the commit sha current at stage 1"})
790
+ return {
791
+ "claim_class": "derived",
792
+ "run_id": intake["run_id"],
793
+ "modes": modes,
794
+ "stop_rule": stop,
795
+ "targets": target_rows,
796
+ "budget": cap,
797
+ "time_boxes_h": boxes,
798
+ "report_reserve_h": intake["budget"].get("report_reserve_h",
799
+ round(hours * REPORT_RESERVE, 3) if hours else None),
800
+ "stage_timeboxes": intake["budget"].get("stage_timeboxes", True),
801
+ "affordability": goals.get("affordability"),
802
+ "precision_rule": _precision_rule(intake["model"], fidelity, intake["modality"]),
803
+ "fidelity_rule": {"value": fidelity, "fidelity": fidelity, "source": rules["fidelity"]["source"],
804
+ "text": ("exact: equality of the declared deterministic/seeded outputs is required; "
805
+ "legacy lossless distribution checks alone are insufficient"
806
+ if intake["quality"].get("preference") == "exact"
807
+ else _fidelity_text(fidelity, qbudget, intake["modality"]))},
808
+ "quality_budget": qbudget or None,
809
+ "quality_preference": intake["quality"].get("preference"),
810
+ "architecture_changes": intake["model"].get("architecture_changes", "forbid"),
811
+ "interaction": {"checkpoints": interaction.get("checkpoints", "required"),
812
+ "show_plan": interaction.get("show_plan", False)},
813
+ **({"external_quality_proof": {
814
+ "required": True, "quality_verdict": "UNPROVEN",
815
+ "protocol": intake["quality"]["protocol"],
816
+ "protocol_sha256": intake["quality"]["protocol_sha256"],
817
+ "budget_basis": "relative degradation; positive increase/drop is worse",
818
+ "text": "External protocol quality proof required on the exact speed configuration, including "
819
+ "its reference/A/A, cohort and CI rules. Intake validation is not a quality PASS; "
820
+ "no absolute scorer or bitwise output test evaluates this relative budget.",
821
+ **({"acceptance": PERCEPTUAL_ACCEPTANCE,
822
+ "diagnostic_only": intake["quality"].get("diagnostic_only", []),
823
+ "blinded_perceptual_review_required": True,
824
+ "perceptual_verdict": "UNPROVEN",
825
+ "text": "External blinded perceptual and task-quality proof required on the exact speed "
826
+ "configuration under the hash-bound protocol. Numerical similarity is diagnostic, "
827
+ "not an independent rejection gate. Proxy metrics alone do not establish perceptual "
828
+ "quality; missing viewing/listening stays UNPROVEN. Intake validation is not quality PASS."}
829
+ if intake["quality"].get("acceptance") == PERCEPTUAL_ACCEPTANCE else {}),
830
+ }} if qbudget and "relative" in qbudget else {}),
831
+ "speculation": {**rules["speculation"], "text": _speculation_text(rules["speculation"]["value"], fidelity,
832
+ intake["modality"])},
833
+ "route": {**rules["route"], "text": ROUTE_TEXT[rules["route"]["value"]]},
834
+ "questions": questions,
835
+ "chat_template_kwargs": (intake.get("workload") or {}).get("chat_template_kwargs") or None,
836
+ }
837
+
838
+
839
+ def _drop_nulls(node, path=()):
840
+ """Legacy empty YAML blocks are absent; explicit typed quality declarations must validate."""
841
+ if isinstance(node, dict):
842
+ return {k: _drop_nulls(v, path + (k,)) for k, v in node.items()
843
+ if v is not None or path == ("quality", "budget", "relative")
844
+ or (path == ("quality", "budget") and k == "relative")
845
+ or (path == ("quality",) and k in ("acceptance", "diagnostic_only"))}
846
+ if isinstance(node, list):
847
+ return [_drop_nulls(v, path + (i,)) for i, v in enumerate(node)]
848
+ return node
849
+
850
+
851
+ def check(data: dict, allow_placeholders: bool = False) -> dict:
852
+ """Validate a parsed intake. Raises OpError listing every error; returns the normalized result."""
853
+ data = _drop_nulls(data)
854
+ errors, warnings = schema_problems(data)
855
+ if errors:
856
+ raise OpError("intake is invalid:\n - " + "\n - ".join(errors + [f"(warning) {w}" for w in warnings]))
857
+ data = _drop_unknown(data, load_schema())
858
+ holes = placeholders(data)
859
+ intake, errs, warns = normalize(data)
860
+ errors += errs
861
+ warnings += warns
862
+ if holes:
863
+ msg = f"placeholder values still present at {holes}: replace them before a run"
864
+ (warnings if allow_placeholders else errors).append(msg)
865
+ if errors:
866
+ raise OpError("intake is invalid:\n - " + "\n - ".join(errors + [f"(warning) {w}" for w in warnings]))
867
+ return {"ok": True, "intake": intake, "warnings": warnings, "placeholders": holes, "derived": derive(intake)}
868
+
869
+
870
+ # --------------------------------------------------------------------------- run directory
871
+
872
+ def initial_state(result: dict, intake_sha: str) -> dict:
873
+ now = utc_now()
874
+ derived = result["derived"]
875
+ stages = {k: {"status": "pending", "started": None, "ended": None, "time_box_h": derived["time_boxes_h"][k],
876
+ "evidence": [], "notes": []} for k, _ in STAGES}
877
+ stages["1_intake"].update(status="in_progress", started=now)
878
+ state = {
879
+ "state_version": 1,
880
+ "run_id": derived["run_id"],
881
+ "intake_sha256": intake_sha,
882
+ "created": now,
883
+ "updated": now,
884
+ "current_stage": "1_intake",
885
+ "stages": stages,
886
+ "budget": {"host_hours_cap": derived["budget"]["host_hours_cap"], "usd_cap": derived["budget"]["usd_cap"],
887
+ "price_usd_h": result["intake"]["hardware"].get("price_usd_h"), "run_started": now,
888
+ "meter": [], "spend_cap": derived["budget"].get("spend_cap", "bounded"),
889
+ "deadline": derived["budget"].get("deadline"),
890
+ "stage_timeboxes": derived["stage_timeboxes"],
891
+ "report_reserve_h": derived["report_reserve_h"]},
892
+ "model": {"id": result["intake"]["model"]["id"], "revision": result["intake"]["model"].get("revision"),
893
+ "revision_resolved": None},
894
+ "candidates": {},
895
+ "best": None,
896
+ "decisions": [],
897
+ "next_action": "stage 1: export INFERENCEKIT_RUNS_DIR=<abs path>/runs/<run-id>/evidence (here and on the GPU "
898
+ "host), resolve the model revision to a commit sha, write QUESTIONS.md, then stage 2",
899
+ }
900
+ pin = (result["intake"].get("workload") or {}).get("chat_template_kwargs")
901
+ if pin:
902
+ text = json.dumps(pin, sort_keys=True, separators=(",", ":"))
903
+ state["workload_pin"] = {"chat_template_kwargs": pin}
904
+ state["next_action"] += (f"; the intake pins chat_template_kwargs {text}: put it in manifest.json "
905
+ "workload.chat_template_kwargs and pass --chat-template-kwargs '" + text + "' to "
906
+ "bench serve, gates run and quality (with --run-dir runs/<run-id> they apply it and "
907
+ "refuse a different value)")
908
+ return state
909
+
910
+
911
+ # --------------------------------------------------------------------------- the workload pin
912
+
913
+ PIN_FILE = "intake.normalized.json"
914
+
915
+
916
+ def intake_pin(run_dir: str | os.PathLike | None) -> dict:
917
+ """The intake's ``workload.chat_template_kwargs`` recorded in ``<run_dir>/intake.normalized.json``
918
+ ({} when there is no run dir, no normalized intake or no pin). The pin is binding: every command that
919
+ generates with that run dir sends it (see apply_pin), and ``manifest validate`` / ``bench rounds`` refuse
920
+ a manifest that contradicts it."""
921
+ if not run_dir:
922
+ return {}
923
+ path = Path(run_dir) / PIN_FILE
924
+ try:
925
+ norm = json.loads(path.read_text())
926
+ except (OSError, ValueError):
927
+ return {}
928
+ pin = (((norm.get("intake") or {}).get("workload") or {}).get("chat_template_kwargs")
929
+ if isinstance(norm, dict) else None)
930
+ return dict(pin) if isinstance(pin, dict) else {}
931
+
932
+
933
+ def pin_conflicts(value: dict | None, pin: dict) -> list[str]:
934
+ """Pinned keys that ``value`` sets to something else (a missing key is not a conflict)."""
935
+ value = value or {}
936
+ return [f"{k}: {json.dumps(value[k])} (pinned {json.dumps(v)})" for k, v in sorted(pin.items())
937
+ if k in value and value[k] != v]
938
+
939
+
940
+ def apply_pin(value: dict | None, run_dir, where: str) -> dict:
941
+ """``value`` with the run dir's intake pin applied: pinned keys are added when missing; a different value
942
+ for a pinned key is refused (OpError naming ``where``)."""
943
+ pin = intake_pin(run_dir)
944
+ if not pin:
945
+ return dict(value or {})
946
+ bad = pin_conflicts(value, pin)
947
+ if bad:
948
+ raise OpError(f"{where}: chat_template_kwargs contradicts the intake's pinned "
949
+ f"workload.chat_template_kwargs in {Path(run_dir) / PIN_FILE}: {'; '.join(bad)}. The intake "
950
+ "is pre-registered: every engine runs with the pinned template settings")
951
+ return {**(value or {}), **pin}
952
+
953
+
954
+ def _home() -> str | None:
955
+ home = os.path.expanduser("~")
956
+ return home if home not in ("", "~", os.sep) else None
957
+
958
+
959
+ def display_path(path: str | os.PathLike) -> str:
960
+ """A path for artifacts: relative to the working directory when under it, else with the home directory as ~."""
961
+ p = Path(path).resolve()
962
+ try:
963
+ return str(p.relative_to(Path.cwd().resolve()))
964
+ except ValueError:
965
+ pass
966
+ home = _home()
967
+ s = str(p)
968
+ if home and (s == home or s.startswith(home.rstrip(os.sep) + os.sep)):
969
+ return "~" + s[len(home.rstrip(os.sep)):]
970
+ return s
971
+
972
+
973
+ def redact(intake: dict) -> dict:
974
+ """A copy of an intake that is safe to keep in the run directory (which is synced, reviewed and published).
975
+
976
+ * every ``hardware.access`` field except ``kind`` becomes ``<redacted>`` (host, user, port, key_path);
977
+ * the host anywhere else (e.g. inside ``quality.reference.base_url``) and ``<user>@`` become ``<redacted>``;
978
+ * the home directory at the start of a path becomes ``~`` (it names the local user).
979
+ """
980
+ d = json.loads(json.dumps(intake, default=str))
981
+ access = (d.get("hardware") or {}).get("access") if isinstance(d.get("hardware"), dict) else None
982
+ host = user = None
983
+ if isinstance(access, dict):
984
+ host = access.get("host") if isinstance(access.get("host"), str) and access.get("host") != REDACTED else None
985
+ user = access.get("user") if isinstance(access.get("user"), str) and access.get("user") != REDACTED else None
986
+ host_re = re.compile(r"(?<![\w.-])" + re.escape(host) + r"(?![\w-])") if host else None
987
+ user_re = re.compile(r"(?<![\w.-])" + re.escape(user) + r"@") if user else None
988
+ home = _home()
989
+ home_prefix = home.rstrip(os.sep) + os.sep if home else None
990
+
991
+ def walk(node):
992
+ if isinstance(node, dict):
993
+ return {k: walk(v) for k, v in node.items()}
994
+ if isinstance(node, list):
995
+ return [walk(v) for v in node]
996
+ if isinstance(node, str):
997
+ if user_re:
998
+ node = user_re.sub(REDACTED + "@", node)
999
+ if host_re:
1000
+ node = host_re.sub(REDACTED, node)
1001
+ if home_prefix and node.startswith(home_prefix):
1002
+ node = "~" + os.sep + node[len(home_prefix):]
1003
+ return node
1004
+
1005
+ d = walk(d)
1006
+ if isinstance(access, dict):
1007
+ d["hardware"]["access"] = {k: (v if k == "kind" else REDACTED) for k, v in access.items()}
1008
+ return d
1009
+
1010
+
1011
+ def _copy_text(raw: bytes, as_json: bool, sha: str) -> str:
1012
+ """The redacted copy of the intake file as written into the run directory."""
1013
+ original = redact(parse_text(raw.decode("utf-8")))
1014
+ if as_json:
1015
+ return json.dumps(original, indent=2, ensure_ascii=False) + "\n"
1016
+ import yaml # parse_text already needed it for a YAML intake
1017
+
1018
+ head = ("# Redacted copy of this run's intake: hardware.access details are removed and the home directory\n"
1019
+ f"# is shown as ~. intake_sha256 of the original file: {sha}\n"
1020
+ "# To resume, validate the ORIGINAL intake file (state.json: intake_source), not this copy.\n")
1021
+ return head + yaml.safe_dump(original, sort_keys=False, allow_unicode=True, default_flow_style=False)
1022
+
1023
+
1024
+ def _recorded_shas(run_dir: Path) -> dict:
1025
+ """intake_sha256 values already recorded in the run directory, by file. Raises on unreadable JSON."""
1026
+ out = {}
1027
+ for name in ("state.json", "intake.normalized.json"):
1028
+ p = run_dir / name
1029
+ if p.exists():
1030
+ try:
1031
+ out[name] = json.loads(p.read_text()).get("intake_sha256")
1032
+ except (ValueError, AttributeError):
1033
+ raise OpError(f"{p} is not valid JSON; fix or move it before resuming") from None
1034
+ return out
1035
+
1036
+
1037
+ def start_run(run_dir: Path, src: Path, raw: bytes, result: dict) -> dict:
1038
+ """Create or resume runs/<run-id>/.
1039
+
1040
+ The intake is pre-registered: if the run directory already records a different intake_sha256, this raises and
1041
+ writes nothing (a changed intake is a new run id). Existing files are never overwritten: the intake copy,
1042
+ intake.normalized.json, state.json, experiments.jsonl and QUESTIONS.md are only created when missing.
1043
+ Access details (host, user, port, key path) never reach the run directory: the copies are redacted.
1044
+ """
1045
+ sha = sha256_hex(raw)
1046
+ recorded = _recorded_shas(run_dir)
1047
+ changed = sorted(name for name, old in recorded.items() if old != sha)
1048
+ if changed:
1049
+ raise OpError(f"the intake changed since this run started: {', '.join(changed)} in {run_dir} record(s) "
1050
+ f"intake_sha256 {recorded[changed[0]]!r}, this file is {sha!r}. Targets are pre-registered, "
1051
+ "so a changed intake is a new run: start a new run id (--run-dir runs/<new-run-id>). "
1052
+ "Nothing was written.")
1053
+ resumed = "state.json" in recorded
1054
+ run_dir.mkdir(parents=True, exist_ok=True)
1055
+ written = []
1056
+ as_json = src.suffix.lower() == ".json"
1057
+ copy_name = "intake.json" if as_json else "intake.yaml"
1058
+ dst = run_dir / copy_name
1059
+ if not dst.exists():
1060
+ dst.write_text(_copy_text(raw, as_json, sha))
1061
+ written.append(copy_name)
1062
+ source = display_path(src)
1063
+ norm_path = run_dir / "intake.normalized.json"
1064
+ if not norm_path.exists():
1065
+ norm = {"intake_sha256": sha, "intake_source": source, "validated": utc_now(),
1066
+ **{k: v for k, v in result.items() if k not in ("intake_sha256", "intake")},
1067
+ "intake": redact(result["intake"])}
1068
+ norm_path.write_text(json.dumps(norm, indent=2, ensure_ascii=False) + "\n")
1069
+ written.append("intake.normalized.json")
1070
+ if not resumed:
1071
+ state = initial_state(result, sha)
1072
+ state["intake_source"] = source
1073
+ (run_dir / "state.json").write_text(json.dumps(state, indent=2) + "\n")
1074
+ written.append("state.json")
1075
+ exp = run_dir / "experiments.jsonl"
1076
+ if not exp.exists():
1077
+ exp.touch()
1078
+ written.append("experiments.jsonl")
1079
+ qs = result["derived"]["questions"]
1080
+ qpath = run_dir / "QUESTIONS.md"
1081
+ if qs and not qpath.exists():
1082
+ lines = ["# Open questions (non_interactive run: the conservative choice was taken)", ""]
1083
+ for i, q in enumerate(qs, 1):
1084
+ lines += [f"{i}. **{q['question']}**", f" - Chosen: {q['choice']}", " - Stage: 1_intake", ""]
1085
+ qpath.write_text("\n".join(lines))
1086
+ written.append("QUESTIONS.md")
1087
+ return {"run_dir": str(run_dir), "resumed": resumed, "written": written, "intake_sha256": sha,
1088
+ "intake_source": source, "evidence_root": str(run_dir / "evidence")}
1089
+
1090
+
1091
+ # --------------------------------------------------------------------------- operations
1092
+
1093
+ TEMPLATE = """\
1094
+ # inferencekit intake v1. Validate with: inferencekit intake validate intake.yaml
1095
+ # Values in <angle brackets> (or __LIKE_THIS__) are placeholders; validate refuses them unless --allow-placeholders.
1096
+ intake_version: 1
1097
+ # run_id: my-model-5090-20261013 # default: <model>-<gpu>-<UTC date>; becomes runs/<run_id>/
1098
+
1099
+ model:
1100
+ id: <org/model-name> # Hugging Face id, or a local path
1101
+ revision: "<40-hex commit sha>" # pin it (quoted); a branch name is resolved to a sha at stage 1
1102
+ precision: <nvfp4|fp8|bf16|q8_0> # what the checkpoint is stored in
1103
+ # precision_allowed: [nvfp4] # default [precision]; engines may only run these (equal precision)
1104
+ kv_cache_dtype_allowed: [checkpoint_default] # adding fp8 (when the checkpoint does not ship it) is lossy
1105
+ # tokenizer: <org/model-name> # default: model.id
1106
+
1107
+ modality: llm # llm | tts | stt | video
1108
+
1109
+ hardware:
1110
+ gpu: RTX 5090
1111
+ count: 1
1112
+ price_usd_h: 0.60 # hourly price of the whole host (for $ caps and cost per 1M)
1113
+ access:
1114
+ kind: ssh # ssh: inferencekit and every engine run ON the GPU host
1115
+ host: <gpu-host>
1116
+ port: 22
1117
+ user: <user>
1118
+ key_path: ~/.ssh/<key>
1119
+ # access: {kind: local} # the GPU is on this machine
1120
+
1121
+ goals:
1122
+ lead: single_user # single_user | capacity (N* = streams per GPU inside the SLO)
1123
+ targets: # optional; omit the block for "as fast as possible"
1124
+ decode_tok_s: 150 # absolute, measured by bench serve (client-side timer)
1125
+ # ttft_ms: 120
1126
+ # decode_ceiling_fraction: 0.70 # relative to the measured-bandwidth roofline ceiling
1127
+ # n_star: 32 # capacity target; needs slo.ttft_p95_ms
1128
+ # usd_per_1m: 0.50
1129
+ # clip_ms: 60000 # modality video: client-timed ms per clip (or clips_per_hour)
1130
+ # buckets: [short, medium] # where the target must hold (default: every bucket)
1131
+ # after_target: stop # once every target is met: stop | keep_climbing (keep improving until the
1132
+ # # budget ends). Default keep_climbing when non_interactive or a cap is set
1133
+ slo: # required for capacity / n_star
1134
+ ttft_p95_ms: 500
1135
+ tpot_p95_ms: 50
1136
+ max_fail: 0
1137
+ # traffic: {arrival_rate_rps: 2.0, burstiness: poisson, session_len: "1 turn"}
1138
+
1139
+ io_modes:
1140
+ input: complete # complete | streaming (prompt arrives incrementally)
1141
+ output: streaming # complete | streaming
1142
+ # input_rate: "30 tok/s" # streaming input pace
1143
+ # latency_focus: "time to first token"
1144
+
1145
+ buckets: [very_short, short, medium, long] # 1-5 words / ~20-50 tok / ~300 tok / 2-4k tok
1146
+
1147
+ workload:
1148
+ max_tokens: 256 # generated per request, identical for every engine (ignore_eos)
1149
+ rounds: 5 # interleaved rounds across engines (>= 3)
1150
+ # concurrency: [1, 2, 4, 8, 16, 32, 64] # load-mode grid (capacity)
1151
+ # chat_template_kwargs: {enable_thinking: false} # binding pin: the manifest and every command must use it
1152
+
1153
+ budget:
1154
+ gpu_hours: 6 # GPU-hours = host hours x count
1155
+ usd: 10
1156
+ wall_clock_h: 6
1157
+ approved: true # required for non_interactive runs
1158
+
1159
+ fidelity: lossless # lossless: numerics as shipped | lossy: numeric changes inside quality.budget
1160
+
1161
+ quality:
1162
+ reference: none # none | {kind: endpoint, base_url: http://<host>:<port>/v1}
1163
+ noise_floor: measure # measure (A/A run) | a fixed agreement floor in [0, 1]
1164
+ # budget: # required when fidelity is lossy; every bound must hold vs the reference
1165
+ # gsm8k_drop_pts_max: 1.0 # GSM8K accuracy drop, percentage points
1166
+ # top1_agreement_min: 0.95 # next-token agreement, fraction
1167
+ # kl_max: 0.01 # mean KL per token, nats
1168
+ # nll_increase_max: 0.02 # reference NLL increase per token, nats
1169
+ # tv_max: 0.05 # mean total variation of the next-token distribution
1170
+ # (modality video: psnr_db_min, ssim_min, lpips_max instead, judged by quality clip)
1171
+
1172
+ repo: # the commit ledger; omit to skip it
1173
+ path: <path to a git repo on the machine running the agent>
1174
+ # branch: perf/<model>-<gpu> # default
1175
+ push: ask # ask | never (non_interactive never pushes)
1176
+
1177
+ constraints: # free text; honoured literally and quoted in the report
1178
+ - engines must be open source
1179
+ # - "speculation: off" # read by validate (quote it): speculation: off | auto (default auto),
1180
+ # - "route: build" # route: build | patch (default: build for single_user, patch for capacity)
1181
+
1182
+ non_interactive: false # true: never block; QUESTIONS.md + the conservative choice
1183
+
1184
+ # harness: # how to install inferencekit on an ssh GPU host
1185
+ # wheel: <path to inferencekit_cli-<version>-py3-none-any.whl>
1186
+ # wheel_sha256: <sha256>
1187
+ # candidates:
1188
+ # include: [] # engines to add (e.g. a custom engine with an OpenAI API)
1189
+ # exclude: []
1190
+ # max_engines: 5
1191
+ """
1192
+
1193
+
1194
+ @operation("intake_template", ("intake", "template"), "Print an annotated intake.yaml to start an "
1195
+ "optimizing-inference run from.",
1196
+ [Arg("out", "string", "Also write it to this file (refuses to overwrite)."),
1197
+ Arg("force", "boolean", "Overwrite --out if it exists.", default=False)])
1198
+ def intake_template(call, out, force):
1199
+ path = "stdout"
1200
+ if out:
1201
+ p = Path(out)
1202
+ if p.exists() and not force:
1203
+ raise OpError(f"{p} exists; pass --force to overwrite")
1204
+ p.parent.mkdir(parents=True, exist_ok=True)
1205
+ p.write_text(TEMPLATE)
1206
+ path = str(p)
1207
+ return {"format": "md", "markdown": TEMPLATE.rstrip("\n"), "path": path}
1208
+
1209
+
1210
+ @operation("intake_validate", ("intake", "validate"), "Validate an intake.yaml: schema + cross-field rules, "
1211
+ "defaults filled, warnings, and the derived plan (stop rule, fidelity rule and quality budget, "
1212
+ "speculation, route, budget cap, stage time boxes). With "
1213
+ "--run-dir, also start or resume the run directory (state.json, QUESTIONS.md).",
1214
+ [Arg("file", "string", "Path to intake.yaml (or .json).", required=True, positional=True),
1215
+ Arg("run_dir", "string", "Start or resume this run directory (e.g. runs/<run-id>): writes a "
1216
+ "redacted copy of the intake (no host/user/port/key) and intake.normalized.json, and creates "
1217
+ "state.json / experiments.jsonl / QUESTIONS.md; never overwrites them. Refuses (writes nothing) "
1218
+ "if the run was started from a different intake: start a new run id."),
1219
+ Arg("allow_placeholders", "boolean", "Accept <placeholder> values (for checking templates).",
1220
+ default=False)])
1221
+ def intake_validate(call, file, run_dir, allow_placeholders):
1222
+ data, raw = load_file(file)
1223
+ access = (data.get("hardware") or {}).get("access") if isinstance(data.get("hardware"), dict) else None
1224
+ if isinstance(access, dict) and REDACTED in access.values():
1225
+ raise OpError(f"{file} is a redacted run-directory copy of an intake (access details removed); validate "
1226
+ "the original intake file instead (state.json: intake_source)")
1227
+ if run_dir and not data.get("run_id"):
1228
+ data["run_id"] = Path(run_dir).name # the run directory names the run
1229
+ result = check(data, allow_placeholders=allow_placeholders)
1230
+ result["intake_sha256"] = sha256_hex(raw)
1231
+ if run_dir and Path(run_dir).name != result["intake"]["run_id"]:
1232
+ result["warnings"].append(f"run_id {result['intake']['run_id']!r} differs from the run directory name "
1233
+ f"{Path(run_dir).name!r}")
1234
+ if run_dir:
1235
+ if result["placeholders"]:
1236
+ raise OpError("refusing to start a run with placeholder values: " + ", ".join(result["placeholders"]))
1237
+ result["run"] = start_run(Path(run_dir), Path(file), raw, result)
1238
+ return result
1239
+
1240
+
1241
+ def _parse_time(s: str) -> _dt.datetime:
1242
+ t = _dt.datetime.fromisoformat(s.replace("Z", "+00:00"))
1243
+ return t if t.tzinfo else t.replace(tzinfo=_dt.timezone.utc)
1244
+
1245
+
1246
+ def _hours(a: _dt.datetime, b: _dt.datetime) -> float:
1247
+ return max(0.0, (b - a).total_seconds() / 3600.0)
1248
+
1249
+
1250
+ def budget_status(state: dict, now: _dt.datetime) -> dict:
1251
+ b = state.get("budget") or {}
1252
+ price_default = b.get("price_usd_h")
1253
+ host_h, usd, open_segments = 0.0, 0.0, 0
1254
+ for seg in b.get("meter") or []:
1255
+ start = _parse_time(seg["start"])
1256
+ end = _parse_time(seg["end"]) if seg.get("end") else now
1257
+ open_segments += 0 if seg.get("end") else 1
1258
+ h = _hours(start, end)
1259
+ host_h += h
1260
+ price = seg.get("price_usd_h", price_default)
1261
+ usd += h * price if price else 0.0
1262
+ wall_h = _hours(_parse_time(b["run_started"]), now) if b.get("run_started") else None
1263
+ cap_h, cap_usd = b.get("host_hours_cap"), b.get("usd_cap")
1264
+ reserve_h = b.get("report_reserve_h", cap_h * REPORT_RESERVE if cap_h else None)
1265
+ left_h = cap_h - max(host_h, wall_h or 0.0) if cap_h else None
1266
+ left_usd = cap_usd - usd if cap_usd else None
1267
+ deadline = _parse_time(b["deadline"]) if b.get("deadline") else None
1268
+ deadline_left_h = (deadline - now).total_seconds() / 3600 if deadline else None
1269
+ if deadline is not None and reserve_h is None:
1270
+ duration = _hours(_parse_time(b["run_started"]), deadline) if b.get("run_started") else 0
1271
+ reserve_h = max(1 / 6, duration * REPORT_RESERVE)
1272
+ stage_key = state.get("current_stage")
1273
+ stage = (state.get("stages") or {}).get(stage_key) or {}
1274
+ stage_used = _hours(_parse_time(stage["started"]), now) if stage.get("started") else None
1275
+ box = stage.get("time_box_h") if b.get("stage_timeboxes", True) else None
1276
+ if ((left_h is not None and left_h <= 0) or (left_usd is not None and left_usd <= 0)
1277
+ or (deadline_left_h is not None and deadline_left_h <= 0)):
1278
+ verdict, action = "over_budget", "stop GPU work now; release the host; write the report (stage 8)"
1279
+ elif (stage_key != "8_report" and reserve_h is not None and
1280
+ ((left_h is not None and left_h <= reserve_h) or
1281
+ (deadline_left_h is not None and deadline_left_h <= reserve_h))):
1282
+ verdict, action = "reserve_only", "only the report reserve is left: go to stage 8 now"
1283
+ elif box is not None and stage_used is not None and stage_used > box:
1284
+ verdict, action = "stage_over", (f"stage {stage_key} is past its {box} h time box: record what is "
1285
+ "unfinished as NOT RUN and move to the next stage")
1286
+ else:
1287
+ verdict, action = "ok", "continue"
1288
+ r3 = (lambda x: None if x is None else round(x, 3))
1289
+ return {"claim_class": "derived", "now": now.isoformat(timespec="seconds").replace("+00:00", "Z"),
1290
+ "verdict": verdict, "action": action,
1291
+ "spent": {"host_hours": r3(host_h), "usd": round(usd, 2), "wall_clock_h": r3(wall_h),
1292
+ "open_meter_segments": open_segments},
1293
+ "cap": {"host_hours": cap_h, "usd": cap_usd, "report_reserve_h": r3(reserve_h),
1294
+ "spend_cap": b.get("spend_cap", "bounded"), "deadline": b.get("deadline")},
1295
+ "left": {"host_hours": r3(left_h), "usd": None if left_usd is None else round(left_usd, 2),
1296
+ "deadline_h": r3(deadline_left_h)},
1297
+ "stage": {"key": stage_key, "used_h": r3(stage_used), "time_box_h": box},
1298
+ "formula": "spent host hours = sum of meter segments (open ones up to now); usd = hours x price; "
1299
+ "left = cap - max(host hours, wall clock)"}
1300
+
1301
+
1302
+ @operation("intake_budget", ("intake", "budget"), "Budget and time-box check for a run directory: spent and left "
1303
+ "(host hours, USD, wall clock) and the current stage's time box, with a verdict "
1304
+ "(ok | stage_over | reserve_only | over_budget). Read-only.",
1305
+ [Arg("run_dir", "string", "The run directory (runs/<run-id>) holding state.json.", required=True,
1306
+ positional=True),
1307
+ Arg("now", "string", "Evaluate at this ISO-8601 UTC time instead of the current time.")])
1308
+ def intake_budget(call, run_dir, now):
1309
+ path = Path(run_dir) / "state.json"
1310
+ if not path.is_file():
1311
+ raise OpError(f"{path} not found: start the run with 'inferencekit intake validate <file> --run-dir "
1312
+ f"{run_dir}'")
1313
+ try:
1314
+ state = json.loads(path.read_text())
1315
+ except ValueError:
1316
+ raise OpError(f"{path} is not valid JSON") from None
1317
+ try:
1318
+ t = _parse_time(now) if now else _dt.datetime.now(_dt.timezone.utc)
1319
+ return {"run_dir": str(run_dir), **budget_status(state, t)}
1320
+ except (KeyError, TypeError, ValueError) as e:
1321
+ raise OpError(f"{path}: malformed budget/stage fields ({e})") from None