Skip to content

Commit 804cc57

Browse files
author
Han Wang
committed
feat(pt_expt): native spin supports multi-rank on the graph lower
pt_expt refused a with-comm artifact for EVERY ``NativeSpinModelKind``, making native spin single-rank only -- a divergence from pt, whose ``SeZMNativeSpinModel`` does NOT override ``supports_edge_parallel`` and so participates in the edge-parallel artifact like any energy model. Nothing about spin needs its own cross-rank exchange: the spin input is per-node and its ghost rows arrive via the LAMMPS ``sp`` forward-comm, so only the per-block ghost FEATURE refresh has to travel -- the same ``border_op`` the energy model already drives. - ``NativeSpinEnergyModel.forward_lower_graph_exportable_with_comm``: the 22-input ABI -- the non-comm spin prefix verbatim (``spin`` keeps positional slot 10) with the 8 comm tensors appended after the conditional fparam/aparam/charge_spin tail, exactly how the energy model appends them. Single make_fx trace, following the energy with-comm precedent. - ``_needs_with_comm_artifact``: native spin is admitted on the GRAPH lower and still refused on nlist, which has no spin with-comm wrapper at all. - The dyn-shape and sample-input builders learn the spin slot for the with-comm ABI. - C++ ``DeepSpinPTExpt::run_model_graph_with_comm`` (twin of the DeepPot one) plus the compute_inner branch: extended node set, device ``n_local`` for the owned-energy mask, all 8 comm tensors on CPU, and the same build-time pair-exclusion seam as the single-rank graph branch. The blanket multi-rank fail-fast becomes a narrow guard for archives frozen before native spin participated in the with-comm export. Tests: the gate (graph True / nlist False) and the 22-input ABI with ``force_mag`` and ``mask_mag`` surviving the with-comm trace. Two existing assertions encoded the old behaviour and now expect the artifact.
1 parent cadd737 commit 804cc57

7 files changed

Lines changed: 443 additions & 34 deletions

File tree

