Skip to content

refact(dpmodel,pt_expt): embedding net#5205

Open
wanghan-iapcm wants to merge 36 commits intodeepmodeling:masterfrom
wanghan-iapcm:refact-embed-net
Open

refact(dpmodel,pt_expt): embedding net#5205
wanghan-iapcm wants to merge 36 commits intodeepmodeling:masterfrom
wanghan-iapcm:refact-embed-net

Conversation

@wanghan-iapcm
Copy link
Collaborator

@wanghan-iapcm wanghan-iapcm commented Feb 8, 2026

EmbeddingNet Refactoring: Factory Function to Concrete Class

Summary

This refactoring converts EmbeddingNet from a factory-generated dynamic class to a concrete class in the dpmodel backend. This change enables the auto-detection registry mechanism in pt_expt to work seamlessly with EmbeddingNet attributes.

This PR is considered after #5194 and #5204

Motivation

Before: EmbeddingNet was created by a factory function make_embedding_network(NativeNet, NativeLayer), producing a dynamically-typed class make_embedding_network.<locals>.EN. This caused two problems:

  1. Cannot be registered: Dynamic classes can't be imported or registered at module import time in the pt_expt registry
  2. Name-based hacks required: pt_expt wrappers had to explicitly check for name == "embedding_net" in __setattr__ instead of using the type-based auto-detection mechanism

After: EmbeddingNet is now a concrete class that can be registered in the pt_expt auto-conversion registry, eliminating the need for name-based special cases.

Changes

1. dpmodel: Concrete EmbeddingNet class

File: deepmd/dpmodel/utils/network.py

  • Replaced factory-generated class with concrete EmbeddingNet(NativeNet) class
  • Moved constructor logic from factory into __init__
  • Fixed deserialize to use type(obj.layers[0]) instead of hardcoding super(EmbeddingNet, obj), allowing pt_expt subclass to preserve its converted torch layers
  • Kept make_embedding_network factory for pt/pd backends that use different base classes (MLP)
class EmbeddingNet(NativeNet):
    """The embedding network."""

    def __init__(self, in_dim, neuron=[24, 48, 96], activation_function="tanh",
                 resnet_dt=False, precision=DEFAULT_PRECISION, seed=None,
                 bias=True, trainable=True):
        layers = []
        i_in = in_dim
        if isinstance(trainable, bool):
            trainable = [trainable] * len(neuron)
        for idx, ii in enumerate(neuron):
            i_ot = ii
            layers.append(
                NativeLayer(
                    i_in, i_ot, bias=bias, use_timestep=resnet_dt,
                    activation_function=activation_function, resnet=True,
                    precision=precision, seed=child_seed(seed, idx),
                    trainable=trainable[idx]
                ).serialize()
            )
            i_in = i_ot
        super().__init__(layers)
        self.in_dim = in_dim
        self.neuron = neuron
        self.activation_function = activation_function
        self.resnet_dt = resnet_dt
        self.precision = precision
        self.bias = bias

    @classmethod
    def deserialize(cls, data):
        data = data.copy()
        check_version_compatibility(data.pop("@version", 1), 2, 1)
        data.pop("@class", None)
        layers = data.pop("layers")
        obj = cls(**data)
        # Use type(obj.layers[0]) to respect subclass layer types
        layer_type = type(obj.layers[0])
        obj.layers = type(obj.layers)(
            [layer_type.deserialize(layer) for layer in layers]
        )
        return obj

2. pt_expt: Wrapper and registration

File: deepmd/pt_expt/utils/network.py

  • Created EmbeddingNet(EmbeddingNetDP, torch.nn.Module) wrapper
  • Converts dpmodel layers to pt_expt NativeLayer (torch modules) in __init__
  • Registered in auto-conversion registry
class EmbeddingNet(EmbeddingNetDP, torch.nn.Module):
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        torch.nn.Module.__init__(self)
        EmbeddingNetDP.__init__(self, *args, **kwargs)
        # Convert dpmodel layers to pt_expt NativeLayer
        self.layers = torch.nn.ModuleList(
            [NativeLayer.deserialize(layer.serialize()) for layer in self.layers]
        )

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        return torch.nn.Module.__call__(self, *args, **kwargs)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.call(x)

register_dpmodel_mapping(
    EmbeddingNetDP,
    lambda v: EmbeddingNet.deserialize(v.serialize()),
)

3. TypeEmbedNet: Simplified to use registry

File: deepmd/pt_expt/utils/type_embed.py

  • No longer needs name-based embedding_net check in __setattr__
  • Uses common dpmodel_setattr which auto-converts via registry
  • Imports network module to ensure EmbeddingNet registration happens first
class TypeEmbedNet(TypeEmbedNetDP, torch.nn.Module):
    def __setattr__(self, name: str, value: Any) -> None:
        # Auto-converts embedding_net via registry
        handled, value = dpmodel_setattr(self, name, value)
        if not handled:
            super().__setattr__(name, value)

Tests

dpmodel tests

File: source/tests/common/dpmodel/test_network.py

Added to TestEmbeddingNet class:

  1. test_is_concrete_class: Verifies EmbeddingNet is now a concrete class, not factory output
  2. test_forward_pass: Tests dpmodel forward pass produces correct shapes
  3. test_trainable_parameter_variants: Tests different trainable configurations (all trainable, all frozen, mixed)

(The existing test_embedding_net test already covers serialization/deserialization round-trip)

pt_expt integration tests

File: source/tests/pt_expt/utils/test_network.py

Created TestEmbeddingNetRefactor test suite with 8 tests:

  1. test_pt_expt_embedding_net_wraps_dpmodel: Verifies pt_expt wrapper inherits correctly and converts layers
  2. test_pt_expt_embedding_net_forward: Tests pt_expt forward pass returns torch.Tensor
  3. test_serialization_round_trip_pt_expt: Tests pt_expt serialize/deserialize
  4. test_deserialize_preserves_layer_type: Tests the key fix - deserialize uses type(obj.layers[0]) to preserve pt_expt's torch layers
  5. test_cross_backend_consistency: Tests numerical consistency between dpmodel and pt_expt
  6. test_registry_converts_dpmodel_to_pt_expt: Tests try_convert_module auto-converts dpmodel to pt_expt
  7. test_auto_conversion_in_setattr: Tests dpmodel_setattr auto-converts EmbeddingNet attributes
  8. test_trainable_parameter_handling: Tests trainable vs frozen parameters work correctly in pt_expt

Verification

All tests pass:

# dpmodel EmbeddingNet tests
python -m pytest source/tests/common/dpmodel/test_network.py::TestEmbeddingNet -v
# 4 passed in 0.41s

# pt_expt EmbeddingNet integration tests
python -m pytest source/tests/pt_expt/utils/test_network.py::TestEmbeddingNetRefactor -v
# 8 passed in 0.41s

# All pt_expt network tests
python -m pytest source/tests/pt_expt/utils/test_network.py -v
# 10 passed in 0.41s

# Descriptor tests (verify refactoring doesn't break existing code)
python -m pytest source/tests/pt_expt/descriptor/test_se_e2_a.py -v -k consistency
# 1 passed

