Skip to content

Fix double-indexing of batch in discrete_fm interpolate_edges - #2

Open
SID-6921 wants to merge 1 commit into
NVIDIA-BioNeMo:mainfrom
SID-6921:fix/discrete-fm-interpolate-edges-double-index
Open

SID-6921 wants to merge 1 commit into
NVIDIA-BioNeMo:mainfrom
SID-6921:fix/discrete-fm-interpolate-edges-double-index

Conversation

@SID-6921

@SID-6921 SID-6921 commented Nov 3, 2026

Copy link
Copy Markdown

What

discrete_fm.py's interpolate_edges reassigns batch = batch[mask_i] (correctly slicing the per-node graph-id array down to the upper-triangular edge positions), then immediately indexes the ALREADY-sliced result with mask_i again: self.interpolate(batch[mask_i], ...). mask_i holds raw node indices (range [0, num_nodes)), not edge positions, so this double-indexes an edge-length array with node indices.

The sibling implementation in discrete_diffusion.py's interpolate_edges (same method name, same docstring, same external reference) does this correctly with a single indexing operation: self.interpolate(batch[mask_i], edge_attr_triu, time), with no premature reassignment of batch.

interpolate_edges is live code: module.py:250 calls it and consumes its second return value (edge_attr_t) directly into batch[f"{interp_param.variable_name}_t"], so every discrete edge-feature (bond-type) flow-matching training step with more than 3 atoms in a molecule graph hits the corrupted path, misassigning which graph's diffusion/interpolation time each edge's corruption mask is drawn from.

Fix

Remove the premature batch = batch[mask_i] reassignment and the redundant second [mask_i] index, matching the sibling implementation exactly.

Verification

Reproduced the node/edge-index slicing logic standalone (not the full model, which needs GPU/PyG deps unavailable in this environment):

  • For graphs where num_upper_triangular_edges < num_nodes, the buggy double-indexing raises IndexError.
  • For graphs where num_upper_triangular_edges >= num_nodes (the common case for any molecule with more than 3 atoms), it doesn't crash — it silently returns the wrong graph id for some edges (confirmed with an 8-node, 2-graph example where one edge gets mislabeled into the wrong graph).

discrete_fm.py's interpolate_edges reassigned `batch = batch[mask_i]`
(correctly slicing the per-node graph-id array down to the upper-
triangular edge positions), then immediately indexed the ALREADY-sliced
result with `mask_i` again: `self.interpolate(batch[mask_i], ...)`.
`mask_i` holds raw node indices (range [0, num_nodes)), not edge
positions, so this is indexing an edge-length array with node indices.

The sibling implementation in discrete_diffusion.py's interpolate_edges
(same method name, same docstring, same external reference) does this
correctly with a single indexing operation: `self.interpolate(batch[mask_i],
edge_attr_triu, time)`, with no premature reassignment of `batch`.

Verified the two failure modes with a standalone node/edge-index
reproduction of the slicing logic (not the full model, which needs
GPU/PyG deps unavailable in this environment):
- For graphs where num_upper_triangular_edges < num_nodes, the second
  indexing raises IndexError.
- For graphs where num_upper_triangular_edges >= num_nodes (the common
  case for any molecule with >3 atoms), it does not crash -- it silently
  returns the wrong graph id for some edges (confirmed: an 8-node,
  2-graph example mislabels one edge's graph id).

interpolate_edges is live code: module.py:250 calls it and consumes its
second return value (edge_attr_t) directly into
batch[f"{interp_param.variable_name}_t"], so every discrete edge-feature
(bond-type) flow-matching training step with more than 3 atoms in a
molecule graph hits the corrupted path, misassigning which graph's
diffusion/interpolation time each edge's corruption mask is drawn from.

Fix: remove the premature `batch = batch[mask_i]` reassignment and the
redundant second `[mask_i]` index, matching the sibling implementation
exactly.

Signed-off-by: Siddhardha Nanda <99672439+SID-6921@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 3, 2026 15:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@SID-6921

Copy link
Copy Markdown
Author

Checking in — this has been open about a week with no review yet. Happy to address any feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants