Skip to content

feat(dpa-adapt): add grouped embedding for system-level property prediction#5741

Open
zirenjin wants to merge 282 commits into
deepmodeling:masterfrom
zirenjin:feat/grouped-embedding
Open

feat(dpa-adapt): add grouped embedding for system-level property prediction#5741
zirenjin wants to merge 282 commits into
deepmodeling:masterfrom
zirenjin:feat/grouped-embedding

Conversation

@zirenjin

@zirenjin zirenjin commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds grouped embedding support to DPA-ADAPT, enabling frame-level atomistic representations to be pooled into group-level embeddings for system-level property prediction.

The main use case is downstream adaptation where one training label corresponds to a collection of structures rather than a single frame, such as polymers, mixtures, reaction assemblies, or catalyst configurations.

Main Changes

  • Add grouped property model support with differentiable frame-to-group pooling.
  • Add group_id, weight, and pool_mask data fields for grouping frames and controlling atom-level pooling.
  • Add grouped data conversion utilities and CLI support.
  • Add grouped-aware dataloading and batch sampling so all frames from the same group stay together.
  • Support distributed training by ensuring each group is assigned to a single rank.
  • Add tests covering grouped data conversion, grouped sampling, grouped loss/model behavior, and DDP-safe batching.

Notes

This feature is intended for DPA-ADAPT tasks where the prediction target is defined at the system/sample level, while the input information is provided by multiple atomistic structures.

Summary by CodeRabbit

  • New Features
    • Added grouped-property training support with new group-level model and loss, including group-aware fitting and grouped-data batching.
    • Introduced grouped data tooling (writers, offline grouped dataset loading), plus a new mark-groups CLI command and grouped polymer dataset generation.
  • Bug Fixes
    • Hardened grouped pooling/masking for padded/virtual atoms and improved validation for per-group label and fparam consistency; distributed sampling now keeps groups intact.
  • Documentation
    • Added a guide for “Assembly Groups for Multi-Component Properties”.
  • Tests
    • Added extensive unit and end-to-end coverage for grouped training, batching, and serialization.

claude and others added 30 commits June 4, 2026 06:03
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…flow-cleanup

Fix CI dependency installation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dpa_tools quickstart demo bundle, convert() glob multi-match, docs cleanup
update readme and demo notebook
…R to deepmd/npy

- New module data/formula.py: parse_formula, infer_base_element,
  random_doping, formula_to_npy
- auto_convert gains fmt='formula' branch with 5 new keyword-only
  parameters (poscar, formula_col, property_col, base_element, sets)
- CLI: --poscar, --base-element, --formula-col, --property-col, --sets
- Exported via data/__init__.py and top-level dpa_tools/__init__.py
- Tests: 8 passing (parse, dopant, CSV conversion, header-skip)
- README updated with formula_to_npy examples
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.92857% with 90 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.63%. Comparing base (8de6596) to head (f86712e).
⚠️ Report is 64 commits behind head on master.

Files with missing lines Patch % Lines
deepmd/pt/model/model/group_property_model.py 74.62% 51 Missing ⚠️
deepmd/pt/model/task/group_property.py 87.61% 14 Missing ⚠️
deepmd/pt/loss/group_property.py 83.58% 11 Missing ⚠️
deepmd/pt/utils/grouped.py 90.47% 8 Missing ⚠️
deepmd/pt/utils/dataloader.py 92.30% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5741      +/-   ##
==========================================
- Coverage   81.29%   78.63%   -2.67%     
==========================================
  Files         990     1058      +68     
  Lines      111018   122339   +11321     
  Branches     4234     4411     +177     
==========================================
+ Hits        90250    96197    +5947     
- Misses      19240    24565    +5325     
- Partials     1528     1577      +49     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

CodeRabbit (correctness / data integrity):
- group_property_model: forward charge_spin into the descriptor; derive the
  default pool_mask from atype>=0 so padded/virtual atoms are excluded from
  frame embeddings; add deserialize() to match serialize().
- dataloader: partition grouped batches across ranks with an epoch-independent
  seed and reshuffle only each rank's own batch order, so ranks that wrap
  epochs at different times can no longer duplicate/drop groups; grouped.py
  rejects setups with fewer groups than ranks.
- grouped/_core: clear stale system dirs on overwrite; write fparam.npy for
  every group (schema order, defaults filled) when the manifest advertises a
  uniform fparam field.
- grouped/_convert: regenerate/remove stale pool_mask.npy on overwrite; drop
  unused loop var (Ruff B007).
- grouped/_offline: discover systems recursively (match mark_groups); thread
  the pool mask into offline descriptor pooling.
- finetuner: exclude virtual atoms from offline pooling; grouped evaluate()
  uses group-level labels.
- trainer: infer grouped numb_fparam before the fparam preflight so grouped
  fparam.npy files are validated.

CodeQL / pre-commit:
- remove deprecated dpa_adapt.data grouped shims and unused imports; break the
  finetuner<->_offline import cycle; drop unnecessary del and redundant
  annotation quotes; guard possibly-uninitialized test vars; isort/ruff/
  ruff-format/mdformat.
Comment thread source/tests/dpa_adapt/test_grouped_end_to_end.py Fixed
Comment thread source/tests/dpa_adapt/test_grouped_end_to_end.py Fixed
Comment thread source/tests/dpa_adapt/test_grouped_end_to_end.py Fixed
Comment thread source/tests/dpa_adapt/test_grouped_end_to_end.py Fixed
Comment thread source/tests/dpa_adapt/test_grouped_end_to_end.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
dpa_adapt/grouped/_core.py (2)

222-229: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restrict overrides to ComponentSpec data fields.

hasattr(component, key) also accepts methods like validate, so a typo or bad override can replace behavior instead of being rejected.

Proposed fix
+from dataclasses import (
+    dataclass,
+    field,
+    fields,
+)
...
     def add_component(
         self, component: ComponentSpec, **overrides: Any
     ) -> ComponentSpec:
+        valid_fields = {item.name for item in fields(ComponentSpec)}
         for key, value in overrides.items():