python -m pytest source/tests/universal/pt_expt/descriptor/test_descriptor.py -v
# 8 passed in 3.27s

Benefits

  1. Type-based auto-detection: No more name-based special cases in __setattr__
  2. Maintainability: Single source of truth for EmbeddingNet in dpmodel
  3. Consistency: Same pattern as other dpmodel classes (AtomExcludeMask, NetworkCollection, etc.)
  4. Future-proof: New attributes in dpmodel automatically work in pt_expt via registry

Backward Compatibility

  • Serialization format unchanged (version 2.1)
  • All existing tests pass
  • make_embedding_network factory kept for pt/pd backends
  • No changes to public API

Files Changed

Modified

  • deepmd/dpmodel/utils/network.py: Concrete EmbeddingNet class + deserialize fix
  • deepmd/pt_expt/utils/network.py: EmbeddingNet wrapper + registration
  • deepmd/pt_expt/utils/type_embed.py: Simplified to use registry
  • source/tests/common/dpmodel/test_network.py: Added dpmodel EmbeddingNet tests (3 new tests)
  • source/tests/pt_expt/utils/test_network.py: Added pt_expt integration tests (8 new tests)

No changes required

  • All descriptor wrappers (se_e2_a, se_r, se_t, se_t_tebd) automatically work via registry
  • No changes to dpmodel logic or array_api_compat code

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced PyTorch framework integration with automatic model conversion capabilities and registry-driven utilities.
    • Improved network wrapper implementations enabling seamless conversion between different model formats.
  • Improvements

    • Network implementations now explicitly defined with stable, deterministic behavior during serialization and deserialization.
    • Added descriptor wrappers for better PyTorch compatibility and module management.
  • Tests

    • Comprehensive test coverage for network serialization workflows and cross-framework numerical consistency.

@wanghan-iapcm wanghan-iapcm requested a review from njzjz February 8, 2026 13:34
@github-actions github-actions bot added the Python label Feb 8, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @wanghan-iapcm, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the deepmd codebase by refactoring the EmbeddingNet to be a concrete class, which is a foundational change enabling seamless integration with the new pt_expt backend. This shift from dynamic class generation to a concrete implementation, coupled with an intelligent attribute auto-conversion registry, streamlines the development of PyTorch exportable models. The changes promote a more maintainable and consistent architecture, moving away from brittle name-based checks towards robust type-based mechanisms, ultimately improving the overall reliability and extensibility of the framework.

Highlights

  • EmbeddingNet Refactoring: The EmbeddingNet class in the dpmodel backend has been refactored from a factory-generated dynamic class into a concrete class. This change addresses issues with dynamic classes not being registrable and requiring name-based hacks in pt_expt wrappers.
  • PyTorch Exportable (pt_expt) Integration: A new pt_expt backend has been introduced, including a wrapper for EmbeddingNet that converts dpmodel layers to torch.nn.Module instances. This allows EmbeddingNet to be registered in the auto-conversion registry, enabling type-based auto-detection of attributes.
  • Automated Attribute Handling: A new dpmodel_setattr mechanism has been implemented in pt_expt to automatically handle attribute assignments based on value type. This converts NumPy arrays to PyTorch buffers and dpmodel objects to their pt_expt module wrappers, eliminating the need for explicit name-based checks.
  • Enhanced Testing: New tests have been added for the dpmodel EmbeddingNet to verify its concrete class behavior, forward pass, and trainable parameter variants. Comprehensive integration tests for the pt_expt EmbeddingNet wrapper cover wrapping, forward pass, serialization, layer type preservation, cross-backend consistency, registry conversion, and __setattr__ auto-conversion.
  • Thread Setting Robustness: Error handling has been added to the PyTorch thread setting functions (torch.set_num_interop_threads and torch.set_num_threads) to prevent RuntimeError when called multiple times, improving stability.
