ho22joshua commited on
Commit
d2c3c35
·
1 Parent(s): 97f7eaf

docs: document the rewritten ROOT-GNN stack

Browse files
Files changed (3) hide show
  1. README.md +233 -25
  2. docs/architecture.md +154 -1
  3. docs/migration.md +137 -11
README.md CHANGED
@@ -1,41 +1,249 @@
1
  # GNN4Colliders
2
 
3
- This repository is being migrated to a clean, testable implementation under
4
- `src/gnn4colliders`.
 
 
 
 
5
 
6
- The existing implementation is preserved in [`legacy/`](legacy/) as the
7
- behavioral reference. New production code belongs in `src/`; notebooks are for
8
- exploration only. Use the parity tests to document and validate behavior while
9
- components are migrated incrementally.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- ## Development environment
 
 
 
12
 
13
  ```bash
14
- git clone <repository-url>
15
- cd GNN4Colliders
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- uv sync
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  uv run pytest
 
20
  uv run ruff check .
21
  uv run ruff format --check .
 
 
22
  ```
23
 
24
- The default environment contains the data, configuration, testing, and
25
- linting dependencies needed for the current package skeleton. PyTorch and DGL
26
- are kept in the opt-in `ml` dependency group because their compatible wheels
27
- depend on the host and CUDA runtime. On a compatible host, install them with:
 
28
 
29
- ```bash
30
- uv sync --extra ml
31
- ```
32
 
33
- For Perlmutter or other HPC systems, follow the site-specific PyTorch/DGL
34
- installation guidance for the loaded CUDA module rather than encoding a CUDA
35
- wheel URL in this project. In particular, the currently resolved PyPI DGL
36
- 2.2.1 distribution has no Linux wheel, so the `ml` extra is not currently
37
- installable on this Linux host without a site-provided or source-built DGL.
38
- Training and inference workflows are not yet implemented.
39
 
40
- See [`docs/migration.md`](docs/migration.md) for migration rules and
41
- [`docs/architecture.md`](docs/architecture.md) for the target design.
 
 
 
1
  # GNN4Colliders
2
 
3
+ GNN4Colliders is a collider-machine-learning toolkit. The repository name
4
+ reflects its first production model family, ROOT-GNN; the Python package is
5
+ `gnn4colliders`, and the configuration identifier is `root_gnn`. Shared ROOT
6
+ ingestion, collider features, metadata, tasks, training, inference, and
7
+ distributed utilities are designed so that a future sequence model can reuse
8
+ them without requiring every event to be a graph.
9
 
10
+ ```text
11
+ ROOT files -> EventSample -> shared collider features
12
+ ├── GraphSample -> ROOT-GNN
13
+ └── future SequenceSample -> ROOT-Transformer
14
+ ```
15
+
16
+ The new implementation lives under [`src/gnn4colliders`](src/gnn4colliders/).
17
+ [`legacy/`](legacy/) is a frozen behavioral reference for parity work and
18
+ historical checkpoint investigation, not a supported runtime backend.
19
+
20
+ ## Installation
21
+
22
+ The supported development environment is Python 3.12 (`>=3.12,<3.13`), with
23
+ PyTorch 2.2.2 and the optional ROOT-GNN stack DGL 2.4.0. The canonical setup
24
+ is:
25
+
26
+ ```bash
27
+ uv sync --dev --extra root-gnn
28
+ ```
29
+
30
+ The core package can be installed without DGL when only shared data or task
31
+ code is needed. ROOT-GNN models, graph construction, and ROOT-GNN parity tests
32
+ require the `root-gnn` extra. The DGL extra uses the validated CUDA 12.1 wheel
33
+ source configured in `pyproject.toml`; a compatible NVIDIA driver is still
34
+ required. Do not add site-specific CUDA, Slurm, or filesystem paths to model
35
+ or task configuration.
36
+
37
+ ## Quick start
38
 
39
+ Prepare a graph cache from a ROOT tree. The feature specifications below are
40
+ illustrative placeholders; replace them with the branches in the input tree.
41
+ The full preparation interface is documented in
42
+ [`docs/configuration.md`](docs/configuration.md).
43
 
