ho22joshua commited on
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 CPUs 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
 
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
- samples = _build_graph_samples(data, files, labels, start, stop)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"