dde-cargo 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.
- dde_cargo-0.1.0/.github/workflows/publish_pypi.yaml +29 -0
- dde_cargo-0.1.0/.gitignore +23 -0
- dde_cargo-0.1.0/PKG-INFO +126 -0
- dde_cargo-0.1.0/README.md +115 -0
- dde_cargo-0.1.0/dde_cargo/__init__.py +1 -0
- dde_cargo-0.1.0/dde_cargo/ddfacet-memory-plan.yml +335 -0
- dde_cargo-0.1.0/dde_cargo/ddfacet.yml +881 -0
- dde_cargo-0.1.0/dde_cargo/ddfacet_stimela_inputs_schema.yml +1119 -0
- dde_cargo-0.1.0/dde_cargo/killms-memory-plan.yml +331 -0
- dde_cargo-0.1.0/dde_cargo/killms_stimela_schema.yml +292 -0
- dde_cargo-0.1.0/pyproject.toml +31 -0
- dde_cargo-0.1.0/tests/test_schema.py +225 -0
- dde_cargo-0.1.0/uv.lock +621 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright (c) 2026 Tim Molteno (tim@elec.ac.nz)
|
|
2
|
+
name: Publish dde-cargo
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
tags: ["v*.*.*"]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pypi:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: pypi
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout repository
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v5
|
|
22
|
+
|
|
23
|
+
- name: Build
|
|
24
|
+
run: uv build
|
|
25
|
+
|
|
26
|
+
- name: Publish to PyPI
|
|
27
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
28
|
+
with:
|
|
29
|
+
packages-dir: dist
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.venv/
|
|
6
|
+
venv/
|
|
7
|
+
|
|
8
|
+
# Build artifacts
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
|
|
12
|
+
# Tooling caches
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
uv.lock.bak
|
|
16
|
+
|
|
17
|
+
# Editors
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
*.swp
|
|
21
|
+
|
|
22
|
+
# Session tooling
|
|
23
|
+
.zcode/
|
dde_cargo-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dde-cargo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Stimela cab definitions for DDFacet and killMS
|
|
5
|
+
Author-email: Tim Molteno <tim@elec.ac.nz>
|
|
6
|
+
License: GPLv3
|
|
7
|
+
Requires-Python: <3.14,>=3.10
|
|
8
|
+
Requires-Dist: pyparsing<3.3.3
|
|
9
|
+
Requires-Dist: stimela>=2.1.4
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# dde-cargo
|
|
13
|
+
Stimela cabs for DDFacet/Killms on both arm64 and x86-64 systems.
|
|
14
|
+
|
|
15
|
+
The cab YAMLs in `dde_cargo/` are verbatim copies of `meerkat_imaging/cabs/`
|
|
16
|
+
(`ddfacet.yml`, its two `_include`d parameter schemas, and the two
|
|
17
|
+
memory-plan cabs). A change to a cab starts in that repository; copy it here,
|
|
18
|
+
bump the version, and tag.
|
|
19
|
+
|
|
20
|
+
## Cabs and images
|
|
21
|
+
|
|
22
|
+
| Cab | Image |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `ddfacet` | `ghcr.io/tmolteno/ddfacet:v0.16.1` |
|
|
25
|
+
| `killms` | `ghcr.io/tmolteno/killms:v3.15.3` |
|
|
26
|
+
| `ddfacet-memory-plan` | `ghcr.io/tmolteno/ddfacet:v0.16.1` |
|
|
27
|
+
| `killms-memory-plan` | `ghcr.io/tmolteno/killms:v3.15.3` |
|
|
28
|
+
| `ddf_makemodel`, `ddf_makemask`, `dilatemask`, `clustercat`, `ddfacet-get-npix`, `bdsf.catalog`, `modelcat`, `autofacet`, `snapnodes` | `ghcr.io/tmolteno/ddfacet:v0.16.1` |
|
|
29
|
+
|
|
30
|
+
All images are published for amd64 and arm64.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
pip install dde-cargo
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires Python >= 3.10,< 3.14 (the same window as `skarabina-cargo`) and
|
|
39
|
+
installs `stimela>=2.1.4` — every cab runs in its container, so the host needs
|
|
40
|
+
nothing else.
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
In a stimela 2 recipe, load the cabs from the installed package with
|
|
45
|
+
configuratt's package-include syntax. The `cultcargo` include must come first:
|
|
46
|
+
`ddfacet.yml` carries an image-only override for cult-cargo's `bdsf.catalog`
|
|
47
|
+
cab (the upstream image is amd64-only; these cabs redirect it to the
|
|
48
|
+
multi-arch ddfacet image), and stimela builds every cab at load time, so
|
|
49
|
+
`bdsf.yml` must already be loaded when `ddfacet.yml` merges in:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
_include:
|
|
53
|
+
- (cultcargo):
|
|
54
|
+
- bdsf.yml
|
|
55
|
+
- (dde_cargo):
|
|
56
|
+
- ddfacet.yml
|
|
57
|
+
- ddfacet-memory-plan.yml
|
|
58
|
+
- killms-memory-plan.yml
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
then reference them from steps:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
steps:
|
|
65
|
+
ddfacet-target:
|
|
66
|
+
cab: ddfacet
|
|
67
|
+
params:
|
|
68
|
+
Data.MS: mydata.ms
|
|
69
|
+
Output.Name: target0
|
|
70
|
+
killms-target:
|
|
71
|
+
cab: killms
|
|
72
|
+
params:
|
|
73
|
+
MSName: mydata.ms
|
|
74
|
+
InCol: CORRECTED_DATA
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Only `ddfacet.yml` is essential; the memory-plan files are optional extras
|
|
78
|
+
(wire them ahead of a step as shown in their header comments — e.g.
|
|
79
|
+
`ddfacet-memory-plan` suggests `Parallel.NCPU`, `Data.ChunkHours` and
|
|
80
|
+
`Selection.ChanStep` for the imaging step that follows).
|
|
81
|
+
|
|
82
|
+
Notes carried over from the upstream cab comments:
|
|
83
|
+
|
|
84
|
+
* The `autofacet` cab imports `auto_facet_tool/auto_facet.py` from the
|
|
85
|
+
*working directory*, so it needs a checkout of `meerkat_imaging` beside the
|
|
86
|
+
recipe (stimela binds the working directory into the container).
|
|
87
|
+
* The memory-plan cabs pin their image "to whatever the recipe uses": a recipe
|
|
88
|
+
with its own image var should override `image:` on the cab instead of
|
|
89
|
+
editing the package.
|
|
90
|
+
* The DDFacet cab wraps every parset namespace (`Data`, `Image`, `Facets`,
|
|
91
|
+
`Deconv`, ...) as dashed CLI options (`--Data-MS`, `--Image-NPix`, ...);
|
|
92
|
+
the killMS cab passes its flat parameter set through `kMS.py`.
|
|
93
|
+
|
|
94
|
+
## Publishing
|
|
95
|
+
|
|
96
|
+
Trusted publishing (OIDC) — there is no API token. The GitHub Actions
|
|
97
|
+
workflow `.github/workflows/publish_pypi.yaml` fires on a `vX.Y.Z` tag, builds
|
|
98
|
+
with `uv`, and publishes from the `pypi` environment.
|
|
99
|
+
|
|
100
|
+
1. Register the pending publisher on PyPI (once):
|
|
101
|
+
[pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/)
|
|
102
|
+
with owner `tmolteno`, repository `dde-cargo`, workflow `publish_pypi.yaml`,
|
|
103
|
+
environment `pypi`. The workflow filename must match exactly.
|
|
104
|
+
2. Bump `version` in `pyproject.toml`.
|
|
105
|
+
3. `git commit -m "..." && git tag v0.1.0 && git push origin main --follow-tags`
|
|
106
|
+
|
|
107
|
+
The workflow run appears under the repo's Actions with the `pypi` environment;
|
|
108
|
+
the publish is live at [pypi.org/project/dde-cargo](https://pypi.org/project/dde-cargo/).
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
uv sync --all-groups
|
|
114
|
+
uv run pytest # schema contracts for the packaged cabs
|
|
115
|
+
uv build # wheel + sdist into dist/
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
An end-to-end smoke check (loads the cabs through stimela's real spec loader,
|
|
119
|
+
as a recipe would):
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
uv run stimela run /tmp/dde-cargo-smoke.yml dde-cargo-smoke -d
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
with `/tmp/dde-cargo-smoke.yml` any recipe that `_include`s the package as
|
|
126
|
+
shown above and runs one or two of the cabs.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# dde-cargo
|
|
2
|
+
Stimela cabs for DDFacet/Killms on both arm64 and x86-64 systems.
|
|
3
|
+
|
|
4
|
+
The cab YAMLs in `dde_cargo/` are verbatim copies of `meerkat_imaging/cabs/`
|
|
5
|
+
(`ddfacet.yml`, its two `_include`d parameter schemas, and the two
|
|
6
|
+
memory-plan cabs). A change to a cab starts in that repository; copy it here,
|
|
7
|
+
bump the version, and tag.
|
|
8
|
+
|
|
9
|
+
## Cabs and images
|
|
10
|
+
|
|
11
|
+
| Cab | Image |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `ddfacet` | `ghcr.io/tmolteno/ddfacet:v0.16.1` |
|
|
14
|
+
| `killms` | `ghcr.io/tmolteno/killms:v3.15.3` |
|
|
15
|
+
| `ddfacet-memory-plan` | `ghcr.io/tmolteno/ddfacet:v0.16.1` |
|
|
16
|
+
| `killms-memory-plan` | `ghcr.io/tmolteno/killms:v3.15.3` |
|
|
17
|
+
| `ddf_makemodel`, `ddf_makemask`, `dilatemask`, `clustercat`, `ddfacet-get-npix`, `bdsf.catalog`, `modelcat`, `autofacet`, `snapnodes` | `ghcr.io/tmolteno/ddfacet:v0.16.1` |
|
|
18
|
+
|
|
19
|
+
All images are published for amd64 and arm64.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pip install dde-cargo
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Python >= 3.10,< 3.14 (the same window as `skarabina-cargo`) and
|
|
28
|
+
installs `stimela>=2.1.4` — every cab runs in its container, so the host needs
|
|
29
|
+
nothing else.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
In a stimela 2 recipe, load the cabs from the installed package with
|
|
34
|
+
configuratt's package-include syntax. The `cultcargo` include must come first:
|
|
35
|
+
`ddfacet.yml` carries an image-only override for cult-cargo's `bdsf.catalog`
|
|
36
|
+
cab (the upstream image is amd64-only; these cabs redirect it to the
|
|
37
|
+
multi-arch ddfacet image), and stimela builds every cab at load time, so
|
|
38
|
+
`bdsf.yml` must already be loaded when `ddfacet.yml` merges in:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
_include:
|
|
42
|
+
- (cultcargo):
|
|
43
|
+
- bdsf.yml
|
|
44
|
+
- (dde_cargo):
|
|
45
|
+
- ddfacet.yml
|
|
46
|
+
- ddfacet-memory-plan.yml
|
|
47
|
+
- killms-memory-plan.yml
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
then reference them from steps:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
steps:
|
|
54
|
+
ddfacet-target:
|
|
55
|
+
cab: ddfacet
|
|
56
|
+
params:
|
|
57
|
+
Data.MS: mydata.ms
|
|
58
|
+
Output.Name: target0
|
|
59
|
+
killms-target:
|
|
60
|
+
cab: killms
|
|
61
|
+
params:
|
|
62
|
+
MSName: mydata.ms
|
|
63
|
+
InCol: CORRECTED_DATA
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Only `ddfacet.yml` is essential; the memory-plan files are optional extras
|
|
67
|
+
(wire them ahead of a step as shown in their header comments — e.g.
|
|
68
|
+
`ddfacet-memory-plan` suggests `Parallel.NCPU`, `Data.ChunkHours` and
|
|
69
|
+
`Selection.ChanStep` for the imaging step that follows).
|
|
70
|
+
|
|
71
|
+
Notes carried over from the upstream cab comments:
|
|
72
|
+
|
|
73
|
+
* The `autofacet` cab imports `auto_facet_tool/auto_facet.py` from the
|
|
74
|
+
*working directory*, so it needs a checkout of `meerkat_imaging` beside the
|
|
75
|
+
recipe (stimela binds the working directory into the container).
|
|
76
|
+
* The memory-plan cabs pin their image "to whatever the recipe uses": a recipe
|
|
77
|
+
with its own image var should override `image:` on the cab instead of
|
|
78
|
+
editing the package.
|
|
79
|
+
* The DDFacet cab wraps every parset namespace (`Data`, `Image`, `Facets`,
|
|
80
|
+
`Deconv`, ...) as dashed CLI options (`--Data-MS`, `--Image-NPix`, ...);
|
|
81
|
+
the killMS cab passes its flat parameter set through `kMS.py`.
|
|
82
|
+
|
|
83
|
+
## Publishing
|
|
84
|
+
|
|
85
|
+
Trusted publishing (OIDC) — there is no API token. The GitHub Actions
|
|
86
|
+
workflow `.github/workflows/publish_pypi.yaml` fires on a `vX.Y.Z` tag, builds
|
|
87
|
+
with `uv`, and publishes from the `pypi` environment.
|
|
88
|
+
|
|
89
|
+
1. Register the pending publisher on PyPI (once):
|
|
90
|
+
[pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/)
|
|
91
|
+
with owner `tmolteno`, repository `dde-cargo`, workflow `publish_pypi.yaml`,
|
|
92
|
+
environment `pypi`. The workflow filename must match exactly.
|
|
93
|
+
2. Bump `version` in `pyproject.toml`.
|
|
94
|
+
3. `git commit -m "..." && git tag v0.1.0 && git push origin main --follow-tags`
|
|
95
|
+
|
|
96
|
+
The workflow run appears under the repo's Actions with the `pypi` environment;
|
|
97
|
+
the publish is live at [pypi.org/project/dde-cargo](https://pypi.org/project/dde-cargo/).
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
uv sync --all-groups
|
|
103
|
+
uv run pytest # schema contracts for the packaged cabs
|
|
104
|
+
uv build # wheel + sdist into dist/
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
An end-to-end smoke check (loads the cabs through stimela's real spec loader,
|
|
108
|
+
as a recipe would):
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
uv run stimela run /tmp/dde-cargo-smoke.yml dde-cargo-smoke -d
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
with `/tmp/dde-cargo-smoke.yml` any recipe that `_include`s the package as
|
|
115
|
+
shown above and runs one or two of the cabs.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Copyright (c) 2026 Tim Molteno (tim@elec.ac.nz)
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Copied verbatim from the installed package in ddfacet:v0.10.0.6
|
|
2
|
+
# (/opt/venv/lib/python3.13/site-packages/DDFacet/stimela/ddfacet-memory-plan.yml):
|
|
3
|
+
# v0.10.0.6 ships both the cab and the module, so nothing is vendored any more.
|
|
4
|
+
# The image pin follows the ddfacet pin (v0.12.1 since 2026-09-25): the planner is
|
|
5
|
+
# exercised on the same line the imaging runs, so its memory model matches.
|
|
6
|
+
# stimela cab: ddfacet-memory-plan
|
|
7
|
+
#
|
|
8
|
+
# Suggests --Parallel-NCPU, --Data-ChunkHours and (only when the band has to
|
|
9
|
+
# give) --Selection-ChanStep for a DDFacet run, from the parset that run will
|
|
10
|
+
# use and a memory budget. This is the command-line form of
|
|
11
|
+
# DDFacet/ToolsDir/ModEstimateMemory.py, and it ships with the package so a
|
|
12
|
+
# recipe can list it directly:
|
|
13
|
+
#
|
|
14
|
+
# - /opt/venv/lib/python3.13/site-packages/DDFacet/stimela/ddfacet-memory-plan.yml
|
|
15
|
+
#
|
|
16
|
+
# or copy the cab into the recipe's own cabs/ directory (that is what
|
|
17
|
+
# meerkat_imaging does with the other DDFacet cabs).
|
|
18
|
+
#
|
|
19
|
+
# Wiring it into a recipe, ahead of the imaging step:
|
|
20
|
+
#
|
|
21
|
+
# memory-plan:
|
|
22
|
+
# cab: ddfacet-memory-plan
|
|
23
|
+
# params:
|
|
24
|
+
# Parset: =steps.<something>.parset_out
|
|
25
|
+
# MS: =recipe.target-ms
|
|
26
|
+
# RamGB: 62
|
|
27
|
+
# ddfacet-target:
|
|
28
|
+
# params:
|
|
29
|
+
# Parallel.NCPU: =steps.memory-plan.NCPU
|
|
30
|
+
# Data.ChunkHours: =steps.memory-plan.ChunkHours
|
|
31
|
+
# Selection.ChanStep: =steps.memory-plan.ChanStep
|
|
32
|
+
#
|
|
33
|
+
# The estimate is +-40 % (see MANAGING_LIMITED_MEMORY.md): use it to choose
|
|
34
|
+
# between "NCPU 3" and "NCPU 8", not to plan within a gigabyte of the limit.
|
|
35
|
+
#
|
|
36
|
+
# THREADED (the rayon line, DDFacet >= v0.12): NCPU IS NOT A MEMORY KNOB ANY MORE.
|
|
37
|
+
# ModEstimateMemory's model is `main + NCPU x per-worker + chunk buffers`,
|
|
38
|
+
# calibrated on the process pool, where every worker held its own copies. On the
|
|
39
|
+
# rayon line the workers are threads in one process, and a sweep of the same
|
|
40
|
+
# dirty image on moist (8 cores, uv1500 fast profile, target 1, 2026-09-27)
|
|
41
|
+
# measured:
|
|
42
|
+
#
|
|
43
|
+
# NCPU ChunkHours wall CPU largest process
|
|
44
|
+
# 2 2.0 3:35 115% 13.8 GB
|
|
45
|
+
# 4 2.0 1:49 205% 13.0 GB
|
|
46
|
+
# 8 2.0 1:11 422% 13.3 GB
|
|
47
|
+
# 2 0.5 3:37 115% 13.7 GB
|
|
48
|
+
# 8 0.5 1:11 423% 13.3 GB
|
|
49
|
+
#
|
|
50
|
+
# i.e. flat in NCPU, 3x faster with every core -- while the model predicts
|
|
51
|
+
# 14.1 GB at NCPU 1 (right) and 2.18 GB per extra worker (wrong: 28.6 GB at 8),
|
|
52
|
+
# so with an 18 GB budget the unmodified planner answered `NCPU 1`. With
|
|
53
|
+
# `Threaded: true` (the default) the memory is therefore planned at ONE worker --
|
|
54
|
+
# ChunkHours and, if the band must give, ChanStep -- and NCPU is every usable
|
|
55
|
+
# core (or WantNCPU). `Threaded: false` restores the process-pool behaviour.
|
|
56
|
+
#
|
|
57
|
+
# RamGB and WantNCPU default to what the host actually offers when the step runs
|
|
58
|
+
# (MemAvailable, capped by a cgroup memory limit if there is one; the CPU
|
|
59
|
+
# affinity set), so the same recipe plans correctly on a 62 GB/12-core run box
|
|
60
|
+
# and on a 30 GB/8-core desktop. MemWatermarkGB is an output (15 % of the
|
|
61
|
+
# budget): wire it to --Parallel-MemWatermarkGB instead of a fixed value, which
|
|
62
|
+
# deadlocks a machine with less than that free (the pool workers pause for
|
|
63
|
+
# memory the parent holds while it waits for them).
|
|
64
|
+
#
|
|
65
|
+
# Bootstrapping, since a step cannot be planned from the parset it is about to
|
|
66
|
+
# write: plan each step from the parset of the *previous* run of that step (the
|
|
67
|
+
# pipeline's pattern), and for a fresh observation -- where no earlier parset
|
|
68
|
+
# exists -- pass the geometry instead of a parset (NPix, and optionally
|
|
69
|
+
# NFacets/NBand/OverS/Support/Nw/ChunkHoursIn). PlanFile is written by the
|
|
70
|
+
# command, so it is declared `must_exist: false`: a File input that stimela
|
|
71
|
+
# checks for existence would fail the step before the planner ran (DDFacet#17).
|
|
72
|
+
|
|
73
|
+
vars:
|
|
74
|
+
ddfacet-memory-plan:
|
|
75
|
+
image:
|
|
76
|
+
registry: ghcr.io/tmolteno
|
|
77
|
+
name: ddfacet
|
|
78
|
+
# Pin this to whatever the recipe uses; a recipe with its own image var
|
|
79
|
+
# should override `image:` on the cab instead.
|
|
80
|
+
version: v0.16.1
|
|
81
|
+
|
|
82
|
+
cabs:
|
|
83
|
+
ddfacet-memory-plan:
|
|
84
|
+
info: |
|
|
85
|
+
Suggest --Parallel-NCPU, --Data-ChunkHours and (only if the band has to
|
|
86
|
+
give) --Selection-ChanStep for a DDFacet run, from its parset and a
|
|
87
|
+
memory budget. Reads the MS for its row/channel/antenna counts when
|
|
88
|
+
one is given; pass NRows/NChan/NA instead when it cannot be read.
|
|
89
|
+
Needs an image carrying DDFacet/ToolsDir/ModEstimateMemory.py.
|
|
90
|
+
image:
|
|
91
|
+
_use: vars.ddfacet-memory-plan.image
|
|
92
|
+
inputs:
|
|
93
|
+
Parset:
|
|
94
|
+
dtype: File
|
|
95
|
+
required: false
|
|
96
|
+
info: |
|
|
97
|
+
the parset whose geometry is being planned (e.g. the parset_out
|
|
98
|
+
of a ddfacet step). Optional: with no parset, the geometry is
|
|
99
|
+
taken from the inputs below, which is the bootstrap case for a
|
|
100
|
+
fresh observation that has no earlier parset to plan from.
|
|
101
|
+
MS:
|
|
102
|
+
dtype: MS
|
|
103
|
+
required: false
|
|
104
|
+
info: measurement set to size the estimate with
|
|
105
|
+
RamGB:
|
|
106
|
+
dtype: float
|
|
107
|
+
required: false
|
|
108
|
+
info: >
|
|
109
|
+
memory the run may use. Default: what the host has available
|
|
110
|
+
when the step runs -- MemAvailable, capped by the cgroup memory
|
|
111
|
+
limit if there is one (a container or systemd scope)
|
|
112
|
+
NPix:
|
|
113
|
+
dtype: int
|
|
114
|
+
required: false
|
|
115
|
+
info: image size, when there is no Parset to read it from
|
|
116
|
+
NFacets:
|
|
117
|
+
dtype: int
|
|
118
|
+
required: false
|
|
119
|
+
NBand:
|
|
120
|
+
dtype: int
|
|
121
|
+
required: false
|
|
122
|
+
OverS:
|
|
123
|
+
dtype: int
|
|
124
|
+
required: false
|
|
125
|
+
Support:
|
|
126
|
+
dtype: int
|
|
127
|
+
required: false
|
|
128
|
+
Nw:
|
|
129
|
+
dtype: int
|
|
130
|
+
required: false
|
|
131
|
+
ChunkHoursIn:
|
|
132
|
+
dtype: float
|
|
133
|
+
required: false
|
|
134
|
+
info: the chunk size you intend, when planning without a Parset
|
|
135
|
+
default: 0.5
|
|
136
|
+
WantNCPU:
|
|
137
|
+
dtype: int
|
|
138
|
+
required: false
|
|
139
|
+
info: >
|
|
140
|
+
worker count you would like; also the cap. Default: the usable
|
|
141
|
+
cores (the CPU affinity set, so a taskset/cpuset is respected)
|
|
142
|
+
Threaded:
|
|
143
|
+
dtype: bool
|
|
144
|
+
default: true
|
|
145
|
+
info: >
|
|
146
|
+
the rayon line (DDFacet >= v0.12), where workers are threads in one
|
|
147
|
+
process and NCPU costs no measurable memory: plan the memory at one
|
|
148
|
+
worker and return every usable core as NCPU. false = the process
|
|
149
|
+
pool, where each worker costs memory (the model's original form)
|
|
150
|
+
Prefer:
|
|
151
|
+
dtype: str
|
|
152
|
+
default: fidelity
|
|
153
|
+
info: "'fidelity' keeps the full band while any worker count fits; 'speed' takes the workers first and subsamples the band if it must"
|
|
154
|
+
NDirections:
|
|
155
|
+
dtype: int
|
|
156
|
+
default: 1
|
|
157
|
+
NCorr:
|
|
158
|
+
dtype: int
|
|
159
|
+
default: 2
|
|
160
|
+
NRows:
|
|
161
|
+
dtype: int
|
|
162
|
+
required: false
|
|
163
|
+
NChan:
|
|
164
|
+
dtype: int
|
|
165
|
+
required: false
|
|
166
|
+
NA:
|
|
167
|
+
dtype: int
|
|
168
|
+
required: false
|
|
169
|
+
PlanOut:
|
|
170
|
+
dtype: File
|
|
171
|
+
default: ddfacet-memory-plan.json
|
|
172
|
+
# the command *writes* this file, so stimela must not require it
|
|
173
|
+
# to exist before the step starts (DDFacet#17)
|
|
174
|
+
must_exist: false
|
|
175
|
+
info: where to write the plan, as JSON
|
|
176
|
+
outputs:
|
|
177
|
+
NCPU:
|
|
178
|
+
dtype: int
|
|
179
|
+
info: pass to --Parallel-NCPU
|
|
180
|
+
ChunkHours:
|
|
181
|
+
dtype: float
|
|
182
|
+
info: pass to --Data-ChunkHours
|
|
183
|
+
ChanStep:
|
|
184
|
+
dtype: int
|
|
185
|
+
info: pass to --Selection-ChanStep
|
|
186
|
+
MemWatermarkGB:
|
|
187
|
+
dtype: float
|
|
188
|
+
info: >
|
|
189
|
+
pass to --Parallel-MemWatermarkGB: 15 % of the budget, so the
|
|
190
|
+
throttle scales with the machine instead of a fixed floor that
|
|
191
|
+
can exceed what is free (and then deadlocks)
|
|
192
|
+
RamGBUsed:
|
|
193
|
+
dtype: float
|
|
194
|
+
info: the budget the plan was made for (RamGB, or what the host offered)
|
|
195
|
+
PeakGB:
|
|
196
|
+
dtype: float
|
|
197
|
+
info: predicted peak as per-process accounting sees it (RSS sums / stimela's Mem GB)
|
|
198
|
+
HostRAMGB:
|
|
199
|
+
dtype: float
|
|
200
|
+
info: predicted peak as `free -g` sees it -- the shared arrays count as RAM only when /dev/shm is a tmpfs
|
|
201
|
+
Flags:
|
|
202
|
+
dtype: str
|
|
203
|
+
info: the three flags, ready to paste
|
|
204
|
+
Plan:
|
|
205
|
+
dtype: File
|
|
206
|
+
must_exist: false
|
|
207
|
+
info: the JSON plan (chosen settings, every combination that fits, notes)
|
|
208
|
+
flavour:
|
|
209
|
+
kind: python-code
|
|
210
|
+
command: |
|
|
211
|
+
import json
|
|
212
|
+
import math
|
|
213
|
+
from DDFacet.ToolsDir.ModEstimateMemory import suggestParameters
|
|
214
|
+
|
|
215
|
+
# Optional inputs may arrive unset, in which case stimela does not
|
|
216
|
+
# necessarily define the name at all -- look them up defensively.
|
|
217
|
+
_g = globals()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _opt(name, default=None):
|
|
221
|
+
value = _g.get(name, default)
|
|
222
|
+
return default if value is None else value
|
|
223
|
+
|
|
224
|
+
# No Parset? Build the geometry from the inputs instead, so a fresh
|
|
225
|
+
# observation can be planned before any parset exists (DDFacet#17).
|
|
226
|
+
_geometry = {}
|
|
227
|
+
for _key in ("NPix", "NFacets", "NBand", "OverS", "Support", "Nw"):
|
|
228
|
+
_value = _opt(_key)
|
|
229
|
+
if _value is not None:
|
|
230
|
+
_geometry[_key] = _value
|
|
231
|
+
if _opt("Parset") is None and not _geometry:
|
|
232
|
+
raise RuntimeError(
|
|
233
|
+
"ddfacet-memory-plan: give either Parset or the geometry "
|
|
234
|
+
"(NPix, and optionally NFacets/NBand/OverS/Support/Nw)")
|
|
235
|
+
_pardict = None
|
|
236
|
+
if _opt("Parset") is None:
|
|
237
|
+
_pardict = {
|
|
238
|
+
"Image": {"NPix": _geometry.get("NPix", 5000)},
|
|
239
|
+
"Facets": {"NFacets": _geometry.get("NFacets", 3), "Padding": 1.7},
|
|
240
|
+
"Freq": {"NBand": _geometry.get("NBand", 1)},
|
|
241
|
+
"Output": {"NGridPol": 1},
|
|
242
|
+
"CF": {"OverS": _geometry.get("OverS", 11),
|
|
243
|
+
"Support": _geometry.get("Support", 7),
|
|
244
|
+
"Nw": _geometry.get("Nw", 100)},
|
|
245
|
+
"Data": {"ChunkHours": _opt("ChunkHoursIn", 0.5)},
|
|
246
|
+
"Parallel": {"NCPU": 1},
|
|
247
|
+
}
|
|
248
|
+
print("no Parset given: planning from the inputs (NPix %d, %d "
|
|
249
|
+
"facet(s), %d band(s))" % (_pardict["Image"]["NPix"],
|
|
250
|
+
_pardict["Facets"]["NFacets"],
|
|
251
|
+
_pardict["Freq"]["NBand"]))
|
|
252
|
+
|
|
253
|
+
import os
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _usable_cores():
|
|
257
|
+
try:
|
|
258
|
+
return len(os.sched_getaffinity(0))
|
|
259
|
+
except AttributeError:
|
|
260
|
+
return os.cpu_count() or 1
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _available_gb():
|
|
264
|
+
"""MemAvailable, capped by the cgroup (v2) limit: what this run can use."""
|
|
265
|
+
avail = None
|
|
266
|
+
with open("/proc/meminfo") as _fh:
|
|
267
|
+
for _line in _fh:
|
|
268
|
+
if _line.startswith("MemAvailable:"):
|
|
269
|
+
avail = int(_line.split()[1]) * 1024
|
|
270
|
+
try:
|
|
271
|
+
_cg = open("/proc/self/cgroup").read().strip().split("::", 1)[-1]
|
|
272
|
+
_dir = "/sys/fs/cgroup" + _cg
|
|
273
|
+
_max = open(_dir + "/memory.max").read().strip()
|
|
274
|
+
if _max != "max":
|
|
275
|
+
_free = int(_max) - int(open(_dir + "/memory.current").read())
|
|
276
|
+
avail = _free if avail is None else min(avail, _free)
|
|
277
|
+
except (OSError, ValueError):
|
|
278
|
+
pass
|
|
279
|
+
return avail / float(1 << 30)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
ram_gb = _opt("RamGB")
|
|
283
|
+
ram_source = "RamGB"
|
|
284
|
+
if ram_gb is None:
|
|
285
|
+
ram_gb = _available_gb()
|
|
286
|
+
ram_source = "available on this host"
|
|
287
|
+
cores = int(_opt("WantNCPU") or _usable_cores())
|
|
288
|
+
threaded = bool(_opt("Threaded", True))
|
|
289
|
+
print("budget %.1f GB (%s); %d core(s)%s; %s model"
|
|
290
|
+
% (ram_gb, ram_source, cores,
|
|
291
|
+
"" if _opt("WantNCPU") else " usable",
|
|
292
|
+
"threaded (memory planned at one worker)" if threaded
|
|
293
|
+
else "process-pool"))
|
|
294
|
+
|
|
295
|
+
# Threaded: the memory does not move with NCPU (measured flat, header),
|
|
296
|
+
# so the feasibility search runs at one worker and NCPU is every core.
|
|
297
|
+
suggestion = suggestParameters(
|
|
298
|
+
GD=_pardict, parset=_opt("Parset"), ms=_opt("MS"), ram_gb=ram_gb,
|
|
299
|
+
ncpu=1 if threaded else cores, max_ncpu=1 if threaded else cores,
|
|
300
|
+
prefer=_opt("Prefer", "fidelity"),
|
|
301
|
+
ndirections=_opt("NDirections", 1), ncorr=_opt("NCorr", 2),
|
|
302
|
+
nrows=_opt("NRows"), nchan=_opt("NChan"), na=_opt("NA"))
|
|
303
|
+
if threaded:
|
|
304
|
+
print("the memory model's own summary, at ONE worker (its NCPU line is "
|
|
305
|
+
"not the plan -- see 'plan:' below):")
|
|
306
|
+
print(suggestion)
|
|
307
|
+
|
|
308
|
+
NCPU = cores if threaded else int(suggestion.best["ncpu"])
|
|
309
|
+
ChunkHours = float(suggestion.best["chunk_hours"])
|
|
310
|
+
ChanStep = int(suggestion.best["chan_step"])
|
|
311
|
+
peak = float(suggestion.best["peak_gb"])
|
|
312
|
+
PeakGB = peak if math.isfinite(peak) else 0.0
|
|
313
|
+
host = float(suggestion.best.get("host_ram_gb", peak))
|
|
314
|
+
HostRAMGB = host if math.isfinite(host) else 0.0
|
|
315
|
+
MemWatermarkGB = float(max(1, round(0.15 * ram_gb)))
|
|
316
|
+
RamGBUsed = float(ram_gb)
|
|
317
|
+
Flags = " ".join(
|
|
318
|
+
(["--Selection-ChanStep %d" % ChanStep] if ChanStep != 1 else [])
|
|
319
|
+
+ ["--Parallel-NCPU %d" % NCPU, "--Data-ChunkHours %g" % ChunkHours,
|
|
320
|
+
"--Parallel-MemWatermarkGB %g" % MemWatermarkGB])
|
|
321
|
+
print("plan: %s" % Flags)
|
|
322
|
+
Plan = _opt("PlanOut", "ddfacet-memory-plan.json")
|
|
323
|
+
with open(Plan, "w") as _fh:
|
|
324
|
+
json.dump(dict(ncpu=NCPU, chunk_hours=ChunkHours, chan_step=ChanStep,
|
|
325
|
+
peak_gb=PeakGB, host_ram_gb=HostRAMGB,
|
|
326
|
+
mem_watermark_gb=MemWatermarkGB, ram_gb=RamGBUsed,
|
|
327
|
+
ram_source=ram_source, threaded=threaded,
|
|
328
|
+
flags=Flags, notes=suggestion.notes,
|
|
329
|
+
feasible=[{k: v for k, v in f.items() if k != "estimate"}
|
|
330
|
+
for f in suggestion.feasible]),
|
|
331
|
+
_fh, indent=2)
|
|
332
|
+
if not suggestion.feasible:
|
|
333
|
+
raise RuntimeError(
|
|
334
|
+
"ddfacet-memory-plan: nothing in the search space fits %.1f GB "
|
|
335
|
+
"-- see the plan at %s" % (ram_gb, Plan))
|