@@ -548,6 +548,11 @@ def _model_uses_graph_lower(model: torch.nn.Module) -> bool:
548548 so the compiled path matches eager training (which already default-flips to
549549 the carry-all graph forward); when False the dense ``forward_lower`` is
550550 compiled (se_e2_a / dpa2 / dpa3 / linear / zbl).
551+
552+ ASSUMPTION: training uses the default ``neighbor_graph_method`` (None). If a
553+ user-facing ``"legacy"`` opt-out is ever plumbed into the trainer, this gate
554+ must also honor it (else eager would run dense while the compiled path runs
555+ the graph lower, re-introducing the eager!=compiled divergence this fixes).
551556 """
552557 if not hasattr (model , "mixed_types" ):
553558 return False
@@ -1276,6 +1281,12 @@ def _forward_graph(
12761281 N = nframes * nloc
12771282 out : dict [str , torch .Tensor ] = {}
12781283 for key , val in result .items ():
1284+ # ``N != nframes`` distinguishes node-level keys (lead dim N) from
1285+ # frame-level keys (lead dim nf) by shape. DEGENERATE: when nloc==1,
1286+ # N == nframes, so node-level keys are NOT unravelled and stay
1287+ # (nf, *) instead of (nf, 1, *). Harmless for the varying-natoms
1288+ # trainer (nloc >> 1); a single-atom-per-frame system would need an
1289+ # explicit per-key category check instead of the shape heuristic.
12791290 if val is not None and val .shape [:1 ] == torch .Size ([N ]) and N != nframes :
12801291 out [key ] = val .reshape (nframes , nloc , * val .shape [1 :])
12811292 else :
0 commit comments