deepmd/pt_expt/model/native_spin_model.py

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ def forward_lower_graph_exportable(
133133
before the conditional ``fparam``/``aparam`` tail; the conditional
134134
``charge_spin`` tail follows at index 13 (combined native-spin +
135135
charge-spin FiLM models, review 3638047227; ``None`` otherwise).
136-
There is NO with-comm variant (single-rank only; multi-rank
137-
graph-spin is a follow-up).
136+
:meth:`forward_lower_graph_exportable_with_comm` extends this SAME
137+
prefix with the 8 comm tensors for multi-rank.
138138
139139
Two-layer make_fx trace, mirroring
140140
:meth:`~deepmd.pt_expt.model.ener_model.EnergyModel.forward_lower_graph_exportable`:
@@ -278,3 +278,163 @@ def fn(
278278
aparam,
279279
charge_spin,
280280
)
281+
282+
def forward_lower_graph_exportable_with_comm(
283+
self,
284+
atype: torch.Tensor,
285+
n_node: torch.Tensor,
286+
n_local: torch.Tensor,
287+
edge_index: torch.Tensor,
288+
edge_vec: torch.Tensor,
289+
edge_mask: torch.Tensor,
290+
destination_order: torch.Tensor,
291+
destination_row_ptr: torch.Tensor,
292+
source_order: torch.Tensor,
293+
source_row_ptr: torch.Tensor,
294+
spin: torch.Tensor,
295+
fparam: torch.Tensor | None,
296+
aparam: torch.Tensor | None,
297+
charge_spin: torch.Tensor | None,
298+
send_list: torch.Tensor,
299+
send_proc: torch.Tensor,
300+
recv_proc: torch.Tensor,
301+
send_num: torch.Tensor,
302+
recv_num: torch.Tensor,
303+
communicator: torch.Tensor,
304+
nlocal: torch.Tensor,
305+
nghost: torch.Tensor,
306+
do_atomic_virial: bool = False,
307+
**make_fx_kwargs: Any,
308+
) -> torch.nn.Module:
309+
"""Trace the multi-rank graph-spin lower into an exportable module.
310+
311+
The with-comm counterpart of
312+
:meth:`forward_lower_graph_exportable`: same positional prefix,
313+
``spin`` still at index 10, then the 8 comm tensors appended after
314+
the conditional ``fparam``/``aparam``/``charge_spin`` tail (indices
315+
14-21) -- exactly how
316+
:meth:`~deepmd.pt_expt.model.ener_model.EnergyModel.forward_lower_graph_exportable_with_comm`
317+
appends them for the energy model.
318+
319+
``spin`` is the EXTENDED per-node spin ``(N, 3)``: ghost rows carry
320+
their owner's spin, delivered by the LAMMPS ``sp`` forward-comm
321+
before the call, so the descriptor's spin embedding sees the same
322+
value on every rank that holds the node. The per-block ghost
323+
FEATURE refresh rides ``deepmd_export::border_op`` exactly as in the
324+
energy model; spin needs no border exchange of its own because it is
325+
an input, not a derived feature.
326+
327+
Single make_fx trace (the energy with-comm precedent), unlike the
328+
two-layer trace of the non-comm spin path: the comm-dict packing,
329+
the ``forward_common_lower_graph`` call with ``spin`` as a second
330+
autograd leaf, and the public-key translation all live in one traced
331+
``fn``.
332+
333+
Parameters
334+
----------
335+
atype, n_node, n_local, edge_index, edge_vec, edge_mask, destination_order, destination_row_ptr, source_order, source_row_ptr, spin, fparam, aparam, charge_spin
336+
As in :meth:`forward_lower_graph_exportable`.
337+
send_list, send_proc, recv_proc, send_num, recv_num, communicator, nlocal, nghost
338+
The 8 comm tensors, packed into ``comm_dict`` inside the traced
339+
function. Same runtime device contract as the energy model's:
340+
ALL 8 stay on CPU (host control metadata for ``border_op``),
341+
while the device-side owned count is the separate ``n_local``
342+
input at slot 2.
343+
do_atomic_virial
344+
Whether to also return ``atom_virial``.
345+
**make_fx_kwargs
346+
Extra keyword arguments forwarded to ``make_fx``.
347+
348+
Returns
349+
-------
350+
torch.nn.Module
351+
A traced module accepting the 22-input ABI above and returning
352+
the same public keys as :meth:`forward_lower_graph_exportable`
353+
(``atom_energy``, ``energy``, ``force``, ``force_mag``,
354+
``virial``, ``mask_mag``, plus ``atom_virial`` when requested).
355+
"""
356+
model = self
357+
358+
def fn(
359+
atype: torch.Tensor,
360+
n_node: torch.Tensor,
361+
n_local: torch.Tensor,
362+
edge_index: torch.Tensor,
363+
edge_vec: torch.Tensor,
364+
edge_mask: torch.Tensor,
365+
destination_order: torch.Tensor,
366+
destination_row_ptr: torch.Tensor,
367+
source_order: torch.Tensor,
368+
source_row_ptr: torch.Tensor,
369+
spin: torch.Tensor,
370+
fparam: torch.Tensor | None,
371+
aparam: torch.Tensor | None,
372+
charge_spin: torch.Tensor | None,
373+
send_list: torch.Tensor,
374+
send_proc: torch.Tensor,
375+
recv_proc: torch.Tensor,
376+
send_num: torch.Tensor,
377+
recv_num: torch.Tensor,
378+
communicator: torch.Tensor,
379+
nlocal: torch.Tensor,
380+
nghost: torch.Tensor,
381+
) -> dict[str, torch.Tensor]:
382+
comm_dict = {
383+
"send_list": send_list,
384+
"send_proc": send_proc,
385+
"recv_proc": recv_proc,
386+
"send_num": send_num,
387+
"recv_num": recv_num,
388+
"communicator": communicator,
389+
"nlocal": nlocal,
390+
"nghost": nghost,
391+
}
392+
model_ret = model.forward_common_lower_graph(
393+
atype,
394+
n_node,
395+
n_local,
396+
edge_index,
397+
edge_vec,
398+
edge_mask,
399+
destination_order,
400+
destination_row_ptr,
401+
source_order,
402+
source_row_ptr,
403+
destination_sorted=True,
404+
do_atomic_virial=do_atomic_virial,
405+
fparam=fparam,
406+
aparam=aparam,
407+
charge_spin=charge_spin,
408+
spin=spin,
409+
comm_dict=comm_dict,
410+
)
411+
# Same single-owner translation as the eager forward and the
412+
# non-comm lower, so mask_mag is emitted here too.
413+
return self._translate_eager_call(
414+
model_ret, atype, do_atomic_virial=do_atomic_virial
415+
)
416+
417+
return make_fx(fn, **make_fx_kwargs)(
418+
atype,
419+
n_node,
420+
n_local,
421+
edge_index,
422+
edge_vec,
423+
edge_mask,
424+
destination_order,
425+
destination_row_ptr,
426+
source_order,
427+
source_row_ptr,
428+
spin,
429+
fparam,
430+
aparam,
431+
charge_spin,
432+
send_list,
433+
send_proc,
434+
recv_proc,
435+
send_num,
436+
recv_num,
437+
communicator,
438+
nlocal,
439+
nghost,
440+
)

deepmd/pt_expt/utils/serialization.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,13 @@ def _needs_with_comm_artifact(
148148
(absent on descriptors, such as dpa2/dpa3, whose dense lower always
149149
supports comm — treated as ``True``).
150150
151-
FIRST rule (checked before any descriptor-based logic): the native-spin
152-
native-spin model (``NativeSpinEnergyModel``, type ``native_spin`` /
153-
``sezm_native_spin``) always returns ``False``, regardless of
154-
``lower_kind`` or the wrapped backbone descriptor's own
155-
``has_message_passing_across_ranks()``. Ghost-atom SPIN exchange across
156-
MPI ranks is not implemented -- the wrapper's graph-spin ``.pt2`` ABI
157-
(``forward_lower_graph_exportable``, spin at positional index 10) is
158-
single-rank only. The backbone's own (energy-only) descriptor may well
159-
report ``True`` for cross-rank message passing -- that capability
160-
belongs to the energy-only backbone, not to this wrapper's (unported)
161-
spin threading, so it must not leak through.
151+
Native spin participates on the GRAPH lower, matching pt's
152+
``SeZMModel.supports_edge_parallel`` (which ``SeZMNativeSpinModel`` does
153+
not override): the spin input is per-node and its ghost rows arrive via
154+
the LAMMPS ``sp`` forward-comm, so nothing about spin needs its own
155+
cross-rank exchange -- the per-block ghost FEATURE refresh is the same
156+
``border_op`` the energy model uses. It is excluded only on the dense
157+
(nlist) lower, which has no spin with-comm wrapper.
162158
163159
Parameters
164160
----------
@@ -180,7 +176,9 @@ def _needs_with_comm_artifact(
180176
# Cross-backend family test: the dpmodel and pt_expt concrete classes
181177
# are parallel factory products with no subclass relation, so the shared
182178
# marker base -- not a concrete class -- is the membership check.
183-
if isinstance(model, NativeSpinModelKind):
179+
# Native spin rides the GRAPH lower only; its dense lower has no
180+
# with-comm wrapper at all.
181+
if isinstance(model, NativeSpinModelKind) and lower_kind != "graph":
184182
return False
185183

186184
# Analytical bridging models are single-rank only (pt's
@@ -835,6 +833,7 @@ def _build_graph_dynamic_shapes(
835833

836834
def _build_graph_dynamic_shapes_with_comm(
837835
*sample_inputs: torch.Tensor | None,
836+
is_native_spin: bool = False,
838837
) -> tuple:
839838
"""Build dynamic-shape specs for the with-comm graph-form export.
840839
@@ -855,16 +854,23 @@ def _build_graph_dynamic_shapes_with_comm(
855854
source_row_ptr, fparam, aparam, charge_spin, send_list, send_proc,
856855
recv_proc, send_num, recv_num, communicator, nlocal, nghost)`` --
857856
21 entries matching ``forward_lower_graph_exportable_with_comm``.
857+
Native-spin ABI (``is_native_spin=True``): 22 entries, with ``spin``
858+
inserted at slot 10 and the conditional tail shifted to 11-13, so
859+
the comm block starts at 14.
860+
is_native_spin : bool
861+
Whether ``sample_inputs`` follows the native-spin positional ABI.
858862
859863
Returns
860864
-------
861865
tuple
862866
Per-input dynamic-shape specs (dicts of ``torch.export.Dim`` or
863867
``None``) in the same order as ``sample_inputs``.
864868
"""
865-
fparam = sample_inputs[10]
866-
aparam = sample_inputs[11]
867-
charge_spin = sample_inputs[12]
869+
tail_start = 11 if is_native_spin else 10
870+
spin = sample_inputs[10] if is_native_spin else None
871+
fparam = sample_inputs[tail_start]
872+
aparam = sample_inputs[tail_start + 1]
873+
charge_spin = sample_inputs[tail_start + 2]
868874
nframes_val = 1
869875
n_node_total_dim = torch.export.Dim("n_node_total", min=1)
870876
nedge_dim = torch.export.Dim("nedge", min=2)
@@ -879,6 +885,13 @@ def _build_graph_dynamic_shapes_with_comm(
879885
{0: n_node_total_dim + 1}, # destination_row_ptr: (N + 1,)
880886
{0: nedge_dim}, # source_order: (E,)
881887
{0: n_node_total_dim + 1}, # source_row_ptr: (N + 1,)
888+
# spin: (N, 3) — EXTENDED node axis, shares atype's symbol; present
889+
# only in the native-spin ABI, where it occupies slot 10.
890+
*(
891+
({0: n_node_total_dim} if spin is not None else None,)
892+
if is_native_spin
893+
else ()
894+
),
882895
{0: nframes_val} if fparam is not None else None, # fparam
883896
# aparam: (N, nda) — flat on the SAME extended node axis as atype
884897
# (owned prefix + ghost rows).
@@ -1672,6 +1685,7 @@ def _trace_and_export(
16721685
dtype=torch.float64,
16731686
edge_dtype=edge_dtype,
16741687
device=torch.device("cpu"),
1688+
want_spin=is_native_spin,
16751689
)
16761690
comm_inputs = _make_comm_sample_inputs(
16771691
nloc=nlocal_sample,
@@ -1687,7 +1701,9 @@ def _trace_and_export(
16871701
tracing_mode="symbolic",
16881702
_allow_non_fake_inputs=True,
16891703
)
1690-
dynamic_shapes = _build_graph_dynamic_shapes_with_comm(*sample_inputs)
1704+
dynamic_shapes = _build_graph_dynamic_shapes_with_comm(
1705+
*sample_inputs, is_native_spin=is_native_spin
1706+
)
16911707
else:
16921708
edge_dtype = (
16931709
torch.float32

source/api_cc/include/DeepSpinPTExpt.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,32 @@ class DeepSpinPTExpt : public DeepSpinBackend {
267267
const torch::Tensor& fparam,
268268
const torch::Tensor& aparam);
269269

270+
/**
271+
* @brief Run the native-spin parallel GRAPH artifact: run_model_graph's
272+
* ABI (spin at positional index 10) with the 8 border_op comm tensors
273+
* appended after the conditional fparam/aparam/charge_spin tail.
274+
*
275+
* ``spin`` is the EXTENDED per-node spin -- ghost rows carry their
276+
* owner's value from the LAMMPS ``sp`` forward-comm, so spin itself needs
277+
* no cross-rank exchange; only the per-block ghost FEATURE refresh rides
278+
* ``border_op``. Twin of ``DeepPotPTExpt::run_model_graph_with_comm``.
279+
*/
280+
std::vector<torch::Tensor> run_model_graph_with_comm(
281+
const torch::Tensor& atype,
282+
const torch::Tensor& n_node,
283+
const torch::Tensor& n_local,
284+
const torch::Tensor& edge_index,
285+
const torch::Tensor& edge_vec,
286+
const torch::Tensor& edge_mask,
287+
const torch::Tensor& destination_order,
288+
const torch::Tensor& destination_row_ptr,
289+
const torch::Tensor& source_order,
290+
const torch::Tensor& source_row_ptr,
291+
const torch::Tensor& spin,
292+
const torch::Tensor& fparam,
293+
const torch::Tensor& aparam,
294+
const std::vector<at::Tensor>& comm_tensors);
295+
270296
/**
271297
* @brief Run the native-spin parallel edge artifact: the energy edge
272298
* with-comm schema (coord and extended types span the extended node set)

0 commit comments

Comments
 (0)