Summary
For a model that adds an analytical contribution at the model layer (SeZM + InnerPotential/ZBL bridging), bias_adjust_mode="set-by-statistic" fits the atomic output bias to the raw labels. Because the analytical term is then added on top of the atomic output at inference, the mean analytical contribution is counted twice.
change-by-statistic was recently corrected for this in #5910, which routes it through a complete model-level predictor. set-by-statistic was left as-is, so the two modes now disagree about what the bias represents for bridged models.
Mechanism
compute_output_stats chooses what gets fitted based on whether a predictor is supplied:
https://github.com/deepmodeling/deepmd-kit/blob/799252d40da5e4d9a9f48ef1444cd4936964a4ab/deepmd/pt/utils/stat.py#L678-L687
With model_forward=None the fit target is merged_output, i.e. the raw labels. set-by-statistic is exactly the branch that passes no predictor:
|
) |
|
self._store_out_stat(delta_bias, out_std, add=True) |
|
elif bias_adjust_mode == "set-by-statistic": |
|
bias_out, std_out = compute_output_stats( |
|
sample_merged, |
|
self.get_ntypes(), |
|
keys=self.bias_keys, |
|
stat_file_path=stat_file_path, |
|
rcond=self.rcond, |
|
preset_bias=self.preset_out_bias, |
|
stats_distinguish_types=self.get_compute_stats_distinguish_types(), |
|
intensive=self.get_intensive(), |
|
) |
|
self._store_out_stat(bias_out, std_out) |
|
else: |
|
raise RuntimeError("Unknown bias_adjust_mode mode: " + bias_adjust_mode) |
For a bridged model the label is the true total energy, analytical term included. So the per-type bias absorbs the mean analytical contribution, and the model then adds that contribution again during the forward pass.
Why the #5910 fix does not cover it
#5910 added a model-level change_out_bias override that short-circuits change-by-statistic through predict_atomic_outputs_for_stat, and delegates every other mode to the atomic model unchanged. Separately, the initial statistics path never reaches the model layer at all: compute_or_load_out_stat is an atomic-model method calling the atomic change_out_bias directly, so a model-level override cannot intercept it.
|
self.change_out_bias( |
|
merged, |
|
stat_file_path=stat_file_path, |
|
bias_adjust_mode="set-by-statistic", |
|
) |
Where this is reachable
Three call sites use set-by-statistic:
compute_or_load_out_stat — the initial output-statistics computation, run at the start of every training run (link above).
dp change-bias --mode set —
|
) |
|
multi_task = "model_dict" in model_params |
|
bias_adjust_mode = "change-by-statistic" if mode == "change" else "set-by-statistic" |
|
if multi_task: |
|
assert model_branch is not None, ( |
- The finetune path in
deepmd/pt/train/training.py around L955.
Case 1 is self-correcting in the sense that training can learn to compensate a poor initial bias, so the practical cost there is a worse starting point rather than a permanent error. Cases 2 and 3 apply the shift to an already-trained model, where nothing corrects it.
What I have not verified
- The magnitude of the effect. It is the mean analytical contribution over the calibration set, which for a short-range repulsive term at equilibrium geometries may be small. Somebody should measure it before deciding how urgent this is.
- Whether
DPZBLModel (the separate DPZBLLinearEnergyAtomicModel blending implementation used by dpmodel/pt_expt) is affected. It composes differently — the blend happens inside the atomic model, and it is a smooth switch rather than a plain sum — so the analysis above does not carry over directly.
- Whether the current behaviour is in fact intended for some workflow I have not considered.
This is not a regression from #5910; the same behaviour is present before it. It surfaced while reviewing that PR, because fixing one mode made the asymmetry between the two visible.
Possible direction
Neither existing mode expresses the right thing. Passing the model-level predictor to set-by-statistic would just turn it into change-by-statistic. What the bridged case needs is to subtract only the analytical contribution from the labels and then fit absolutely — a third behaviour. A narrower alternative is to reject set-by-statistic for models carrying a model-level analytical term, so the wrong number is never silently produced.
Whichever direction is taken, a regression test in the shape of test_change_out_bias_is_invariant_for_self_labels from #5910 would pin it: label the data with the model's own output, apply the mode, and assert the resulting predictions still reproduce the labels.
Summary
For a model that adds an analytical contribution at the model layer (SeZM +
InnerPotential/ZBL bridging),bias_adjust_mode="set-by-statistic"fits the atomic output bias to the raw labels. Because the analytical term is then added on top of the atomic output at inference, the mean analytical contribution is counted twice.change-by-statisticwas recently corrected for this in #5910, which routes it through a complete model-level predictor.set-by-statisticwas left as-is, so the two modes now disagree about what the bias represents for bridged models.Mechanism
compute_output_statschooses what gets fitted based on whether a predictor is supplied:https://github.com/deepmodeling/deepmd-kit/blob/799252d40da5e4d9a9f48ef1444cd4936964a4ab/deepmd/pt/utils/stat.py#L678-L687
With
model_forward=Nonethe fit target ismerged_output, i.e. the raw labels.set-by-statisticis exactly the branch that passes no predictor:deepmd-kit/deepmd/pt/model/atomic_model/base_atomic_model.py
Lines 630 to 645 in 799252d
For a bridged model the label is the true total energy, analytical term included. So the per-type bias absorbs the mean analytical contribution, and the model then adds that contribution again during the forward pass.
Why the #5910 fix does not cover it
#5910 added a model-level
change_out_biasoverride that short-circuitschange-by-statisticthroughpredict_atomic_outputs_for_stat, and delegates every other mode to the atomic model unchanged. Separately, the initial statistics path never reaches the model layer at all:compute_or_load_out_statis an atomic-model method calling the atomicchange_out_biasdirectly, so a model-level override cannot intercept it.deepmd-kit/deepmd/pt/model/atomic_model/base_atomic_model.py
Lines 565 to 569 in 799252d
Where this is reachable
Three call sites use
set-by-statistic:compute_or_load_out_stat— the initial output-statistics computation, run at the start of every training run (link above).dp change-bias --mode set—deepmd-kit/deepmd/pt/entrypoints/main.py
Lines 608 to 612 in 799252d
deepmd/pt/train/training.pyaround L955.Case 1 is self-correcting in the sense that training can learn to compensate a poor initial bias, so the practical cost there is a worse starting point rather than a permanent error. Cases 2 and 3 apply the shift to an already-trained model, where nothing corrects it.
What I have not verified
DPZBLModel(the separateDPZBLLinearEnergyAtomicModelblending implementation used by dpmodel/pt_expt) is affected. It composes differently — the blend happens inside the atomic model, and it is a smooth switch rather than a plain sum — so the analysis above does not carry over directly.This is not a regression from #5910; the same behaviour is present before it. It surfaced while reviewing that PR, because fixing one mode made the asymmetry between the two visible.
Possible direction
Neither existing mode expresses the right thing. Passing the model-level predictor to
set-by-statisticwould just turn it intochange-by-statistic. What the bridged case needs is to subtract only the analytical contribution from the labels and then fit absolutely — a third behaviour. A narrower alternative is to rejectset-by-statisticfor models carrying a model-level analytical term, so the wrong number is never silently produced.Whichever direction is taken, a regression test in the shape of
test_change_out_bias_is_invariant_for_self_labelsfrom #5910 would pin it: label the data with the model's own output, apply the mode, and assert the resulting predictions still reproduce the labels.