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.
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ plots/
4
+ experiment_results.json
5
+ .venv
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .pytest_cache/
@@ -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,5 @@
1
+ """Runtime experiment harness for vLLM / guidellm on NVLink GPU clusters."""
2
+
3
+ __version__ = "0.2.0"
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,8 @@
1
+ """Allow `python -m runtime_harness` alongside the `rex` console script."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -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()