Changelog
  • deepmd/backend/pt_expt.py
    • Added new file defining the PyTorchExportableBackend.
  • deepmd/dpmodel/common.py
    • Modified to_numpy_array to handle TypeError during conversion and explicitly move arrays to the CPU device for numpy compatibility.
  • deepmd/dpmodel/descriptor/dpa1.py
    • Modified compute_input_stats to pass the device argument to xp.asarray when setting mean and stddev.
  • deepmd/dpmodel/descriptor/repflows.py
    • Modified compute_input_stats to pass the device argument to xp.asarray when setting mean and stddev.
  • deepmd/dpmodel/descriptor/repformers.py
    • Modified compute_input_stats to pass the device argument to xp.asarray when setting mean and stddev.
  • deepmd/dpmodel/descriptor/se_e2_a.py
    • Modified compute_input_stats to pass the device argument to xp.asarray when setting davg and dstd.
    • Modified call method to pass the device argument to xp.zeros for gr array initialization.
  • deepmd/dpmodel/descriptor/se_r.py
    • Modified compute_input_stats to pass the device argument to xp.asarray when setting davg and dstd.
    • Modified call method to pass the device argument to xp.zeros for xyz_scatter array initialization.
  • deepmd/dpmodel/descriptor/se_t.py
    • Modified compute_input_stats to pass the device argument to xp.asarray when setting davg and dstd.
  • deepmd/dpmodel/descriptor/se_t_tebd.py
    • Modified compute_input_stats to pass the device argument to xp.asarray when setting mean and stddev.
  • deepmd/dpmodel/utils/network.py
    • Replaced the factory function for EmbeddingNet with a concrete EmbeddingNet class.
    • Moved constructor logic into the __init__ method of the new EmbeddingNet class.
    • Fixed the deserialize method to use type(obj.layers[0]) to correctly preserve subclass layer types during deserialization.
  • deepmd/env.py
    • Corrected the environment variable lookup for DP_INTER_OP_PARALLELISM_THREADS to use TF_INTER_OP_PARALLELISM_THREADS instead of TF_INTRA_OP_PARALLELISM_THREADS.
  • deepmd/pt/utils/env.py
    • Added try-except blocks around torch.set_num_interop_threads and torch.set_num_threads to handle RuntimeError if threads are already set.
    • Corrected the assignment order of intra_nthreads and inter_nthreads from get_default_nthreads().
  • deepmd/pt_expt/init.py
    • Added new file.
  • deepmd/pt_expt/common.py
    • Added new file defining register_dpmodel_mapping for registering dpmodel to pt_expt converters.
    • Added try_convert_module to look up and apply registered converters.
    • Added dpmodel_setattr for automatic attribute detection and conversion (NumPy arrays to buffers, dpmodel objects to pt_expt modules).
    • Added to_torch_array utility to convert arrays to PyTorch tensors on the pt_expt device.
  • deepmd/pt_expt/descriptor/init.py
    • Added new file.
  • deepmd/pt_expt/descriptor/base_descriptor.py
    • Added new file defining BaseDescriptor for the pt_expt backend.
  • deepmd/pt_expt/descriptor/se_e2_a.py
    • Added new file defining DescrptSeA as a PyTorch exportable wrapper for dpmodel.descriptor.se_e2_a.DescrptSeAArrayAPI.
  • deepmd/pt_expt/descriptor/se_r.py
    • Added new file defining DescrptSeR as a PyTorch exportable wrapper for dpmodel.descriptor.se_r.DescrptSeR.
  • deepmd/pt_expt/utils/init.py
    • Added new file.
  • deepmd/pt_expt/utils/env.py
    • Added new file defining environment settings specific to the pt_expt backend, including thread management with error handling.
  • deepmd/pt_expt/utils/exclude_mask.py
    • Added new file defining AtomExcludeMask and PairExcludeMask wrappers for pt_expt and registering them for auto-conversion.
  • deepmd/pt_expt/utils/network.py
    • Added new file defining TorchArrayParam, NativeLayer, NativeNet, EmbeddingNet, FittingNet, and NetworkCollection for the pt_expt backend.
    • Implemented EmbeddingNet wrapper that converts dpmodel layers to pt_expt.NativeLayer (torch modules).
    • Registered EmbeddingNetDP and NetworkCollectionDP for auto-conversion to their pt_expt counterparts.
  • pyproject.toml
    • Added deepmd.pt_expt to the banned-module-level-imports list.
    • Added source/tests/pt_expt/** to the TID253 and ANN rules for linting and analysis.
  • source/tests/common/dpmodel/test_network.py
    • Added test_is_concrete_class to verify EmbeddingNet is a concrete class.
    • Added test_forward_pass to check correct output shapes for EmbeddingNet.
    • Added test_trainable_parameter_variants to test different trainable configurations for EmbeddingNet.
  • source/tests/consistent/common.py
    • Introduced INSTALLED_PT_EXPT flag and pt_expt_class attribute to CommonTest.
    • Added skip_pt_expt property and eval_pt_expt method for PyTorch exportable backend.
    • Updated RefBackend enum to include PT_EXPT.
    • Modified get_reference_backend and get_reference_ret_serialization to support pt_expt.
    • Added test_pt_expt_consistent_with_ref and test_pt_expt_self_consistent for consistency checks.
  • source/tests/consistent/descriptor/common.py
    • Updated imports to include INSTALLED_PT_EXPT.
    • Added eval_pt_expt_descriptor method for evaluating pt_expt descriptors.
  • source/tests/consistent/descriptor/test_se_e2_a.py
    • Updated imports to include INSTALLED_PT_EXPT.
    • Added DescrptSeAPTExpt class variable.
    • Updated skip_pt_expt property and eval_pt_expt method to support pt_expt descriptor testing.
  • source/tests/consistent/descriptor/test_se_r.py
    • Updated imports to include INSTALLED_PT_EXPT.
    • Added DescrptSeRPTExpt class variable.
    • Updated skip_pt_expt property and eval_pt_expt method to support pt_expt descriptor testing.
  • source/tests/pt/test_env_threads.py
    • Added new test test_env_threads_guard_handles_runtimeerror to verify robust thread setting in deepmd.pt.utils.env.
  • source/tests/pt_expt/init.py
    • Added new file.
  • source/tests/pt_expt/conftest.py
    • Added new file to ensure torch is imported for pt_expt tests.
  • source/tests/pt_expt/descriptor/init.py
    • Added new file.
  • source/tests/pt_expt/descriptor/test_se_e2_a.py
    • Added new tests for pt_expt.descriptor.se_e2_a.DescrptSeA, including consistency with dpmodel and exportability.
  • source/tests/pt_expt/descriptor/test_se_r.py
    • Added new tests for pt_expt.descriptor.se_r.DescrptSeR, including consistency with dpmodel and exportability.
  • source/tests/pt_expt/model/init.py
    • Added new file.
  • source/tests/pt_expt/utils/init.py
    • Added new file.
  • source/tests/pt_expt/utils/test_common.py
    • Added new test test_to_torch_array_moves_device to verify correct device placement.
  • source/tests/pt_expt/utils/test_env.py
    • Added new test test_env_threads_guard_handles_runtimeerror to verify robust thread setting in deepmd.pt_expt.utils.env.
  • source/tests/pt_expt/utils/test_exclusion_mask.py
    • Added new tests for pt_expt.utils.exclude_mask.AtomExcludeMask and PairExcludeMask, verifying mask building and buffer registration.
  • source/tests/pt_expt/utils/test_network.py
    • Added tests for NativeLayer parameter/buffer clearing.
    • Added TestEmbeddingNetRefactor suite with tests for pt_expt EmbeddingNet wrapping, forward pass, serialization, layer type preservation, cross-backend consistency, registry conversion, dpmodel_setattr auto-conversion, and trainable parameter handling.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the enhancement label Feb 8, 2026
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request is a significant and well-executed refactoring that converts EmbeddingNet from a factory-generated class to a concrete class, enabling a new, more robust pt_expt backend. The introduction of a registry-based auto-conversion mechanism is a great architectural improvement for maintainability. Additionally, this PR includes several important bug fixes related to thread management and device handling for GPU tensors, which greatly improves the correctness and robustness of the system. The changes are thoroughly tested, ensuring a high-quality contribution.

Comment on lines 94 to 109
intra_nthreads, inter_nthreads = get_default_nthreads()
if inter_nthreads > 0: # the behavior of 0 is not documented
torch.set_num_interop_threads(inter_nthreads)
# torch.set_num_interop_threads can only be called once per process.
# Guard to avoid RuntimeError when multiple backends are imported.
try:
if torch.get_num_interop_threads() != inter_nthreads:
torch.set_num_interop_threads(inter_nthreads)
except RuntimeError as err:
log.warning(f"Could not set torch interop threads: {err}")
if intra_nthreads > 0:
torch.set_num_threads(intra_nthreads)
# torch.set_num_threads can also fail if called after threads are created.
try:
if torch.get_num_threads() != intra_nthreads:
torch.set_num_threads(intra_nthreads)
except RuntimeError as err:
log.warning(f"Could not set torch intra threads: {err}")

Choose a reason for hiding this comment

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

critical

This change fixes a critical bug where inter_nthreads and intra_nthreads were swapped, leading to incorrect thread settings for PyTorch. The addition of try...except blocks and checking the current thread counts before setting them also significantly improves the robustness of the environment setup, especially when multiple backends might be imported. Great improvement!

Comment on lines 124 to 129
except (ValueError, AttributeError, TypeError):
xp = array_api_compat.array_namespace(x)
# to fix BufferError: Cannot export readonly array since signalling readonly is unsupported by DLPack.
x = xp.asarray(x, copy=True)
# Move to CPU device to ensure numpy compatibility
x = xp.asarray(x, device="cpu", copy=True)
return np.from_dlpack(x)

Choose a reason for hiding this comment

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

high

Adding device="cpu" before converting to a NumPy array via DLPack is a crucial fix. This prevents BufferError when the input array resides on a GPU, as np.from_dlpack expects a CPU tensor. Also, including TypeError in the exception handling makes the conversion more robust. Well done.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 8, 2026

📝 Walkthrough

Walkthrough

The PR replaces dynamic factory-generated EmbeddingNet with a concrete class, introduces a registry-driven conversion system to wrap dpmodel objects as PyTorch modules, and adds PyTorch-compatible wrapper classes for descriptors, networks, and utilities to enable dpmodel integration with pt_expt.

Changes

Cohort / File(s) Summary
EmbeddingNet Concrete Class
deepmd/dpmodel/utils/network.py
Replaces factory-generated EmbeddingNet with explicit class definition inheriting NativeNet. Adds __init__ with layer construction, metadata storage, serialize method returning detailed dict, and deserialize classmethod with version validation and layer-type-aware rehydration.
Registry and Conversion Utilities
deepmd/pt_expt/common.py
Introduces registry-driven system: register_dpmodel_mapping registers dpmodel-to-pt_expt converters, try_convert_module invokes registered converters, dpmodel_setattr centralizes attribute handling (numpy/torch conversion, dpmodel conversion via registry), to_torch_array converts inputs to torch tensors using deferred device resolution.
Descriptor Wrappers
deepmd/pt_expt/descriptor/se_e2_a.py, deepmd/pt_expt/descriptor/se_r.py
Introduces DescrptSeA and DescrptSeR wrapper classes inheriting from dpmodel equivalents and torch.nn.Module. Both override __call__ for export/tracing, __setattr__ for dpmodel_setattr delegation, and forward to accept extended coordinates/atom types/neighbor lists and return descriptor tuples.
PyTorch Network Wrappers
deepmd/pt_expt/utils/network.py
Introduces comprehensive wrapper layer: TorchArrayParam for array parameters, NativeLayer/NativeNet for multilayer networks, EmbeddingNet adaptation to PyTorch, FittingNet composition, NetworkCollection for network type mapping. Includes registry registrations for EmbeddingNetDP and NetworkCollectionDP deserialization, LayerNorm wrapper, and state_dict/device handling via ModuleList/ModuleDict.
Exclude Mask Wrappers
deepmd/pt_expt/utils/exclude_mask.py
Adds AtomExcludeMask and PairExcludeMask classes inheriting from dpmodel equivalents and torch.nn.Module. Both provide init and setattr delegation to dpmodel_setattr. Includes registry mappings for automatic conversion from dpmodel instances to pt_expt wrappers.
Utils Export
deepmd/pt_expt/utils/__init__.py
Adds NetworkCollection to re-exports in __all__ from network module.
Tests
source/tests/common/dpmodel/test_network.py, source/tests/pt_expt/utils/test_network.py
Adds comprehensive test coverage for concrete EmbeddingNet class (concrete type verification, forward pass shape/dtype, trainable variants) and pt_expt EmbeddingNet integration (NativeLayer behavior, serialization round-trips, cross-backend numerical consistency, registry conversion, auto-conversion via dpmodel_setattr, trainable parameter handling).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant dpmodel as dpmodel Component
    participant registry as Registry
    participant converter as Converter
    participant pt_expt as pt_expt Module

    User->>dpmodel: Create dpmodel object (e.g., EmbeddingNet)
    dpmodel-->>User: dpmodel instance
    
    User->>registry: Call try_convert_module(dpmodel_instance)
    registry->>registry: Look up registered converter for type
    alt Converter registered
        registry->>converter: Invoke converter callable
        converter->>converter: Deserialize or instantiate
        converter-->>pt_expt: Create pt_expt wrapper
        pt_expt-->>registry: pt_expt module instance
        registry-->>User: Return converted module
    else Converter not registered
        registry-->>User: Return None
    end
    
    alt Attribute assignment flow
        User->>pt_expt: setattr(obj, name, dpmodel_value)
        pt_expt->>pt_expt: __setattr__ calls dpmodel_setattr
        dpmodel_setattr->>registry: try_convert_module(value)
        registry-->>dpmodel_setattr: converted pt_expt module or None
        alt Conversion succeeded
            dpmodel_setattr-->>pt_expt: Use converted module instead
        else Conversion failed or not registered
            dpmodel_setattr->>pt_expt: Handle as numpy/torch/default
        end
    end
    
    User->>pt_expt: Use wrapped module for torch training/export
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #5194: Introduces related PyTorch-exportable network wrappers and registry-based EmbeddingNet deserialization for pt_expt integration.

Suggested reviewers

  • iProzd
  • anyangml
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.34% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refact(dpmodel,pt_expt): embedding net' directly and accurately describes the main refactoring work: converting EmbeddingNet from a factory-generated dynamic class to a concrete class across dpmodel and pt_expt backends.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
deepmd/pt_expt/common.py (1)

45-82: No duplicate-registration guard — silent overwrite possible.

If register_dpmodel_mapping is called twice for the same dpmodel_cls (e.g., due to a module being re-imported or a copy-paste mistake), the second converter silently overwrites the first. A debug-level log or a warning when overwriting would help catch accidental re-registrations during development.

Optional: warn on overwrite
+import logging
+
+log = logging.getLogger(__name__)
+
 def register_dpmodel_mapping(
     dpmodel_cls: type, converter: Callable[[Any], torch.nn.Module]
 ) -> None:
+    if dpmodel_cls in _DPMODEL_TO_PT_EXPT:
+        log.debug("Overwriting converter for %s", dpmodel_cls)
     _DPMODEL_TO_PT_EXPT[dpmodel_cls] = converter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@deepmd/backend/pt_expt.py`:
- Around line 37-42: The class-level features tuple in pt_expt.py declares
Backend.Feature.DEEP_EVAL, NEIGHBOR_STAT, and IO but the corresponding property
methods deep_eval, neighbor_stat, serialize_hook, and deserialize_hook raise
NotImplementedError; either remove those flags from the features tuple or
implement the properties consistent with the patterns in
pytorch.py/tensorflow.py: provide a working deep_eval method, return a proper
neighbor_stat callable/object, and implement serialize_hook/deserialize_hook to
handle IO serialization for the backend, ensuring the symbols features,
deep_eval, neighbor_stat, serialize_hook, and deserialize_hook are updated
together so the advertised features match actual implementations.

In `@deepmd/dpmodel/utils/network.py`:
- Around line 889-896: The code crashes when obj.layers is empty because it does
type(obj.layers[0]); update cls.deserialize to guard that access: if obj.layers
is empty then if serialized layers (the local variable layers) are also empty
just return obj, otherwise determine the correct layer-deserializer from the
serialized data or a class-level fallback (e.g. a cls._layer_type or metadata
stored in each serialized layer) and use that to build obj.layers via obj.layers
= type(obj.layers)([layer_deserializer.deserialize(layer) for layer in layers]);
otherwise (obj.layers not empty) keep the existing logic using
type(obj.layers[0]) to preserve MRO behavior.

In `@deepmd/pt_expt/utils/env.py`:
- Around line 33-38: The fallback using os.cpu_count() can return None and cause
TypeError in NUM_WORKERS = int(os.environ.get("NUM_WORKERS", min(4, ncpus))):
ensure ncpus is a concrete int after the except path by handling None (e.g., set
ncpus = os.cpu_count(); if ncpus is None: ncpus = 1 or another safe default) so
min(4, ncpus) always receives an int; update the except block where ncpus is
assigned (reference symbols: os.sched_getaffinity, os.cpu_count, ncpus,
NUM_WORKERS) and keep NUM_WORKERS computation the same.
🧹 Nitpick comments (9)
deepmd/dpmodel/utils/network.py (2)

819-819: Mutable default argument for neuron.

Ruff B006 flags this. While the same pattern exists in the factory version (line 714), it's worth noting that the list default [24, 48, 96] is shared across all callers. If any caller mutates it (unlikely but possible), it would affect subsequent calls.

Suggested fix
     def __init__(
         self,
         in_dim: int,
-        neuron: list[int] = [24, 48, 96],
+        neuron: list[int] | None = None,
         activation_function: str = "tanh",
         resnet_dt: bool = False,
         precision: str = DEFAULT_PRECISION,
         seed: int | list[int] | None = None,
         bias: bool = True,
         trainable: bool | list[bool] = True,
     ) -> None:
+        if neuron is None:
+            neuron = [24, 48, 96]
         layers = []

791-853: Significant code duplication between EmbeddingNet and the factory EN class.

The concrete EmbeddingNet (lines 791–896) largely duplicates the __init__, serialize, and deserialize of the factory-produced EN class (lines 688–788). The factory is retained for backends like JAX (per relevant snippet deepmd/jax/utils/network.py:68-69), so both must coexist. Consider whether the concrete class could inherit from the factory output (like JAX does) to reduce duplication, or extract shared logic into a mixin/helper.

Also applies to: 688-788

source/tests/pt_expt/utils/test_common.py (1)

14-23: Tests cover the happy path well. Consider adding a None input test.

The to_torch_array function also handles None input (returning None), which isn't exercised here. A one-liner like assert to_torch_array(None) is None would complete the coverage for the three documented input branches.

source/tests/pt_expt/utils/test_network.py (1)

163-164: Non-deterministic RNG in test — consider seeding for reproducibility.

Unlike other tests in this file that use GLOBAL_SEED, this RNG is unseeded. While correctness isn't affected (both backends receive the same x_np), a failing test would be hard to reproduce.

Suggested fix
-        rng = np.random.default_rng()
+        rng = np.random.default_rng(GLOBAL_SEED)
source/tests/consistent/descriptor/test_se_r.py (1)

92-100: Unused variables in skip_pt_expt — matches existing pattern in skip_pt.

Static analysis flags resnet_dt, excluded_types, and precision as unused. This mirrors the identical pattern in skip_pt (Lines 83-90), so this is consistent with the codebase style. A future cleanup could use _ prefixes for both properties, but that's out of scope here.

source/tests/consistent/descriptor/test_se_e2_a.py (1)

546-566: eval_pt_expt uses env.DEVICE from the PT backend, not from pt_expt.

Lines 551–555 reference env.DEVICE imported under the INSTALLED_PT guard (line 32–34). This works in practice because pt_expt likely depends on pt being installed, but it's conceptually wrong for a pt_expt test path to rely on PT's device. If the two backends ever diverge in device selection, this would silently use the wrong device.

Consider importing pt_expt's env and using its DEVICE instead, or at minimum add a comment clarifying the intentional reuse.

deepmd/pt_expt/common.py (1)

45-82: No duplicate-registration guard.

register_dpmodel_mapping silently overwrites if called twice with the same dpmodel_cls. This is fine for the current module-level registration pattern, but if registration ever moves to a more dynamic context, a warning or error on duplicate keys would help catch bugs.

deepmd/pt_expt/utils/env.py (1)

22-29: force=True on set_start_method may conflict with other backends.

If both deepmd.pt.utils.env and deepmd.pt_expt.utils.env are imported in the same process (which happens in tests and when mixing backends), force=True will override whatever start method was previously set. The try/except mitigates runtime errors, but the forced override could cause subtle issues if another module intentionally set a different method.

Consider using force=False or checking multiprocessing.get_start_method(allow_none=True) first.

deepmd/pt_expt/utils/network.py (1)

27-37: Remove unused noqa directive.

Ruff reports PYI034 is not enabled, so the # noqa: PYI034 on line 28 is unnecessary.

Proposed fix
-    def __new__(  # noqa: PYI034
+    def __new__(
         cls, data: Any = None, requires_grad: bool = True
     ) -> "TorchArrayParam":

Comment on lines 37 to 42
features: ClassVar[Backend.Feature] = (
Backend.Feature.ENTRY_POINT
| Backend.Feature.DEEP_EVAL
| Backend.Feature.NEIGHBOR_STAT
| Backend.Feature.IO
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's examine the pt_expt.py file to see the complete implementation
cat -n deepmd/backend/pt_expt.py

Repository: deepmodeling/deepmd-kit

Length of output: 3606


🏁 Script executed:

#!/bin/bash
# Search for how Backend.Feature flags are actually used
rg -n 'hasattr.*Feature|\.features' --type=py -B2 -A2 | head -100

Repository: deepmodeling/deepmd-kit

Length of output: 352


🏁 Script executed:

#!/bin/bash
# Check if there's a Backend class definition to understand Feature enum
find . -name "*.py" -type f | xargs grep -l "class Backend" | head -5

Repository: deepmodeling/deepmd-kit

Length of output: 154


🏁 Script executed:

#!/bin/bash
# Get more context around line 92 in backend.py to see how features are used
sed -n '80,110p' deepmd/backend/backend.py

Repository: deepmodeling/deepmd-kit

Length of output: 975


🏁 Script executed:

#!/bin/bash
# Search for callers of deep_eval and neighbor_stat properties to see if they check features first
rg -n 'backend\.deep_eval|backend\.neighbor_stat' --type=py -B3 -A3 | head -80

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check other backends to see if they implement these properties or declare features they don't support
find deepmd/backend -name "*.py" -type f | xargs grep -l "Backend.Feature" | head -10

Repository: deepmodeling/deepmd-kit

Length of output: 271


🏁 Script executed:

#!/bin/bash
# Search for get_backends_by_feature usage to see how features are checked before accessing properties
rg -n 'get_backends_by_feature' --type=py -B2 -A5

Repository: deepmodeling/deepmd-kit

Length of output: 2044


🏁 Script executed:

#!/bin/bash
# Check what other backends implement for deep_eval and neighbor_stat
for file in deepmd/backend/pytorch.py deepmd/backend/tensorflow.py deepmd/backend/dpmodel.py; do
  echo "=== $file ==="
  sed -n '1,20p' "$file"
  echo "---"
  grep -n 'deep_eval\|neighbor_stat\|serialize_hook\|deserialize_hook\|Backend.Feature' "$file" | head -20
done

Repository: deepmodeling/deepmd-kit

Length of output: 3048


🏁 Script executed:

#!/bin/bash
# Search for actual calls to .deep_eval or accessing backend properties
rg -n '\.deep_eval\(|\.neighbor_stat\(|serialize_hook|deserialize_hook' --type=py -B2 -A2 | head -100

Repository: deepmodeling/deepmd-kit

Length of output: 5487


Remove undeclared feature flags or implement the properties.

features declares DEEP_EVAL, NEIGHBOR_STAT, and IO, but the corresponding properties (deep_eval, neighbor_stat, serialize_hook, deserialize_hook) all raise NotImplementedError. Code paths like deepmd/entrypoints/neighbor_stat.py will crash at runtime if a user selects "pt-expt" as the backend because it passes the feature filter but fails when accessing the property.

Either remove these feature flags until implementations are ready, or implement the properties (see how pytorch.py and tensorflow.py do it).

🤖 Prompt for AI Agents
In `@deepmd/backend/pt_expt.py` around lines 37 - 42, The class-level features
tuple in pt_expt.py declares Backend.Feature.DEEP_EVAL, NEIGHBOR_STAT, and IO
but the corresponding property methods deep_eval, neighbor_stat, serialize_hook,
and deserialize_hook raise NotImplementedError; either remove those flags from
the features tuple or implement the properties consistent with the patterns in
pytorch.py/tensorflow.py: provide a working deep_eval method, return a proper
neighbor_stat callable/object, and implement serialize_hook/deserialize_hook to
handle IO serialization for the backend, ensuring the symbols features,
deep_eval, neighbor_stat, serialize_hook, and deserialize_hook are updated
together so the advertised features match actual implementations.

Comment on lines +889 to +896
obj = cls(**data)
# Reinitialize layers from serialized data, using the same layer type
# that __init__ created (respects subclass overrides via MRO).
layer_type = type(obj.layers[0])
obj.layers = type(obj.layers)(
[layer_type.deserialize(layer) for layer in layers]
)
return obj
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

type(obj.layers[0]) will raise IndexError if neuron is empty.

If deserialize is called with data where neuron=[], __init__ produces an empty layers list, and line 892 crashes on obj.layers[0]. The factory version's deserialize (line 785) doesn't have this issue since it calls super().__init__(layers) directly.

Suggested fix
         obj = cls(**data)
         # Reinitialize layers from serialized data, using the same layer type
         # that __init__ created (respects subclass overrides via MRO).
-        layer_type = type(obj.layers[0])
-        obj.layers = type(obj.layers)(
-            [layer_type.deserialize(layer) for layer in layers]
-        )
+        if obj.layers:
+            layer_type = type(obj.layers[0])
+            obj.layers = type(obj.layers)(
+                [layer_type.deserialize(layer) for layer in layers]
+            )
+        else:
+            obj.layers = type(obj.layers)([])
         return obj
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
obj = cls(**data)
# Reinitialize layers from serialized data, using the same layer type
# that __init__ created (respects subclass overrides via MRO).
layer_type = type(obj.layers[0])
obj.layers = type(obj.layers)(
[layer_type.deserialize(layer) for layer in layers]
)
return obj
obj = cls(**data)
# Reinitialize layers from serialized data, using the same layer type
# that __init__ created (respects subclass overrides via MRO).
if obj.layers:
layer_type = type(obj.layers[0])
obj.layers = type(obj.layers)(
[layer_type.deserialize(layer) for layer in layers]
)
else:
obj.layers = type(obj.layers)([])
return obj
🤖 Prompt for AI Agents
In `@deepmd/dpmodel/utils/network.py` around lines 889 - 896, The code crashes
when obj.layers is empty because it does type(obj.layers[0]); update
cls.deserialize to guard that access: if obj.layers is empty then if serialized
layers (the local variable layers) are also empty just return obj, otherwise
determine the correct layer-deserializer from the serialized data or a
class-level fallback (e.g. a cls._layer_type or metadata stored in each
serialized layer) and use that to build obj.layers via obj.layers =
type(obj.layers)([layer_deserializer.deserialize(layer) for layer in layers]);
otherwise (obj.layers not empty) keep the existing logic using
type(obj.layers[0]) to preserve MRO behavior.

Comment on lines 33 to 38
try:
# only linux
ncpus = len(os.sched_getaffinity(0))
except AttributeError:
ncpus = os.cpu_count()
NUM_WORKERS = int(os.environ.get("NUM_WORKERS", min(4, ncpus)))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

os.cpu_count() can return None, causing TypeError in min(4, None).

On some platforms (or in restricted environments), os.cpu_count() returns None. The fallback on line 37 assigns None to ncpus, and then min(4, ncpus) on line 38 will raise a TypeError.

Proposed fix
 try:
     # only linux
     ncpus = len(os.sched_getaffinity(0))
 except AttributeError:
-    ncpus = os.cpu_count()
+    ncpus = os.cpu_count() or 1
 NUM_WORKERS = int(os.environ.get("NUM_WORKERS", min(4, ncpus)))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
# only linux
ncpus = len(os.sched_getaffinity(0))
except AttributeError:
ncpus = os.cpu_count()
NUM_WORKERS = int(os.environ.get("NUM_WORKERS", min(4, ncpus)))
try:
# only linux
ncpus = len(os.sched_getaffinity(0))
except AttributeError:
ncpus = os.cpu_count() or 1
NUM_WORKERS = int(os.environ.get("NUM_WORKERS", min(4, ncpus)))
🤖 Prompt for AI Agents
In `@deepmd/pt_expt/utils/env.py` around lines 33 - 38, The fallback using
os.cpu_count() can return None and cause TypeError in NUM_WORKERS =
int(os.environ.get("NUM_WORKERS", min(4, ncpus))): ensure ncpus is a concrete
int after the except path by handling None (e.g., set ncpus = os.cpu_count(); if
ncpus is None: ncpus = 1 or another safe default) so min(4, ncpus) always
receives an int; update the except block where ncpus is assigned (reference
symbols: os.sched_getaffinity, os.cpu_count, ncpus, NUM_WORKERS) and keep
NUM_WORKERS computation the same.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba8e7abfaa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

class PyTorchExportableBackend(Backend):
"""PyTorch exportable backend."""

name = "PyTorch Exportable"

Choose a reason for hiding this comment

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

P1 Badge Make pt-expt backend name key-compatible

main.py maps CLI aliases to backend.name.lower() and then validates the result against registered backend keys, so setting name to "PyTorch Exportable" makes --backend pt-expt resolve to "pytorch exportable", which is not a registered key (pt-expt/pytorch-exportable). This causes train/freeze-style commands to fail immediately with Unknown backend whenever users select this backend via CLI.

Useful? React with 👍 / 👎.

Comment on lines 37 to 41
features: ClassVar[Backend.Feature] = (
Backend.Feature.ENTRY_POINT
| Backend.Feature.DEEP_EVAL
| Backend.Feature.NEIGHBOR_STAT
| Backend.Feature.IO

Choose a reason for hiding this comment

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

P1 Badge Remove unimplemented features from pt-expt backend

This backend advertises DEEP_EVAL, NEIGHBOR_STAT, and IO support, but the corresponding hooks (deep_eval, neighbor_stat, serialize_hook, deserialize_hook) currently raise NotImplementedError. Any workflow that selects the backend for .pte files (for example dp test, dp show, or convert-backend) will deterministically crash when those hooks are accessed, so the feature flags and suffix exposure are currently misleading and break user flows.

Useful? React with 👍 / 👎.

@codecov
Copy link

codecov bot commented Feb 8, 2026

Codecov Report

❌ Patch coverage is 97.70115% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.00%. Comparing base (5c2ca51) to head (222cd6a).

Files with missing lines Patch % Lines
deepmd/pt_expt/common.py 92.85% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5205      +/-   ##
==========================================
+ Coverage   81.99%   82.00%   +0.01%     
==========================================
  Files         724      724              
  Lines       73807    73828      +21     
  Branches     3616     3617       +1     
==========================================
+ Hits        60519    60546      +27     
+ Misses      12124    12118       -6     
  Partials     1164     1164              

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@source/tests/pt_expt/descriptor/test_se_t_tebd.py`:
- Around line 153-158: The test constructs an inputs tuple that passes
type_embedding as the 4th positional argument, which binds to the forward(...)
parameter extended_atype_embd rather than the intended type_embedding; update
the test in test_se_t_tebd.py so the call matches the forward signature — either
supply extended_atype_embd explicitly (e.g. None) and pass type_embedding as a
keyword (type_embedding=type_embedding) or pass both optional args by keyword
(extended_atype_embd=..., type_embedding=...) when calling the module's forward
method to remove the positional mismatch.
- Around line 58-59: The test assigns torch.Tensor values to dd0.davg and
dd0.dstd which bypass dpmodel_setattr (it only converts numpy arrays) and
therefore do not update the inner descriptor block used by serialization;
instead set the statistics on the inner block dd0.se_ttebd (e.g., via its
mapping interface) so serialization reads the intended values—replace
assignments to dd0.davg/dd0.dstd with assignments into dd0.se_ttebd (updating
"davg" and "dstd"), ensuring dpmodel_setattr and dd0.serialize will observe the
non-default statistics and the deserialized dd1 will reflect them.
🧹 Nitpick comments (2)
source/tests/consistent/descriptor/test_se_t.py (1)

99-107: The skip_pt_expt property unpacks self.param without using any of the unpacked variables.

Ruff flags resnet_dt, excluded_types, precision, and env_protection as unused. This mirrors the same pattern in skip_pt and skip_dp, so it's a pre-existing convention in this file. Consider prefixing with underscores if you want to silence the linter across all three properties in a follow-up.

source/tests/pt_expt/descriptor/test_se_t_tebd.py (1)

66-71: type_embedding keyword is accepted but immediately deleted in forward.

The descriptor computes its own type embedding internally via self.type_embedding.call(). The externally passed type_embedding is discarded (see del extended_atype_embd, mapping, type_embedding in forward). This is not a test bug per se — the consistency check is still valid — but it means the test isn't exercising external type-embedding injection as the setup comments suggest.

Comment on lines 58 to 59
dd0.davg = torch.tensor(davg, dtype=dtype, device=self.device)
dd0.dstd = torch.tensor(dstd, dtype=dtype, device=self.device)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for dpmodel_setattr implementation in pt_expt/common.py
rg -n "def dpmodel_setattr" --type=py -A 20 deepmd/pt_expt/

Repository: deepmodeling/deepmd-kit

Length of output: 1701


🏁 Script executed:

#!/bin/bash
# Check DescrptBlockSeTTebd implementation for __setitem__ and davg/dstd handling
rg -n "__setitem__|davg|dstd" --type=py deepmd/dpmodel/descriptor/se_t_tebd.py | head -50

Repository: deepmodeling/deepmd-kit

Length of output: 1207


🏁 Script executed:

#!/bin/bash
# Check how other PT-EXPT tests set davg/dstd
find . -path "*pt_expt*" -name "*test*.py" -type f -exec rg -l "davg|dstd" {} \;

Repository: deepmodeling/deepmd-kit

Length of output: 262


🏁 Script executed:

#!/bin/bash
# Look at the actual DescrptSeTTebd wrapper class in PT-EXPT
rg -n "class DescrptSeTTebd" --type=py -A 50 deepmd/pt_expt/ | grep -A 50 "pt_expt"

Repository: deepmodeling/deepmd-kit

Length of output: 2935


🏁 Script executed:

#!/bin/bash
# Get the full dpmodel_setattr implementation to see if it routes davg/dstd
rg -n "def dpmodel_setattr" --type=py -A 100 deepmd/pt_expt/common.py | head -120

Repository: deepmodeling/deepmd-kit

Length of output: 4596


🏁 Script executed:

#!/bin/bash
# Check if DescrptSeTTebd wrapper has __getitem__/__setitem__ for delegation
rg -n "def __getitem__|def __setitem__" --type=py deepmd/pt_expt/descriptor/se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check how other PT-EXPT descriptor tests set davg/dstd (test_se_t.py, test_se_r.py, etc.)
rg -n "\.davg\s*=|\.dstd\s*=|\[\"davg\"\]|\.se_.*\[" --type=py -B 2 -A 2 source/tests/pt_expt/descriptor/test_se_t.py | head -60

Repository: deepmodeling/deepmd-kit

Length of output: 705


🏁 Script executed:

#!/bin/bash
# Check the parent class DescrptSeTTebdDP in dpmodel to see if it has __getitem__/__setitem__
rg -n "class DescrptSeTTebdDP|def __getitem__|def __setitem__" --type=py deepmd/dpmodel/descriptor/se_t_tebd.py | head -30

Repository: deepmodeling/deepmd-kit

Length of output: 179


🏁 Script executed:

#!/bin/bash
# Check if DescrptSeTTebd (wrapper) initializes davg/dstd as buffers
rg -n "register_buffer.*davg|self.davg|self.dstd" --type=py deepmd/pt_expt/descriptor/se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check DescrptSeTTebdDP (parent class in dpmodel) initialization for davg/dstd
rg -n "def __init__|self.davg|self.dstd|se_ttebd\[" --type=py -A 5 deepmd/dpmodel/descriptor/se_t_tebd.py | head -80

Repository: deepmodeling/deepmd-kit

Length of output: 1092


🏁 Script executed:

#!/bin/bash
# Verify how deserialization in dpmodel properly sets davg/dstd on inner block
rg -n "def restore_from_dict|se_ttebd\[\"davg\"\]|se_ttebd\[\"dstd\"\]" --type=py -B 3 -A 3 deepmd/dpmodel/descriptor/se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 879


🏁 Script executed:

#!/bin/bash
# Check if DescrptSeTTebdDP has `@property` for davg/dstd that routes to se_ttebd
rg -n "@property|def davg|def dstd" --type=py deepmd/dpmodel/descriptor/se_t_tebd.py | head -30

Repository: deepmodeling/deepmd-kit

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Check the initialization of DescrptSeTTebdDP to see if davg/dstd buffers are registered
rg -n "def __init__" --type=py -A 100 deepmd/dpmodel/descriptor/se_t_tebd.py | grep -A 100 "class DescrptSeTTebdDP" | head -120

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Verify what the test setup actually initializes - check if se_ttebd is created with stats
rg -n "se_ttebd.*=" --type=py -B 2 -A 2 deepmd/dpmodel/descriptor/se_t_tebd.py | head -50

Repository: deepmodeling/deepmd-kit

Length of output: 1339


🏁 Script executed:

#!/bin/bash
# Check what those `@property` decorators are for (lines 252, 256, 646, 651, 656)
sed -n '252,260p; 256,264p; 646,654p; 651,659p; 656,664p' deepmd/dpmodel/descriptor/se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 1315


🏁 Script executed:

#!/bin/bash
# Check if davg/dstd are `@property` in DescrptSeTTebdDP
rg -n "@property" --type=py -A 2 deepmd/dpmodel/descriptor/se_t_tebd.py | grep -A 2 "davg\|dstd"

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Look for `@property` definitions more carefully
ast-grep --pattern $'@property
def $_($_) {
  $$$
}'  deepmd/dpmodel/descriptor/se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check if DescrptSeTTebdDP has __getattr__ or __setattr__ that routes davg/dstd
rg -n "def __getattr__|def __setattr__" --type=py -A 10 deepmd/dpmodel/descriptor/se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# More direct check - look for "davg" or "dstd" in class DescrptSeTTebdDP
sed -n '100,300p' deepmd/dpmodel/descriptor/se_t_tebd.py | rg -n "davg|dstd|property"

Repository: deepmodeling/deepmd-kit

Length of output: 185


🏁 Script executed:

#!/bin/bash
# Check the full __init__ method of DescrptSeTTebdDP
sed -n '123,300p' deepmd/dpmodel/descriptor/se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 6322


🏁 Script executed:

#!/bin/bash
# Check what happens when the test runs - trace the actual behavior
# Look for how se_ttebd is accessed in the forward method
rg -n "self.se_ttebd\[" --type=py deepmd/dpmodel/descriptor/se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Final verification: trace exactly what dpmodel_setattr does with torch.Tensor
# Show the exact logic path
cat deepmd/pt_expt/common.py | sed -n '199,218p'

Repository: deepmodeling/deepmd-kit

Length of output: 762


🏁 Script executed:

#!/bin/bash
# Check if DescrptSeTTebd wrapper has `@property` davg/dstd that delegates
rg -n "def davg|def dstd" --type=py deepmd/pt_expt/descriptor/

Repository: deepmodeling/deepmd-kit

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Verify test behavior: check if setter actually works or if it's creating new attributes
# Look at what the test expects after setting davg/dstd
sed -n '55,80p' source/tests/pt_expt/descriptor/test_se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 1277


🏁 Script executed:

#!/bin/bash
# Verify the critical detail: does davg/dstd get used in forward computation?
# Check the DescrptBlockSeTTebd forward to see where mean/stddev are accessed
rg -n "self.mean|self.stddev" --type=py -B 2 -A 2 deepmd/dpmodel/descriptor/se_t_tebd.py | grep -A 2 -B 2 "mean\|stddev" | head -40

Repository: deepmodeling/deepmd-kit

Length of output: 1631


🏁 Script executed:

#!/bin/bash
# Check what serialize() actually serializes - does it include davg/dstd from outer wrapper?
rg -n "def serialize" --type=py -A 30 deepmd/dpmodel/descriptor/se_t_tebd.py | head -50

Repository: deepmodeling/deepmd-kit

Length of output: 2228


🏁 Script executed:

#!/bin/bash
# Final confirmation: verify the actual test path - what stats do dd0 and dd1 actually use in forward?
sed -n '54,82p' source/tests/pt_expt/descriptor/test_se_t_tebd.py

Repository: deepmodeling/deepmd-kit

Length of output: 1380


dd0.davg and dd0.dstd assignments do not propagate to the inner descriptor block's statistics.

When dd0.davg = torch.tensor(...) is assigned, dpmodel_setattr does not handle torch.Tensor values (it only converts numpy.ndarray). The assignment falls through to standard attribute creation on the outer wrapper, leaving dd0.se_ttebd.mean and dd0.se_ttebd.stddev at their defaults (zeros/ones). During serialization, dd0.serialize() reads statistics from the inner block (not the outer attributes), so the random values are lost. Both dd0 and the deserialized dd1 use identical default statistics, which is why the test passes—but it doesn't actually test with non-trivial statistics as intended.

Assign directly to the inner block instead:

dd0.se_ttebd["davg"] = torch.tensor(davg, dtype=dtype, device=self.device)
dd0.se_ttebd["dstd"] = torch.tensor(dstd, dtype=dtype, device=self.device)
🤖 Prompt for AI Agents
In `@source/tests/pt_expt/descriptor/test_se_t_tebd.py` around lines 58 - 59, The
test assigns torch.Tensor values to dd0.davg and dd0.dstd which bypass
dpmodel_setattr (it only converts numpy arrays) and therefore do not update the
inner descriptor block used by serialization; instead set the statistics on the
inner block dd0.se_ttebd (e.g., via its mapping interface) so serialization
reads the intended values—replace assignments to dd0.davg/dd0.dstd with
assignments into dd0.se_ttebd (updating "davg" and "dstd"), ensuring
dpmodel_setattr and dd0.serialize will observe the non-default statistics and
the deserialized dd1 will reflect them.

Comment on lines 153 to 158
inputs = (
torch.tensor(self.coord_ext, dtype=dtype, device=self.device),
torch.tensor(self.atype_ext, dtype=int, device=self.device),
torch.tensor(self.nlist, dtype=int, device=self.device),
type_embedding,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Positional argument mismatch: type_embedding tensor is passed as extended_atype_embd.

The forward signature is (extended_coord, extended_atype, nlist, extended_atype_embd=None, mapping=None, type_embedding=None). Passing type_embedding as the 4th positional arg maps it to extended_atype_embd, not type_embedding. Since forward deletes all optional args and computes type embedding internally, this doesn't cause a runtime error, but it's semantically misleading for a test.

Consider using a keyword argument or a tuple that matches the signature:

Proposed fix
             inputs = (
                 torch.tensor(self.coord_ext, dtype=dtype, device=self.device),
                 torch.tensor(self.atype_ext, dtype=int, device=self.device),
                 torch.tensor(self.nlist, dtype=int, device=self.device),
-                type_embedding,
+                None,   # extended_atype_embd
+                None,   # mapping
+                type_embedding,  # type_embedding
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
inputs = (
torch.tensor(self.coord_ext, dtype=dtype, device=self.device),
torch.tensor(self.atype_ext, dtype=int, device=self.device),
torch.tensor(self.nlist, dtype=int, device=self.device),
type_embedding,
)
inputs = (
torch.tensor(self.coord_ext, dtype=dtype, device=self.device),
torch.tensor(self.atype_ext, dtype=int, device=self.device),
torch.tensor(self.nlist, dtype=int, device=self.device),
None, # extended_atype_embd
None, # mapping
type_embedding, # type_embedding
)
🤖 Prompt for AI Agents
In `@source/tests/pt_expt/descriptor/test_se_t_tebd.py` around lines 153 - 158,
The test constructs an inputs tuple that passes type_embedding as the 4th
positional argument, which binds to the forward(...) parameter
extended_atype_embd rather than the intended type_embedding; update the test in
test_se_t_tebd.py so the call matches the forward signature — either supply
extended_atype_embd explicitly (e.g. None) and pass type_embedding as a keyword
(type_embedding=type_embedding) or pass both optional args by keyword
(extended_atype_embd=..., type_embedding=...) when calling the module's forward
method to remove the positional mismatch.

@wanghan-iapcm wanghan-iapcm changed the title refact: embedding net refact(dpmodel,pt_expt): embedding net Feb 8, 2026
@njzjz
Copy link
Member

njzjz commented Feb 8, 2026

This refactoring converts EmbeddingNet from a factory-generated dynamic class to a concrete class in the dpmodel backend.

What does it mean? What's the benefit of this PR?

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.

2 participants