Commit ·
2c479af
1
Parent(s): c5cf2cc
feat: add restartable sharded graph preparation
Browse files
docs/perlmutter.md
CHANGED
|
@@ -80,7 +80,7 @@ allocation capacity:
|
|
| 80 |
sbatch --array=0-255%32 scripts/slurm/prepare_tth_cp_even_odd.sh
|
| 81 |
```
|
| 82 |
|
| 83 |
-
The script defaults to 64
|
| 84 |
`SHARD_COUNT`, or `CONFIG` at submission time to use another location or
|
| 85 |
configuration. The current training loader still expects a single cache, so
|
| 86 |
the next step after extraction is a streaming sharded loader; do not merge the
|
|
|
|
| 80 |
sbatch --array=0-255%32 scripts/slurm/prepare_tth_cp_even_odd.sh
|
| 81 |
```
|
| 82 |
|
| 83 |
+
The script defaults to 64 process workers per task and 256 shards. Set `SHARD_DIR`,
|
| 84 |
`SHARD_COUNT`, or `CONFIG` at submission time to use another location or
|
| 85 |
configuration. The current training loader still expects a single cache, so
|
| 86 |
the next step after extraction is a streaming sharded loader; do not merge the
|
scripts/slurm/prepare_tth_cp_even_odd.sh
CHANGED
|
@@ -29,4 +29,5 @@ uv run gnn4colliders prepare-shard \
|
|
| 29 |
--config-name "${CONFIG}" \
|
| 30 |
--shard-index "${SLURM_ARRAY_TASK_ID}" \
|
| 31 |
--shard-count "${SHARD_COUNT}" \
|
| 32 |
-
--output-dir "${SHARD_DIR}"
|
|
|
|
|
|
| 29 |
--config-name "${CONFIG}" \
|
| 30 |
--shard-index "${SLURM_ARRAY_TASK_ID}" \
|
| 31 |
--shard-count "${SHARD_COUNT}" \
|
| 32 |
+
--output-dir "${SHARD_DIR}" \
|
| 33 |
+
data.num_workers="${SLURM_CPUS_PER_TASK:-1}"
|
src/gnn4colliders/config/application.py
CHANGED
|
@@ -178,6 +178,11 @@ def _write_graph_shard(args: tuple[Any, ...]) -> tuple[Path, int]:
|
|
| 178 |
return shard_path, len(samples)
|
| 179 |
|
| 180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
def _shard_range(total: int, shard_index: int, shard_count: int) -> tuple[int, int]:
|
| 182 |
if shard_count < 1:
|
| 183 |
raise ValueError("shard_count must be positive")
|
|
@@ -237,7 +242,29 @@ def prepare_shard(
|
|
| 237 |
logger.warning("discarding incomplete or incompatible shard %s", target)
|
| 238 |
|
| 239 |
data = plain(config.data)
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
if len(samples) != expected:
|
| 242 |
raise RuntimeError(
|
| 243 |
f"shard {shard_index} produced {len(samples)} samples, expected {expected}"
|
|
|
|
| 178 |
return shard_path, len(samples)
|
| 179 |
|
| 180 |
|
| 181 |
+
def _build_graph_samples_from_args(args: tuple[Any, ...]) -> list[GraphSample]:
|
| 182 |
+
data, files, labels, start, stop = args
|
| 183 |
+
return _build_graph_samples(data, files, labels, start, stop)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
def _shard_range(total: int, shard_index: int, shard_count: int) -> tuple[int, int]:
|
| 187 |
if shard_count < 1:
|
| 188 |
raise ValueError("shard_count must be positive")
|
|
|
|
| 242 |
logger.warning("discarding incomplete or incompatible shard %s", target)
|
| 243 |
|
| 244 |
data = plain(config.data)
|
| 245 |
+
workers = max(0, int(data.get("num_workers", 0)))
|
| 246 |
+
local_shards = max(1, workers)
|
| 247 |
+
local_ranges = [
|
| 248 |
+
(
|
| 249 |
+
start + (stop - start) * index // local_shards,
|
| 250 |
+
start + (stop - start) * (index + 1) // local_shards,
|
| 251 |
+
)
|
| 252 |
+
for index in range(local_shards)
|
| 253 |
+
]
|
| 254 |
+
args = [
|
| 255 |
+
(data, files, labels, local_start, local_stop)
|
| 256 |
+
for local_start, local_stop in local_ranges
|
| 257 |
+
if local_start < local_stop
|
| 258 |
+
]
|
| 259 |
+
if workers > 1:
|
| 260 |
+
with ProcessPoolExecutor(
|
| 261 |
+
max_workers=workers,
|
| 262 |
+
mp_context=multiprocessing.get_context("spawn"),
|
| 263 |
+
) as pool:
|
| 264 |
+
local_results = list(pool.map(_build_graph_samples_from_args, args))
|
| 265 |
+
else:
|
| 266 |
+
local_results = [_build_graph_samples_from_args(item) for item in args]
|
| 267 |
+
samples = [sample for result in local_results for sample in result]
|
| 268 |
if len(samples) != expected:
|
| 269 |
raise RuntimeError(
|
| 270 |
f"shard {shard_index} produced {len(samples)} samples, expected {expected}"
|
tests/integration/test_parallel_prepare.py
CHANGED
|
@@ -93,6 +93,7 @@ def test_shards_are_restartable_and_cover_source(tmp_path: Path):
|
|
| 93 |
"global_features": [],
|
| 94 |
"fold_var": "eventNumber",
|
| 95 |
"weight_var": "weight",
|
|
|
|
| 96 |
}
|
| 97 |
config = OmegaConf.create({"data": data})
|
| 98 |
output = tmp_path / "shards"
|
|
|
|
| 93 |
"global_features": [],
|
| 94 |
"fold_var": "eventNumber",
|
| 95 |
"weight_var": "weight",
|
| 96 |
+
"num_workers": 2,
|
| 97 |
}
|
| 98 |
config = OmegaConf.create({"data": data})
|
| 99 |
output = tmp_path / "shards"
|