pie-bench 0.1.2__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 (193) hide show
  1. pie_bench/__init__.py +1 -0
  2. pie_bench/cli.py +204 -0
  3. pie_bench/clients/bench_pie.py +1687 -0
  4. pie_bench/clients/browser/bench.mjs +103 -0
  5. pie_bench/clients/browser/native.mjs +24 -0
  6. pie_bench/clients/common.py +1046 -0
  7. pie_bench/clients/engram_token_map.py +93 -0
  8. pie_bench/clients/llamacpp_bench.py +192 -0
  9. pie_bench/clients/mlx_bench.py +269 -0
  10. pie_bench/clients/sglang_bench.py +358 -0
  11. pie_bench/clients/shrink_checkpoint.py +1957 -0
  12. pie_bench/clients/vllm_bench.py +812 -0
  13. pie_bench/config.json +73 -0
  14. pie_bench/inferlets/first-token-topk/Cargo.toml +13 -0
  15. pie_bench/inferlets/first-token-topk/src/lib.rs +55 -0
  16. pie_bench/inferlets/prefill-while-typing/Cargo.toml +12 -0
  17. pie_bench/inferlets/prefill-while-typing/src/lib.rs +434 -0
  18. pie_bench/local.py +199 -0
  19. pie_bench/matrix/engines.yaml +94 -0
  20. pie_bench/matrix/first-token-ref/deepseek-v4-flash-mini.json +1 -0
  21. pie_bench/matrix/first-token-ref/deepseek-v4.1-flash-mini8.json +1 -0
  22. pie_bench/matrix/first-token-ref/gemma-4-12b-mini12.json +1 -0
  23. pie_bench/matrix/first-token-ref/gemma-4-26b-a4b-mini-e24.json +1 -0
  24. pie_bench/matrix/first-token-ref/gemma-4-26b-a4b-mini-e32.json +1 -0
  25. pie_bench/matrix/first-token-ref/gemma-4-26b-a4b-mini.json +1 -0
  26. pie_bench/matrix/first-token-ref/gemma-4-26b-a4b-mini12-e32.json +1 -0
  27. pie_bench/matrix/first-token-ref/gemma-4-26b-a4b-mlx4-slice.json +1 -0
  28. pie_bench/matrix/first-token-ref/gemma-4-31b-mini.json +1 -0
  29. pie_bench/matrix/first-token-ref/gemma-4-31b-mini12.json +1 -0
  30. pie_bench/matrix/first-token-ref/gemma-4-31b-mini18.json +1 -0
  31. pie_bench/matrix/first-token-ref/gemma-4-31b-mini6.json +1 -0
  32. pie_bench/matrix/first-token-ref/gemma-4-31b-mlx4-slice-l30.json +1 -0
  33. pie_bench/matrix/first-token-ref/gemma-4-31b-mlx4-slice.json +1 -0
  34. pie_bench/matrix/first-token-ref/glm-5.3-flash-mini4-tpu.json +1 -0
  35. pie_bench/matrix/first-token-ref/glm-5.3-flash-mini8-fp8.json +1 -0
  36. pie_bench/matrix/first-token-ref/glm-5.3-mini.json +1 -0
  37. pie_bench/matrix/first-token-ref/glm-5.3-mini78-e12.json +1 -0
  38. pie_bench/matrix/first-token-ref/glm-5.3-mini78.json +1 -0
  39. pie_bench/matrix/first-token-ref/gpt-oss-120b-mlx-mxfp4-slice-e10.json +1 -0
  40. pie_bench/matrix/first-token-ref/gpt-oss-120b-mxfp4-mini12-e32.json +1 -0
  41. pie_bench/matrix/first-token-ref/gpt-oss-120b-mxfp4-mini24-e32.json +1 -0
  42. pie_bench/matrix/first-token-ref/gpt-oss-20b-mlx4-slice.json +1 -0
  43. pie_bench/matrix/first-token-ref/gpt-oss-20b-mxfp4-mini12.json +1 -0
  44. pie_bench/matrix/first-token-ref/hy4-mini6.json +1 -0
  45. pie_bench/matrix/first-token-ref/kimi-k3-mini4-e16.json +1 -0
  46. pie_bench/matrix/first-token-ref/kimi-k3-mini4.json +1 -0
  47. pie_bench/matrix/first-token-ref/kimi-k3-mini8.json +1 -0
  48. pie_bench/matrix/first-token-ref/mimo-v2.6-flash-mini.json +1 -0
  49. pie_bench/matrix/first-token-ref/minimax-m3.1-flash-mini.json +1 -0
  50. pie_bench/matrix/first-token-ref/minimax-m3.1-flash-mini60-e16.json +1 -0
  51. pie_bench/matrix/first-token-ref/muse-glimmer-30b-mini.json +1 -0
  52. pie_bench/matrix/first-token-ref/muse-glimmer-30b-mini12.json +1 -0
  53. pie_bench/matrix/first-token-ref/muse-glimmer-30b-mini4.json +1 -0
  54. pie_bench/matrix/first-token-ref/muse-glimmer-30b-mini8.json +1 -0
  55. pie_bench/matrix/first-token-ref/muse-glimmer-30b-mlx4-slice.json +1 -0
  56. pie_bench/matrix/first-token-ref/muse-spark-mini2-e8.json +1 -0
  57. pie_bench/matrix/first-token-ref/muse-spark-mini4-e10.json +1 -0
  58. pie_bench/matrix/first-token-ref/muse-spark-mini4.json +1 -0
  59. pie_bench/matrix/first-token-ref/muse-spark-mlx4-slice-l2-e8.json +1 -0
  60. pie_bench/matrix/first-token-ref/nemotron-3-super-mini.json +1 -0
  61. pie_bench/matrix/first-token-ref/nemotron-3-super-mini22.json +1 -0
  62. pie_bench/matrix/first-token-ref/nemotron-3-super-mini88-e64.json +1 -0
  63. pie_bench/matrix/first-token-ref/nemotron-3-super-mini88.json +1 -0
  64. pie_bench/matrix/first-token-ref/nemotron-3-ultra-mini-e16.json +1 -0
  65. pie_bench/matrix/first-token-ref/nemotron-3-ultra-mini-e32.json +1 -0
  66. pie_bench/matrix/first-token-ref/nemotron-3-ultra-mini.json +1 -0
  67. pie_bench/matrix/first-token-ref/qwen3.6-27b-mini.json +1 -0
  68. pie_bench/matrix/first-token-ref/qwen3.6-27b-mini12.json +1 -0
  69. pie_bench/matrix/first-token-ref/qwen3.6-27b-mini8.json +1 -0
  70. pie_bench/matrix/first-token-ref/qwen3.6-27b-mlx4-slice.json +1 -0
  71. pie_bench/matrix/first-token-ref/qwen3.6-35b-a3b-mini-e16.json +1 -0
  72. pie_bench/matrix/first-token-ref/qwen3.6-35b-a3b-mini-e32.json +1 -0
  73. pie_bench/matrix/first-token-ref/qwen3.6-35b-a3b-mini.json +1 -0
  74. pie_bench/matrix/first-token-ref/qwen3.8-27b-mini.json +1 -0
  75. pie_bench/matrix/first-token-ref/qwen3.8-27b-mini12.json +1 -0
  76. pie_bench/matrix/first-token-ref/qwen3.8-27b-mini20.json +1 -0
  77. pie_bench/matrix/first-token-ref/qwen3.8-27b-mini8.json +1 -0
  78. pie_bench/matrix/first-token-ref/qwen3.8-27b-mlx4-slice.json +1 -0
  79. pie_bench/matrix/first-token-ref/qwen3.8-flash-next-mini.json +1 -0
  80. pie_bench/matrix/first-token-ref/qwen3.8-flash-next-mini10.json +1 -0
  81. pie_bench/matrix/hardware.yaml +1609 -0
  82. pie_bench/matrix/install.yaml +224 -0
  83. pie_bench/matrix/models.yaml +2579 -0
  84. pie_bench/matrix/modes.yaml +13 -0
  85. pie_bench/matrix/parity-texts/deepseek-v4.1-flash-mini8.json +49 -0
  86. pie_bench/matrix/parity-texts/minimax-m3.1-flash-mini60-e16.json +49 -0
  87. pie_bench/matrix/parity-texts/nemotron-3-super-mini88-e64.json +49 -0
  88. pie_bench/matrix/phones.yaml +28 -0
  89. pie_bench/matrix/platforms.yaml +973 -0
  90. pie_bench/matrix/programs.yaml +140 -0
  91. pie_bench/matrix/suites.yaml +172 -0
  92. pie_bench/matrix/support.yaml +347 -0
  93. pie_bench/matrix/workloads.yaml +310 -0
  94. pie_bench/node/__init__.py +4 -0
  95. pie_bench/node/accuracy/__init__.py +13 -0
  96. pie_bench/node/accuracy/gate.py +233 -0
  97. pie_bench/node/accuracy/parity.py +156 -0
  98. pie_bench/node/accuracy/prompts.py +51 -0
  99. pie_bench/node/accuracy/reference.py +247 -0
  100. pie_bench/node/baselines.py +410 -0
  101. pie_bench/node/build.py +237 -0
  102. pie_bench/node/engines/__init__.py +1 -0
  103. pie_bench/node/engines/base.py +535 -0
  104. pie_bench/node/engines/browser/.gitignore +1 -0
  105. pie_bench/node/engines/browser/bridge.mjs +178 -0
  106. pie_bench/node/engines/browser/llamacpp/build.sh +39 -0
  107. pie_bench/node/engines/browser/llamacpp/lw.cpp +143 -0
  108. pie_bench/node/engines/browser/package-lock.json +63 -0
  109. pie_bench/node/engines/browser/package.json +10 -0
  110. pie_bench/node/engines/browser/page/driver.mjs +79 -0
  111. pie_bench/node/engines/browser/page/engines/llamacpp-worker.mjs +60 -0
  112. pie_bench/node/engines/browser/page/engines/llamacpp.mjs +51 -0
  113. pie_bench/node/engines/browser/page/engines/pie.mjs +63 -0
  114. pie_bench/node/engines/browser/page/engines/webllm.mjs +40 -0
  115. pie_bench/node/engines/browser/page/index.html +4 -0
  116. pie_bench/node/engines/browser.py +282 -0
  117. pie_bench/node/engines/lifeline.py +31 -0
  118. pie_bench/node/engines/llamacpp.py +103 -0
  119. pie_bench/node/engines/mlxlm.py +91 -0
  120. pie_bench/node/engines/pie.py +455 -0
  121. pie_bench/node/engines/recipes/__init__.py +119 -0
  122. pie_bench/node/engines/recipes/llamacpp-webgpu.yaml +20 -0
  123. pie_bench/node/engines/recipes/llamacpp.yaml +107 -0
  124. pie_bench/node/engines/recipes/mlxlm.yaml +62 -0
  125. pie_bench/node/engines/recipes/pie.yaml +884 -0
  126. pie_bench/node/engines/recipes/sglang.yaml +219 -0
  127. pie_bench/node/engines/recipes/splash.yaml +35 -0
  128. pie_bench/node/engines/recipes/trtllm.yaml +128 -0
  129. pie_bench/node/engines/recipes/vllm-metal.yaml +51 -0
  130. pie_bench/node/engines/recipes/vllm.yaml +434 -0
  131. pie_bench/node/engines/recipes/webllm.yaml +28 -0
  132. pie_bench/node/engines/scripts/browser_bench.py +117 -0
  133. pie_bench/node/engines/scripts/mlx_quantize_in_place.py +87 -0
  134. pie_bench/node/engines/scripts/mlx_unfused.py +39 -0
  135. pie_bench/node/engines/scripts/no_peer_site/sitecustomize.py +35 -0
  136. pie_bench/node/engines/scripts/openai_bench.py +233 -0
  137. pie_bench/node/engines/scripts/parity_mlx.py +41 -0
  138. pie_bench/node/engines/scripts/parity_mlxvlm.py +34 -0
  139. pie_bench/node/engines/scripts/parity_pie_serve.py +39 -0
  140. pie_bench/node/engines/scripts/pie_xla.py +52 -0
  141. pie_bench/node/engines/scripts/quality_mlxvlm.py +14 -0
  142. pie_bench/node/engines/scripts/quality_pie.py +60 -0
  143. pie_bench/node/engines/scripts/trtllm_bench.py +134 -0
  144. pie_bench/node/engines/scripts/vllm_tpu_site/sitecustomize.py +116 -0
  145. pie_bench/node/engines/served.py +170 -0
  146. pie_bench/node/engines/sglang.py +247 -0
  147. pie_bench/node/engines/shape.py +114 -0
  148. pie_bench/node/engines/splash.py +66 -0
  149. pie_bench/node/engines/trtllm.py +130 -0
  150. pie_bench/node/engines/vllm.py +216 -0
  151. pie_bench/node/engines/vllm_metal.py +60 -0
  152. pie_bench/node/fair.py +104 -0
  153. pie_bench/node/gguf_slice.py +231 -0
  154. pie_bench/node/importer.py +96 -0
  155. pie_bench/node/machine.py +48 -0
  156. pie_bench/node/metrics/__init__.py +7 -0
  157. pie_bench/node/metrics/stats.py +74 -0
  158. pie_bench/node/miniature.py +184 -0
  159. pie_bench/node/mlx_quantize.py +62 -0
  160. pie_bench/node/preflight.py +447 -0
  161. pie_bench/node/profile.py +43 -0
  162. pie_bench/node/provenance.py +248 -0
  163. pie_bench/node/reclaim.py +92 -0
  164. pie_bench/node/runner.py +699 -0
  165. pie_bench/node/snapshots.py +136 -0
  166. pie_bench/node/validate.py +28 -0
  167. pie_bench/node/widen.py +128 -0
  168. pie_bench/node/workloads/__init__.py +1 -0
  169. pie_bench/node/workloads/synthetic.py +70 -0
  170. pie_bench/paths.py +7 -0
  171. pie_bench/plan/__init__.py +1 -0
  172. pie_bench/plan/jobs.py +154 -0
  173. pie_bench/plan/matrix.py +341 -0
  174. pie_bench/plan/suites.py +31 -0
  175. pie_bench/quality/__init__.py +0 -0
  176. pie_bench/quality/browser.py +129 -0
  177. pie_bench/quality/dist_run.py +536 -0
  178. pie_bench/quality/engines.py +188 -0
  179. pie_bench/quality/kl.py +161 -0
  180. pie_bench/quality/parity.py +83 -0
  181. pie_bench/quality/run.py +153 -0
  182. pie_bench/quality/tasks.py +177 -0
  183. pie_bench/results/__init__.py +1 -0
  184. pie_bench/results/store.py +287 -0
  185. pie_bench/schema/__init__.py +21 -0
  186. pie_bench/schema/cell.py +307 -0
  187. pie_bench/schema/job.py +48 -0
  188. pie_bench/schema/record.py +275 -0
  189. pie_bench/submit.py +90 -0
  190. pie_bench-0.1.2.dist-info/METADATA +76 -0
  191. pie_bench-0.1.2.dist-info/RECORD +193 -0
  192. pie_bench-0.1.2.dist-info/WHEEL +4 -0
  193. pie_bench-0.1.2.dist-info/entry_points.txt +2 -0
