Skip to content

Commit d884779

Browse files
authored
fix(train): share post-training bias adjustment (#5745)
## Summary - add dpmodel shared helpers for post-training output bias adjustment - route TF2/JAX/PT/PT-expt change-bias paths through the shared helper - enable change_bias_after_training in JAX and pt_expt training and preserve PT wrapper APIs ## Tests - ruff format . && ruff check . - pytest source/tests/common/test_dpmodel_train.py -q - pytest source/tests/pt_expt/test_change_bias.py::TestChangeBiasFittingStats -q - pytest source/tests/pt/test_training.py::TestModelChangeOutBiasFittingStat::test_fitting_stat_consistency -q <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an optional post-training output-bias adjustment step for supported training workflows. * Extended bias updates across both single-task and multi-task models. * Exposed shared bias-update helpers as part of the public backend-independent training API. * **Bug Fixes** * Ensured updated bias state is propagated correctly in distributed JAX runs and checkpoints are saved after the post-training adjustment. * **Tests** * Added coverage for bias-update helper behavior and JAX broadcast during post-training. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent ea80517 commit d884779

8 files changed

Lines changed: 299 additions & 43 deletions

File tree

deepmd/dpmodel/train/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
TrainingTask,
2121
TrainingTaskCollection,
2222
TrainStepResult,
23+
change_model_out_bias,
24+
change_model_out_bias_by_task,
2325
)
2426