44
  ```bash
45
+ uv run gnn4colliders prepare \
46
+ data.files=[data/events.root] \
47
+ data.tree_name=Events \
48
+ data.cache.path=cache/events.pt \
49
+ 'data.feature_branches=[["jet_pt"],["jet_eta"],["jet_phi"],CALC_E,[1.0],[0.0],NODE_TYPE]' \
50
+ data.object_types=[vector] \
51
+ data.scales=[1,1,1,1,1,1,1]
52
+ ```
53
+
54
+ Train, evaluate, and predict from that cache:
55
+
56
+ ```bash
57
+ uv run gnn4colliders train \
58
+ data.cache.path=cache/events.pt \
59
+ trainer.max_epochs=1 \
60
+ environment.output_root=outputs/pretraining_multiclass
61
+
62
+ uv run gnn4colliders evaluate \
63
+ data.cache.path=cache/events.pt \
64
+ inference.checkpoint=outputs/pretraining_multiclass/checkpoints/epoch_0000.pt
65
+
66
+ uv run gnn4colliders predict \
67
+ data.cache.path=cache/events.pt \
68
+ inference.checkpoint=outputs/pretraining_multiclass/checkpoints/epoch_0000.pt \
69
+ inference.output=outputs/pretraining_multiclass/predictions.npz
70
+ ```
71
+
72
+ For a dependency-complete, temporary-data version of this flow, run
73
+ `uv run python scripts/dev/smoke_end_to_end.py`.
74
+
75
+ ## Core concepts
76
+
77
+ `EventSample` is the architecture-neutral event boundary. It contains the
78
+ selected `objects`, `label`, `global_features`, and named `EventMetadata`.
79
+ Metadata includes `fold`, `weight`, and stable `sample_id`; callers should not
80
+ interpret public `tracking[:, N]` columns. Legacy tracking mappings exist only
81
+ at compatibility boundaries.
82
+
83
+ The ROOT-GNN adapter converts shared features to a directed, fully connected
84
+ graph with no self-loops: an event with `N` nodes has `N * (N - 1)` edges.
85
+ Node columns are, in order, `pt`, `eta`, `phi`, `energy`, `btag`, `charge`,
86
+ and `node_type`. Edge columns are `deta`, wrapped `dphi`, and `dR`.
87
+ Object collections are concatenated in configured object-type order. The
88
+ compatibility energy is `pt * cosh(eta)` before per-column scaling.
89
+
90
+ `GraphSampleCache` stores processed graph samples and schema metadata. It is a
91
+ Level-2 graph cache, not the universal event cache. Feature, graph, and cache
92
+ schema versions are checked when loading; incompatible versions fail before
93
+ training.
94
+
95
+ ## ROOT-GNN training and transfer
96
+
97
+ `EdgeNetwork` encodes node, edge, and global features, performs iterative
98
+ edge/node/global message passing, decodes a graph representation, and applies
99
+ the classifier. Its output is raw logits; sigmoid or softmax is task-owned.
100
+
101
+ Multiclass pretraining uses the semantic `model=root_gnn/edge_network` and
102
+ `task=pretraining_multiclass` groups:
103
+
104
+ ```bash
105
+ uv run gnn4colliders train \
106
+ data.cache.path=cache/events.pt \
107
+ model=root_gnn/edge_network task=pretraining_multiclass \
108
+ trainer.max_epochs=20 data.batch_size=64 \
109
+ environment.output_root=outputs/pretraining_multiclass
110
+ ```
111
+
112
+ Fine-tuning is a separate workflow. It loads a pretrained backbone, replaces
113
+ the classifier, and creates a new task/head optimizer:
114
+
115
+ ```bash
116
+ uv run gnn4colliders train \
117
+ data.cache.path=cache/target.pt \
118
+ model=root_gnn/fine_tuned_edge_network \
119
+ task=binary_classification \
120
+ checkpoint.pretrained=/path/to/pretrained.pt \
121
+ model.freeze_backbone=true \
122
+ trainer.max_epochs=10
123
+ ```
124
+
125
+ Set `model.freeze_backbone=false` to train the reused backbone as well.
126
+ Transfer learning is not resume training:
127
+
128
+ | Workflow | Meaning | Restored state |
129
+ | --- | --- | --- |
130
+ | Resume | Continue the same task/run | model, optimizer, scheduler, trainer, early stopping, and RNG state when present |
131
+ | Transfer | Start a new task from a pretrained backbone | model weights only; new classifier and optimizer |
132
 
133
+ Resume example:
134
 
135
+ ```bash
136
+ uv run gnn4colliders train \
137
+ data.cache.path=cache/events.pt \
138
+ checkpoint.resume=outputs/pretraining_multiclass/checkpoints/epoch_0000.pt \
139
+ trainer.max_epochs=20
140
+ ```
141
+
142
+ Validation is evaluated each epoch and drives scheduling/early stopping;
143
+ `test` remains held out. Evaluation computes task metrics over the complete
144
+ selected split, including weighted ROC AUC where defined:
145
+
146
+ ```bash
147
+ uv run gnn4colliders evaluate \
148
+ data.cache.path=cache/events.pt \
149
+ inference.split=test \
150
+ inference.checkpoint=/path/to/checkpoint.pt
151
+ ```
152
+
153
+ Prediction writes a named compressed NPZ. Labeled data includes `labels`;
154
+ `fold` and `weight` are included when available. Every result includes
155
+ `sample_id`, `logits`, `scores`, and `predictions`:
156
+
157
+ ```bash
158
+ uv run gnn4colliders predict \
159
+ data.cache.path=cache/events.pt \
160
+ inference.checkpoint=/path/to/checkpoint.pt \
161
+ inference.output=outputs/predictions.npz
162
+ ```
163
+
164
+ Optional Python-level ROOT writing is provided by
165
+ `gnn4colliders.inference.write_root_scores`. It clones the selected tree,
166
+ adds `score` (or `score_class_N`), and writes `selection_pass`; IDs ending in
167
+ `:<entry>` preserve alignment and unselected entries receive NaN scores. The
168
+ CLI currently exposes NPZ output only.
169
+
170
+ The supported legacy checkpoint, metadata, and output boundary is documented
171
+ in [`docs/compatibility.md`](docs/compatibility.md). New code should use named
172
+ metadata fields; positional tracking is accepted only by the explicit
173
+ compatibility adapter.
174
+
175
+ ### ONNX export
176
+
177
+ Install the optional export dependencies and export a prepared graph-cache
178
+ checkpoint with numerical ONNX validation:
179
+
180
+ ```bash
181
+ uv sync --extra root-gnn --extra onnx
182
+ uv run gnn4colliders export \
183
+ export.checkpoint=/path/to/checkpoint.pt \
184
+ export.output=model.onnx \
185
+ data.cache.path=/path/to/graph-cache.pt
186
+ ```
187
+
188
+ The model accepts processed graph tensors and returns raw logits. See
189
+ [`docs/export.md`](docs/export.md) for the tensor contract and limitations.
190
+
191
+ ## Configuration and environments
192
+
193
+ Hydra groups are `data`, `model`, `task`, `trainer`, `checkpoint`,
194
+ `inference`, `environment`, and `distributed`. Use configuration for a new
195
+ experiment and Python for new behavior. Examples:
196
+
197
+ ```bash
198
+ uv run gnn4colliders train trainer.max_epochs=50 data.batch_size=64
199
+ uv run gnn4colliders train environment=perlmutter environment.device=cuda
200
+ uv run gnn4colliders train distributed=ddp environment=perlmutter
201
+ ```
202
+
203
+ Each run writes a resolved configuration to
204
+ `<environment.output_root>/resolved_config.yaml`. See
205
+ [`docs/configuration.md`](docs/configuration.md) for the group reference and
206
+ [`docs/perlmutter.md`](docs/perlmutter.md) for launch examples.
207
+
208
+ ## Distributed execution and reproducibility
209
+
210
+ Launch DDP with `torchrun` or the provided Slurm wrappers. `data.batch_size`
211
+ and `data.num_workers` are per process, so the ordinary effective batch size
212
+ is `batch_size * world_size`. Training shards may be padded for equal steps;
213
+ validation and prediction are unpadded. Rank 0 writes shared checkpoints,
214
+ configs, and predictions, and metrics/results are gathered across ranks.
215
+
216
+ The configured seed controls initialization and deterministic local loader
217
+ ordering; distributed process seeds are rank-offset and samplers use
218
+ `set_epoch`. CPU runs are reproducible for fixed inputs and environment. GPU
219
+ kernels, DGL, and distributed scheduling can remain nondeterministic, so the
220
+ project does not promise bitwise GPU reproducibility.
221
+
222
+ ## Development and validation
223
+
224
+ ```bash
225
  uv run pytest
226
+ GNN4COLLIDERS_REQUIRE_ROOT_GNN=1 uv run pytest tests/parity -v
227
  uv run ruff check .
228
  uv run ruff format --check .
229
+ uv run python benchmarks/benchmark_preprocessing.py
230
+ uv run python benchmarks/benchmark_training.py --device cpu
231
  ```
232
 
233
+ Unit tests cover isolated components, integration tests cover small workflows,
234
+ and parity tests compare deterministic behavior with the frozen legacy
235
+ reference. Performance guidance and measured caveats are in
236
+ [`docs/performance.md`](docs/performance.md) and
237
+ [`benchmarks/README.md`](benchmarks/README.md).
238
 
239
+ ## Architecture and migration status
 
 
240
 
241
+ See [`docs/architecture.md`](docs/architecture.md) for responsibility
242
+ boundaries and the future sequence-model extension point. See
243
+ [`docs/migration.md`](docs/migration.md) for the migration matrix,
244
+ intentional redesigns, compatibility limits, and deferred work.
 
 
245
 
246
+ ROOT-GNN v1 covers ROOT preparation, validated feature/graph/model/task
247
+ behavior, training, fine-tuning, checkpoint resume, evaluation, prediction,
248
+ single-process/DDP execution, and validated ONNX export. Streaming distributed
249
+ output, legacy cleanup, and ROOT-Transformer remain follow-up work.
docs/architecture.md CHANGED
@@ -1,4 +1,79 @@
1
- # Reverse-engineered architecture: `root_gnn_dgl`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  This document covers the target system in `legacy/root_gnn_dgl/`. The sibling
4
  `legacy/physicsnemo/` tree is a prior rewrite attempt and is not a behavioral
@@ -6,6 +81,12 @@ target.
6
 
7
  ## 1. High-level system description
8
 
 
 
 
 
 
 
9
  `root_gnn_dgl` is a ROOT-to-DGL graph classification system. YAML selects
10
  dataset, model, loss, and finish-function classes by import path. The dataset
11
  reads ROOT trees, converts collider objects to fully connected DGL graphs, and
@@ -137,6 +218,55 @@ Keys strip `module.` and compiled models save the underlying `_orig_mod` state;
137
  to a cloned ROOT tree ([`training_script.py:57-140`](../legacy/root_gnn_dgl/scripts/training_script.py),
138
  [`inference.py:328-385`](../legacy/root_gnn_dgl/scripts/inference.py)).
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  ## Randomness, external services, and coupling
141
 
142
  The CLI exposes `--seed`, but `main` passes it to model construction rather
@@ -187,11 +317,34 @@ when a config has a validation fold ([`training_script.py:682-747`](../legacy/ro
187
 
188
  ## 3. De facto interfaces to preserve
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  The rewrite's shared data boundary is `gnn4colliders.data`: it reads selected
191
  ROOT/Awkward branches and returns architecture-neutral event samples. Feature
192
  construction and graph building remain separate downstream boundaries, so the
193
  same samples can be reused by non-graph model families.
194
 
 
 
 
 
 
 
 
 
 
 
195
  1. YAML `module`, `class`, `args`, plus runtime `sample_graph` and
196
  `sample_global` injection.
197
  2. Dataset items `(DGLGraph, label, tracking, global_features)`.
 
1
+ # GNN4Colliders architecture
2
+
3
+ ## Current v1 architecture
4
+
5
+ The supported rewrite is layered around an architecture-neutral event boundary:
6
+
7
+ ```text
8
+ ROOT/Awkward
9
+
10
+ EventSample + EventMetadata
11
+
12
+ shared collider feature construction
13
+ ├── GraphSample -> versioned graph cache -> GraphBatch
14
+ └── future SequenceSample -> ROOT-Transformer (not implemented)
15
+
16
+ ROOT-GNN EdgeNetwork
17
+
18
+ raw logits -> Task
19
+ ├── loss
20
+ ├── predictions
21
+ └── full-split metrics
22
+
23
+ Trainer / Predictor / outputs
24
+ ```
25
+
26
+ | Layer | Responsibility |
27
+ | --- | --- |
28
+ | `data` | ROOT/Awkward ingestion, event samples, metadata, graph caches, folds, and batching |
29
+ | `features` | Shared collider-object features and derived physics quantities |
30
+ | `graphs` | Topology, edge features, and the DGL representation adapter |
31
+ | `models/root_gnn` | ROOT-GNN encoders, message passing, classifier, and transfer boundary |
32
+ | `tasks` | Loss, score/prediction, labels, weights, and metrics |
33
+ | `training` | Optimizer lifecycle, validation, early stopping, checkpointing, and reproducibility |
34
+ | `inference` | Ordered prediction/evaluation and NPZ/ROOT output adapters |
35
+ | `distributed` | Rank-local devices, sharding, DDP, and cross-rank collection |
36
+ | `config` / `cli` | Semantic Hydra composition and thin user-facing commands |
37
+
38
+ `EventSample` is shared infrastructure, not a ROOT-GNN object. `GraphSample`
39
+ is the current representation-specific adapter. This separation is the
40
+ extension point for a future sequence/token representation.
41
+
42
+ The new public metadata contract is named `EventMetadata(fold, weight,
43
+ sample_id, extra)`. The legacy positional tracking tensor is accepted only by
44
+ compatibility-facing ingestion code. A `GraphSampleCache` is deliberately a
45
+ Level-2 graph cache; replacing it with a universal cache would couple future
46
+ model families to DGL.
47
+
48
+ For deployment, a prepared `GraphBatch` can pass through the isolated
49
+ `RootGNNExportAdapter` into an ONNX model. This is an inference boundary only;
50
+ the native model continues to consume DGL graphs and ONNX does not read ROOT
51
+ or construct collider features.
52
+
53
+ ## Current public workflow
54
+
55
+ `prepare` reads ROOT through `RootEventDataset`, builds shared features and
56
+ DGL graphs, and saves a schema-checked cache. `train` creates a model/task and
57
+ `Trainer`; validation is the model-selection split and test is held out.
58
+ `evaluate` computes metrics after collecting the complete split. `predict`
59
+ returns detached CPU tensors in loader order and writes named NPZ fields.
60
+ `write_root_scores` is an optional Python adapter with explicit entry alignment;
61
+ the CLI currently exposes NPZ output.
62
+
63
+ Compatibility responsibilities are isolated in `gnn4colliders.compat`.
64
+ Supported historical checkpoint prefixes, classifier names, and the two-column
65
+ tracking conversion are listed in [`compatibility.md`](compatibility.md).
66
+ The modern pipeline does not propagate positional tracking or historical NPZ
67
+ fields.
68
+
69
+ Checkpoints are independent of the model implementation: they carry model and
70
+ task metadata, lifecycle state, schema versions, and optional RNG state.
71
+ Prefix normalization supports DDP `module.` and compiled `_orig_mod.` weights,
72
+ plus the active ROOT-GNN historical classifier-name compatibility path.
73
+
74
+ ---
75
+
76
+ ## Historical behavioral reference
77
 
78
  This document covers the target system in `legacy/root_gnn_dgl/`. The sibling
79
  `legacy/physicsnemo/` tree is a prior rewrite attempt and is not a behavioral
 
81
 
82
  ## 1. High-level system description
83
 
84
+ The active rewrite exposes `gnn4colliders.models.root_gnn.EdgeNetwork`. Its
85
+ encoders and message-passing blocks form a reusable backbone whose decoded
86
+ graph representation is passed to an explicit classifier. `FineTunedEdgeNetwork`
87
+ reuses that backbone and replaces only the task-specific classifier, with
88
+ explicit frozen or trainable-backbone control.
89
+
90
  `root_gnn_dgl` is a ROOT-to-DGL graph classification system. YAML selects
91
  dataset, model, loss, and finish-function classes by import path. The dataset
92
  reads ROOT trees, converts collider objects to fully connected DGL graphs, and
 
218
  to a cloned ROOT tree ([`training_script.py:57-140`](../legacy/root_gnn_dgl/scripts/training_script.py),
219
  [`inference.py:328-385`](../legacy/root_gnn_dgl/scripts/inference.py)).
220
 
221
+ ### Training lifecycle boundary
222
+
223
+ The active rewrite keeps lifecycle orchestration architecture-independent:
224
+
225
+ ```text
226
+ GraphDataLoader -> GraphBatch -> Model -> Task -> Trainer
227
+ loss/metrics
228
+ ```
229
+
230
+ `gnn4colliders.training.Trainer` owns device placement, train/evaluation mode,
231
+ gradient and optimizer steps, epoch aggregation, optional scheduler stepping,
232
+ early stopping, and in-memory history. Tasks own loss and metric semantics;
233
+ the trainer does not inspect positional tracking columns or collider-specific
234
+ features. Evaluation concatenates detached outputs across the complete split
235
+ before calling task metrics, so ROC AUC is not computed per mini-batch.
236
+
237
+ Checkpoint persistence and the Python inference/output layer are implemented
238
+ as separate adapters. The semantic CLI and distributed application boundary
239
+ are implemented in the current stack. `gnn4colliders.inference.Predictor` accumulates detached CPU
240
+ logits, task-defined scores/predictions, labels, and named event metadata in
241
+ loader order; `write_npz` is the primary named-field format and ROOT score
242
+ writing is an optional alignment-aware adapter.
243
+
244
+ The new lifecycle uses conventional split semantics. `train` updates model
245
+ parameters, `validation` is evaluated after every epoch and drives scheduler,
246
+ early-stopping, and later model selection, and `test` is held out. The trainer
247
+ does not accept a test loader in `fit`; callers evaluate the held-out test set
248
+ separately after training. This deliberately corrects the legacy convention
249
+ where a loader named `test` was used for model selection and `val` represented
250
+ held-out testing.
251
+
252
+ ## Distributed execution
253
+
254
+ `gnn4colliders.distributed` contains the small DDP boundary used by the
255
+ application layer. `DistributedContext` reads the standard `torchrun`
256
+ environment (`RANK`, `LOCAL_RANK`, and `WORLD_SIZE`), selects the rank-local
257
+ device, and owns process-group cleanup. Graph samples are sharded before
258
+ batching; training may pad rank shards for equal step counts, while validation
259
+ and prediction use unpadded shards so events are not counted twice.
260
+
261
+ The configured graph `batch_size` is per process. DDP wraps an otherwise
262
+ ordinary model after device placement, and checkpoint state is normalized to
263
+ the underlying model keys. Loss gradients are synchronized by DDP; epoch
264
+ metrics and evaluation outputs are gathered across ranks. Rank 0 writes
265
+ resolved configuration, checkpoints, and NPZ predictions. Moderate-size
266
+ prediction gathering is in-memory; streaming/sharded output is a future
267
+ extension.
268
+
269
+
270
  ## Randomness, external services, and coupling
271
 
272
  The CLI exposes `--seed`, but `main` passes it to model construction rather
 
317
 
318
  ## 3. De facto interfaces to preserve
319
 
320
+ ### Metadata-aware dataset boundary
321
+
322
+ The rewrite uses named `EventMetadata` (`fold`, `weight`, and stable
323
+ `sample_id`) instead of exposing the legacy positional tracking tensor.
324
+ `GraphSample`, `GraphBatch`, `SplitDefinition`, and `GraphDataLoader` form the
325
+ ROOT-GNN orchestration boundary. Graph caches carry feature, graph, and cache
326
+ schema versions and reject incompatible artifacts before loading.
327
+
328
+ The current cache implementation stores processed `GraphSample` values (the
329
+ Level-2 cache). The separation from `RootEventDataset` is intentional: a
330
+ future Level-1 cache can store normalized `EventSample`/feature data for
331
+ sequence or transformer representations without requiring DGL graph caches.
332
+
333
  The rewrite's shared data boundary is `gnn4colliders.data`: it reads selected
334
  ROOT/Awkward branches and returns architecture-neutral event samples. Feature
335
  construction and graph building remain separate downstream boundaries, so the
336
  same samples can be reused by non-graph model families.
337
 
338
+ ### Configuration and CLI boundary
339
+
340
+ Hydra composes semantic YAML groups under `configs/` and passes the resolved
341
+ configuration to explicit application factories in `gnn4colliders.config`.
342
+ Those factories allow-list supported models, tasks, and trainer components;
343
+ YAML is never treated as an arbitrary Python import specification. The thin
344
+ `gnn4colliders` CLI selects `prepare`, `train`, `evaluate`, or `predict` and
345
+ delegates to the stable data, training, checkpoint, and inference APIs. A new
346
+ experiment should generally be a YAML change; new behavior belongs in Python.
347
+
348
  1. YAML `module`, `class`, `args`, plus runtime `sample_graph` and
349
  `sample_global` injection.
350
  2. Dataset items `(DGLGraph, label, tracking, global_features)`.
docs/migration.md CHANGED
@@ -86,8 +86,24 @@ padding. Compare against `RootDataset.save/load`, `LazyDataset`, and
86
  Treat `NONE`, `STEPS`, `FIXED`, and `NODE` as explicit features; do not hide
87
  the hardcoded fixed padding sizes.
88
 
 
 
 
 
 
 
89
  ## Phase 4 — active model parity
90
 
 
 
 
 
 
 
 
 
 
 
91
  Port `models.GCN.Edge_Network` first. Preserve constructor parameters,
92
  `forward(graph, global_feats)`, feature keys, processor order, MLP LayerNorm
93
  placement, and logits shape. Compare intermediate and final tensors on fixed
@@ -117,6 +133,19 @@ classes only with dedicated tests; do not substitute their reductions.
117
 
118
  ## Phase 6 — checkpoint and lifecycle
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  Create a checkpoint adapter preserving `model_epoch_<epoch>.pt` and keys
121
  `epoch`, `model_state_dict`, `optimizer_state_dict`, and `early_stop`
122
  ([`training_script.py:565-604`](../legacy/root_gnn_dgl/scripts/training_script.py)).
@@ -130,12 +159,22 @@ resume, restart, early termination, and `.npz` fields before distributed work.
130
 
131
  ## Phase 7 — CLI, inference, and export
132
 
 
 
 
 
 
 
 
 
 
 
133
  Build thin new applications around tested library interfaces in this order:
134
 
135
  1. preprocessing/cache generation (`scripts/prep_data.py`);
136
  2. training/evaluation (`scripts/training_script.py`);
137
  3. inference to `.npz` and ROOT (`scripts/inference.py`);
138
- 4. ONNX export after PyTorch parity (`scripts/export_onnx.py`).
139
 
140
  Use subprocess integration tests with tiny fixtures. Preserve CLI options only
141
  where they serve an active workflow; document removed diagnostic/cluster-only
@@ -143,20 +182,107 @@ options.
143
 
144
  ## Phase 8 — reproducibility and deployment
145
 
146
- Introduce one explicit seed policy covering Python, NumPy, Torch, sampling,
147
- augmentation, and clustering. Add deterministic-mode tests and document GPU
148
- nondeterminism. Isolate Slurm/NCCL, Podman-HPC, ROOT, and Hugging Face data
149
- download integrations behind adapters only after local behavior is stable.
 
 
 
 
 
 
 
 
 
150
 
151
  ## Checkpoint compatibility checklist
152
 
153
- - [ ] Load the checked-in multiclass pretrained checkpoint.
154
- - [ ] Load a legacy fine-tuning checkpoint after prefix normalization.
155
- - [ ] Resume optimizer and early-stop state.
156
- - [ ] Produce equivalent logits on a deterministic graph fixture.
157
- - [ ] Produce equivalent `.npz` score, label, and tracking fields.
158
- - [ ] Preserve ROOT scalar/vector score branch conventions if ROOT output stays.
159
 
160
  Known risks are documented in [`architecture.md`](architecture.md): edge order,
161
  self-loops, weight semantics, validation/test naming, padding, dynamic
162
  selection evaluation, reproducibility, and the experimental model/loss surface.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  Treat `NONE`, `STEPS`, `FIXED`, and `NODE` as explicit features; do not hide
87
  the hardcoded fixed padding sizes.
88
 
89
+ Task 7 establishes the metadata-aware orchestration boundary around this
90
+ phase: `EventMetadata`, `GraphSample`, `GraphBatch`, fold-based split
91
+ selection, deterministic batching, and a version-checked graph-sample cache.
92
+ The cache is deliberately Level 2; normalized event caching remains a future
93
+ extension so non-graph model families can reuse ROOT preprocessing.
94
+
95
  ## Phase 4 — active model parity
96
 
97
+ Task 8 adds the active `EdgeNetwork` and `FineTunedEdgeNetwork` under
98
+ `gnn4colliders.models.root_gnn`. The update order and MLP ordering follow the
99
+ legacy active path. The rewrite uses an explicit backbone/classifier boundary,
100
+ local DGL graph scope, and does not mutate global RNG state in constructors.
101
+ Model parity now covers fixed-weight pretraining and transfer paths, including
102
+ historical checkpoint prefixes. The legacy transfer implementation has an
103
+ active bug when nonempty globals are supplied (`Pretrained_Output` ignores its
104
+ argument); parity therefore characterizes its supported no-global path, while
105
+ the rewritten model supports both global and fallback modes.
106
+
107
  Port `models.GCN.Edge_Network` first. Preserve constructor parameters,
108
  `forward(graph, global_feats)`, feature keys, processor order, MLP LayerNorm
109
  placement, and logits shape. Compare intermediate and final tensors on fixed
 
133
 
134
  ## Phase 6 — checkpoint and lifecycle
135
 
136
+ Task 10 implemented the in-memory single-process training lifecycle before the
137
+ checkpoint portion of this phase: `Trainer`, explicit optimizer/scheduler
138
+ builders, `EarlyStopping`, reproducibility seeding, `GraphBatch.to`, and
139
+ epoch/history result types. Checkpoint persistence/resume and the Python
140
+ inference/evaluation and named NPZ/ROOT output layers are now implemented.
141
+ Distributed execution and CLI wiring were completed in the later phases.
142
+
143
+ Task 10 also establishes corrected split semantics: validation is evaluated
144
+ every epoch and is the only split used for model selection or early stopping;
145
+ the test split remains held out and is evaluated separately after fitting. The
146
+ legacy loader naming inversion (`test` used for selection and `val` held out)
147
+ is not carried into the rewrite.
148
+
149
  Create a checkpoint adapter preserving `model_epoch_<epoch>.pt` and keys
150
  `epoch`, `model_state_dict`, `optimizer_state_dict`, and `early_stop`
151
  ([`training_script.py:565-604`](../legacy/root_gnn_dgl/scripts/training_script.py)).
 
159
 
160
  ## Phase 7 — CLI, inference, and export
161
 
162
+ Task 12 implemented ordered prediction/evaluation, task-owned score
163
+ semantics, checkpoint weight-only loading, named metadata retention, NPZ
164
+ output, and explicit ROOT entry alignment. The semantic CLI and the validated
165
+ ROOT-GNN ONNX export adapter are implemented.
166
+
167
+ Task 13 adds Hydra composition and a single-process CLI around those existing
168
+ APIs. The current application data boundary is a versioned
169
+ `GraphSampleCache`; ROOT preparation converts events through the shared
170
+ feature and graph builders before writing that cache.
171
+
172
  Build thin new applications around tested library interfaces in this order:
173
 
174
  1. preprocessing/cache generation (`scripts/prep_data.py`);
175
  2. training/evaluation (`scripts/training_script.py`);
176
  3. inference to `.npz` and ROOT (`scripts/inference.py`);
177
+ 4. ONNX export after PyTorch parity (`gnn4colliders export`).
178
 
179
  Use subprocess integration tests with tiny fixtures. Preserve CLI options only
180
  where they serve an active workflow; document removed diagnostic/cluster-only
 
182
 
183
  ## Phase 8 — reproducibility and deployment
184
 
185
+ Task 14 adds the initial deployment boundary: CPU/GPU DDP through standard
186
+ `torchrun` variables, rank-local graph-sample sharding, global metric/output
187
+ gathering, rank-0 checkpoint/config writing, and Perlmutter-oriented Slurm
188
+ examples. Evaluation deliberately avoids sampler padding duplicates. The
189
+ remaining follow-up is a streaming or sharded output path for very large
190
+ distributed inference jobs.
191
+
192
+ The seed policy remains explicit: the configured seed is offset by rank for
193
+ process-local randomness, while distributed sample assignment is derived from
194
+ the configured seed, world size, and epoch. GPU kernel nondeterminism and
195
+ exact per-rank RNG checkpoint replay remain environment-dependent. Slurm/NCCL,
196
+ Podman-HPC, ROOT, and Hugging Face integrations stay in launcher/adapters
197
+ rather than package code.
198
 
199
  ## Checkpoint compatibility checklist
200
 
201
+ - [x] Load a checked-in or generated multiclass pretrained checkpoint.
202
+ - [x] Load a legacy fine-tuning checkpoint after prefix normalization.
203
+ - [x] Resume optimizer and early-stop state.
204
+ - [x] Produce equivalent logits on a deterministic graph fixture.
205
+ - [x] Produce equivalent `.npz` score, label, and metadata fields.
206
+ - [x] Preserve ROOT scalar/vector score branch conventions in the Python adapter.
207
 
208
  Known risks are documented in [`architecture.md`](architecture.md): edge order,
209
  self-loops, weight semantics, validation/test naming, padding, dynamic
210
  selection evaluation, reproducibility, and the experimental model/loss surface.
211
+
212
+ ## Migration closure status
213
+
214
+ ### Task 18 compatibility closure
215
+
216
+ The compatibility boundary is now explicit in `gnn4colliders.compat`.
217
+ Production ingestion stores named `EventMetadata`; legacy two-column tracking
218
+ is converted only at the compatibility boundary. Checkpoint prefix cleanup and
219
+ the historical ROOT-GNN `classify` to `classifier` mapping have one canonical
220
+ implementation. The new checkpoint schema and named NPZ output remain
221
+ canonical. See [`compatibility.md`](compatibility.md) for the supported and
222
+ intentionally unsupported historical artifacts.
223
+
224
+ The following matrix describes the supported new stack, rather than every
225
+ class that exists in `legacy/`:
226
+
227
+ | Legacy area | New-stack status | Notes |
228
+ | --- | --- | --- |
229
+ | ROOT/Awkward ingestion | migrated | `RootEventDataset` returns `EventSample` in file/event order |
230
+ | node features | migrated + parity-tested | seven-column schema, `CALC_E`, ordering, scales, float32 |
231
+ | edge construction | migrated + parity-tested | directed source-major topology and `[deta,dphi,dR]` |
232
+ | graph cache | migrated | versioned `GraphSampleCache`; graph-level cache only |
233
+ | folds and weights | migrated | named `EventMetadata.fold` and `.weight` |
234
+ | batching | migrated | deterministic local loader and DDP sharding |
235
+ | legacy padding modes | deferred | no active new-stack consumer |
236
+ | `Edge_Network` | migrated + parity-tested | `EdgeNetwork`, raw logits |
237
+ | transfer/fine-tuning | migrated + parity-tested | frozen or trainable backbone |
238
+ | loss and metrics | migrated + parity-tested | task-owned weighted reductions and full-split AUC |
239
+ | training lifecycle | migrated | `Trainer`, validation semantics, scheduler, early stopping |
240
+ | checkpoints/resume | migrated | schema v1; historical weight/prefix adapter |
241
+ | inference/NPZ | migrated | named output fields and ordered accumulation |
242
+ | ROOT score output | compatibility adapter | Python API supported; CLI currently NPZ-only |
243
+ | DDP | migrated | torchrun boundary, rank-0 artifacts, gathered metrics |
244
+ | Slurm/Perlmutter | launcher examples | site policy remains outside package code |
245
+ | ONNX export | migrated for ROOT-GNN | tensor-only adapter, ONNX Runtime validation, and `export` CLI; raw graph tensors are the input contract |
246
+
247
+ ### Intentional redesigns
248
+
249
+ These are deliberate new-stack contracts, not accidental parity failures:
250
+
251
+ * `tracking[:, 0]` and `tracking[:, 1]` become named `metadata.fold` and
252
+ `metadata.weight`; public consumers do not depend on positional columns.
253
+ * Dynamic legacy YAML `module`/`class` construction becomes allow-listed
254
+ semantic Hydra configuration.
255
+ * The monolithic training script becomes `Task` + `Trainer` + checkpoint and
256
+ inference adapters.
257
+ * Graph state is scoped to the forward pass rather than relying on persistent
258
+ mutation of shared graph state.
259
+ * Model constructors do not mutate global RNG state; seeding is explicit in
260
+ the training/application boundary.
261
+ * Validation is the selection/early-stopping split and test is held out. This
262
+ corrects the legacy loader-name inversion.
263
+
264
+ Compatibility preserves externally observable scientific behavior where it is
265
+ validated; it does not promise to preserve every legacy implementation bug.
266
+ The characterized legacy transfer path had a nonempty-global handling bug;
267
+ the rewrite supports named globals. Negative weights, rare empty graphs,
268
+ historical checkpoint variants, and legacy padding edge cases remain areas to
269
+ audit when a supported consumer requires them.
270
+
271
+ ## ROOT-GNN v1 completion checklist
272
+
273
+ - [x] active ROOT data path and graph cache
274
+ - [x] validated feature, graph, model, task, and metric behavior
275
+ - [x] train from scratch and fine-tune a pretrained backbone
276
+ - [x] resume new-stack checkpoints and load supported historical weights
277
+ - [x] evaluate and predict named outputs
278
+ - [x] single-process and DDP application boundaries
279
+ - [x] Perlmutter/Slurm launcher examples and profiling guidance
280
+ - [x] ROOT-GNN ONNX export and CPU Runtime parity
281
+ - [ ] streaming/sharded large-scale prediction output
282
+ - [ ] removal of frozen legacy reference
283
+ - [ ] ROOT-Transformer representation/model
284
+
285
+ ROOT-GNN v1 is complete when the checked-in new stack can prepare active data,
286
+ reproduce validated legacy behavior, train, transfer, resume, evaluate,
287
+ predict, and run single-process or DDP workflows. The remaining unchecked
288
+ items are intentionally deferred rather than undocumented promises.