rexs 0.1.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.
- rexs-0.1.0/PKG-INFO +278 -0
- rexs-0.1.0/README.md +256 -0
- rexs-0.1.0/pyproject.toml +39 -0
- rexs-0.1.0/setup.cfg +4 -0
- rexs-0.1.0/src/rexs/__init__.py +14 -0
- rexs-0.1.0/src/rexs/__main__.py +4 -0
- rexs-0.1.0/src/rexs/auth.py +48 -0
- rexs-0.1.0/src/rexs/beaker.py +47 -0
- rexs-0.1.0/src/rexs/cli.py +363 -0
- rexs-0.1.0/src/rexs/compiler.py +694 -0
- rexs-0.1.0/src/rexs/config.py +136 -0
- rexs-0.1.0/src/rexs/controller.py +258 -0
- rexs-0.1.0/src/rexs/dryrun.py +164 -0
- rexs-0.1.0/src/rexs/errors.py +10 -0
- rexs-0.1.0/src/rexs/links.py +28 -0
- rexs-0.1.0/src/rexs/metrics.py +287 -0
- rexs-0.1.0/src/rexs/resources.py +85 -0
- rexs-0.1.0/src/rexs/server.py +455 -0
- rexs-0.1.0/src/rexs/state.py +412 -0
- rexs-0.1.0/src/rexs/static/rexs-logo.png +0 -0
- rexs-0.1.0/src/rexs/web.py +611 -0
- rexs-0.1.0/src/rexs.egg-info/PKG-INFO +278 -0
- rexs-0.1.0/src/rexs.egg-info/SOURCES.txt +35 -0
- rexs-0.1.0/src/rexs.egg-info/dependency_links.txt +1 -0
- rexs-0.1.0/src/rexs.egg-info/entry_points.txt +2 -0
- rexs-0.1.0/src/rexs.egg-info/requires.txt +14 -0
- rexs-0.1.0/src/rexs.egg-info/top_level.txt +1 -0
- rexs-0.1.0/tests/test_beaker_evaluation.py +38 -0
- rexs-0.1.0/tests/test_cli.py +32 -0
- rexs-0.1.0/tests/test_compiler.py +383 -0
- rexs-0.1.0/tests/test_controller.py +312 -0
- rexs-0.1.0/tests/test_dryrun.py +108 -0
- rexs-0.1.0/tests/test_gpu_assignment.py +26 -0
- rexs-0.1.0/tests/test_links.py +39 -0
- rexs-0.1.0/tests/test_metrics.py +181 -0
- rexs-0.1.0/tests/test_resources.py +59 -0
- rexs-0.1.0/tests/test_server_auth.py +123 -0
rexs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rexs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reproducible Experiments, eXecuted on Slurm
|
|
5
|
+
Project-URL: Homepage, https://github.com/goncalorafaria/rexs
|
|
6
|
+
Project-URL: Documentation, https://goncalorafaria.github.io/rexs/
|
|
7
|
+
Project-URL: Repository, https://github.com/goncalorafaria/rexs
|
|
8
|
+
Project-URL: Issues, https://github.com/goncalorafaria/rexs/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: fire>=0.7
|
|
12
|
+
Requires-Dist: PyYAML>=6.0
|
|
13
|
+
Provides-Extra: wandb
|
|
14
|
+
Requires-Dist: wandb<0.29,>=0.19; extra == "wandb"
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
17
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
18
|
+
Requires-Dist: wandb<0.29,>=0.19; extra == "dev"
|
|
19
|
+
Provides-Extra: docs
|
|
20
|
+
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
21
|
+
Requires-Dist: mkdocs-material>=9.6; extra == "docs"
|
|
22
|
+
|
|
23
|
+
<div align="center">
|
|
24
|
+
<img src="src/rexs/static/rexs-logo.png" width="160" alt="REXS green T-rex badge">
|
|
25
|
+
<h1>REXS</h1>
|
|
26
|
+
<p><strong>Reproducible Experiments, eXecuted on Slurm.</strong></p>
|
|
27
|
+
<p>Run the Beaker experiment configurations you already have on Slurm and Apptainer.</p>
|
|
28
|
+
|
|
29
|
+
[](https://github.com/goncalorafaria/rexs/actions/workflows/ci.yml)
|
|
30
|
+
[](https://goncalorafaria.github.io/rexs/)
|
|
31
|
+
[](https://www.python.org/)
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
REXS is a small compatibility layer between Beaker v2 experiment files and a
|
|
36
|
+
Slurm cluster. It compiles each experiment into an auditable `sbatch` script,
|
|
37
|
+
runs every task replica as an exclusive `srun` step inside Apptainer, and uses
|
|
38
|
+
Slurm itself as the execution engine. A lightweight SQLite controller records
|
|
39
|
+
the submitted configuration, job state, task replicas, logs, and lifecycle
|
|
40
|
+
events.
|
|
41
|
+
|
|
42
|
+
It focuses deliberately on the Beaker features exercised by `datadev` and
|
|
43
|
+
LiteRegistry. Unsupported fields produce warnings; `--strict` promotes any
|
|
44
|
+
lossy translation to an error before submission.
|
|
45
|
+
|
|
46
|
+
## How it works
|
|
47
|
+
|
|
48
|
+
```mermaid
|
|
49
|
+
flowchart LR
|
|
50
|
+
A[Beaker v2<br>YAML / JSON] --> B[REXS compiler]
|
|
51
|
+
P[Site profile<br>images · mounts · Slurm] --> B
|
|
52
|
+
B --> C[Auditable<br>sbatch script]
|
|
53
|
+
C --> D[Slurm allocation]
|
|
54
|
+
D --> E1[srun task 0<br>Apptainer]
|
|
55
|
+
D --> E2[srun task 1<br>Apptainer]
|
|
56
|
+
D --> EN[srun task N<br>Apptainer]
|
|
57
|
+
D --> S[(SQLite state)]
|
|
58
|
+
S --> UI[CLI + web dashboard]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
One Beaker experiment becomes one Slurm allocation. Task replicas are assigned
|
|
62
|
+
to nodes deterministically, receive Beaker-compatible replica environment
|
|
63
|
+
variables, share the host network, and write individual logs and result trees.
|
|
64
|
+
If one replica fails, REXS terminates its siblings and exits the allocation
|
|
65
|
+
with a failure.
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git clone https://github.com/goncalorafaria/rexs.git
|
|
71
|
+
cd rexs
|
|
72
|
+
python -m venv .venv
|
|
73
|
+
. .venv/bin/activate
|
|
74
|
+
pip install -e .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The submission host needs `sbatch`, `squeue`, `sacct`, and `scancel`. Compute
|
|
78
|
+
nodes need `srun`, `scontrol`, and `apptainer`.
|
|
79
|
+
|
|
80
|
+
## Quick start
|
|
81
|
+
|
|
82
|
+
Copy the sample profile and adapt it to your Slurm site:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cp examples/profile.yaml profile.yaml
|
|
86
|
+
$EDITOR profile.yaml
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Validate and render without touching Slurm:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
rexs validate experiment.yaml --profile profile.yaml
|
|
93
|
+
rexs render experiment.yaml \
|
|
94
|
+
--profile profile.yaml \
|
|
95
|
+
--output experiment.sbatch
|
|
96
|
+
bash -n experiment.sbatch
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Submit and inspect the run:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
rexs submit experiment.yaml --profile profile.yaml --name my-run --strict
|
|
103
|
+
rexs experiments # queued and running only
|
|
104
|
+
rexs experiments --all # include finished experiments
|
|
105
|
+
rexs status 123456
|
|
106
|
+
rexs logs 123456 --task=trainer --replica=0
|
|
107
|
+
rexs cancel 123456
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Start the dashboard in the background:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
rexs server --host=127.0.0.1 --port=8765 --daemon
|
|
114
|
+
# Open http://127.0.0.1:8765
|
|
115
|
+
rexs server_stop
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The dashboard and all API routes require HTTP Basic authentication by default.
|
|
119
|
+
Use username `rexs`. On first start, REXS generates a unique password and saves it
|
|
120
|
+
with owner-only permissions in `~/.local/state/rexs/server.password` (beside a
|
|
121
|
+
custom state database when `--db` is used). Read it with:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cat ~/.local/state/rexs/server.password
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The saved password persists across restarts. Set `REXS_SERVER_PASSWORD` in the
|
|
128
|
+
server's environment to override it; empty passwords are rejected. The password
|
|
129
|
+
is never included in server URLs, process arguments, or logs. After changing a
|
|
130
|
+
password, restart the server and sign in again. Authenticated API POST requests
|
|
131
|
+
must also send `X-REXS-Request: 1`; the dashboard adds this automatically.
|
|
132
|
+
Keep the loopback binding and access it through SSH port forwarding. Basic
|
|
133
|
+
authentication should use HTTPS if exposing the server beyond that tunnel.
|
|
134
|
+
|
|
135
|
+
The dashboard shows searchable experiment history, status filters, submitted
|
|
136
|
+
YAML, state transitions, task-level logs, and guarded cancellation.
|
|
137
|
+
|
|
138
|
+
## Site profile
|
|
139
|
+
|
|
140
|
+
Experiment intent remains in the Beaker file. Cluster policy belongs in a
|
|
141
|
+
separate REXS profile:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
account: research
|
|
145
|
+
partition: gpu
|
|
146
|
+
qos: normal
|
|
147
|
+
time_limit: "24:00:00"
|
|
148
|
+
cpus_per_task: 8
|
|
149
|
+
memory: 64G
|
|
150
|
+
|
|
151
|
+
apptainer_binary: apptainer
|
|
152
|
+
image_cache: /shared/rexs/images
|
|
153
|
+
image_dir: /shared/apptainer/images
|
|
154
|
+
dataset_root: /shared/datasets
|
|
155
|
+
run_root: /shared/rexs/runs
|
|
156
|
+
secret_file: ~/.config/rexs/secrets.env
|
|
157
|
+
|
|
158
|
+
setup_commands:
|
|
159
|
+
- module load apptainer
|
|
160
|
+
|
|
161
|
+
images:
|
|
162
|
+
org/trainer: /shared/apptainer/images/trainer.sif
|
|
163
|
+
org/service: docker://ghcr.io/org/service:latest
|
|
164
|
+
|
|
165
|
+
datasets:
|
|
166
|
+
weka:shared-data: /shared/data
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Docker references are converted to `docker://` URIs automatically. Beaker
|
|
170
|
+
image names should be mapped to an OCI URI or existing SIF. Unmapped resources
|
|
171
|
+
fall back to predictable paths and generate warnings.
|
|
172
|
+
|
|
173
|
+
Secrets are loaded inside the allocation from `secret_file`; their values are
|
|
174
|
+
never written to the generated script or SQLite database.
|
|
175
|
+
|
|
176
|
+
## Supported Beaker subset
|
|
177
|
+
|
|
178
|
+
- Beaker v2 YAML and JSON
|
|
179
|
+
- `image.beaker` and `image.docker`
|
|
180
|
+
- command plus arguments
|
|
181
|
+
- plain and secret-backed environment variables
|
|
182
|
+
- task replicas and leader selection
|
|
183
|
+
- Beaker replica/job/hostname compatibility variables
|
|
184
|
+
- Weka, host-path, and mapped Beaker dataset mounts
|
|
185
|
+
- persistent per-replica result directories
|
|
186
|
+
- GPU, CPU, memory, and shared-memory advisories
|
|
187
|
+
- host-networked multi-service deployments
|
|
188
|
+
- `${VARIABLE}` configuration substitution
|
|
189
|
+
|
|
190
|
+
Workspace, budget, cluster constraints, priority, retry policy, scheduler
|
|
191
|
+
groups, and preemption metadata are not translated. Slurm policy comes only
|
|
192
|
+
from the site profile. See the [compatibility reference](docs/compatibility.md)
|
|
193
|
+
for exact behavior.
|
|
194
|
+
|
|
195
|
+
## Dry-run a repository
|
|
196
|
+
|
|
197
|
+
REXS can audit a directory full of experiment configurations without a Slurm
|
|
198
|
+
installation. It compiles every Beaker v2 YAML/JSON file and checks the output
|
|
199
|
+
with `bash -n`:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
rexs dry_run ~/datadev/beaker_experiments
|
|
203
|
+
rexs dry_run ~/datadev/beaker_experiments --strict --details
|
|
204
|
+
rexs dry_run ~/other/experiments --output_dir=/tmp/rexs-scripts
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
No Slurm command is invoked in dry-run mode.
|
|
208
|
+
|
|
209
|
+
## State and output
|
|
210
|
+
|
|
211
|
+
Global state defaults to `~/.local/state/rexs/state.sqlite3` and can be changed
|
|
212
|
+
with `--db` or `REXS_STATE_DB`. SQLite uses WAL mode so the CLI and background
|
|
213
|
+
server can safely share it.
|
|
214
|
+
|
|
215
|
+
Runtime files are organized as:
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
<run-root>/<slurm-job-id>/
|
|
219
|
+
├── logs/<task>.<replica>.log
|
|
220
|
+
└── results/<task>/<replica>/
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Slurm remains authoritative. The controller reconciles active records through
|
|
224
|
+
`squeue` and completed records through `sacct`.
|
|
225
|
+
|
|
226
|
+
## Optional W&B capture
|
|
227
|
+
|
|
228
|
+
Install `pip install -e '.[wandb]'` to let the REXS server capture local W&B
|
|
229
|
+
logs automatically. Every 30 seconds it scans registered jobs' output directories
|
|
230
|
+
for `.wandb` files, including offline runs. No W&B login or upload is required.
|
|
231
|
+
Jobs without W&B continue normally; this does not change their launch commands.
|
|
232
|
+
|
|
233
|
+
Open a run and select **GPU metrics**, after **History**. Each GPU card plots utilization and allocated memory over time, with hover
|
|
234
|
+
values and the latest sample. Long histories retain extrema when reduced for display.
|
|
235
|
+
Samples remain available after the job finishes. Missing values display a dash;
|
|
236
|
+
stale samples are marked. GPU indices are local to each logger, so separate
|
|
237
|
+
sources may report the same physical devices.
|
|
238
|
+
|
|
239
|
+
Scalar history, summary and system metrics, plus W&B console output records,
|
|
240
|
+
are saved incrementally in the existing SQLite database (`wandb_sources`,
|
|
241
|
+
`wandb_records`, and `wandb_gpu_latest`). Incomplete trailing records are retried
|
|
242
|
+
on the next scan. Original files are read only. This is not a W&B artifact or
|
|
243
|
+
media archive; capture requires the log files to remain accessible to the server.
|
|
244
|
+
Set `REXS_CAPTURE_WANDB=0` before starting the server to disable collection
|
|
245
|
+
without deleting saved samples. Source errors do not interrupt jobs.
|
|
246
|
+
|
|
247
|
+
## Documentation
|
|
248
|
+
|
|
249
|
+
The full documentation lives in [`docs/`](docs/index.md) and is published with
|
|
250
|
+
GitHub Pages at **https://goncalorafaria.github.io/rexs/**.
|
|
251
|
+
|
|
252
|
+
To preview it locally:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
pip install -e '.[docs]'
|
|
256
|
+
mkdocs serve
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Development
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
pip install -e '.[dev,docs]'
|
|
263
|
+
pytest -q
|
|
264
|
+
ruff check src tests
|
|
265
|
+
mkdocs build --strict
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
REXS is a clean-slate implementation inspired by
|
|
269
|
+
[`slurmcompose`](https://github.com/goncalorafaria/slurmcompose), scoped around
|
|
270
|
+
reproducible Beaker-to-Slurm execution rather than a fixed service composition.
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
[MIT](LICENSE)
|
|
275
|
+
|
|
276
|
+
### GPU isolation with shared CPUs
|
|
277
|
+
|
|
278
|
+
With `shared_cpus_per_node`, CPU-only service steps use `--overlap --gres=none`. GPU steps use `--exclusive --exact --gpus-per-task=N --gpus-per-node=N` and their own CPU count, allowing Slurm to allocate disjoint GPUs to concurrent model replicas. GPU steps must fit together within the shared CPU budget. The container wrapper must preserve Slurm's `CUDA_VISIBLE_DEVICES` through `APPTAINERENV_CUDA_VISIBLE_DEVICES` when using `--cleanenv`.
|
rexs-0.1.0/README.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="src/rexs/static/rexs-logo.png" width="160" alt="REXS green T-rex badge">
|
|
3
|
+
<h1>REXS</h1>
|
|
4
|
+
<p><strong>Reproducible Experiments, eXecuted on Slurm.</strong></p>
|
|
5
|
+
<p>Run the Beaker experiment configurations you already have on Slurm and Apptainer.</p>
|
|
6
|
+
|
|
7
|
+
[](https://github.com/goncalorafaria/rexs/actions/workflows/ci.yml)
|
|
8
|
+
[](https://goncalorafaria.github.io/rexs/)
|
|
9
|
+
[](https://www.python.org/)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
REXS is a small compatibility layer between Beaker v2 experiment files and a
|
|
14
|
+
Slurm cluster. It compiles each experiment into an auditable `sbatch` script,
|
|
15
|
+
runs every task replica as an exclusive `srun` step inside Apptainer, and uses
|
|
16
|
+
Slurm itself as the execution engine. A lightweight SQLite controller records
|
|
17
|
+
the submitted configuration, job state, task replicas, logs, and lifecycle
|
|
18
|
+
events.
|
|
19
|
+
|
|
20
|
+
It focuses deliberately on the Beaker features exercised by `datadev` and
|
|
21
|
+
LiteRegistry. Unsupported fields produce warnings; `--strict` promotes any
|
|
22
|
+
lossy translation to an error before submission.
|
|
23
|
+
|
|
24
|
+
## How it works
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
flowchart LR
|
|
28
|
+
A[Beaker v2<br>YAML / JSON] --> B[REXS compiler]
|
|
29
|
+
P[Site profile<br>images · mounts · Slurm] --> B
|
|
30
|
+
B --> C[Auditable<br>sbatch script]
|
|
31
|
+
C --> D[Slurm allocation]
|
|
32
|
+
D --> E1[srun task 0<br>Apptainer]
|
|
33
|
+
D --> E2[srun task 1<br>Apptainer]
|
|
34
|
+
D --> EN[srun task N<br>Apptainer]
|
|
35
|
+
D --> S[(SQLite state)]
|
|
36
|
+
S --> UI[CLI + web dashboard]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
One Beaker experiment becomes one Slurm allocation. Task replicas are assigned
|
|
40
|
+
to nodes deterministically, receive Beaker-compatible replica environment
|
|
41
|
+
variables, share the host network, and write individual logs and result trees.
|
|
42
|
+
If one replica fails, REXS terminates its siblings and exits the allocation
|
|
43
|
+
with a failure.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/goncalorafaria/rexs.git
|
|
49
|
+
cd rexs
|
|
50
|
+
python -m venv .venv
|
|
51
|
+
. .venv/bin/activate
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The submission host needs `sbatch`, `squeue`, `sacct`, and `scancel`. Compute
|
|
56
|
+
nodes need `srun`, `scontrol`, and `apptainer`.
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
Copy the sample profile and adapt it to your Slurm site:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cp examples/profile.yaml profile.yaml
|
|
64
|
+
$EDITOR profile.yaml
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Validate and render without touching Slurm:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
rexs validate experiment.yaml --profile profile.yaml
|
|
71
|
+
rexs render experiment.yaml \
|
|
72
|
+
--profile profile.yaml \
|
|
73
|
+
--output experiment.sbatch
|
|
74
|
+
bash -n experiment.sbatch
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Submit and inspect the run:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
rexs submit experiment.yaml --profile profile.yaml --name my-run --strict
|
|
81
|
+
rexs experiments # queued and running only
|
|
82
|
+
rexs experiments --all # include finished experiments
|
|
83
|
+
rexs status 123456
|
|
84
|
+
rexs logs 123456 --task=trainer --replica=0
|
|
85
|
+
rexs cancel 123456
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Start the dashboard in the background:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
rexs server --host=127.0.0.1 --port=8765 --daemon
|
|
92
|
+
# Open http://127.0.0.1:8765
|
|
93
|
+
rexs server_stop
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The dashboard and all API routes require HTTP Basic authentication by default.
|
|
97
|
+
Use username `rexs`. On first start, REXS generates a unique password and saves it
|
|
98
|
+
with owner-only permissions in `~/.local/state/rexs/server.password` (beside a
|
|
99
|
+
custom state database when `--db` is used). Read it with:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cat ~/.local/state/rexs/server.password
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The saved password persists across restarts. Set `REXS_SERVER_PASSWORD` in the
|
|
106
|
+
server's environment to override it; empty passwords are rejected. The password
|
|
107
|
+
is never included in server URLs, process arguments, or logs. After changing a
|
|
108
|
+
password, restart the server and sign in again. Authenticated API POST requests
|
|
109
|
+
must also send `X-REXS-Request: 1`; the dashboard adds this automatically.
|
|
110
|
+
Keep the loopback binding and access it through SSH port forwarding. Basic
|
|
111
|
+
authentication should use HTTPS if exposing the server beyond that tunnel.
|
|
112
|
+
|
|
113
|
+
The dashboard shows searchable experiment history, status filters, submitted
|
|
114
|
+
YAML, state transitions, task-level logs, and guarded cancellation.
|
|
115
|
+
|
|
116
|
+
## Site profile
|
|
117
|
+
|
|
118
|
+
Experiment intent remains in the Beaker file. Cluster policy belongs in a
|
|
119
|
+
separate REXS profile:
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
account: research
|
|
123
|
+
partition: gpu
|
|
124
|
+
qos: normal
|
|
125
|
+
time_limit: "24:00:00"
|
|
126
|
+
cpus_per_task: 8
|
|
127
|
+
memory: 64G
|
|
128
|
+
|
|
129
|
+
apptainer_binary: apptainer
|
|
130
|
+
image_cache: /shared/rexs/images
|
|
131
|
+
image_dir: /shared/apptainer/images
|
|
132
|
+
dataset_root: /shared/datasets
|
|
133
|
+
run_root: /shared/rexs/runs
|
|
134
|
+
secret_file: ~/.config/rexs/secrets.env
|
|
135
|
+
|
|
136
|
+
setup_commands:
|
|
137
|
+
- module load apptainer
|
|
138
|
+
|
|
139
|
+
images:
|
|
140
|
+
org/trainer: /shared/apptainer/images/trainer.sif
|
|
141
|
+
org/service: docker://ghcr.io/org/service:latest
|
|
142
|
+
|
|
143
|
+
datasets:
|
|
144
|
+
weka:shared-data: /shared/data
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Docker references are converted to `docker://` URIs automatically. Beaker
|
|
148
|
+
image names should be mapped to an OCI URI or existing SIF. Unmapped resources
|
|
149
|
+
fall back to predictable paths and generate warnings.
|
|
150
|
+
|
|
151
|
+
Secrets are loaded inside the allocation from `secret_file`; their values are
|
|
152
|
+
never written to the generated script or SQLite database.
|
|
153
|
+
|
|
154
|
+
## Supported Beaker subset
|
|
155
|
+
|
|
156
|
+
- Beaker v2 YAML and JSON
|
|
157
|
+
- `image.beaker` and `image.docker`
|
|
158
|
+
- command plus arguments
|
|
159
|
+
- plain and secret-backed environment variables
|
|
160
|
+
- task replicas and leader selection
|
|
161
|
+
- Beaker replica/job/hostname compatibility variables
|
|
162
|
+
- Weka, host-path, and mapped Beaker dataset mounts
|
|
163
|
+
- persistent per-replica result directories
|
|
164
|
+
- GPU, CPU, memory, and shared-memory advisories
|
|
165
|
+
- host-networked multi-service deployments
|
|
166
|
+
- `${VARIABLE}` configuration substitution
|
|
167
|
+
|
|
168
|
+
Workspace, budget, cluster constraints, priority, retry policy, scheduler
|
|
169
|
+
groups, and preemption metadata are not translated. Slurm policy comes only
|
|
170
|
+
from the site profile. See the [compatibility reference](docs/compatibility.md)
|
|
171
|
+
for exact behavior.
|
|
172
|
+
|
|
173
|
+
## Dry-run a repository
|
|
174
|
+
|
|
175
|
+
REXS can audit a directory full of experiment configurations without a Slurm
|
|
176
|
+
installation. It compiles every Beaker v2 YAML/JSON file and checks the output
|
|
177
|
+
with `bash -n`:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
rexs dry_run ~/datadev/beaker_experiments
|
|
181
|
+
rexs dry_run ~/datadev/beaker_experiments --strict --details
|
|
182
|
+
rexs dry_run ~/other/experiments --output_dir=/tmp/rexs-scripts
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
No Slurm command is invoked in dry-run mode.
|
|
186
|
+
|
|
187
|
+
## State and output
|
|
188
|
+
|
|
189
|
+
Global state defaults to `~/.local/state/rexs/state.sqlite3` and can be changed
|
|
190
|
+
with `--db` or `REXS_STATE_DB`. SQLite uses WAL mode so the CLI and background
|
|
191
|
+
server can safely share it.
|
|
192
|
+
|
|
193
|
+
Runtime files are organized as:
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
<run-root>/<slurm-job-id>/
|
|
197
|
+
├── logs/<task>.<replica>.log
|
|
198
|
+
└── results/<task>/<replica>/
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Slurm remains authoritative. The controller reconciles active records through
|
|
202
|
+
`squeue` and completed records through `sacct`.
|
|
203
|
+
|
|
204
|
+
## Optional W&B capture
|
|
205
|
+
|
|
206
|
+
Install `pip install -e '.[wandb]'` to let the REXS server capture local W&B
|
|
207
|
+
logs automatically. Every 30 seconds it scans registered jobs' output directories
|
|
208
|
+
for `.wandb` files, including offline runs. No W&B login or upload is required.
|
|
209
|
+
Jobs without W&B continue normally; this does not change their launch commands.
|
|
210
|
+
|
|
211
|
+
Open a run and select **GPU metrics**, after **History**. Each GPU card plots utilization and allocated memory over time, with hover
|
|
212
|
+
values and the latest sample. Long histories retain extrema when reduced for display.
|
|
213
|
+
Samples remain available after the job finishes. Missing values display a dash;
|
|
214
|
+
stale samples are marked. GPU indices are local to each logger, so separate
|
|
215
|
+
sources may report the same physical devices.
|
|
216
|
+
|
|
217
|
+
Scalar history, summary and system metrics, plus W&B console output records,
|
|
218
|
+
are saved incrementally in the existing SQLite database (`wandb_sources`,
|
|
219
|
+
`wandb_records`, and `wandb_gpu_latest`). Incomplete trailing records are retried
|
|
220
|
+
on the next scan. Original files are read only. This is not a W&B artifact or
|
|
221
|
+
media archive; capture requires the log files to remain accessible to the server.
|
|
222
|
+
Set `REXS_CAPTURE_WANDB=0` before starting the server to disable collection
|
|
223
|
+
without deleting saved samples. Source errors do not interrupt jobs.
|
|
224
|
+
|
|
225
|
+
## Documentation
|
|
226
|
+
|
|
227
|
+
The full documentation lives in [`docs/`](docs/index.md) and is published with
|
|
228
|
+
GitHub Pages at **https://goncalorafaria.github.io/rexs/**.
|
|
229
|
+
|
|
230
|
+
To preview it locally:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
pip install -e '.[docs]'
|
|
234
|
+
mkdocs serve
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Development
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
pip install -e '.[dev,docs]'
|
|
241
|
+
pytest -q
|
|
242
|
+
ruff check src tests
|
|
243
|
+
mkdocs build --strict
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
REXS is a clean-slate implementation inspired by
|
|
247
|
+
[`slurmcompose`](https://github.com/goncalorafaria/slurmcompose), scoped around
|
|
248
|
+
reproducible Beaker-to-Slurm execution rather than a fixed service composition.
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
[MIT](LICENSE)
|
|
253
|
+
|
|
254
|
+
### GPU isolation with shared CPUs
|
|
255
|
+
|
|
256
|
+
With `shared_cpus_per_node`, CPU-only service steps use `--overlap --gres=none`. GPU steps use `--exclusive --exact --gpus-per-task=N --gpus-per-node=N` and their own CPU count, allowing Slurm to allocate disjoint GPUs to concurrent model replicas. GPU steps must fit together within the shared CPU budget. The container wrapper must preserve Slurm's `CUDA_VISIBLE_DEVICES` through `APPTAINERENV_CUDA_VISIBLE_DEVICES` when using `--cleanenv`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rexs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Reproducible Experiments, eXecuted on Slurm"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = ["fire>=0.7", "PyYAML>=6.0"]
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
wandb = ["wandb>=0.19,<0.29"]
|
|
15
|
+
dev = ["pytest>=8.0", "ruff>=0.8", "wandb>=0.19,<0.29"]
|
|
16
|
+
docs = ["mkdocs>=1.6", "mkdocs-material>=9.6"]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://github.com/goncalorafaria/rexs"
|
|
20
|
+
Documentation = "https://goncalorafaria.github.io/rexs/"
|
|
21
|
+
Repository = "https://github.com/goncalorafaria/rexs"
|
|
22
|
+
Issues = "https://github.com/goncalorafaria/rexs/issues"
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
rexs = "rexs.cli:main"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
29
|
+
include = ["rexs*"]
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.package-data]
|
|
32
|
+
rexs = ["static/*.png"]
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 120
|
|
39
|
+
target-version = "py311"
|
rexs-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Beaker-to-Slurm compiler for datadev workloads."""
|
|
2
|
+
|
|
3
|
+
from rexs.compiler import CompileResult, compile_experiment
|
|
4
|
+
from rexs.config import SlurmProfile, load_experiment, load_profile
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"CompileResult",
|
|
8
|
+
"SlurmProfile",
|
|
9
|
+
"compile_experiment",
|
|
10
|
+
"load_experiment",
|
|
11
|
+
"load_profile",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Persistent, per-account credentials for the local dashboard."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import secrets
|
|
6
|
+
import stat
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
USERNAME = 'rexs'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def dashboard_password(state_dir: Path) -> str:
|
|
14
|
+
"""Use an environment override or atomically create a private default password."""
|
|
15
|
+
override = os.environ.get('REXS_SERVER_PASSWORD')
|
|
16
|
+
if override is not None:
|
|
17
|
+
if not override.strip():
|
|
18
|
+
raise ValueError('REXS_SERVER_PASSWORD must not be empty')
|
|
19
|
+
return override
|
|
20
|
+
state_dir.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
path = state_dir / 'server.password'
|
|
22
|
+
# Publish only a fully written file, including when two servers start together.
|
|
23
|
+
if not path.exists():
|
|
24
|
+
temporary = None
|
|
25
|
+
try:
|
|
26
|
+
with tempfile.NamedTemporaryFile(mode='w', dir=state_dir, delete=False) as stream:
|
|
27
|
+
temporary = Path(stream.name)
|
|
28
|
+
os.fchmod(stream.fileno(), 0o600)
|
|
29
|
+
stream.write('rexs-' + secrets.token_urlsafe(18) + '\n')
|
|
30
|
+
stream.flush()
|
|
31
|
+
os.fsync(stream.fileno())
|
|
32
|
+
try:
|
|
33
|
+
os.link(temporary, path)
|
|
34
|
+
except FileExistsError:
|
|
35
|
+
pass
|
|
36
|
+
finally:
|
|
37
|
+
if temporary is not None:
|
|
38
|
+
temporary.unlink(missing_ok=True)
|
|
39
|
+
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
|
40
|
+
with os.fdopen(fd, 'r', encoding='utf-8') as stream:
|
|
41
|
+
info = os.fstat(stream.fileno())
|
|
42
|
+
if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid():
|
|
43
|
+
raise ValueError('dashboard password must be a regular file owned by this user')
|
|
44
|
+
os.fchmod(stream.fileno(), 0o600)
|
|
45
|
+
password = stream.read().rstrip('\r\n')
|
|
46
|
+
if not password.strip():
|
|
47
|
+
raise ValueError(f'dashboard password file is empty: {path}')
|
|
48
|
+
return password
|