@@ -189,7 +189,8 @@ class DeepEval(DeepEvalBackend):
189189 at each eval call (CUDA: ``nv`` if importable; else ``vesin`` only when
190190 ``nf == 1`` and importable; else ``dense``). Explicit
191191 ``"dense"`` / ``"ase"`` / ``"cell"`` / ``"vesin"`` / ``"nv"``
192- choices are preserved.
192+ choices are preserved. The CPU-only ``cell`` search runs on the host;
193+ its graph tensors are transferred to the model device before inference.
193194 A non-default value on any other artifact raises at construction because
194195 the knob would silently do nothing there; use ``nlist_backend`` for the
195196 nlist path instead. All builders emit the same neighbor set, so the
@@ -2558,11 +2559,13 @@ def _build_eval_graph(
25582559 call-time via
25592560 :func:`~deepmd.pt_expt.utils.graph_builder.resolve_auto_graph_builder`
25602561 using the batch frame count (vesin only when ``nf == 1``);
2561- ``dense``/``ase`` run backend-agnostic (numpy); ``cell``/``vesin``/``nv``
2562- run on-device (torch, O(N)), and ``cell`` threads its search. All backends emit the SAME neighbor set
2562+ ``dense``/``ase`` run backend-agnostic (numpy), ``cell`` runs its
2563+ threaded search on the CPU, and ``vesin``/``nv`` run on the requested
2564+ device (torch, O(N)). All backends emit the SAME neighbor set
25632565 (carry-all, sel-free), so the selection is a pure performance choice
25642566 and results are unchanged. The result is canonicalized to the
2565- destination-major graph-form ``.pt2`` ABI after construction.
2567+ destination-major graph-form ``.pt2`` ABI after construction; the
2568+ caller transfers its fields to the model device.
25662569 """
25672570 method = self ._neighbor_graph_method
25682571 if method == "auto" :
@@ -2574,6 +2577,7 @@ def _build_eval_graph(
25742577 # pre-excluded ``edge_mask`` and never re-applies it (mirrors the C++
25752578 # ``applyPairExclusion`` and the eager dpmodel/pt_expt build path).
25762579 pair_excl = self ._model_pair_excl ()
2580+ builder_device = torch .device ("cpu" ) if method == "cell" else device
25772581 # The fused builder writes the whole destination-major payload from one
25782582 # search. It applies only where nothing has to be filtered or masked
25792583 # afterwards, because it has no stage in which to do so, and only for
@@ -2598,17 +2602,17 @@ def _build_eval_graph(
25982602 torch .as_tensor (
25992603 np .asarray (coord_input ).reshape (- 1 , 3 ),
26002604 dtype = torch .float64 ,
2601- device = device ,
2605+ device = builder_device ,
26022606 ),
26032607 torch .as_tensor (
26042608 np .asarray (atom_types ).reshape (- 1 ),
26052609 dtype = torch .int64 ,
2606- device = device ,
2610+ device = builder_device ,
26072611 ),
26082612 torch .as_tensor (
26092613 np .asarray (box_input ).reshape (3 , 3 ),
26102614 dtype = torch .float64 ,
2611- device = device ,
2615+ device = builder_device ,
26122616 )
26132617 if box_input is not None
26142618 else None ,
@@ -2642,12 +2646,14 @@ def _build_eval_graph(
26422646 pair_excl = pair_excl ,
26432647 )
26442648 if method in ("cell" , "vesin" , "nv" ):
2645- cc = torch .as_tensor (coord_input , dtype = torch .float64 , device = device )
2649+ cc = torch .as_tensor (
2650+ coord_input , dtype = torch .float64 , device = builder_device
2651+ )
26462652 aa = torch .as_tensor (
2647- np .asarray (atom_types ), dtype = torch .int64 , device = device
2653+ np .asarray (atom_types ), dtype = torch .int64 , device = builder_device
26482654 )
26492655 bb = (
2650- torch .as_tensor (box_input , dtype = torch .float64 , device = device )
2656+ torch .as_tensor (box_input , dtype = torch .float64 , device = builder_device )
26512657 if box_input is not None
26522658 else None
26532659 )
@@ -2703,10 +2709,10 @@ def _model_pair_excl(self) -> "PairExcludeMask | None":
27032709 FRESH numpy-backed mask.
27042710
27052711 A numpy ``type_mask`` converts cleanly onto whichever namespace/device the
2706- builder's ``atype`` uses (dense/ase pass numpy; vesin/nv pass torch). The
2707- dpmodel's own ``pair_excl`` is NOT reused: as a pt_expt module attribute
2708- its ``type_mask`` is a torch (possibly CUDA) buffer, which cannot convert
2709- to a numpy ``atype`` on the dense/ase build path.
2712+ builder's ``atype`` uses (dense/ase pass numpy; cell/ vesin/nv pass torch).
2713+ The dpmodel's own ``pair_excl`` is NOT reused: as a pt_expt module
2714+ attribute its ``type_mask`` is a torch (possibly CUDA) buffer, which
2715+ cannot convert to a numpy ``atype`` on the dense/ase build path.
27102716
27112717 Returns
27122718 -------
0 commit comments