runtime-experiment-harness 0.2.0__tar.gz
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.
- runtime_experiment_harness-0.2.0/.gitignore +9 -0
- runtime_experiment_harness-0.2.0/LICENSE +21 -0
- runtime_experiment_harness-0.2.0/PKG-INFO +152 -0
- runtime_experiment_harness-0.2.0/README.md +118 -0
- runtime_experiment_harness-0.2.0/pyproject.toml +59 -0
- runtime_experiment_harness-0.2.0/runtime_harness/__init__.py +5 -0
- runtime_experiment_harness-0.2.0/runtime_harness/__main__.py +8 -0
- runtime_experiment_harness-0.2.0/runtime_harness/banner.py +149 -0
- runtime_experiment_harness-0.2.0/runtime_harness/checks.py +168 -0
- runtime_experiment_harness-0.2.0/runtime_harness/cli.py +142 -0
- runtime_experiment_harness-0.2.0/runtime_harness/data/experiments.yaml +48 -0
- runtime_experiment_harness-0.2.0/runtime_harness/experiments.py +424 -0
- runtime_experiment_harness-0.2.0/tests/test_cli.py +79 -0
- runtime_experiment_harness-0.2.0/tests/test_cuda_check.py +52 -0
- runtime_experiment_harness-0.2.0/tests/test_runtime_banner.py +47 -0
- runtime_experiment_harness-0.2.0/tests/test_server.py +150 -0
- runtime_experiment_harness-0.2.0/tests/test_workspace.py +133 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Git Machine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runtime-experiment-harness
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Runtime experiment harness for vLLM / guidellm benchmarking on NVLink GPU clusters.
|
|
5
|
+
Project-URL: Homepage, https://github.com/GitM-Labs/runtime-experiment-harness
|
|
6
|
+
Project-URL: Repository, https://github.com/GitM-Labs/runtime-experiment-harness
|
|
7
|
+
Project-URL: Issues, https://github.com/GitM-Labs/runtime-experiment-harness
|
|
8
|
+
Author-email: Adit Chawdhary <adit@gitmachine.ai>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: benchmark,gpu,guidellm,inference,llm,nvlink,vllm
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: GPU :: NVIDIA CUDA
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: System :: Benchmark
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Requires-Dist: rich>=13.0.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: twine; extra == 'dev'
|
|
28
|
+
Provides-Extra: gpu
|
|
29
|
+
Requires-Dist: guidellm; extra == 'gpu'
|
|
30
|
+
Requires-Dist: vllm; extra == 'gpu'
|
|
31
|
+
Provides-Extra: plot
|
|
32
|
+
Requires-Dist: plotly>=5.20.0; extra == 'plot'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Runtime Experiment Harness
|
|
36
|
+
|
|
37
|
+
A Python experiment harness for benchmarking vLLM with guidellm on NVIDIA
|
|
38
|
+
H100 / NVLink clusters. Ships as the `rex` command.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install runtime-experiment-harness # harness only
|
|
44
|
+
pip install "runtime-experiment-harness[plot]" # + Plotly HTML result plots
|
|
45
|
+
pip install "runtime-experiment-harness[gpu]" # + vllm and guidellm (CUDA hosts only)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`vllm` and `guidellm` are intentionally optional. They are large, CUDA-specific,
|
|
49
|
+
and will not build on a machine without an NVIDIA toolchain — making them
|
|
50
|
+
required would break `pip install` on a laptop. The harness invokes both as
|
|
51
|
+
subprocesses, so install the `gpu` extra on the cluster (or let `rex run
|
|
52
|
+
--install` fetch them at runtime).
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
rex init # write a starter experiments.yaml
|
|
58
|
+
rex check # verify the host and provision it
|
|
59
|
+
rex run # provision, then run every experiment
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`rex check` prepares the box in five steps and stops at the first failure:
|
|
63
|
+
|
|
64
|
+
1. Verify CUDA >= 13.0 (vLLM dropped everything older)
|
|
65
|
+
2. Print GPU count and the `nvidia-smi` NVLink topology matrix
|
|
66
|
+
3. Install `torch`, `vllm`, `guidellm`, `huggingface-hub`, `plotly`
|
|
67
|
+
4. Create the HF cache, `experiments/`, and `/workspace/guidellm_reports`
|
|
68
|
+
5. Point `HF_HOME` at the cache
|
|
69
|
+
|
|
70
|
+
`rex run` performs the same five steps, then runs the sweep. Useful flags on
|
|
71
|
+
both:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
rex check --no-install # verify and set up, skip pip
|
|
75
|
+
rex check --hf-home /mnt/models # default is /workspace/hf_hub
|
|
76
|
+
rex run --config sweep.yaml --output-dir ./results
|
|
77
|
+
rex run --experiments-dir /workspace/experiments
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`python -m runtime_harness` works identically to `rex` if the console script is
|
|
81
|
+
not on your PATH.
|
|
82
|
+
|
|
83
|
+
## Results
|
|
84
|
+
|
|
85
|
+
Each guidellm run is written per-experiment as:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
experiments/<experiment-id>/<timestamp>_<experiment-id>.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
where `<experiment-id>` is `<name>-gpu<N>` — so one manifest entry swept across
|
|
92
|
+
2, 4, and 8 GPUs produces three directories. Timestamps are UTC and
|
|
93
|
+
filename-safe (`20260803T142305Z`); colons are avoided because they are illegal
|
|
94
|
+
in filenames on macOS and Windows and need quoting in every shell.
|
|
95
|
+
|
|
96
|
+
guidellm's own JSON report is written to `/workspace/guidellm_reports` as
|
|
97
|
+
`<timestamp>_<experiment-id>_guidellm.json`, and a copy is placed beside the
|
|
98
|
+
harness record so each run directory is self-contained. A manifest that sets
|
|
99
|
+
`--output kind=json,path=...` explicitly on its `guidellm_command` keeps that
|
|
100
|
+
path — an explicit config value beats the default.
|
|
101
|
+
|
|
102
|
+
## vLLM startup
|
|
103
|
+
|
|
104
|
+
`vllm_args` from the manifest are passed through verbatim, with
|
|
105
|
+
`--tensor-parallel-size` and `--enable-expert-parallel` filled in from the
|
|
106
|
+
`tp`/`ep` fields when not already present. The harness then polls
|
|
107
|
+
`http://localhost:<port>/health` until the server actually accepts traffic
|
|
108
|
+
rather than sleeping a fixed interval — a cold HF cache can take many minutes to
|
|
109
|
+
load a large checkpoint. `--port` is read from `vllm_args` (default 8000). If
|
|
110
|
+
the server dies or fails to come up within 15 minutes, that experiment is
|
|
111
|
+
recorded as failed and the sweep moves on.
|
|
112
|
+
|
|
113
|
+
A combined `experiment_results.json` and a `plots/` folder also land in the
|
|
114
|
+
working directory (override with `--output-dir`).
|
|
115
|
+
|
|
116
|
+
### `HF_HOME` and your shell
|
|
117
|
+
|
|
118
|
+
`rex` exports `HF_HOME` into its own process, so every vLLM and guidellm
|
|
119
|
+
subprocess it launches inherits the cache location. A child process cannot
|
|
120
|
+
change its parent's environment, so this does **not** persist into your shell —
|
|
121
|
+
`rex` prints the matching `export` line if you want it there too.
|
|
122
|
+
|
|
123
|
+
## Requirements
|
|
124
|
+
|
|
125
|
+
- Python 3.10+
|
|
126
|
+
- CUDA 13.0 or newer — vLLM has dropped support for anything older, so `rex
|
|
127
|
+
check` and `rex run` both refuse to proceed below that and tell you what was
|
|
128
|
+
detected.
|
|
129
|
+
- NVIDIA drivers with `nvidia-smi` on `PATH`
|
|
130
|
+
|
|
131
|
+
## Config
|
|
132
|
+
|
|
133
|
+
`experiments.yaml` defines the sweep: `gpu_counts` to iterate over, then one
|
|
134
|
+
entry per experiment with model, prompt, and EP/DP/TP/PP parallelism settings.
|
|
135
|
+
`vllm_args` and `guidellm_args` are passed through verbatim, so any flag the
|
|
136
|
+
underlying tools accept works without changes here. `rex init` writes a
|
|
137
|
+
worked example to start from.
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pip install -e ".[dev,plot]"
|
|
143
|
+
pytest
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Release
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
python -m build
|
|
150
|
+
python -m twine check dist/*
|
|
151
|
+
python -m twine upload dist/*
|
|
152
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Runtime Experiment Harness
|
|
2
|
+
|
|
3
|
+
A Python experiment harness for benchmarking vLLM with guidellm on NVIDIA
|
|
4
|
+
H100 / NVLink clusters. Ships as the `rex` command.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install runtime-experiment-harness # harness only
|
|
10
|
+
pip install "runtime-experiment-harness[plot]" # + Plotly HTML result plots
|
|
11
|
+
pip install "runtime-experiment-harness[gpu]" # + vllm and guidellm (CUDA hosts only)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`vllm` and `guidellm` are intentionally optional. They are large, CUDA-specific,
|
|
15
|
+
and will not build on a machine without an NVIDIA toolchain — making them
|
|
16
|
+
required would break `pip install` on a laptop. The harness invokes both as
|
|
17
|
+
subprocesses, so install the `gpu` extra on the cluster (or let `rex run
|
|
18
|
+
--install` fetch them at runtime).
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
rex init # write a starter experiments.yaml
|
|
24
|
+
rex check # verify the host and provision it
|
|
25
|
+
rex run # provision, then run every experiment
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`rex check` prepares the box in five steps and stops at the first failure:
|
|
29
|
+
|
|
30
|
+
1. Verify CUDA >= 13.0 (vLLM dropped everything older)
|
|
31
|
+
2. Print GPU count and the `nvidia-smi` NVLink topology matrix
|
|
32
|
+
3. Install `torch`, `vllm`, `guidellm`, `huggingface-hub`, `plotly`
|
|
33
|
+
4. Create the HF cache, `experiments/`, and `/workspace/guidellm_reports`
|
|
34
|
+
5. Point `HF_HOME` at the cache
|
|
35
|
+
|
|
36
|
+
`rex run` performs the same five steps, then runs the sweep. Useful flags on
|
|
37
|
+
both:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
rex check --no-install # verify and set up, skip pip
|
|
41
|
+
rex check --hf-home /mnt/models # default is /workspace/hf_hub
|
|
42
|
+
rex run --config sweep.yaml --output-dir ./results
|
|
43
|
+
rex run --experiments-dir /workspace/experiments
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`python -m runtime_harness` works identically to `rex` if the console script is
|
|
47
|
+
not on your PATH.
|
|
48
|
+
|
|
49
|
+
## Results
|
|
50
|
+
|
|
51
|
+
Each guidellm run is written per-experiment as:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
experiments/<experiment-id>/<timestamp>_<experiment-id>.json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
where `<experiment-id>` is `<name>-gpu<N>` — so one manifest entry swept across
|
|
58
|
+
2, 4, and 8 GPUs produces three directories. Timestamps are UTC and
|
|
59
|
+
filename-safe (`20260803T142305Z`); colons are avoided because they are illegal
|
|
60
|
+
in filenames on macOS and Windows and need quoting in every shell.
|
|
61
|
+
|
|
62
|
+
guidellm's own JSON report is written to `/workspace/guidellm_reports` as
|
|
63
|
+
`<timestamp>_<experiment-id>_guidellm.json`, and a copy is placed beside the
|
|
64
|
+
harness record so each run directory is self-contained. A manifest that sets
|
|
65
|
+
`--output kind=json,path=...` explicitly on its `guidellm_command` keeps that
|
|
66
|
+
path — an explicit config value beats the default.
|
|
67
|
+
|
|
68
|
+
## vLLM startup
|
|
69
|
+
|
|
70
|
+
`vllm_args` from the manifest are passed through verbatim, with
|
|
71
|
+
`--tensor-parallel-size` and `--enable-expert-parallel` filled in from the
|
|
72
|
+
`tp`/`ep` fields when not already present. The harness then polls
|
|
73
|
+
`http://localhost:<port>/health` until the server actually accepts traffic
|
|
74
|
+
rather than sleeping a fixed interval — a cold HF cache can take many minutes to
|
|
75
|
+
load a large checkpoint. `--port` is read from `vllm_args` (default 8000). If
|
|
76
|
+
the server dies or fails to come up within 15 minutes, that experiment is
|
|
77
|
+
recorded as failed and the sweep moves on.
|
|
78
|
+
|
|
79
|
+
A combined `experiment_results.json` and a `plots/` folder also land in the
|
|
80
|
+
working directory (override with `--output-dir`).
|
|
81
|
+
|
|
82
|
+
### `HF_HOME` and your shell
|
|
83
|
+
|
|
84
|
+
`rex` exports `HF_HOME` into its own process, so every vLLM and guidellm
|
|
85
|
+
subprocess it launches inherits the cache location. A child process cannot
|
|
86
|
+
change its parent's environment, so this does **not** persist into your shell —
|
|
87
|
+
`rex` prints the matching `export` line if you want it there too.
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
- Python 3.10+
|
|
92
|
+
- CUDA 13.0 or newer — vLLM has dropped support for anything older, so `rex
|
|
93
|
+
check` and `rex run` both refuse to proceed below that and tell you what was
|
|
94
|
+
detected.
|
|
95
|
+
- NVIDIA drivers with `nvidia-smi` on `PATH`
|
|
96
|
+
|
|
97
|
+
## Config
|
|
98
|
+
|
|
99
|
+
`experiments.yaml` defines the sweep: `gpu_counts` to iterate over, then one
|
|
100
|
+
entry per experiment with model, prompt, and EP/DP/TP/PP parallelism settings.
|
|
101
|
+
`vllm_args` and `guidellm_args` are passed through verbatim, so any flag the
|
|
102
|
+
underlying tools accept works without changes here. `rex init` writes a
|
|
103
|
+
worked example to start from.
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install -e ".[dev,plot]"
|
|
109
|
+
pytest
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Release
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python -m build
|
|
116
|
+
python -m twine check dist/*
|
|
117
|
+
python -m twine upload dist/*
|
|
118
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runtime-experiment-harness"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Runtime experiment harness for vLLM / guidellm benchmarking on NVLink GPU clusters."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Adit Chawdhary", email = "adit@gitmachine.ai" }]
|
|
14
|
+
keywords = ["vllm", "guidellm", "benchmark", "gpu", "nvlink", "llm", "inference"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: GPU :: NVIDIA CUDA",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
"Topic :: System :: Benchmark",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# vllm and guidellm are deliberately NOT core dependencies: they are large,
|
|
28
|
+
# CUDA-specific, and unbuildable on non-NVIDIA hosts, which would make a plain
|
|
29
|
+
# `pip install` fail on any laptop. Both are invoked as subprocesses, so the
|
|
30
|
+
# harness imports fine without them. Install via the `gpu` extra on the cluster.
|
|
31
|
+
dependencies = [
|
|
32
|
+
"pyyaml>=6.0",
|
|
33
|
+
"rich>=13.0.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
plot = ["plotly>=5.20.0"]
|
|
38
|
+
gpu = ["vllm", "guidellm"]
|
|
39
|
+
dev = ["pytest>=7.0", "build", "twine"]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
rex = "runtime_harness.cli:main"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/GitM-Labs/runtime-experiment-harness"
|
|
46
|
+
Repository = "https://github.com/GitM-Labs/runtime-experiment-harness"
|
|
47
|
+
Issues = "https://github.com/GitM-Labs/runtime-experiment-harness"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.version]
|
|
50
|
+
path = "runtime_harness/__init__.py"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["runtime_harness"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = ["runtime_harness", "tests", "README.md", "LICENSE"]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Startup banner for runtime.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from banner import banner
|
|
5
|
+
print(banner()) # auto-detects colour support
|
|
6
|
+
print(banner("runtime", subtitle="v0.4.1"))
|
|
7
|
+
|
|
8
|
+
Run directly to preview: python banner.py
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
|
|
17
|
+
# --- 5x5 block font -------------------------------------------------------
|
|
18
|
+
# '#' = filled cell, '.' = empty. Add glyphs here to support more names.
|
|
19
|
+
|
|
20
|
+
FONT: dict[str, tuple[str, ...]] = {
|
|
21
|
+
"R": ("####.", "#...#", "####.", "#..#.", "#...#"),
|
|
22
|
+
"U": ("#...#", "#...#", "#...#", "#...#", ".###."),
|
|
23
|
+
"N": ("#...#", "##..#", "#.#.#", "#..##", "#...#"),
|
|
24
|
+
"T": ("#####", "..#..", "..#..", "..#..", "..#.."),
|
|
25
|
+
"I": ("#####", "..#..", "..#..", "..#..", "#####"),
|
|
26
|
+
"M": ("#...#", "##.##", "#.#.#", "#...#", "#...#"),
|
|
27
|
+
"E": ("#####", "#....", "####.", "#....", "#####"),
|
|
28
|
+
"A": (".###.", "#...#", "#####", "#...#", "#...#"),
|
|
29
|
+
"C": (".####", "#....", "#....", "#....", ".####"),
|
|
30
|
+
"D": ("####.", "#...#", "#...#", "#...#", "####."),
|
|
31
|
+
"O": (".###.", "#...#", "#...#", "#...#", ".###."),
|
|
32
|
+
"S": (".####", "#....", ".###.", "....#", "####."),
|
|
33
|
+
"L": ("#....", "#....", "#....", "#....", "#####"),
|
|
34
|
+
"P": ("####.", "#...#", "####.", "#....", "#...."),
|
|
35
|
+
"Y": ("#...#", ".#.#.", "..#..", "..#..", "..#.."),
|
|
36
|
+
"X": ("#...#", ".#.#.", "..#..", ".#.#.", "#...#"),
|
|
37
|
+
"H": ("#...#", "#...#", "#####", "#...#", "#...#"),
|
|
38
|
+
"V": ("#...#", "#...#", "#...#", ".#.#.", "..#.."),
|
|
39
|
+
"G": (".####", "#....", "#..##", "#...#", ".###."),
|
|
40
|
+
"B": ("####.", "#...#", "####.", "#...#", "####."),
|
|
41
|
+
"F": ("#####", "#....", "####.", "#....", "#...."),
|
|
42
|
+
" ": (".....", ".....", ".....", ".....", "....."),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
BLOCK = "\u25a6" # ▦
|
|
46
|
+
HEIGHT = 5
|
|
47
|
+
|
|
48
|
+
# Vertical gradient, top row brightest. Truecolor RGB.
|
|
49
|
+
GRADIENT = [(0xE6, 0xF7, 0xFF), (0x9A, 0xD9, 0xF5),
|
|
50
|
+
(0x5C, 0xB3, 0xE8), (0x2E, 0x86, 0xC8), (0x1B, 0x5A, 0x9E)]
|
|
51
|
+
|
|
52
|
+
DIM = "\u001b[2m"
|
|
53
|
+
RESET = "\u001b[0m"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def supports_color(stream=None) -> bool:
|
|
57
|
+
"""True when it is safe to emit ANSI escapes."""
|
|
58
|
+
stream = stream or sys.stdout
|
|
59
|
+
if os.environ.get("NO_COLOR") is not None:
|
|
60
|
+
return False
|
|
61
|
+
if os.environ.get("FORCE_COLOR"):
|
|
62
|
+
return True
|
|
63
|
+
if not hasattr(stream, "isatty") or not stream.isatty():
|
|
64
|
+
return False
|
|
65
|
+
return os.environ.get("TERM", "") != "dumb"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _rows(text: str, cell: str, gap: int) -> list[str]:
|
|
69
|
+
glyphs = []
|
|
70
|
+
for ch in text.upper():
|
|
71
|
+
try:
|
|
72
|
+
glyphs.append(FONT[ch])
|
|
73
|
+
except KeyError:
|
|
74
|
+
raise ValueError(f"no glyph for {ch!r}; add one to FONT") from None
|
|
75
|
+
|
|
76
|
+
spacer = " " * gap
|
|
77
|
+
rows = []
|
|
78
|
+
for y in range(HEIGHT):
|
|
79
|
+
parts = ["".join(cell if c == "#" else " " * len(cell) for c in g[y])
|
|
80
|
+
for g in glyphs]
|
|
81
|
+
rows.append(spacer.join(parts).rstrip())
|
|
82
|
+
return rows
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def banner(
|
|
86
|
+
text: str = "runtime",
|
|
87
|
+
subtitle: str | Sequence[str] | None = None,
|
|
88
|
+
*,
|
|
89
|
+
color: bool | None = None,
|
|
90
|
+
cell: str = BLOCK * 2,
|
|
91
|
+
gap: int = 1,
|
|
92
|
+
indent: int = 2,
|
|
93
|
+
) -> str:
|
|
94
|
+
"""Render `text` as a block banner.
|
|
95
|
+
|
|
96
|
+
color=None auto-detects; pass True/False to force.
|
|
97
|
+
cell is the glyph used per filled pixel (two blocks keeps the
|
|
98
|
+
aspect ratio close to square in most terminals).
|
|
99
|
+
"""
|
|
100
|
+
rows = _rows(text, cell, gap)
|
|
101
|
+
pad = " " * indent
|
|
102
|
+
use_color = supports_color() if color is None else color
|
|
103
|
+
|
|
104
|
+
out = []
|
|
105
|
+
for i, row in enumerate(rows):
|
|
106
|
+
if use_color:
|
|
107
|
+
r, g, b = GRADIENT[i % len(GRADIENT)]
|
|
108
|
+
out.append(f"{pad}\u001b[38;2;{r};{g};{b}m{row}{RESET}")
|
|
109
|
+
else:
|
|
110
|
+
out.append(pad + row)
|
|
111
|
+
|
|
112
|
+
if subtitle:
|
|
113
|
+
lines = [subtitle] if isinstance(subtitle, str) else list(subtitle)
|
|
114
|
+
width = max(len(r) for r in rows)
|
|
115
|
+
out.append("")
|
|
116
|
+
for line in lines:
|
|
117
|
+
centered = line.center(width).rstrip()
|
|
118
|
+
out.append(f"{pad}{DIM}{centered}{RESET}" if use_color else pad + centered)
|
|
119
|
+
|
|
120
|
+
return "\n".join(out)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def harness_banner(
|
|
124
|
+
version: str = "",
|
|
125
|
+
model: str | None = None,
|
|
126
|
+
*,
|
|
127
|
+
color: bool | None = None,
|
|
128
|
+
) -> str:
|
|
129
|
+
"""The artwork shown when the harness starts.
|
|
130
|
+
|
|
131
|
+
Renders REX in the block font over a dim strapline. `model` is included only
|
|
132
|
+
when known, so `rex check` (which has no manifest) stays uncluttered.
|
|
133
|
+
"""
|
|
134
|
+
strapline = "runtime experiment harness"
|
|
135
|
+
if version:
|
|
136
|
+
strapline += f" \u00b7 v{version}"
|
|
137
|
+
# strapline += "vLLM \u00b7 guidellm \u00b7 NVLink fabric"
|
|
138
|
+
|
|
139
|
+
subtitles = [strapline]
|
|
140
|
+
if model:
|
|
141
|
+
subtitles.append(model)
|
|
142
|
+
|
|
143
|
+
return banner("RUNTIME", subtitle=subtitles, color=color)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
print()
|
|
148
|
+
print(harness_banner("0.2.0", color=True))
|
|
149
|
+
print()
|