pie_bench/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.2"
pie_bench/cli.py ADDED
@@ -0,0 +1,204 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+
11
+ import click
12
+
13
+ from pie_bench import __version__, local, submit
14
+ from pie_bench.paths import MATRIX
15
+ from pie_bench.plan.matrix import Matrix
16
+ from pie_bench.plan.suites import SUITES
17
+
18
+ PATHS = [Path.home() / ".local/bin", Path.home() / ".cargo/bin", Path("/opt/homebrew/bin")]
19
+
20
+
21
+ def _split(values: tuple[str, ...]) -> list[str] | None:
22
+ out = [v.strip() for s in values for v in s.split(",") if v.strip()]
23
+ return out or None
24
+
25
+
26
+ def _say(msg: str) -> None:
27
+ click.echo(msg, err=True)
28
+
29
+
30
+ def _env() -> None:
31
+ os.environ["PATH"] = os.pathsep.join([*(str(p) for p in PATHS if p.is_dir()), os.environ.get("PATH", "")])
32
+
33
+
34
+ def _mask_gpus(count: int) -> None:
35
+ if os.environ.get("CUDA_VISIBLE_DEVICES") or not shutil.which("nvidia-smi"):
36
+ return
37
+ visible = sum(1 for ln in subprocess.run(["nvidia-smi", "-L"], capture_output=True, text=True).stdout.splitlines() if ln.startswith("GPU "))
38
+ if visible > count:
39
+ os.environ["CUDA_VISIBLE_DEVICES"] = ",".join(str(i) for i in range(count))
40
+
41
+
42
+ def _upload(out: Path) -> None:
43
+ path = out / submit.SUBMISSION
44
+ _say(f"uploading {path} to {submit.API}")
45
+ try:
46
+ res = submit.upload(path)
47
+ except submit.ApiError as e:
48
+ _say(f"upload failed: {e}\nyour results are safe in {out}; retry with: pie-bench upload {out}")
49
+ sys.exit(1)
50
+ _say(f"uploaded: run {res.get('run_id')}, {res.get('results', 0)} results")
51
+
52
+
53
+ @click.group()
54
+ @click.version_option(__version__, prog_name="pie-bench")
55
+ def main():
56
+ _env()
57
+
58
+
59
+ @main.command(help="Measure pie against the other engines on this machine, then upload the results.")
60
+ @click.option("--models", multiple=True, help="model names to measure, comma-separated (default: the release suite)")
61
+ @click.option("--engines", multiple=True, help="engines to measure, comma-separated (default: every engine on this machine)")
62
+ @click.option("--workloads", multiple=True, help="workload ids, comma-separated (default: the release workloads)")
63
+ @click.option("--suite", type=click.Choice(SUITES), default="targeted", show_default=True)
64
+ @click.option("--machine", "machine_id", default=None, help="this machine's id when it is not detected")
65
+ @click.option("--pie", "pie_ref", default="latest", show_default=True, help="pie release tag, branch or commit")
66
+ @click.option("--pie-root", type=click.Path(path_type=Path), default=None, help="an existing pie checkout to build and use")
67
+ @click.option("--out", type=click.Path(path_type=Path), default=None, help="folder for the results (default: ~/.pie-bench/runs/<time>)")
68
+ @click.option("--upload/--no-upload", default=True, show_default=True)
69
+ @click.option("-y", "--yes", is_flag=True, help="start without asking")
70
+ def run(models, engines, workloads, suite, machine_id, pie_ref, pie_root, out, upload, yes):
71
+ m = Matrix.load(MATRIX)
72
+ machine = local.detect(m, machine_id)
73
+ missing = local.prerequisites(machine)
74
+ if missing:
75
+ _say("install these first:\n " + "\n ".join(missing))
76
+ sys.exit(1)
77
+ commit, tag = local.resolve_pie(pie_ref)
78
+ job = local.plan(m, machine, pie_commit=commit, models=_split(models), engines=_split(engines), workloads=_split(workloads), suite=suite)
79
+ if job is None:
80
+ _say(f"nothing to measure on {machine.platform.id}; try `pie-bench plan` with other --models")
81
+ sys.exit(1)
82
+ groups = job.cells_by_process()
83
+ _say(f"machine {machine.platform.id} ({machine.platform.accelerator}, {machine.platform.memory_gib:g} GiB){'' if machine.known else ', new to the catalog'}")
84
+ _say(f"pie {tag} ({commit[:9]})")
85
+ _say(f"measure {len(job.cells)} cells, {len({c.artifact.id for c in job.cells})} models, engines {', '.join(sorted({str(c.engine) for c in job.cells}))}")
86
+ _say(f"estimate about {max(1, round(job.est_minutes + 3 * len(groups)))} minutes, plus model downloads on a first run")
87
+ if not yes and not click.confirm("start?", default=True, err=True):
88
+ return
89
+ out = (out or local.HOME / "runs" / datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")).resolve()
90
+ out.mkdir(parents=True, exist_ok=True)
91
+ (out / "job.json").write_text(job.model_dump_json(indent=1))
92
+ os.environ.update(local.setup_engines(m, job, log=_say))
93
+ _mask_gpus(machine.platform.count)
94
+ from pie_bench.node.machine import MachineBusy, machine_lock
95
+ from pie_bench.node.runner import NodeRunner
96
+
97
+ try:
98
+ with machine_lock(job.job_id, max_wait_s=3600, log=_say):
99
+ runner = NodeRunner(job, pie_root=(pie_root or local.HOME / "pie").resolve(), out_dir=out, download=True)
100
+ recs = runner.run()
101
+ except MachineBusy as e:
102
+ _say(str(e))
103
+ sys.exit(3)
104
+ by: dict[str, int] = {}
105
+ for r in recs:
106
+ by[str(r.status)] = by.get(str(r.status), 0) + 1
107
+ submit.pack(out, {"run_id": runner.run_id, "tier": str(job.tier), "tag": tag, "tag_kind": "release" if not tag.startswith("manual-") else "manual",
108
+ "platform": machine.platform.model_dump(mode="json"), "pie_commit": commit})
109
+ _say(f"done: {len(recs)} results ({', '.join(f'{v} {k}' for k, v in sorted(by.items()))}) in {out}")
110
+ if upload:
111
+ _upload(out)
112
+
113
+
114
+ @main.command(help="Show what `pie-bench run` would measure here, without running anything.")
115
+ @click.option("--models", multiple=True)
116
+ @click.option("--engines", multiple=True)
117
+ @click.option("--workloads", multiple=True)
118
+ @click.option("--suite", type=click.Choice(SUITES), default="targeted", show_default=True)
119
+ @click.option("--machine", "machine_id", default=None)
120
+ @click.option("--json", "as_json", is_flag=True)
121
+ def plan(models, engines, workloads, suite, machine_id, as_json):
122
+ m = Matrix.load(MATRIX)
123
+ machine = local.detect(m, machine_id)
124
+ job = local.plan(m, machine, pie_commit="0" * 40, models=_split(models), engines=_split(engines), workloads=_split(workloads), suite=suite)
125
+ cells = job.cells if job else []
126
+ if as_json:
127
+ click.echo(json.dumps([c.cell_key for c in cells], indent=1))
128
+ return
129
+ click.echo(f"{machine.platform.id}: {len(cells)} cells")
130
+ for (engine, artifact, _), group in (job.cells_by_process().items() if job else []):
131
+ click.echo(f" {engine:<10} {artifact:<60} {', '.join(sorted({c.workload.id for c in group}))}")
132
+
133
+
134
+ @main.command(help="Every machine id the catalog knows.")
135
+ def machines():
136
+ m = Matrix.load(MATRIX)
137
+ for p in sorted(m.platforms.values(), key=lambda p: (p.os, p.id)):
138
+ click.echo(f"{p.id:<22} {p.os:<6} {p.accelerator} x{p.count}, {p.memory_gib:g} GiB")
139
+
140
+
141
+ @main.command(help="Get an upload token for this machine (once).")
142
+ @click.option("--name", default=None, help="a name for this machine's token (default: its host name)")
143
+ def login(name):
144
+ try:
145
+ res = submit.login(name)
146
+ except submit.ApiError as e:
147
+ _say(f"login failed: {e}")
148
+ sys.exit(1)
149
+ _say(f"token saved to {submit.TOKEN_FILE} (quota {res.get('quota')} uploads)")
150
+
151
+
152
+ @main.command(help="Upload a finished run's folder.")
153
+ @click.argument("out", type=click.Path(exists=True, file_okay=False, path_type=Path))
154
+ def upload(out):
155
+ if not (out / submit.SUBMISSION).exists():
156
+ job = json.loads((out / "job.json").read_text())
157
+ run_id = (out / "run_id.txt").read_text().strip() if (out / "run_id.txt").exists() else job["job_id"]
158
+ submit.pack(out, {"run_id": run_id, "tier": job["tier"], "tag": None, "tag_kind": "manual",
159
+ "platform": job["cells"][0]["platform"] if job["cells"] else None, "pie_commit": job.get("pie_commit")})
160
+ _upload(out.resolve())
161
+
162
+
163
+ @main.command(help="Score model accuracy on question sets, then upload the scores.")
164
+ @click.option("--models", multiple=True, required=True, help="model artifact ids, comma-separated")
165
+ @click.option("--task", "tasks", multiple=True, default=("gsm8k", "ifeval", "mmlu", "arc", "math500"), show_default=True)
166
+ @click.option("--n", type=int, default=100, show_default=True)
167
+ @click.option("--pie-root", type=click.Path(path_type=Path), default=None)
168
+ @click.option("--out", type=click.Path(path_type=Path), default=None)
169
+ @click.option("--upload/--no-upload", default=True, show_default=True)
170
+ def quality(models, tasks, n, pie_root, out, upload):
171
+ from pie_bench.quality.run import run_quality
172
+
173
+ m = Matrix.load(MATRIX)
174
+ machine = local.detect(m)
175
+ out = (out or local.HOME / "runs" / f"quality-{datetime.now(timezone.utc):%Y%m%d-%H%M%S}").resolve()
176
+ out.mkdir(parents=True, exist_ok=True)
177
+ failed = []
178
+ for a in _split(models) or []:
179
+ try:
180
+ run_quality(a, _split(tasks), n, store_dir=str(out), pie_root=(pie_root or local.HOME / "pie").resolve(),
181
+ platform=machine.platform.id, log=_say)
182
+ except Exception as e:
183
+ failed.append(a)
184
+ _say(f"{a}: {type(e).__name__}: {e}")
185
+ submit.pack(out, {"run_id": out.name, "tier": "quality", "tag": None, "tag_kind": "manual", "platform": machine.platform.model_dump(mode="json"), "pie_commit": None})
186
+ if upload:
187
+ _upload(out)
188
+ sys.exit(1 if failed else 0)
189
+
190
+
191
+ @main.command(help="Check this machine is ready to run pie-bench.")
192
+ def doctor():
193
+ m = Matrix.load(MATRIX)
194
+ machine = local.detect(m)
195
+ click.echo(f"machine {machine.platform.id} ({machine.platform.accelerator}){'' if machine.known else ', new to the catalog'}")
196
+ missing = local.prerequisites(machine)
197
+ click.echo("tools " + ("ready" if not missing else "missing: " + "; ".join(missing)))
198
+ click.echo(f"token {'saved' if submit.saved_token() else 'not yet (made on the first upload)'}")
199
+ click.echo(f"api {submit.API}")
200
+ sys.exit(1 if missing else 0)
201
+
202
+
203
+ if __name__ == "__main__":
204
+ main()