Commit ·
c5cf2cc
1
Parent(s): d2d4ccf
feat: add restartable sharded graph preparation
Browse files
docs/perlmutter.md
CHANGED
|
@@ -56,3 +56,32 @@ An unavailable DGL wheel, incompatible driver, missing cache, or mismatched
|
|
| 56 |
cache schema should be fixed in the environment/input rather than hidden with
|
| 57 |
package-level path changes. GPU kernels and distributed execution are not
|
| 58 |
promised bitwise deterministic.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
cache schema should be fixed in the environment/input rather than hidden with
|
| 57 |
package-level path changes. GPU kernels and distributed execution are not
|
| 58 |
promised bitwise deterministic.
|
| 59 |
+
|
| 60 |
+
## Large ROOT graph preparation
|
| 61 |
+
|
| 62 |
+
Large samples should be prepared as persistent shards. Each Slurm array task
|
| 63 |
+
reads one contiguous global event range and writes one `shard-NNNNN.pt` file;
|
| 64 |
+
completed shards are validated and reused when an interrupted array is
|
| 65 |
+
resubmitted. No full-cache in-memory merge is performed.
|
| 66 |
+
|
| 67 |
+
Create the range manifest once:
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
uv run gnn4colliders prepare-manifest \
|
| 71 |
+
--config-name config_tth_cp_even_odd \
|
| 72 |
+
--shard-count 256 \
|
| 73 |
+
--output-dir outputs/ttH_cp_even_odd/shards
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
Submit the CPU array, limiting concurrent tasks to match the filesystem and
|
| 77 |
+
allocation capacity:
|
| 78 |
+
|
| 79 |
+
```bash
|
| 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
|
| 87 |
+
full benchmark into one `.pt` file.
|
scripts/slurm/prepare_tth_cp_even_odd.sh
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Prepare one independent graph-cache shard for the ttH CP benchmark.
|
| 3 |
+
# Submit with: sbatch --array=0-255%32 scripts/slurm/prepare_tth_cp_even_odd.sh
|
| 4 |
+
# Override SHARD_DIR, SHARD_COUNT, and CONFIG when needed.
|
| 5 |
+
#SBATCH -N 1
|
| 6 |
+
#SBATCH -C cpu
|
| 7 |
+
#SBATCH -t 04:00:00
|
| 8 |
+
#SBATCH -n 1
|
| 9 |
+
#SBATCH -c 64
|
| 10 |
+
#SBATCH -o logs/tth_cp_even_odd_%A_%a.out
|
| 11 |
+
#SBATCH -e logs/tth_cp_even_odd_%A_%a.err
|
| 12 |
+
|
| 13 |
+
set -euo pipefail
|
| 14 |
+
cd "${SLURM_SUBMIT_DIR:-$(pwd)}"
|
| 15 |
+
|
| 16 |
+
: "${SLURM_ARRAY_TASK_ID:?submit this script as a Slurm array}"
|
| 17 |
+
SHARD_COUNT="${SHARD_COUNT:-256}"
|
| 18 |
+
SHARD_DIR="${SHARD_DIR:-outputs/ttH_cp_even_odd/shards}"
|
| 19 |
+
CONFIG="${CONFIG:-config_tth_cp_even_odd}"
|
| 20 |
+
|
| 21 |
+
# Prevent native math libraries from multiplying the Slurm CPU allocation.
|
| 22 |
+
export OMP_NUM_THREADS=1
|
| 23 |
+
export MKL_NUM_THREADS=1
|
| 24 |
+
export OPENBLAS_NUM_THREADS=1
|
| 25 |
+
export NUMEXPR_NUM_THREADS=1
|
| 26 |
+
|
| 27 |
+
mkdir -p logs "${SHARD_DIR}"
|
| 28 |
+
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}"
|
src/gnn4colliders/cli/__init__.py
CHANGED
|
@@ -14,10 +14,20 @@ from gnn4colliders.config.application import (
|
|
| 14 |
export_onnx,
|
| 15 |
predict_or_evaluate,
|
| 16 |
prepare,
|
|
|
|
| 17 |
train,
|
|
|
|
| 18 |
)
|
| 19 |
|
| 20 |
-
_COMMANDS = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
def _is_main_process() -> bool:
|
|
@@ -30,7 +40,8 @@ def _help(command: str | None = None) -> None:
|
|
| 30 |
print("Compose Hydra configuration with semantic overrides.")
|
| 31 |
else:
|
| 32 |
print(
|
| 33 |
-
"Usage: gnn4colliders <prepare|
|
|
|
|
| 34 |
"[key=value ...]"
|
| 35 |
)
|
| 36 |
print("Use gnn4colliders <command> --help for command help.")
|
|
@@ -68,6 +79,39 @@ def _config_name(args: list[str]) -> tuple[str, list[str]]:
|
|
| 68 |
return config_name, remaining
|
| 69 |
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
def main(argv: list[str] | None = None) -> int:
|
| 72 |
args = list(sys.argv[1:] if argv is None else argv)
|
| 73 |
if not args or args[0] in {"-h", "--help"}:
|
|
@@ -85,12 +129,35 @@ def main(argv: list[str] | None = None) -> int:
|
|
| 85 |
return 0
|
| 86 |
try:
|
| 87 |
config_name, overrides = _config_name(args)
|
|
|
|
| 88 |
config = _config(overrides, config_name=config_name)
|
| 89 |
logging.basicConfig(
|
| 90 |
level=getattr(logging, str(config.logging.level).upper(), logging.INFO)
|
| 91 |
)
|
| 92 |
if command == "prepare":
|
| 93 |
print(f"prepared cache: {prepare(config)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
elif command == "train":
|
| 95 |
if _is_main_process():
|
| 96 |
print(f"saved checkpoint: {train(config)}")
|
|
|
|
| 14 |
export_onnx,
|
| 15 |
predict_or_evaluate,
|
| 16 |
prepare,
|
| 17 |
+
prepare_shard,
|
| 18 |
train,
|
| 19 |
+
write_shard_manifest,
|
| 20 |
)
|
| 21 |
|
| 22 |
+
_COMMANDS = {
|
| 23 |
+
"prepare",
|
| 24 |
+
"prepare-manifest",
|
| 25 |
+
"prepare-shard",
|
| 26 |
+
"train",
|
| 27 |
+
"evaluate",
|
| 28 |
+
"predict",
|
| 29 |
+
"export",
|
| 30 |
+
}
|
| 31 |
|
| 32 |
|
| 33 |
def _is_main_process() -> bool:
|
|
|
|
| 40 |
print("Compose Hydra configuration with semantic overrides.")
|
| 41 |
else:
|
| 42 |
print(
|
| 43 |
+
"Usage: gnn4colliders <prepare|prepare-manifest|prepare-shard|train|"
|
| 44 |
+
"evaluate|predict|export> "
|
| 45 |
"[key=value ...]"
|
| 46 |
)
|
| 47 |
print("Use gnn4colliders <command> --help for command help.")
|
|
|
|
| 79 |
return config_name, remaining
|
| 80 |
|
| 81 |
|
| 82 |
+
def _shard_options(
|
| 83 |
+
args: list[str],
|
| 84 |
+
) -> tuple[int | None, int | None, str | None, list[str]]:
|
| 85 |
+
values: dict[str, int | str] = {}
|
| 86 |
+
remaining: list[str] = []
|
| 87 |
+
names = {
|
| 88 |
+
"--shard-index": "index",
|
| 89 |
+
"--shard-count": "count",
|
| 90 |
+
"--output-dir": "output",
|
| 91 |
+
}
|
| 92 |
+
index = 0
|
| 93 |
+
while index < len(args):
|
| 94 |
+
argument = args[index]
|
| 95 |
+
matched = next((name for name in names if argument == name), None)
|
| 96 |
+
if matched:
|
| 97 |
+
if index + 1 >= len(args):
|
| 98 |
+
raise ValueError(f"{matched} requires a value")
|
| 99 |
+
values[names[matched]] = args[index + 1]
|
| 100 |
+
index += 2
|
| 101 |
+
continue
|
| 102 |
+
prefix = next((name for name in names if argument.startswith(name + "=")), None)
|
| 103 |
+
if prefix:
|
| 104 |
+
values[names[prefix]] = argument.split("=", 1)[1]
|
| 105 |
+
index += 1
|
| 106 |
+
continue
|
| 107 |
+
remaining.append(argument)
|
| 108 |
+
index += 1
|
| 109 |
+
shard_index = int(values["index"]) if "index" in values else None
|
| 110 |
+
shard_count = int(values["count"]) if "count" in values else None
|
| 111 |
+
output_dir = str(values["output"]) if "output" in values else None
|
| 112 |
+
return shard_index, shard_count, output_dir, remaining
|
| 113 |
+
|
| 114 |
+
|
| 115 |
def main(argv: list[str] | None = None) -> int:
|
| 116 |
args = list(sys.argv[1:] if argv is None else argv)
|
| 117 |
if not args or args[0] in {"-h", "--help"}:
|
|
|
|
| 129 |
return 0
|
| 130 |
try:
|
| 131 |
config_name, overrides = _config_name(args)
|
| 132 |
+
shard_index, shard_count, output_dir, overrides = _shard_options(overrides)
|
| 133 |
config = _config(overrides, config_name=config_name)
|
| 134 |
logging.basicConfig(
|
| 135 |
level=getattr(logging, str(config.logging.level).upper(), logging.INFO)
|
| 136 |
)
|
| 137 |
if command == "prepare":
|
| 138 |
print(f"prepared cache: {prepare(config)}")
|
| 139 |
+
elif command == "prepare-manifest":
|
| 140 |
+
if shard_count is None or output_dir is None:
|
| 141 |
+
raise ValueError(
|
| 142 |
+
"prepare-manifest requires --shard-count and --output-dir"
|
| 143 |
+
)
|
| 144 |
+
manifest = write_shard_manifest(
|
| 145 |
+
config, shard_count=shard_count, output_dir=output_dir
|
| 146 |
+
)
|
| 147 |
+
print(f"wrote shard manifest: {manifest}")
|
| 148 |
+
elif command == "prepare-shard":
|
| 149 |
+
if shard_index is None or shard_count is None or output_dir is None:
|
| 150 |
+
raise ValueError(
|
| 151 |
+
"prepare-shard requires --shard-index, --shard-count, and "
|
| 152 |
+
"--output-dir"
|
| 153 |
+
)
|
| 154 |
+
shard = prepare_shard(
|
| 155 |
+
config,
|
| 156 |
+
shard_index=shard_index,
|
| 157 |
+
shard_count=shard_count,
|
| 158 |
+
output_dir=output_dir,
|
| 159 |
+
)
|
| 160 |
+
print(f"prepared graph shard: {shard}")
|
| 161 |
elif command == "train":
|
| 162 |
if _is_main_process():
|
| 163 |
print(f"saved checkpoint: {train(config)}")
|
src/gnn4colliders/config/application.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
| 5 |
import logging
|
| 6 |
import multiprocessing
|
| 7 |
import os
|
|
@@ -177,6 +178,108 @@ def _write_graph_shard(args: tuple[Any, ...]) -> tuple[Path, int]:
|
|
| 177 |
return shard_path, len(samples)
|
| 178 |
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
def prepare(config: DictConfig) -> Path:
|
| 181 |
if bool(config.distributed.enabled) or int(os.environ.get("WORLD_SIZE", "1")) > 1:
|
| 182 |
raise ValueError(
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
+
import json
|
| 6 |
import logging
|
| 7 |
import multiprocessing
|
| 8 |
import os
|
|
|
|
| 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")
|
| 184 |
+
if shard_index < 0 or shard_index >= shard_count:
|
| 185 |
+
raise ValueError("shard_index must be in [0, shard_count)")
|
| 186 |
+
return (
|
| 187 |
+
total * shard_index // shard_count,
|
| 188 |
+
total * (shard_index + 1) // shard_count,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _prepare_source(config: DictConfig):
|
| 193 |
+
data = config.data
|
| 194 |
+
files, labels = resolve_data_files(data)
|
| 195 |
+
labels_per_file = (
|
| 196 |
+
list(labels) if isinstance(labels, (list, tuple)) else [labels] * len(files)
|
| 197 |
+
)
|
| 198 |
+
if not isinstance(labels, (list, tuple)) and isinstance(labels, Sequence):
|
| 199 |
+
labels_per_file = list(labels)
|
| 200 |
+
source = RootEventDataset(
|
| 201 |
+
files,
|
| 202 |
+
tree_name=str(data.tree_name),
|
| 203 |
+
label=labels_per_file,
|
| 204 |
+
feature_branches=plain(data.get("feature_branches")),
|
| 205 |
+
global_features=plain(data.get("global_features", [])),
|
| 206 |
+
fold_var=str(data.get("fold_var", "eventNumber")),
|
| 207 |
+
weight_var=data.get("weight_var"),
|
| 208 |
+
)
|
| 209 |
+
return source, files, labels_per_file
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def prepare_shard(
|
| 213 |
+
config: DictConfig,
|
| 214 |
+
*,
|
| 215 |
+
shard_index: int,
|
| 216 |
+
shard_count: int,
|
| 217 |
+
output_dir: str | Path,
|
| 218 |
+
) -> Path:
|
| 219 |
+
"""Prepare one persistent, restartable graph-cache shard.
|
| 220 |
+
|
| 221 |
+
Shards use contiguous global event ranges and are independent of one
|
| 222 |
+
another, making this function suitable for a Slurm job array. Existing
|
| 223 |
+
valid shards are reused so interrupted arrays can be resubmitted safely.
|
| 224 |
+
"""
|
| 225 |
+
source, files, labels = _prepare_source(config)
|
| 226 |
+
start, stop = _shard_range(len(source), shard_index, shard_count)
|
| 227 |
+
target_dir = Path(output_dir)
|
| 228 |
+
target_dir.mkdir(parents=True, exist_ok=True)
|
| 229 |
+
target = target_dir / f"shard-{shard_index:05d}.pt"
|
| 230 |
+
expected = stop - start
|
| 231 |
+
if target.is_file():
|
| 232 |
+
try:
|
| 233 |
+
if len(GraphSampleCache(target).load()) == expected:
|
| 234 |
+
logger.info("reusing completed shard %s", target)
|
| 235 |
+
return target
|
| 236 |
+
except (OSError, RuntimeError, ValueError, EOFError):
|
| 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}"
|
| 244 |
+
)
|
| 245 |
+
temporary = target.with_suffix(target.suffix + f".tmp-{os.getpid()}")
|
| 246 |
+
GraphSampleCache(temporary).save(samples)
|
| 247 |
+
os.replace(temporary, target)
|
| 248 |
+
return target
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def write_shard_manifest(
|
| 252 |
+
config: DictConfig, *, shard_count: int, output_dir: str | Path
|
| 253 |
+
) -> Path:
|
| 254 |
+
"""Write an immutable description of the event ranges in a shard set."""
|
| 255 |
+
source, files, labels = _prepare_source(config)
|
| 256 |
+
target_dir = Path(output_dir)
|
| 257 |
+
target_dir.mkdir(parents=True, exist_ok=True)
|
| 258 |
+
manifest = {
|
| 259 |
+
"format": 1,
|
| 260 |
+
"total_events": len(source),
|
| 261 |
+
"shard_count": int(shard_count),
|
| 262 |
+
"tree_name": str(config.data.tree_name),
|
| 263 |
+
"files": [str(path) for path in files],
|
| 264 |
+
"labels": labels,
|
| 265 |
+
"shards": [
|
| 266 |
+
{
|
| 267 |
+
"index": index,
|
| 268 |
+
"start": start,
|
| 269 |
+
"stop": stop,
|
| 270 |
+
"path": f"shard-{index:05d}.pt",
|
| 271 |
+
}
|
| 272 |
+
for index in range(shard_count)
|
| 273 |
+
for start, stop in [_shard_range(len(source), index, shard_count)]
|
| 274 |
+
],
|
| 275 |
+
}
|
| 276 |
+
target = target_dir / "manifest.json"
|
| 277 |
+
temporary = target.with_suffix(".json.tmp")
|
| 278 |
+
temporary.write_text(json.dumps(manifest, indent=2) + "\n")
|
| 279 |
+
os.replace(temporary, target)
|
| 280 |
+
return target
|
| 281 |
+
|
| 282 |
+
|
| 283 |
def prepare(config: DictConfig) -> Path:
|
| 284 |
if bool(config.distributed.enabled) or int(os.environ.get("WORLD_SIZE", "1")) > 1:
|
| 285 |
raise ValueError(
|
tests/integration/test_parallel_prepare.py
CHANGED
|
@@ -6,7 +6,11 @@ import pytest
|
|
| 6 |
import uproot
|
| 7 |
from omegaconf import OmegaConf
|
| 8 |
|
| 9 |
-
from gnn4colliders.config.application import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from gnn4colliders.data import GraphSampleCache
|
| 11 |
|
| 12 |
|
|
@@ -65,3 +69,49 @@ def test_parallel_prepare_matches_serial_cache(tmp_path: Path):
|
|
| 65 |
assert left.label.equal(right.label)
|
| 66 |
assert left.graph.ndata["features"].equal(right.graph.ndata["features"])
|
| 67 |
assert left.graph.edata["features"].equal(right.graph.edata["features"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
import uproot
|
| 7 |
from omegaconf import OmegaConf
|
| 8 |
|
| 9 |
+
from gnn4colliders.config.application import (
|
| 10 |
+
prepare,
|
| 11 |
+
prepare_shard,
|
| 12 |
+
write_shard_manifest,
|
| 13 |
+
)
|
| 14 |
from gnn4colliders.data import GraphSampleCache
|
| 15 |
|
| 16 |
|
|
|
|
| 69 |
assert left.label.equal(right.label)
|
| 70 |
assert left.graph.ndata["features"].equal(right.graph.ndata["features"])
|
| 71 |
assert left.graph.edata["features"].equal(right.graph.edata["features"])
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
@pytest.mark.integration
|
| 75 |
+
def test_shards_are_restartable_and_cover_source(tmp_path: Path):
|
| 76 |
+
pytest.importorskip("dgl")
|
| 77 |
+
root_file = tmp_path / "events.root"
|
| 78 |
+
_write_fixture(root_file, events=10)
|
| 79 |
+
data = {
|
| 80 |
+
"files": [str(root_file)],
|
| 81 |
+
"tree_name": "Events",
|
| 82 |
+
"feature_branches": [
|
| 83 |
+
["jet_pt"],
|
| 84 |
+
["jet_eta"],
|
| 85 |
+
["jet_phi"],
|
| 86 |
+
"CALC_E",
|
| 87 |
+
[1.0],
|
| 88 |
+
[0.0],
|
| 89 |
+
"NODE_TYPE",
|
| 90 |
+
],
|
| 91 |
+
"object_types": ["vector"],
|
| 92 |
+
"scales": [1, 1, 1, 1, 1, 1, 1],
|
| 93 |
+
"global_features": [],
|
| 94 |
+
"fold_var": "eventNumber",
|
| 95 |
+
"weight_var": "weight",
|
| 96 |
+
}
|
| 97 |
+
config = OmegaConf.create({"data": data})
|
| 98 |
+
output = tmp_path / "shards"
|
| 99 |
+
|
| 100 |
+
manifest = write_shard_manifest(config, shard_count=3, output_dir=output)
|
| 101 |
+
assert manifest.is_file()
|
| 102 |
+
paths = [
|
| 103 |
+
prepare_shard(
|
| 104 |
+
config,
|
| 105 |
+
shard_index=index,
|
| 106 |
+
shard_count=3,
|
| 107 |
+
output_dir=output,
|
| 108 |
+
)
|
| 109 |
+
for index in range(3)
|
| 110 |
+
]
|
| 111 |
+
assert [len(GraphSampleCache(path).load()) for path in paths] == [3, 3, 4]
|
| 112 |
+
assert prepare_shard(
|
| 113 |
+
config,
|
| 114 |
+
shard_index=1,
|
| 115 |
+
shard_count=3,
|
| 116 |
+
output_dir=output,
|
| 117 |
+
) == paths[1]
|