Skip to content

set-by-statistic double-counts the model-level analytical contribution for bridged models #5927

Description

@wanghan-iapcm

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:

  1. compute_or_load_out_stat — the initial output-statistics computation, run at the start of every training run (link above).
  2. 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, (
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions