Skip to content

Commit 26ad0fb

Browse files
authored
feat(tf2): add training workflow (#5735)
## Summary - add TensorFlow 2 training/freeze/compress entrypoints and trainer support - wire multi-task, validation, finetune/pretrain, TensorBoard, checkpointing, and bias adjustment paths - keep TF2 training atomic virial disabled and add performance optimizations, including DP_JIT-controlled lower-forward XLA compilation ## Validation - `python -m pytest source/tests/tf2/test_training.py -q` - `ruff check .` - `ruff format --check deepmd/tf2 source/tests/tf2` - `DP_JIT=1 srun --gres=gpu:1 dp --tf2 train input.json --skip-neighbor-stat` on `examples/water/se_e2_a` temp copy: XLA lower compiled; first step ~140s, steady windows ~0.0305 s/step through step 900 before timeout - `DP_JIT=1 srun --gres=gpu:1 dp --tf2 train input.json --skip-neighbor-stat` on fixed-selection dpa3 temp input: XLA lower compiled; first step ~227s, steady windows ~0.119 s/step through step 500 before timeout ## Notes - `DP_JIT` is opt-in for training lower forward only; the whole train/eval step is intentionally not XLA compiled because neighbor/outer training logic is too broad and previously hit unsupported XLA ops. - dpa3 lower JIT has high first-compile cost and high memory pressure, so benchmark numbers above separate warm steady-state from first-step compile cost. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Expanded TensorFlow 2 backend with new train, freeze, and checkpoint compression entry points, including full validation support. * Added multi-task model-branch selection for freeze/compress via `--head` / `--model-branch`. * Introduced optional TF2 JIT acceleration and TF2-specific automatic batch sizing. * **Bug Fixes** * Improved compression dtype handling for more consistent outputs. * Enabled additional TF2 runtime neighbor-statistics and corrected TF2 backend wiring so more workflows complete end-to-end. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 3d73c67 commit 26ad0fb

29 files changed

Lines changed: 4618 additions & 137 deletions

.github/workflows/test_python.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
restore-keys: |
6161
test2-durations-combined-${{ matrix.python }}-${{ github.sha }}
6262
test2-durations-combined-${{ matrix.python }}
63-
- run: pytest --cov=deepmd source/tests --splits 12 --group ${{ matrix.group }} --store-durations --clean-durations --durations-path=.test_durations --splitting-algorithm least_duration
63+
- run: pytest --cov=deepmd source/tests --ignore=source/tests/tf2 --splits 12 --group ${{ matrix.group }} --store-durations --clean-durations --durations-path=.test_durations --splitting-algorithm least_duration
6464
env:
6565
NUM_WORKERS: 0
6666
DP_CI_IMPORT_PADDLE_BEFORE_TF: 1
@@ -81,6 +81,10 @@ jobs:
8181
return "$status"
8282
}
8383
84+
run_pytest_allow_no_tests --cov=deepmd --cov-append \
85+
source/tests/tf2 \
86+
--splits 12 \
87+
--group ${{ matrix.group }}
8488
run_pytest_allow_no_tests --cov=deepmd --cov-append \
8589
source/tests/consistent/io/test_io.py \
8690
source/jax2tf_tests \

deepmd/backend/tf2.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ class TensorFlow2Backend(Backend):
3333
"""TensorFlow 2 eager backend."""
3434

3535
name = "TensorFlow2"
36-
features: ClassVar[Backend.Feature] = Backend.Feature.DEEP_EVAL | Backend.Feature.IO
36+
features: ClassVar[Backend.Feature] = (
37+
Backend.Feature.ENTRY_POINT
38+
| Backend.Feature.DEEP_EVAL
39+
| Backend.Feature.NEIGHBOR_STAT
40+
| Backend.Feature.IO
41+
)
3742
suffixes: ClassVar[list[str]] = [".savedmodeltf"]
3843

3944
@classmethod
@@ -45,7 +50,11 @@ def is_available(self) -> bool:
4550

4651
@property
4752
def entry_point_hook(self) -> Callable[["Namespace"], None]:
48-
raise NotImplementedError("Training entry point is not implemented for TF2")
53+
from deepmd.tf2.entrypoints.main import (
54+
main,
55+
)
56+
57+
return main
4958

5059
@property
5160
def deep_eval(self) -> type["DeepEvalBackend"]:
@@ -57,7 +66,11 @@ def deep_eval(self) -> type["DeepEvalBackend"]:
5766

5867
@property
5968
def neighbor_stat(self) -> type["NeighborStat"]:
60-
raise NotImplementedError("Neighbor statistics are not implemented for TF2")
69+
from deepmd.dpmodel.utils.neighbor_stat import (
70+
NeighborStat,
71+
)
72+
73+
return NeighborStat
6174