-            if not hasattr(component, key):
+            if key not in valid_fields:
                 raise TypeError(f"Unknown ComponentSpec field: {key}")
             setattr(component, key, value)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dpa_adapt/grouped/_core.py` around lines 222 - 229, The add_component method
is allowing overrides for any attribute that exists on ComponentSpec, including
methods like validate, which can overwrite behavior instead of rejecting bad
keys. Update add_component to validate overrides only against ComponentSpec’s
data fields (for example, its declared dataclass fields) and keep the TypeError
for any unknown or non-field key so only real component data can be overridden.

350-358: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Honor fparam_schema even when all values come from defaults.

_has_fparam() ignores self.fparam_schema, so calling set_fparam_schema() with defaults but no per-group fparam leaves tensor_fields.fparam as None and skips fparam.npy entirely.

Proposed fix
     def _has_fparam(self) -> bool:
-        return any(group.fparam for group in self.groups)
+        return bool(self.fparam_schema) or any(group.fparam for group in self.groups)

Also applies to: 383-405

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dpa_adapt/grouped/_core.py` around lines 350 - 358, The grouped adaptation
path is dropping fparam data whenever only defaults are provided because
`_has_fparam()` does not account for `self.fparam_schema`. Update the logic in
the grouped core flow (the `tensor_fields` setup and the related `fparam_schema`
handling in the same class, including the later save/load path) so
`set_fparam_schema()` alone is enough to enable `fparam` processing and
`fparam.npy` generation. Use the existing `fparam_schema` and `_has_fparam()`
symbols in `dpa_adapt/grouped/_core.py` to ensure `tensor_fields["fparam"]` is
populated whenever the schema is present, even if no per-group `fparam` values
exist.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deepmd/pt/model/model/group_property_model.py`:
- Around line 242-251: The neighbor-list construction in group_property_model’s
input handling is hard-coding mixed neighbor lists, which breaks descriptors
that need type-distinguished neighbors. Update the
extend_input_and_build_neighbor_list call inside the relevant model method to
use the descriptor’s mixed_types() result from the descriptor/model contract, or
add an explicit initialization-time check in the group_property_model path to
reject unsupported non-mixed descriptors. Keep the fix centered on the
mixed_types() behavior so the selected descriptor and neighbor-list mode stay
consistent.

In `@dpa_adapt/grouped/_core.py`:
- Around line 326-343: Sanitized group system paths can collide when different
group.key values map to the same _safe_name(), causing _write_group_system and
the manifest entries built in the self.groups loop to target the same directory.
Update the grouped/_core.py logic around _safe_name, systems_root,
_write_group_system, and _group_manifest so each group gets a unique system path
even after sanitization, for example by incorporating group_idx or another
stable disambiguator into the directory name before writing and recording the
manifest.

---

Outside diff comments:
In `@dpa_adapt/grouped/_core.py`:
- Around line 222-229: The add_component method is allowing overrides for any
attribute that exists on ComponentSpec, including methods like validate, which
can overwrite behavior instead of rejecting bad keys. Update add_component to
validate overrides only against ComponentSpec’s data fields (for example, its
declared dataclass fields) and keep the TypeError for any unknown or non-field
key so only real component data can be overridden.
- Around line 350-358: The grouped adaptation path is dropping fparam data
whenever only defaults are provided because `_has_fparam()` does not account for
`self.fparam_schema`. Update the logic in the grouped core flow (the
`tensor_fields` setup and the related `fparam_schema` handling in the same
class, including the later save/load path) so `set_fparam_schema()` alone is
enough to enable `fparam` processing and `fparam.npy` generation. Use the
existing `fparam_schema` and `_has_fparam()` symbols in
`dpa_adapt/grouped/_core.py` to ensure `tensor_fields["fparam"]` is populated
whenever the schema is present, even if no per-group `fparam` values exist.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 56fdff0e-02f8-4de0-aadb-a2b1ec2d5869

📥 Commits

Reviewing files that changed from the base of the PR and between 82c1d73 and 8bb0d6b.

📒 Files selected for processing (25)
  • deepmd/pt/loss/__init__.py
  • deepmd/pt/loss/group_property.py
  • deepmd/pt/model/model/group_property_model.py
  • deepmd/pt/model/task/group_property.py
  • deepmd/pt/train/training.py
  • deepmd/pt/utils/dataloader.py
  • deepmd/pt/utils/grouped.py
  • doc/dpa_adapt/assembly_scenarios.md
  • dpa_adapt/finetuner.py
  • dpa_adapt/grouped/_aggregation.py
  • dpa_adapt/grouped/_convert.py
  • dpa_adapt/grouped/_core.py
  • dpa_adapt/grouped/_offline.py
  • dpa_adapt/grouped/_polymer.py
  • dpa_adapt/trainer.py
  • source/tests/dpa_adapt/test_assemblies.py
  • source/tests/dpa_adapt/test_grouped_convert.py
  • source/tests/dpa_adapt/test_grouped_dataset.py
  • source/tests/dpa_adapt/test_grouped_end_to_end.py
  • source/tests/dpa_adapt/test_grouped_finetuner.py
  • source/tests/dpa_adapt/test_grouped_hardening.py
  • source/tests/dpa_adapt/test_polymer_builder.py
  • source/tests/dpa_adapt/test_pooling.py
  • source/tests/pt/test_group_property.py
  • source/tests/pt/test_group_property_hardening.py
✅ Files skipped from review due to trivial changes (1)
  • doc/dpa_adapt/assembly_scenarios.md
🚧 Files skipped from review as they are similar to previous changes (22)
  • deepmd/pt/loss/init.py
  • source/tests/dpa_adapt/test_grouped_finetuner.py
  • source/tests/pt/test_group_property.py
  • deepmd/pt/train/training.py
  • source/tests/dpa_adapt/test_pooling.py
  • source/tests/dpa_adapt/test_grouped_hardening.py
  • source/tests/dpa_adapt/test_grouped_dataset.py
  • source/tests/dpa_adapt/test_assemblies.py
  • source/tests/dpa_adapt/test_polymer_builder.py
  • dpa_adapt/grouped/_offline.py
  • deepmd/pt/utils/dataloader.py
  • source/tests/dpa_adapt/test_grouped_end_to_end.py
  • deepmd/pt/loss/group_property.py
  • dpa_adapt/trainer.py
  • deepmd/pt/utils/grouped.py
  • source/tests/dpa_adapt/test_grouped_convert.py
  • dpa_adapt/grouped/_polymer.py
  • deepmd/pt/model/task/group_property.py
  • source/tests/pt/test_group_property_hardening.py
  • dpa_adapt/finetuner.py
  • dpa_adapt/grouped/_aggregation.py
  • dpa_adapt/grouped/_convert.py

Comment thread deepmd/pt/model/model/group_property_model.py
Comment thread dpa_adapt/grouped/_core.py
zirenjin added 4 commits July 6, 2026 22:03
- isort: reorder imports in group_property_model.py and finetuner.py
- disallow-caps: spell the product name "DeePMD" (not "DeepMD") in the
  grouped module/doc/test comments and docstrings
- test_grouped_end_to_end: use pytest.importorskip so the backend names are
  always bound (resolves py/uninitialized-local-variable; try/except + skip
  isn't modelled as terminating by CodeQL).
- finetuner._fit_sklearn: return without a value in the grouped branch so the
  None-returning method no longer mixes implicit/explicit returns
  (py/mixed-returns).
The grouped property head pools an un-normalized frame/group embedding
concatenated with fparam (no descriptor-style input statistics). At that
input scale tanh saturates, so the head learns only its bias and collapses
predictions to the global label mean. Direct-overfit evidence: on the same
few samples, tanh plateaus at ~30 RMSE (constant output) while GELU reaches
<0.5 and fits distinct labels.

- deepmd/pt/model/task/group_property.py: default activation_function -> "gelu"
- deepmd/utils/argcheck.py: fitting_group_property defaults activation to gelu
  (property head keeps tanh)
- dpa_adapt/trainer.py: grouped config emits activation_function="gelu"
  (a user fitting_net_params override still wins)
- test: assert the generated grouped config uses gelu

@njzjz-bot njzjz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the large addition. I focused on the grouped-training integration and found several correctness issues that should be fixed before merge:

  1. Grouped frozen-sklearn extraction ignores real_atom_types.npy.
    The grouped writer intentionally writes type.raw as a uniform placeholder and stores the real per-frame atom types, including virtual -1 padding, in real_atom_types.npy. The frozen-sklearn descriptor extraction path still tiles system.data["atom_types"] for every frame, so heterogeneous grouped systems are described as if every atom had the first type. pool_mask only affects final pooling and cannot fix descriptor typing or neighbor-list construction.

  2. Masked offline pooling is not NaN-safe.
    The offline masked mean / sum / std paths multiply descriptors by the mask. If an excluded virtual atom has a non-finite descriptor, 0 * NaN remains NaN, and the later nan_to_num can zero/corrupt the whole pooled feature instead of dropping only the masked atoms. Please use the same torch.where(mask > 0, descriptor, 0)-style masking pattern as the grouped PT model path before reduction.

  3. Descriptor cache keys miss grouped inputs.
    The descriptor cache currently fingerprints coordinates/types/cells, but grouped frozen-sklearn features also depend on pool_mask.npy and, after fixing item 1, real_atom_types.npy. Changing group markers or mixed-type real atom types can silently reuse stale cached descriptors.

  4. Missing/HDF5 group_id handling is inconsistent between sampler and loss.
    The grouped dataloader falls back to an all-zero group id when filesystem group_id.npy cannot be loaded, treating the whole system as one group. The loss, however, falls back to one group per frame when group_id is absent. This can ignore the configured batch size, break DDP when the implicit single group is fewer than the number of ranks, and incorrectly treats HDF5 systems with valid in-data group_id as missing because the loader only scans set.* files.

  5. Grouped-mode detection only checks the first set of the first system.
    dpa_adapt decides whether to switch to group_property from a single set directory. If later systems/sets contain markers but the first one does not, grouped training is not enabled; if the first is grouped but later sets are missing markers, training fails later or becomes inconsistent. Please scan all train/valid set dirs and reject mixed marker presence with a clear error.

  6. group_reduce is implemented but not exposed in config validation.
    The fitting head supports group_reduce="mean"|"sum", but the argcheck schema reuses the property schema without adding this option, so users cannot set sum through normal input JSON even though direct Python tests cover it.

  7. Grouped frozen-sklearn does not support the CLI's comma-separated multi-target API.
    Non-grouped _load_labels() accepts list[str], and the CLI turns --target-key a,b into a list. The grouped route passes that list into GroupedDataset, which resolves a single string key and will fail on a list target.

  8. Several property-fitting options are accepted but ignored.
    The grouped fitting arg schema inherits property options, but GroupPropertyFittingNet swallows many of them via **kwargs (seed, numb_aparam, default_fparam, intensive, distinguish_types, resnet_dt, etc.). trainable list semantics are also collapsed to all(trainable). This makes normal configs look accepted while silently changing behavior.

I would recommend adding integration tests for: trained serialize/deserialize prediction equality, JSON config construction with group_reduce, grouped frozen-sklearn on mixed real_atom_types.npy, cache invalidation when masks/types change, HDF5 or in-data group_id, DDP/missing-group fallback, and grouped multi-target CLI input.

Reviewed by OpenClaw (model: custom-chat-jinzhezeng-group/gpt-5.5)

zirenjin added 7 commits July 9, 2026 02:33
FinetuneRuleBuilder already auto-selects a multitask pretrained checkpoints
only branch (and re-initializes the fitting net) when a single-task target
like GroupPropertyModel finetunes with no --model-branch/finetune_head set,
but this exact path -- direct DPA foundation-model checkpoint to
GroupPropertyModel -- had no test coverage. Add two tests: the auto-pick
+ random-fitting case, and documenting the deterministic first-branch pick
when several branches are available with no explicit selection.
…e group system names

- GroupPropertyModel.forward hard-coded mixed_types=True when building the
  neighbor list instead of calling self.mixed_types(), so descriptors that
  require type-distinguished neighbor lists silently got a mixed one.
- Assembly.write() sanitized group keys into system directory names without
  checking for collisions; two keys that sanitize to the same name (e.g.
  differing only in stripped punctuation) silently overwrote one another and
  the manifest recorded a duplicate path.

Addresses the two still-open threads from the PR deepmodeling#5741 review.
…, group_id fallback

Four correctness issues flagged in review, all specific to heterogeneous
grouped systems (padded/virtual atoms, uniform type.raw placeholder):

- frozen-sklearn extract_features() tiled the single, uniform atom_types
  array for every frame instead of reading the real, per-frame local types
  (with -1 padding) grouped systems store in real_atom_types.npy, so every
  atom in every frame was described as if it had the placeholder type.
  Added _real_atom_types_for_system() + remap_atom_types_preserving_padding()
  (which, unlike remap_atom_types, does not wrap -1 to the last checkpoint
  type via numpy negative-index fancy indexing).

- _pool_descriptor() multiplied the raw descriptor by the pool mask before
  reducing (mean/sum/std); a non-finite value on a masked/virtual atom kept
  0 * NaN == NaN and poisoned the whole frames pooled feature. Sanitize
  masked rows via torch.where(mask > 0, descrpt, 0) before any reduction,
  matching the pattern already used in GroupPropertyModel.forward.

- The descriptor cache fingerprinted coords/types/cells but not
  pool_mask.npy or real_atom_types.npy, so changing group markers or real
  atom types on otherwise-identical padded geometry silently reused a stale
  cached descriptor.

- GroupCompleteBatchSampler/GroupDistributedBatchSampler fell back to one
  giant group (all-zero group_id) when group_id.npy is missing, while
  GroupPropertyLoss falls back to one group per frame (torch.arange).  The
  mismatch let the sampler ignore batch_size and could break DDP by handing
  an implicit single group to more ranks than it has frames for. Also
  rewrote load_group_ids_for_system() to read set.* through the systems own
  DPPath dirs (already backend-resolved by DeepmdData) instead of a raw
  pathlib glob, so HDF5-backed systems are checked the same way as on-disk
  ones instead of an in-data group_id being silently reported as missing.
…st the first

_systems_are_grouped() only looked at the first set.* directory of the first
train system to decide whether to switch the fitting/loss to group_property.
If a later system had markers while the first did not, grouped mode stayed
off and that systems group labels were silently dropped; if the first had
markers but a later one did not, grouped mode turned on and then failed (or
trained inconsistently) once that system was reached. valid_systems was not
checked at all, so a grouped/ungrouped mismatch between train and valid went
unnoticed too.

Scan every set.* directory across both train_systems and valid_systems, and
raise a clear DPADataError on any mix of grouped/ungrouped sets or a
partially-marked set (some but not all of group_id/weight/pool_mask) instead
of guessing.
GroupPropertyFittingNet supports group_reduce="mean"|"sum" (the
frame-embedding -> group-embedding reduction), but fitting_group_property()
reused the property schema verbatim aside from the activation-function
default, with no group_reduce field. dargs strict-mode input.json validation
(the same check dp --pt train runs) rejected it as an unknown key, so
group_reduce="sum" was only reachable by constructing the model in Python
directly, never through normal config files.
…rouped fit

Non-grouped _load_labels() already accepts target_key as a list (the CLI
turns --target-key a,b into ["a", "b"]), stacking each keys column into a
(n_frames, n_keys) label array. GroupedDataset only accepted a single str
and resolved/loaded one property.npy-style file, so passing the same list
through the grouped route (_fit_sklearn_grouped already forwards whatever
target_key it was given) failed.

GroupedDataset.target_keys is now always a list; _read_system_group_rows
reads one label file per key from each set.* dir and stacks them per frame,
same convention as _load_labels (single key keeps its original shape,
multiple keys are column-stacked) -- aggregate_weighted_groups already
supported (n_items, task_dim) labels, so this was the only missing piece.
… options

GroupPropertyFittingNet took **kwargs and accepted the full property-schema
field list (numb_aparam, default_fparam, dim_case_embd, resnet_dt, intensive,
distinguish_types, seed, ...) since it is a standalone MLP that never went
through GeneralFitting, the base class that actually implements those. A
config setting any of them passed validation and construction, then silently
had no effect. trainable as a per-layer list was also collapsed to
all(trainable), losing the per-layer freeze the property schema documents.

- Replace **kwargs with two explicit, named, no-op parameters (type,
  mixed_types) for the two fields the generic model-building path always
  injects; any other unrecognized field now fails construction immediately
  with a normal TypeError instead of vanishing into kwargs.
- fitting_group_property() drops numb_aparam/default_fparam/dim_case_embd/
  resnet_dt/intensive/distinguish_types from the schema entirely, so setting
  one is a dargs strict-mode validation error, not a silently accepted no-op.
- seed is now used: layer init is deterministic when given (scoped via
  fork_rng so it does not perturb the caller's global RNG state), and
  unseeded construction still draws from the global stream as before.
- trainable=[...] now freezes each Linear layer individually instead of
  collapsing to all(trainable), matching the documented per-layer semantics
  and raising if the list length does not match len(neuron)+1.
Comment thread source/tests/dpa_adapt/test_cache.py Fixed
Comment thread source/tests/dpa_adapt/test_cache.py Fixed
Comment thread dpa_adapt/config/manager.py Fixed
zirenjin added 2 commits July 10, 2026 21:42
…ring

GroupPropertyFittingNet had no case-embedding support at all (dim_case_embd
was explicitly stripped from its argcheck schema), so a group_property head
could never share a descriptor with another branch in multi-task training:
deepmd-kit's trainer requires every model_dict branch to declare the same
dim_case_embd (deepmd.pt.train.training.get_case_embd_config), and a real
finetune of an existing multi-branch checkpoint needs it to match that
checkpoint's own branch count.

- GroupPropertyFittingNet gains dim_case_embd/case_embd/set_case_embd,
  mirroring GeneralFitting's pattern: a one-hot buffer concatenated onto the
  aggregated group embedding (after the existing fparam/fparam_neuron
  fusion), sized into the first Linear layer's input width.
- GroupPropertyModel gains a set_case_embd proxy to its fitting net: it does
  not go through make_model() like PropertyModel/EnergyModel do, so it does
  not inherit the generic model -> atomic_model -> fitting_net proxy chain
  the multi-task trainer calls on every branch.
- fitting_group_property() un-strips dim_case_embd from the argcheck schema
  (it was previously grouped with fields group_property genuinely has no
  wiring for; it now does).
Wires MFTFineTuner/MFTConfigManager to build a group_property downstream
head, jointly trained with an aux ener branch on a shared descriptor
(preventing representation collapse per arXiv:2601.08486), so grouped/
assembly targets (e.g. an OER O*/OH*/OOH* overpotential, or the polymer
cloud-point task) can use MFT instead of plain single-task finetuning.

- MFTFineTuner.__init__ accepts downstream_task_type='group_property'
  alongside 'property'/'ener', plus a group_reduce param; property_name/
  task_dim validation now applies to both property-like modes.
- MFTConfigManager gets its own _build_group_property_fitting_net/_loss,
  independent of the property builders: GroupPropertyFittingNet is a small
  standalone MLP, not built on GeneralFitting, so several property-schema
  fields (resnet_dt, intensive, distinguish_types, numb_aparam) don't exist
  on it and dargs strict mode rejects them outright rather than ignoring
  them -- reusing/trimming the property builder would have silently carried
  one over.
- dim_case_embd on the downstream head is read from the aux branch's own
  (checkpoint-derived) fitting_net_params via a shared _aux_dim_case_embd
  helper, not hardcoded: it must equal the branch count of whatever
  multi-task checkpoint the aux branch was itself pretrained as part of
  (31 for DPA-3.1-3M, but e.g. 23 for the OMol25/Organic_Reactions/ODAC23
  checkpoint used for the cloud-point OOD run) -- hardcoding 31 silently
  mismatches every checkpoint that isn't DPA-3.1-3M.
- evaluate()/predict() gain _check_no_multi_frame_groups: dp --pt test never
  threads group_id/weight/pool_mask through its evaluation path, so a frozen
  group_property head silently scores every test frame as its own one-frame
  group. Harmless for single-frame groups, silently wrong for genuine
  multi-frame assemblies -- refuse instead of returning numbers that look
  plausible but average over the wrong rows.
@zirenjin
zirenjin force-pushed the feat/grouped-embedding branch from 58bf292 to 6026dbb Compare July 10, 2026 13:44
@njzjz

njzjz commented Jul 18, 2026

Copy link
Copy Markdown
Member

Possible reviewers based on changed lines, exact file history, and exact-file review history:

  • @iProzd — 39 commits on changed files (deepmd/pt/loss/__init__.py, deepmd/pt/model/model/__init__.py, deepmd/pt/model/task/__init__.py, deepmd/pt/train/training.py, deepmd/pt/utils/dataloader.py, deepmd/utils/argcheck.py).

No review request was made automatically.

Coding agent: Codex
Codex version: codex-cli 0.144.4
Model: gpt-5.6-sol
Reasoning effort: xhigh

@njzjz
njzjz requested review from iProzd and removed request for iProzd July 18, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants