Skip to content

Commit c67b7f7

Browse files
njzjzpre-commit-ci[bot]njzjz-botCopilot
authored
fix(pt/pd): fix incompatibility between AutoBatchSize and eval hooks (#5181)
Fix #5180. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Optional automatic OOM-retry during evaluation that retries with adjusted batch size and a retry loop. * New public control to enable or disable OOM-retry mode for evaluation flows. * **Bug Fixes** * Evaluation retry now guarantees cleanup: evaluation hooks and OOM-retry mode are reliably cleared after success or failure. * **Tests** * Added unit tests verifying OOM-retry behavior, retry cleanup, and state/hook reset across attempts. <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/deepmodeling/deepmd-kit/pull/5181?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Jinzhe Zeng <njzjz@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: A bot of @njzjz <48687836+njzjz-bot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent c6f2479 commit c67b7f7

6 files changed

Lines changed: 446 additions & 52 deletions

File tree

deepmd/pd/infer/deep_eval.py

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
to_numpy_array,
6565
to_paddle_tensor,
6666
)
67+
from deepmd.utils.batch_size import (
68+
RetrySignal,
69+
)
6770
from deepmd.utils.econf_embd import (
6871
sort_element_type,
6972
)
@@ -830,19 +833,30 @@ def eval_descriptor(
830833
model = (
831834
self.dp.model["Default"] if isinstance(self.dp, ModelWrapper) else self.dp
832835
)
833-
model.set_eval_descriptor_hook(True)
834-
self.eval(
835-
coords,
836-
cells,
837-
atom_types,
838-
atomic=False,
839-
fparam=fparam,
840-
aparam=aparam,
841-
**kwargs,
842-
)
843-
descriptor = model.eval_descriptor()
844-
model.set_eval_descriptor_hook(False)
845-
return to_numpy_array(descriptor)
836+
while True:
837+
if self.auto_batch_size is not None:
838+
self.auto_batch_size.set_oom_retry_mode(True)
839+
model.set_eval_descriptor_hook(True)
840+
retry = False
841+
try:
842+
self.eval(
843+
coords,
844+
cells,
845+
atom_types,
846+
atomic=False,
847+
fparam=fparam,
848+
aparam=aparam,
849+
**kwargs,
850+
)
851+
descriptor = model.eval_descriptor()
852+
except RetrySignal:
853+
retry = True
854+
finally:
855+
model.set_eval_descriptor_hook(False)
856+
if self.auto_batch_size is not None:
857+
self.auto_batch_size.set_oom_retry_mode(False)
858+
if not retry:
859+
return to_numpy_array(descriptor)
846860

847861
def eval_fitting_last_layer(
848862
self,
@@ -885,16 +899,27 @@ def eval_fitting_last_layer(
885899
Fitting output before last layer.
886900
"""
887901
model = self.dp.model["Default"]
888-
model.set_eval_fitting_last_layer_hook(True)
889-
self.eval(
890-
coords,
891-
cells,
892-
atom_types,
893-
atomic=False,
894-
fparam=fparam,
895-
aparam=aparam,
896-
**kwargs,
897-
)
898-
fitting_net = model.eval_fitting_last_layer()
899-
model.set_eval_fitting_last_layer_hook(False)
900-
return to_numpy_array(fitting_net)
902+
while True:
903+
if self.auto_batch_size is not None:
904+
self.auto_batch_size.set_oom_retry_mode(True)
905+
model.set_eval_fitting_last_layer_hook(True)
906+
retry = False
907+
try:
908+
self.eval(
909+
coords,
910+
cells,
911+
atom_types,
912+
atomic=False,
913+
fparam=fparam,
914+
aparam=aparam,
915+
**kwargs,
916+
)
917+
fitting_net = model.eval_fitting_last_layer()
918+
except RetrySignal:
919+
retry = True
920+
finally:
921+
model.set_eval_fitting_last_layer_hook(False)
922+
if self.auto_batch_size is not None:
923+
self.auto_batch_size.set_oom_retry_mode(False)
924+
if not retry:
925+
return to_numpy_array(fitting_net)

deepmd/pt/infer/deep_eval.py

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
to_numpy_array,
6868
to_torch_tensor,
6969
)
70+
from deepmd.utils.batch_size import (
71+
RetrySignal,
72+
)
7073
from deepmd.utils.econf_embd import (
7174
sort_element_type,
7275
)
@@ -863,19 +866,30 @@ def eval_descriptor(
863866
Descriptors.
864867
"""
865868
model = self.dp.model["Default"]
866-
model.set_eval_descriptor_hook(True)
867-
self.eval(
868-
coords,
869-
cells,
870-
atom_types,
871-
atomic=False,
872-
fparam=fparam,
873-
aparam=aparam,
874-
**kwargs,
875-
)
876-
descriptor = model.eval_descriptor()
877-
model.set_eval_descriptor_hook(False)
878-
return to_numpy_array(descriptor)
869+
while True:
870+
if self.auto_batch_size is not None:
871+
self.auto_batch_size.set_oom_retry_mode(True)
872+
model.set_eval_descriptor_hook(True)
873+
retry = False
874+
try:
875+
self.eval(
876+
coords,
877+
cells,
878+
atom_types,
879+
atomic=False,
880+
fparam=fparam,
881+
aparam=aparam,
882+
**kwargs,
883+
)
884+
descriptor = model.eval_descriptor()
885+
except RetrySignal:
886+
retry = True
887+
finally:
888+
model.set_eval_descriptor_hook(False)
889+
if self.auto_batch_size is not None:
890+
self.auto_batch_size.set_oom_retry_mode(False)
891+
if not retry:
892+
return to_numpy_array(descriptor)
879893

880894
def eval_fitting_last_layer(
881895
self,
@@ -918,16 +932,27 @@ def eval_fitting_last_layer(
918932
Fitting output before last layer.
919933
"""
920934
model = self.dp.model["Default"]
921-
model.set_eval_fitting_last_layer_hook(True)
922-
self.eval(
923-
coords,
924-
cells,
925-
atom_types,
926-
atomic=False,
927-
fparam=fparam,
928-
aparam=aparam,
929-
**kwargs,
930-
)
931-
fitting_net = model.eval_fitting_last_layer()
932-
model.set_eval_fitting_last_layer_hook(False)
933-
return to_numpy_array(fitting_net)
935+
while True:
936+
if self.auto_batch_size is not None:
937+
self.auto_batch_size.set_oom_retry_mode(True)
938+
model.set_eval_fitting_last_layer_hook(True)
939+
retry = False
940+
try:
941+
self.eval(
942+
coords,
943+
cells,
944+
atom_types,
945+
atomic=False,
946+
fparam=fparam,
947+
aparam=aparam,
948+
**kwargs,
949+
)
950+
fitting_net = model.eval_fitting_last_layer()
951+
except RetrySignal:
952+
retry = True
953+
finally:
954+
model.set_eval_fitting_last_layer_hook(False)
955+
if self.auto_batch_size is not None:
956+
self.auto_batch_size.set_oom_retry_mode(False)
957+
if not retry:
958+
return to_numpy_array(fitting_net)

deepmd/utils/batch_size.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
log = logging.getLogger(__name__)
2323

2424

25+
class RetrySignal(Exception):
26+
"""Signal to retry execution after OOM error."""
27+
28+
2529
class AutoBatchSize(ABC):
2630
"""This class allows DeePMD-kit to automatically decide the maximum
2731
batch size that will not cause an OOM error.
@@ -85,6 +89,7 @@ def __init__(
8589
)
8690

8791
self.factor = factor
92+
self.oom_retry_mode = False
8893

8994
def execute(
9095
self, callable: Callable, start_index: int, natoms: int
@@ -135,6 +140,8 @@ def execute(
135140
) from e
136141
# adjust the next batch size
137142
self._adjust_batch_size(1.0 / self.factor)
143+
if self.oom_retry_mode:
144+
raise RetrySignal from e
138145
return 0, None
139146
else:
140147
n_tot = n_batch * natoms
@@ -292,3 +299,20 @@ def is_oom_error(self, e: Exception) -> bool:
292299
bool
293300
True if the exception is an OOM error
294301
"""
302+
303+
def set_oom_retry_mode(self, enable: bool) -> None:
304+
"""Set OOM retry mode.
305+
306+
In OOM retry mode, an OOM during execution may reduce the current
307+
batch size and raise :class:`RetrySignal` to indicate that execution
308+
should be retried.
309+
310+
Callers that want all data to be re-executed must catch
311+
:class:`RetrySignal` and restart the full evaluation themselves.
312+
313+
Parameters
314+
----------
315+
enable : bool
316+
True to enable OOM retry mode
317+
"""
318+
self.oom_retry_mode = enable
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# SPDX-License-Identifier: LGPL-3.0-or-later
2+
import unittest
3+
4+
from deepmd.utils.batch_size import (
5+
AutoBatchSize,
6+
RetrySignal,
7+
)
8+
from deepmd.utils.errors import (
9+
OutOfMemoryError,
10+
)
11+
12+
13+
class CustomizedAutoBatchSizeGPU(AutoBatchSize):
14+
def is_gpu_available(self) -> bool:
15+
return True
16+
17+
def is_oom_error(self, e):
18+
return isinstance(e, OutOfMemoryError)
19+
20+
21+
class TestOOMRetry(unittest.TestCase):
22+
def test_execute_oom_retry_mode_raises_retry_signal(self) -> None:
23+
auto_batch_size = CustomizedAutoBatchSizeGPU(256, 2.0)
24+
25+
oom = OutOfMemoryError("oom")
26+
27+
def executor(batch_size: int, start_index: int) -> tuple[int, None]:
28+
raise oom
29+
30+
auto_batch_size.set_oom_retry_mode(True)
31+
with self.assertRaises(RetrySignal) as context:
32+
auto_batch_size.execute(executor, 0, 1)
33+
self.assertIs(context.exception.__cause__, oom)
34+
self.assertEqual(auto_batch_size.current_batch_size, 128)
35+
36+
def test_execute_oom_retry_mode_false_returns_zero(self) -> None:
37+
auto_batch_size = CustomizedAutoBatchSizeGPU(256, 2.0)
38+
39+
def executor(batch_size: int, start_index: int) -> tuple[int, None]:
40+
raise OutOfMemoryError("oom")
41+
42+
auto_batch_size.set_oom_retry_mode(False)
43+
n_batch, result = auto_batch_size.execute(executor, 0, 1)
44+
self.assertEqual(n_batch, 0)
45+
self.assertIsNone(result)
46+
self.assertEqual(auto_batch_size.current_batch_size, 128)
47+
48+
49+
if __name__ == "__main__":
50+
unittest.main()

0 commit comments

Comments
 (0)