6275
@property
6376
def serialize_hook(self) -> Callable[[str], dict]:

deepmd/dpmodel/common.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ def get_xp_precision(
9090
raise ValueError(f"unsupported precision {precision} for {xp}")
9191

9292

93+
def to_numpy_dtype(dtype: Any) -> np.dtype:
94+
"""Normalize backend dtype objects to a NumPy dtype."""
95+
dtype = getattr(dtype, "as_numpy_dtype", dtype)
96+
try:
97+
return np.dtype(dtype)
98+
except TypeError:
99+
dtype_name = getattr(dtype, "name", None)
100+
if dtype_name is not None:
101+
return np.dtype(dtype_name)
102+
raise
103+
104+
93105
class NativeOP(ABC):
94106
"""The unit operation of a native model."""
95107

deepmd/dpmodel/descriptor/dpa1.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
cast_precision,
2929
get_xp_precision,
3030
to_numpy_array,
31+
to_numpy_dtype,
3132
)
3233
from deepmd.dpmodel.utils import (
3334
EmbeddingNet,
@@ -1448,7 +1449,7 @@ def enable_compression(
14481449
) -> None:
14491450
"""Store tabulated geometric embedding-net data."""
14501451
net = "filter_net"
1451-
dtype = self.mean.dtype
1452+
dtype = to_numpy_dtype(self.mean.dtype)
14521453
self.compress_info = [
14531454
np.asarray(
14541455
[

deepmd/dpmodel/descriptor/se_e2_a.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from deepmd.dpmodel.common import (
2323
cast_precision,
2424
to_numpy_array,
25+
to_numpy_dtype,
2526
)
2627
from deepmd.dpmodel.utils import (
2728
EmbeddingNet,
@@ -431,7 +432,7 @@ def _store_compress_data(
431432
"""Store tabulated embedding-net data in the descriptor state."""
432433
compress_data = []
433434
compress_info = []
434-
dtype = self.davg.dtype
435+
dtype = to_numpy_dtype(self.davg.dtype)
435436
ndim = 1 if self.type_one_side else 2
436437
for embedding_idx in range(self.ntypes**ndim):
437438
if self.type_one_side:

deepmd/dpmodel/descriptor/se_r.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
cast_precision,
2323
get_xp_precision,
2424
to_numpy_array,
25+
to_numpy_dtype,
2526
)
2627
from deepmd.dpmodel.utils import (
2728
EmbeddingNet,
@@ -410,7 +411,7 @@ def _store_compress_data(
410411
"""Store tabulated embedding-net data in the descriptor state."""
411412
compress_data = []
412413
compress_info = []
413-
dtype = self.davg.dtype
414+
dtype = to_numpy_dtype(self.davg.dtype)
414415
for embedding_idx in range(self.ntypes):
415416
net = "filter_-1_net_" + str(embedding_idx)
416417
if net not in table_data:

deepmd/main.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ def main_parser() -> argparse.ArgumentParser:
327327
"--output",
328328
type=str,
329329
default="frozen_model",
330-
help="Filename (prefix) of the output model file. TensorFlow backend: suffix is .pb; PyTorch backend: suffix is .pth; Paddle backend: suffix is .json and .pdiparams",
330+
help="Filename (prefix) of the output model file. TensorFlow backend: suffix is .pb; TensorFlow2 backend: suffix is .savedmodeltf; PyTorch backend: suffix is .pth; Paddle backend: suffix is .json and .pdiparams",
331331
)
332332
parser_frz.add_argument(
333333
"-n",
@@ -605,14 +605,14 @@ def main_parser() -> argparse.ArgumentParser:
605605
"--input",
606606
default="frozen_model",
607607
type=str,
608-
help="The original frozen model, which will be compressed by the code. Filename (prefix) of the input model file. TensorFlow backend: suffix is .pb; PyTorch backend: suffix is .pth; DPModel backend: suffix is .dp; JAX backend: suffix is .hlo or .jax",
608+
help="The original frozen model or checkpoint, which will be compressed by the code. Filename (prefix) of the input model file. TensorFlow backend: suffix is .pb; TensorFlow2 backend: .tf2 checkpoint directory or checkpoint prefix; PyTorch backend: suffix is .pth; DPModel backend: suffix is .dp; JAX backend: suffix is .hlo or .jax",
609609
)
610610
parser_compress.add_argument(
611611
"-o",
612612
"--output",
613613
default="frozen_model_compressed",
614614
type=str,
615-
help="The compressed model. Filename (prefix) of the output model file. TensorFlow backend: suffix is .pb; PyTorch backend: suffix is .pth; DPModel backend: suffix is .dp; JAX backend: suffix is .hlo or .jax",
615+
help="The compressed model. Filename (prefix) of the output model file. TensorFlow backend: suffix is .pb; TensorFlow2 backend: suffix is .savedmodeltf; PyTorch backend: suffix is .pth; DPModel backend: suffix is .dp; JAX backend: suffix is .hlo or .jax",
616616
)
617617
parser_compress.add_argument(
618618
"-s",
@@ -659,6 +659,13 @@ def main_parser() -> argparse.ArgumentParser:
659659
default=None,
660660
help="The training script of the input frozen model",
661661
)
662+
parser_compress.add_argument(
663+
"--head",
664+
"--model-branch",
665+
default=None,
666+
type=str,
667+
help="Task head (alias: model branch) to compress if in multi-task mode.",
668+
)
662669

663670
# * print docs script **************************************************************
664671
parsers_doc = subparsers.add_parser(

deepmd/tf2/atomic_model/dp_atomic_model.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
)
1212
from deepmd.tf2.env import (
1313
stop_gradient,
14+
tf,
1415
xp,
1516
)
1617
from deepmd.tf2.fitting.base_fitting import (
@@ -41,6 +42,13 @@ class tf2_atomic_model(dpmodel_atomic_model):
4142
base_fitting_cls = BaseFitting
4243
"""The base fitting class."""
4344

45+
@tf.autograph.experimental.do_not_convert
46+
def make_atom_mask(
47+
self,
48+
atype: xp.ndarray,
49+
) -> xp.ndarray:
50+
return atype >= 0
51+
4452
def forward_common_atomic(
4553
self,
4654
extended_coord: xp.ndarray,

deepmd/tf2/entrypoints/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# SPDX-License-Identifier: LGPL-3.0-or-later
2+
"""Entry points for the TensorFlow 2 backend."""
3+
4+
from deepmd.tf2.entrypoints.compress import (
5+
enable_compression,
6+
)
7+
from deepmd.tf2.entrypoints.freeze import (
8+
freeze,
9+
)
10+
from deepmd.tf2.entrypoints.train import (
11+
train,
12+
)
13+
14+
__all__ = ["enable_compression", "freeze", "train"]

deepmd/tf2/entrypoints/compress.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# SPDX-License-Identifier: LGPL-3.0-or-later
2+
"""Compress TensorFlow 2 checkpoints by tabulating embedding networks."""
3+
4+
from __future__ import (
5+
annotations,
6+
)
7+
8+
import logging
9+
from typing import (
10+
Any,
11+
)
12+
13+
from deepmd.backend.suffix import (
14+
format_model_suffix,
15+
)
16+
from deepmd.dpmodel.entrypoints.compress_common import (
17+
enable_model_compression,
18+
resolve_min_nbor_dist,
19+
)
20+
from deepmd.dpmodel.utils.update_sel import (
21+
UpdateSel,
22+
)
23+
from deepmd.tf2.entrypoints.freeze import (
24+
select_model_branch,
25+
)
26+
from deepmd.tf2.model.base_model import (
27+
BaseModel,
28+
)
29+
from deepmd.tf2.utils.serialization import (
30+
deserialize_to_file,
31+
serialize_from_file,
32+
)
33+
34+
log = logging.getLogger(__name__)
35+
36+
37+
def enable_compression(
38+
input_file: str,
39+
output: str,
40+
stride: float = 0.01,
41+
extrapolate: int = 5,
42+
check_frequency: int = -1,
43+
training_script: str | None = None,
44+
head: str | None = None,
45+
**kwargs: Any,
46+
) -> None:
47+
"""Compress a TF2 training checkpoint and export a SavedModel."""
48+
del kwargs
49+
output = format_model_suffix(
50+
output,
51+
preferred_backend="tf2",
52+
strict_prefer=True,
53+
)
54+
data = serialize_from_file(input_file)
55+
data = select_model_branch(data, head=head)
56+
model = BaseModel.deserialize(data["model"])
57+
min_nbor_dist = resolve_min_nbor_dist(
58+
model,
59+
[data],
60+
training_script,
61+
UpdateSel,
62+
)
63+
enable_model_compression(
64+
model,
65+
min_nbor_dist,
66+
stride,
67+
extrapolate,
68+
check_frequency,
69+
)
70+
71+
compressed_data = data.copy()
72+
compressed_data["model"] = model.serialize()
73+
compressed_data["min_nbor_dist"] = float(min_nbor_dist)
74+
deserialize_to_file(output, compressed_data)
75+
log.info("Compressed TF2 model saved to %s", output)

0 commit comments

Comments
 (0)