fix(predict+pretrain): rdkit2D predict arg, issue #22, and atom/bond-mode DDP + float-loss fixes - #24
Conversation
The predict command can build rdkit_2d_normalized features (rdkit_2d_normalized_cuik_molmaker), whose featurization reads args.rdkit2D_normalization_type (kermt/data/molgraph.py) and must match the value baked into the checkpoint. The flag was defined only on the finetune parser, so `main.py predict --rdkit2D_normalization_type ...` failed with "unrecognized arguments". Add it to the predict parser, mirroring the finetune definition (choices fast/best/descriptastorus, default fast). Signed-off-by: Eva Xue <evax@nvidia.com>
…VIDIA-BioNeMo#22) predict() set args.bond_drop_rate = 0 on the shared args instance. Training and evaluation reuse one args object and graphs are rebuilt every epoch (caching off by default), so after the first validation pass bond dropout stayed disabled for every subsequent training epoch. Under DDP only rank 0 evaluates, so rank 0 trained with bond_drop_rate=0 while other ranks kept the configured rate, desyncing augmentation across ranks. Shallow-copy args before overriding bond_drop_rate so the override is local to the evaluation call and never leaks back into training or other ranks. Apply the same fix to fingerprint.do_generate, which had the identical pattern. Fixes NVIDIA-BioNeMo#22 Signed-off-by: Eva Xue <evax@nvidia.com>
…modes
The three pretrain trainers (KERMTTrainer, KERMTCMIMTrainer,
KERMTHybridTrainer) wrapped the model in DDP without find_unused_parameters.
In embedding_output_type=atom (or bond) mode the encoder produces only one
aggregation level, so the opposite branch's FFNs and the bond/atom vocab + FG
heads receive no gradient; DDP then aborts on the second iteration ("Expected
to have finished reduction ... parameters that were not used in producing
loss").
Enable find_unused_parameters only when embedding_output_type != 'both' (both
exercises every branch, so keep it off to avoid the per-iteration
graph-traversal overhead; static_graph is not viable because dynamic-depth
sampling varies the graph across steps).
Verified locally: hybrid + atom + DDP (WORLD_SIZE=1) crashed on iteration 2
before the change and trains a full epoch after it.
Signed-off-by: Eva Xue <evax@nvidia.com>
…m/bond) In embedding_output_type=atom (or bond) mode the vocab loss returns a plain float 0.0 for the branch that has no embeddings (e.g. bond-vocab and bond dist loss in atom mode). KERMTTrainer.iter called .item() on the task and dist loss components unconditionally, raising "AttributeError: 'float' object has no attribute 'item'" once the affected branch was logged. Guard every loss-component .item() with `if not isinstance(x, float) else x`, matching the idiom KERMTHybridTrainer already uses (which is why hybrid mode was unaffected). Covers the train accumulation, eval branch, and the wandb logging dict. Verified: vocab + atom + DDP (WORLD_SIZE=1) trains a full epoch + validation. Signed-off-by: Eva Xue <evax@nvidia.com>
Additional fixes on this branch: atom/bond-mode pretrainingWhile validating the predict changes I found two pre-existing bugs that break multi-GPU pretraining with 1. DDP aborts: "parameters that were not used in producing loss" (commit 87bc5a5)Symptom. Pretraining under DDP with Cause. In atom/bond mode the encoder produces only one aggregation level, so the opposite branch's FFNs and the bond/atom vocab + FG heads receive no gradient -- but all three trainers wrapped the model as 2. AttributeError: 'float' object has no attribute 'item' (commit 0898359)Symptom. Vocab-mode pretraining with Cause. In atom/bond mode the vocab loss returns a plain float Verification (local, single-GPU DDP via
|
Greptile SummaryThis PR fixes two correctness bugs: the
Confidence Score: 4/5All three fixes are targeted and correct; the copy.copy approach properly isolates the bond-dropout override, and the DDP parameter flag is gated on an argument that always has a default. The changes are small and well-reasoned. The copy.copy(args) fix correctly stops args mutation, the --rdkit2D_normalization_type addition is a clean mirror of the finetune definition, and find_unused_parameters is safely conditional. A minor style inconsistency remains in the dist-loss accumulation guards across kermttrainer.py where the PR introduced isinstance in some places but left type() != float unchanged in others. The dist-loss accumulation lines in task/kermttrainer.py (validation loop and iter loop) still use the old type() != float idiom while the newly touched metrics-dict lines in the same methods use isinstance. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Rank0 as DDP Rank 0
participant RankN as DDP Rank N
participant Predict as predict()
participant Fingerprint as do_generate()
Note over Rank0,RankN: Training epoch (before fix)
Rank0->>Predict: evaluate(args)
Predict->>Predict: "args.bond_drop_rate = 0 mutates shared args"
Predict-->>Rank0: results
Note over Rank0: Next epoch: bond_drop_rate=0 (wrong)
Note over RankN: Next epoch: bond_drop_rate=configured (desync)
Note over Rank0,RankN: Training epoch (after fix)
Rank0->>Predict: evaluate(args)
Predict->>Predict: "local_args = copy.copy(args)"
Predict->>Predict: "local_args.bond_drop_rate = 0 local only"
Predict-->>Rank0: results
Note over Rank0: Next epoch: bond_drop_rate=configured (correct)
Note over RankN: Next epoch: bond_drop_rate=configured (in sync)
Note over Rank0: Fingerprint path (same fix)
Rank0->>Fingerprint: do_generate(model, args, ...)
Fingerprint->>Fingerprint: "local_args = copy.copy(args)"
Fingerprint->>Fingerprint: "local_args.bond_drop_rate = 0 local only"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Rank0 as DDP Rank 0
participant RankN as DDP Rank N
participant Predict as predict()
participant Fingerprint as do_generate()
Note over Rank0,RankN: Training epoch (before fix)
Rank0->>Predict: evaluate(args)
Predict->>Predict: "args.bond_drop_rate = 0 mutates shared args"
Predict-->>Rank0: results
Note over Rank0: Next epoch: bond_drop_rate=0 (wrong)
Note over RankN: Next epoch: bond_drop_rate=configured (desync)
Note over Rank0,RankN: Training epoch (after fix)
Rank0->>Predict: evaluate(args)
Predict->>Predict: "local_args = copy.copy(args)"
Predict->>Predict: "local_args.bond_drop_rate = 0 local only"
Predict-->>Rank0: results
Note over Rank0: Next epoch: bond_drop_rate=configured (correct)
Note over RankN: Next epoch: bond_drop_rate=configured (in sync)
Note over Rank0: Fingerprint path (same fix)
Rank0->>Fingerprint: do_generate(model, args, ...)
Fingerprint->>Fingerprint: "local_args = copy.copy(args)"
Fingerprint->>Fingerprint: "local_args.bond_drop_rate = 0 local only"
|
Summary
Two related correctness fixes in the prediction / evaluation path:
main.py predictrejects--rdkit2D_normalization_type— the predict command couldn't use rdkit-2D-normalized features.bond_drop_rateis silently disabled after the first validation (and diverges across ranks under DDP) #22.Both are small, self-contained changes with no behavior change for existing correct runs.
Fix 1 — accept
--rdkit2D_normalization_typeon the predict parserpredictcan build rdkit-2D-normalized features (rdkit_2d_normalized_cuik_molmaker), whose featurization readsargs.rdkit2D_normalization_type(kermt/data/molgraph.py) and must match the normalization baked into the checkpoint. The flag was defined only on the finetune parser, so any predict run passing it failed with:Added the argument to
add_predict_args, mirroring the finetune definition (choicesfast/best/descriptastorus, defaultfast).Fix 2 — don't disable bond dropout on the shared
args(Fixes #22)predict()setargs.bond_drop_rate = 0on the sharedargsinstance. Training and evaluation reuse oneargsobject, and graphs are rebuilt every epoch (caching off by default), so after the first validation pass bond dropout stayed disabled for every subsequent training epoch. Under DDP, only rank 0 evaluates, so rank 0 trained withbond_drop_rate=0while the other ranks kept the configured rate, desyncing augmentation across ranks.Fix: shallow-copy
argsbefore overridingbond_drop_rate, so the override is local to the evaluation call and never leaks back into training or other ranks. Applied the same fix tofingerprint.do_generate, which had the identical pattern (impact there is benign since it's a standalone command, fixed for consistency).Changes
kermt/util/parsing.py--rdkit2D_normalization_typeto the predict parsertask/predict.pyargsbefore disabling bond dropout (+import copy)task/fingerprint.pydo_generate(+import copy)Testing
tests/integration/test_pretrain_finetune.py::test_pretrain_ddp_finetune— now PASSES end-to-end (pretrain → finetune → predict). This test previously failed at the predict step on the unrecognized--rdkit2D_normalization_typeargument.--rdkit2D_normalization_type descriptastorus, and that bothpredict()andfingerprint.do_generate()no longer mutate the caller'sargs.Fixes #22