Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions deepmd/pt/model/model/make_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,6 @@ def change_out_bias(
merged,
bias_adjust_mode=bias_adjust_mode,
)
if bias_adjust_mode == "set-by-statistic":
self.atomic_model.compute_fitting_input_stat(merged)

def forward_common_lower(
self,
Expand Down
7 changes: 7 additions & 0 deletions deepmd/pt/train/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,13 @@ def model_change_out_bias(
)
new_bias = deepcopy(_model.get_out_bias())

from deepmd.pt.model.model.dp_model import (
DPModelCommon,
)

if isinstance(_model, DPModelCommon) and _bias_adjust_mode == "set-by-statistic":
_model.get_fitting_net().compute_input_stats(_sample_func)
Comment thread
wanghan-iapcm marked this conversation as resolved.
Comment thread
wanghan-iapcm marked this conversation as resolved.

model_type_map = _model.get_type_map()
log.info(
f"Change output bias of {model_type_map!s} from {to_numpy_array(old_bias).reshape(-1)!s} to {to_numpy_array(new_bias).reshape(-1)!s}."
Expand Down
118 changes: 118 additions & 0 deletions source/tests/pt/test_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Path,
)

import numpy as np
import torch

from deepmd.pt.entrypoints.main import (
Expand Down Expand Up @@ -608,5 +609,122 @@ def tearDown(self) -> None:
DPTrainTest.tearDown(self)


class TestModelChangeOutBiasFittingStat(unittest.TestCase):
"""Verify model_change_out_bias produces the same fitting stat as the old code path.

The old code called compute_fitting_input_stat inside change_out_bias (make_model.py).
The new code calls get_fitting_net().compute_input_stats() separately in
model_change_out_bias (training.py). This test verifies they produce identical
out_bias, fparam_avg, and fparam_inv_std.
"""

def test_fitting_stat_consistency(self) -> None:
from deepmd.pt.model.model import get_model as get_model_pt
from deepmd.pt.model.model.ener_model import EnergyModel as EnergyModelPT
from deepmd.pt.train.training import (
model_change_out_bias,
)
from deepmd.pt.utils.utils import to_numpy_array as torch_to_numpy
from deepmd.pt.utils.utils import to_torch_tensor as numpy_to_torch
from deepmd.utils.argcheck import model_args as model_args_fn

# Build a model with numb_fparam=2 so fitting stat is non-trivial
model_params = model_args_fn().normalize_value(
{
"type_map": ["O", "H"],
"descriptor": {
"type": "se_e2_a",
"sel": [20, 20],
"rcut_smth": 0.50,
"rcut": 6.00,
"neuron": [3, 6],
"resnet_dt": False,
"axis_neuron": 2,
"precision": "float64",
"type_one_side": True,
"seed": 1,
},
"fitting_net": {
"neuron": [5, 5],
"resnet_dt": True,
"precision": "float64",
"seed": 1,
"numb_fparam": 2,
},
},
trim_pattern="_*",
)

# Create two identical models via serialize/deserialize
model_orig = get_model_pt(model_params)
serialized = model_orig.serialize()
model_a = EnergyModelPT.deserialize(deepcopy(serialized))
model_b = EnergyModelPT.deserialize(deepcopy(serialized))

# Build mock stat data with fparam
nframes = 4
natoms = 6
coords = np.random.default_rng(42).random((nframes, natoms, 3)) * 13.0
atype = np.array([[0, 0, 1, 1, 1, 1]] * nframes, dtype=np.int32)
box = np.tile(
np.eye(3, dtype=np.float64).reshape(1, 3, 3) * 13.0, (nframes, 1, 1)
)
natoms_data = np.array([[6, 6, 2, 4]] * nframes, dtype=np.int32)
energy = np.array([10.0, 20.0, 15.0, 25.0]).reshape(nframes, 1)
# fparam with varying values so mean != 0 and std != 0
fparam = np.array(
[[1.0, 3.0], [5.0, 7.0], [2.0, 8.0], [6.0, 4.0]], dtype=np.float64
)

merged = [
{
"coord": numpy_to_torch(coords),
"atype": numpy_to_torch(atype),
"atype_ext": numpy_to_torch(atype),
"box": numpy_to_torch(box),
"natoms": numpy_to_torch(natoms_data),
"energy": numpy_to_torch(energy),
"find_energy": np.float32(1.0),
"fparam": numpy_to_torch(fparam),
"find_fparam": np.float32(1.0),
}
]

# Model A: simulate the OLD code path
# old change_out_bias called both bias adjustment + compute_fitting_input_stat
model_a.change_out_bias(merged, bias_adjust_mode="set-by-statistic")
model_a.atomic_model.compute_fitting_input_stat(merged)

# Model B: use the NEW code path via model_change_out_bias
sample_func = lambda: merged # noqa: E731
model_change_out_bias(model_b, sample_func, "set-by-statistic")

# Compare out_bias
bias_a = torch_to_numpy(model_a.get_out_bias())
bias_b = torch_to_numpy(model_b.get_out_bias())
np.testing.assert_allclose(bias_a, bias_b, rtol=1e-10, atol=1e-10)

# Compare fparam_avg and fparam_inv_std
fit_a = model_a.get_fitting_net()
fit_b = model_b.get_fitting_net()
fparam_avg_a = torch_to_numpy(fit_a.fparam_avg)
fparam_avg_b = torch_to_numpy(fit_b.fparam_avg)
fparam_inv_std_a = torch_to_numpy(fit_a.fparam_inv_std)
fparam_inv_std_b = torch_to_numpy(fit_b.fparam_inv_std)

np.testing.assert_allclose(fparam_avg_a, fparam_avg_b, rtol=1e-10, atol=1e-10)
np.testing.assert_allclose(
fparam_inv_std_a, fparam_inv_std_b, rtol=1e-10, atol=1e-10
)

# Verify non-trivial: avg should not be zeros, inv_std should not be ones
assert not np.allclose(fparam_avg_a, 0.0), (
"fparam_avg is still zero — stat was not computed"
)
assert not np.allclose(fparam_inv_std_a, 1.0), (
"fparam_inv_std is still ones — stat was not computed"
)


if __name__ == "__main__":
unittest.main()
Loading