2527
__all__ = [
@@ -34,6 +36,8 @@
3436
"TrainingTask",
3537
"TrainingTaskCollection",
3638
"TrainingTaskConfig",
39+
"change_model_out_bias",
40+
"change_model_out_bias_by_task",
3741
"iter_training_task_configs",
3842
"make_task_maps",
3943
"print_data_summaries",

deepmd/dpmodel/train/trainer.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@
2222
Callable,
2323
Iterator,
2424
Mapping,
25+
MutableMapping,
2526
Sequence,
2627
)
28+
from copy import (
29+
deepcopy,
30+
)
2731
from dataclasses import (
2832
dataclass,
2933
field,
@@ -38,6 +42,9 @@
3842

3943
import numpy as np
4044

45+
from deepmd.dpmodel.common import (
46+
to_numpy_array,
47+
)
4148
from deepmd.loggers.training import (
4249
format_training_message,
4350
format_training_message_per_task,
@@ -52,6 +59,53 @@
5259
DisplayResults = LossResults | TaskResults
5360

5461

62+
def change_model_out_bias(
63+
model: Any,
64+
sample_func: Callable[[], Any],
65+
*,
66+
bias_adjust_mode: str = "change-by-statistic",
67+
recompute_input_stats: bool = False,
68+
) -> Any:
69+
"""Change one model's output bias and log the before/after values."""
70+
old_bias = deepcopy(model.get_out_bias())
71+
model.change_out_bias(
72+
sample_func,
73+
bias_adjust_mode=bias_adjust_mode,
74+
)
75+
new_bias = deepcopy(model.get_out_bias())
76+
77+
if recompute_input_stats and bias_adjust_mode == "set-by-statistic":
78+
model.get_fitting_net().compute_input_stats(sample_func)
79+
80+
model_type_map = model.get_type_map()
81+
log.info(
82+
f"Change output bias of {model_type_map!s} "
83+
f"from {to_numpy_array(old_bias).reshape(-1)[: len(model_type_map)]!s} "
84+
f"to {to_numpy_array(new_bias).reshape(-1)[: len(model_type_map)]!s}."
85+
)
86+
return model
87+
88+
89+
def change_model_out_bias_by_task(
90+
models: MutableMapping[str, Any],
91+
sample_funcs: Mapping[str, Callable[[], Any]],
92+
model_keys: Sequence[str],
93+
*,
94+
bias_adjust_mode: str = "change-by-statistic",
95+
recompute_input_stats: bool = False,
96+
) -> MutableMapping[str, Any]:
97+
"""Change output bias for all requested training-task models."""
98+
log.info("Changing output bias after training.")
99+
for model_key in model_keys:
100+
models[model_key] = change_model_out_bias(
101+
models[model_key],
102+
sample_funcs[model_key],
103+
bias_adjust_mode=bias_adjust_mode,
104+
recompute_input_stats=recompute_input_stats,
105+
)
106+
return models
107+
108+
55109
@dataclass(frozen=True)
56110
class RankContext:
57111
"""Rank metadata used by a trainer.

deepmd/jax/train/trainer.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import platform
99
import shutil
10+
import time
1011
from collections.abc import (
1112
Mapping,
1213
)
@@ -41,6 +42,7 @@
4142
TrainingTask,
4243
TrainingTaskCollection,
4344
TrainStepResult,
45+
change_model_out_bias_by_task,
4446
)
4547
from deepmd.dpmodel.train.validation import (
4648
resolve_best_checkpoint_dir,
@@ -197,8 +199,8 @@ def __init__(
197199
self.tensorboard_log_dir = tr_data.get("tensorboard_log_dir", "log")
198200
self.tensorboard_freq = tr_data.get("tensorboard_freq", 1)
199201
self.mixed_prec = tr_data.get("mixed_precision", None)
200-
self.change_bias_after_training = tr_data.get(
201-
"change_bias_after_training", False
202+
self.change_bias_after_training = bool(
203+
tr_data.get("change_bias_after_training", False)
202204
)
203205
self.numb_fparam = (
204206
{key: model.get_dim_fparam() for key, model in self.models.items()}
@@ -731,6 +733,39 @@ def save_checkpoint(self, step: int) -> None:
731733
"""Persist a JAX checkpoint for a one-based step."""
732734
self._save_checkpoint(step)
733735

736+
def run(self, tasks: TrainingTaskCollection) -> None:
737+
"""Run JAX training through the backend-independent trainer loop."""
738+
log.info("Start to train %d steps.", self.num_steps)
739+
wall_start = time.time()
740+
super().run(tasks)
741+
if self.change_bias_after_training and self.num_steps > self.start_step:
742+
self._change_bias_after_training()
743+
if self.rank_context.is_chief:
744+
self.save_checkpoint(self.num_steps)
745+
log.info("Training finished. Total wall time: %.2fs", time.time() - wall_start)
746+
747+
def _change_bias_after_training(self) -> None:
748+
if self.rank_context.is_chief:
749+
change_model_out_bias_by_task(
750+
self.models,
751+
self._sample_funcs,
752+
self.model_keys,
753+
bias_adjust_mode="change-by-statistic",
754+
)
755+
if self.rank_context.world_size <= 1:
756+
return
757+
from jax.experimental import (
758+
multihost_utils,
759+
)
760+
761+
for model_key in self.model_keys:
762+
_, state = nnx.split(self.models[model_key])
763+
state = multihost_utils.broadcast_one_to_all(
764+
state.to_pure_dict(),
765+
is_source=self.rank_context.is_chief,
766+
)
767+
nnx.update(self.models[model_key], state)
768+
734769
def run_full_validation(
735770
self,
736771
*,

deepmd/pt/train/training.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
from deepmd.common import (
2929
symlink_prefix_files,
3030
)
31+
from deepmd.dpmodel.train import (
32+
change_model_out_bias,
33+
)
3134
from deepmd.dpmodel.utils import (
3235
compute_total_numb_batch,
3336
resolve_model_prob,
@@ -2618,24 +2621,13 @@ def model_change_out_bias(
26182621
_sample_func: Callable[[], Any],
26192622
_bias_adjust_mode: str = "change-by-statistic",
26202623
) -> Any:
2621-
old_bias = deepcopy(_model.get_out_bias())
2622-
_model.change_out_bias(
2623-
_sample_func,
2624-
bias_adjust_mode=_bias_adjust_mode,
2625-
)
2626-
new_bias = deepcopy(_model.get_out_bias())
2627-
26282624
from deepmd.pt.model.model.dp_model import (
26292625
DPModelCommon,
26302626
)
26312627

2632-
if isinstance(_model, DPModelCommon) and _bias_adjust_mode == "set-by-statistic":
2633-
_model.get_fitting_net().compute_input_stats(_sample_func)
2634-
2635-
model_type_map = _model.get_type_map()
2636-
log.info(
2637-
f"Change output bias of {model_type_map!s} "
2638-
f"from {to_numpy_array(old_bias).reshape(-1)[: len(model_type_map)]!s} "
2639-
f"to {to_numpy_array(new_bias).reshape(-1)[: len(model_type_map)]!s}."
2628+
return change_model_out_bias(
2629+
_model,
2630+
_sample_func,
2631+
bias_adjust_mode=_bias_adjust_mode,
2632+
recompute_input_stats=isinstance(_model, DPModelCommon),
26402633
)
2641-
return _model

deepmd/pt_expt/train/training.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@
2424
import torch
2525
import torch.distributed as dist
2626

27-
from deepmd.dpmodel.common import (
28-
to_numpy_array,
29-
)
3027
from deepmd.dpmodel.train import (
3128
DEFAULT_TASK_KEY,
3229
AbstractTrainer,
@@ -35,6 +32,8 @@
3532
TrainingTask,
3633
TrainingTaskCollection,
3734
TrainStepResult,
35+
change_model_out_bias,
36+
change_model_out_bias_by_task,
3837
)
3938
from deepmd.dpmodel.utils.batch import (
4039
normalize_batch,
@@ -1350,6 +1349,9 @@ def __init__(
13501349
self.max_ckpt_keep = int(training_params.get("max_ckpt_keep", 5))
13511350
self.display_in_training = training_params.get("disp_training", True)
13521351
self.timing_in_training = training_params.get("time_training", True)
1352+
self.change_bias_after_training = bool(
1353+
training_params.get("change_bias_after_training", False)
1354+
)
13531355

13541356
# Model ---------------------------------------------------------------
13551357
self.models: dict[str, torch.nn.Module] = {}
@@ -2139,8 +2141,25 @@ def run(self) -> None:
21392141
log.info("Start to train %d steps.", self.num_steps)
21402142
wall_start = time.time()
21412143
super().run(self.training_tasks)
2144+
if self.change_bias_after_training and self.num_steps > self.start_step:
2145+
self._change_bias_after_training()
2146+
if self.rank_context.is_chief:
2147+
self.save_checkpoint(self.num_steps)
21422148
log.info("Training finished. Total wall time: %.2fs", time.time() - wall_start)
21432149

2150+
def _change_bias_after_training(self) -> None:
2151+
if self.rank == 0:
2152+
change_model_out_bias_by_task(
2153+
self.models,
2154+
self._sample_funcs,
2155+
self.model_keys,
2156+
bias_adjust_mode="change-by-statistic",
2157+
)
2158+
if self.is_distributed:
2159+
for model_key in self.model_keys:
2160+
self._broadcast_model_stat(self.models[model_key])
2161+
self.model = self.models if self.multi_task else self.models[DEFAULT_TASK_KEY]
2162+
21442163
def run_full_validation(
21452164
self,
21462165
*,
@@ -2326,27 +2345,16 @@ def model_change_out_bias(
23262345
-------
23272346
The model with updated bias.
23282347
"""
2329-
old_bias = deepcopy(_model.get_out_bias())
2330-
_model.change_out_bias(
2331-
_sample_func,
2332-
bias_adjust_mode=_bias_adjust_mode,
2333-
)
2334-
new_bias = deepcopy(_model.get_out_bias())
2335-
23362348
from deepmd.dpmodel.model.dp_model import (
23372349
DPModelCommon,
23382350
)
23392351

2340-
if isinstance(_model, DPModelCommon) and _bias_adjust_mode == "set-by-statistic":
2341-
_model.get_fitting_net().compute_input_stats(_sample_func)
2342-
2343-
model_type_map = _model.get_type_map()
2344-
log.info(
2345-
f"Change output bias of {model_type_map!s} "
2346-
f"from {to_numpy_array(old_bias).reshape(-1)[: len(model_type_map)]!s} "
2347-
f"to {to_numpy_array(new_bias).reshape(-1)[: len(model_type_map)]!s}."
2352+
return change_model_out_bias(
2353+
_model,
2354+
_sample_func,
2355+
bias_adjust_mode=_bias_adjust_mode,
2356+
recompute_input_stats=isinstance(_model, DPModelCommon),
23482357
)
2349-
return _model
23502358

23512359

23522360
def _get_case_embd_config(

deepmd/tf2/train/trainer.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
TrainingTask,
4141
TrainingTaskCollection,
4242
TrainStepResult,
43+
change_model_out_bias_by_task,
4344
)
4445
from deepmd.dpmodel.utils.batch import (
4546
normalize_batch,
@@ -1261,12 +1262,12 @@ def _write_tensorboard_step(
12611262
self.summary_writer.flush()
12621263

12631264
def _change_bias_after_training(self) -> None:
1264-
log.info("Changing output bias after training.")
1265-
for model_key in self.model_keys:
1266-
self.models[model_key].change_out_bias(
1267-
self._sample_funcs[model_key],
1268-
bias_adjust_mode="change-by-statistic",
1269-
)
1265+
change_model_out_bias_by_task(
1266+
self.models,
1267+
self._sample_funcs,
1268+
self.model_keys,
1269+
bias_adjust_mode="change-by-statistic",
1270+
)
12701271

12711272
def get_data(
12721273
self,

0 commit comments

Comments
 (0)