Skip to content

Commit 8c1d7e8

Browse files
committed
fix(pt): stabilize accelerated execution paths
1 parent 072748b commit 8c1d7e8

24 files changed

Lines changed: 286 additions & 78 deletions

deepmd/pt/entrypoints/freeze_pt2.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
annotations,
2424
)
2525

26+
import contextlib
2627
import ctypes
2728
import json
2829
import logging
@@ -33,12 +34,18 @@
3334
deepcopy,
3435
)
3536
from typing import (
37+
TYPE_CHECKING,
3638
Any,
3739
)
3840

3941
import numpy as np
4042
import torch
4143

44+
if TYPE_CHECKING:
45+
from collections.abc import (
46+
Iterator,
47+
)
48+
4249
from deepmd.dpmodel.utils.nlist import (
4350
build_neighbor_list,
4451
extend_coord_with_ghosts,
@@ -857,6 +864,7 @@ def _export_with_comm_artifact(
857864
# it defaults to exact float32.
858865
_FREEZE_KERNEL_LEVELS = {"DP_TRITON_INFER": "2", "DP_CUDA_INFER": "1"}
859866
_FREEZE_DISABLED_LEVELS = {"DP_CUTILE_INFER": "0", "DP_CUTE_INFER": "0"}
867+
_INFER_KERNEL_LEVELS = tuple(_FREEZE_KERNEL_LEVELS | _FREEZE_DISABLED_LEVELS)
860868

861869

862870
def _apply_kernel_level_defaults(target_device: torch.device) -> None:
@@ -869,12 +877,7 @@ def _apply_kernel_level_defaults(target_device: torch.device) -> None:
869877
Python-only eager backends and are disabled for every frozen archive.
870878
"""
871879
if target_device.type != "cuda":
872-
for name in (
873-
"DP_TRITON_INFER",
874-
"DP_CUDA_INFER",
875-
"DP_CUTILE_INFER",
876-
"DP_CUTE_INFER",
877-
):
880+
for name in _INFER_KERNEL_LEVELS:
878881
os.environ[name] = "0"
879882
log.info("Freezing for CPU with accelerator-only DPA4 paths disabled.")
880883
return
@@ -892,6 +895,21 @@ def _apply_kernel_level_defaults(target_device: torch.device) -> None:
892895
)
893896

894897

898+
@contextlib.contextmanager
899+
def _kernel_level_defaults(target_device: torch.device) -> Iterator[None]:
900+
"""Apply freeze-time kernel levels without changing the caller's environment."""
901+
saved = {name: os.environ.get(name) for name in _INFER_KERNEL_LEVELS}
902+
try:
903+
_apply_kernel_level_defaults(target_device)
904+
yield
905+
finally:
906+
for name, value in saved.items():
907+
if value is None:
908+
os.environ.pop(name, None)
909+
else:
910+
os.environ[name] = value
911+
912+
895913
def freeze_sezm_to_pt2(
896914
ckpt_path: str,
897915
out_path: str,
@@ -929,14 +947,31 @@ def freeze_sezm_to_pt2(
929947
are ``DP_TRITON_INFER=2`` and ``DP_CUDA_INFER=1``, which is the fastest
930948
combination that keeps every operator in exact float32.
931949
"""
950+
target_device = device if device is not None else DEVICE
951+
with _kernel_level_defaults(target_device):
952+
_freeze_sezm_to_pt2(
953+
ckpt_path,
954+
out_path,
955+
target_device=target_device,
956+
head=head,
957+
atomic_virial=atomic_virial,
958+
)
959+
960+
961+
def _freeze_sezm_to_pt2(
962+
ckpt_path: str,
963+
out_path: str,
964+
*,
965+
target_device: torch.device,
966+
head: str | None,
967+
atomic_virial: bool,
968+
) -> None:
969+
"""Build one AOTInductor archive under an established kernel policy."""
932970
from torch._inductor import (
933971
aoti_compile_and_package,
934972
)
935973
from torch._inductor import config as inductor_config
936974

937-
target_device = device if device is not None else DEVICE
938-
_apply_kernel_level_defaults(target_device)
939-
940975
raw = torch.load(ckpt_path, map_location="cpu", weights_only=False)
941976
state_dict, params = _extract_state_and_params(raw)
942977
state_dict, params = _select_model_head(state_dict, params, head)

deepmd/pt/model/descriptor/env_mat.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def prod_env_mat(
6666
radial_only: bool = False,
6767
protection: float = 0.0,
6868
use_exp_switch: bool = False,
69+
training: bool = False,
6970
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
7071
"""Generate smooth environment matrix from atom coordinates and other context.
7172
@@ -79,19 +80,26 @@ def prod_env_mat(
7980
- radial_only: Whether to return a full description or a radial-only descriptor.
8081
- protection: Protection parameter to prevent division by zero errors during calculations.
8182
- use_exp_switch: Whether to use the exponential switch function.
83+
- training: Whether the caller is in training mode. Training uses the eager
84+
formulation because force losses require higher-order differentiation.
8285
8386
Returns
8487
-------
8588
- env_mat: Shape is [nframes, natoms[1]*nnei*4].
8689
"""
8790
# Opt-in inference (``DP_TRITON_INFER >= 1``, CUDA): the fused Triton kernel
8891
# forms the environment matrix in one node-parallel pass and carries a
89-
# closed-form backward for the force path. Training (level 0) and the CPU
90-
# path keep the dense autograd chain below, which supports higher-order
92+
# closed-form backward for the force path. Training and the CPU path keep
93+
# the dense autograd chain below, which supports higher-order
9194
# differentiation. The block is nested under ``torch.jit.is_scripting`` so
9295
# the whole (non-scriptable) branch is pruned under ``torch.jit.script``.
9396
if not torch.jit.is_scripting():
94-
if TRITON_AVAILABLE and triton_infer_level() >= 1 and extended_coord.is_cuda:
97+
if (
98+
not training
99+
and TRITON_AVAILABLE
100+
and triton_infer_level() >= 1
101+
and extended_coord.is_cuda
102+
):
95103
return _env_mat_triton(
96104
extended_coord,
97105
nlist,

deepmd/pt/model/descriptor/repflows.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ def forward(
472472
self.e_rcut_smth,
473473
protection=self.env_protection,
474474
use_exp_switch=self.use_exp_switch,
475+
training=self.training,
475476
)
476477
nlist_mask = nlist != -1
477478
sw = torch.squeeze(sw, -1)
@@ -492,6 +493,7 @@ def forward(
492493
self.a_rcut_smth,
493494
protection=self.env_protection,
494495
use_exp_switch=self.use_exp_switch,
496+
training=self.training,
495497
)
496498
a_nlist_mask = a_nlist != -1
497499
a_sw = torch.squeeze(a_sw, -1)

deepmd/pt/model/descriptor/repformers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ def forward(
429429
self.rcut,
430430
self.rcut_smth,
431431
protection=self.env_protection,
432+
training=self.training,
432433
)
433434
nlist_mask = nlist != -1
434435
sw = torch.squeeze(sw, -1)

deepmd/pt/model/descriptor/se_a.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,7 @@ def forward(
779779
self.rcut,
780780
self.rcut_smth,
781781
protection=self.env_protection,
782+
training=self.training,
782783
)
783784

784785
dmatrix = dmatrix.view(-1, self.nnei, 4)

deepmd/pt/model/descriptor/se_atten.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ def forward(
781781
self.rcut,
782782
self.rcut_smth,
783783
protection=self.env_protection,
784+
training=self.training,
784785
)
785786
# nb x nloc x nnei
786787
exclude_mask = self.emask(nlist, extended_atype)

deepmd/pt/model/descriptor/se_r.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ def forward(
488488
self.rcut_smth,
489489
True,
490490
protection=self.env_protection,
491+
training=self.training,
491492
)
492493

493494
assert self.filter_layers is not None

deepmd/pt/model/descriptor/se_t.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,7 @@ def forward(
840840
self.rcut,
841841
self.rcut_smth,
842842
protection=self.env_protection,
843+
training=self.training,
843844
)
844845
dmatrix = dmatrix.view(-1, self.nnei, 4)
845846
nfnl = dmatrix.shape[0]

deepmd/pt/model/descriptor/se_t_tebd.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,7 @@ def forward(
936936
self.rcut,
937937
self.rcut_smth,
938938
protection=self.env_protection,
939+
training=self.training,
939940
)
940941
# dmatrix: [1/r, dx/r^2, dy/r^2, dz/r^2], sw: distance weighting
941942
# nb x nloc x nnei

deepmd/pt/model/descriptor/sezm_nn/wignerd.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
import torch
2424
import torch.nn as nn
25+
from packaging.version import (
26+
Version,
27+
)
2528

2629
from deepmd.pt.utils import (
2730
env,
@@ -39,6 +42,8 @@
3942
nvtx_range,
4043
)
4144

45+
_TORCH_RELEASE = Version(torch.__version__).release[:2]
46+
4247

4348
class CaseCoefficients(nn.Module):
4449
"""
@@ -418,6 +423,7 @@ def __init__(
418423
self.dtype = dtype
419424
self.device = env.DEVICE
420425
self.eps = float(eps)
426+
self._materialize_inverse_rotation = _TORCH_RELEASE == (2, 11)
421427
self.dim_full = (self.lmax + 1) ** 2
422428
self.poly_lmin = 11
423429
self.poly_offset = self.poly_lmin * self.poly_lmin
@@ -627,6 +633,12 @@ def forward(
627633
# Consumers address the inverse rotation through explicit strides or
628634
# PyTorch strided operators, so the transpose can share D_full's storage.
629635
Dt_full = D_full.transpose(-1, -2)
636+
if self._materialize_inverse_rotation:
637+
# PyTorch 2.11 Inductor cannot safely lower this escaping transpose
638+
# view after the slice assignments that assemble D_full. The
639+
# materialized layout keeps the compiled graph semantically
640+
# identical; later releases retain the shared-storage view.
641+
Dt_full = Dt_full.contiguous()
630642
return D_full, Dt_full
631643

632644
def forward_zonal(

0 commit comments

Comments
 (0)