Skip to content

[Code scan] Count or reject single-fragment pairwise DPRc maps #5649

Description

@njzjz

This issue comes from a Codex global scan of deepmodeling/deepmd-kit at commit 73de44b1f94471b2e3bdb6b11f57b34d7bc791bb.

Problem

dprc_pairwise_map_cpu() reports every local atom as a local QM atom when the pairwise DPRc index map contains only one non-placeholder fragment.

The mapper groups atoms by fragment index, ignoring -1 placeholders:

  • void deepmd::group_atoms_cpu(std::vector<std::vector<int>>& fragments,
    const std::vector<int>& idxs) {
    int natoms = idxs.size();
    // sort idxs
    std::vector<size_t> idxs_idx = sort_indexes(idxs);
    // now idxs_idx is sorted index, so we can easily group atoms in only one loop
    int last_frag = -1;
    for (size_t ii = 0; ii < idxs.size(); ii++) {
    int frag = idxs[idxs_idx[ii]];
    if (frag == -1) {
    // -1 is the place holder
    continue;
    }
    if (frag != last_frag) {
    last_frag = frag;
    fragments.emplace_back();
    }
    // push to the last fragment
    fragments.back().push_back(idxs_idx[ii]);
    }

The pairwise map then initializes nqm_real to nloc for the nfragments == 1 case:

  • int max_fragment_size = max_fragment_real_size + max_fragment_ghost_size;
    int map_size = nqm + max_fragment_real_size + max_fragment_ghost_size;
    // (3, 4, 0, 1, 2, 10, 11),
    // (3, 4, 5, 6, 7, 10, -1),
    // (3, 4, 8, 9, -1, 10, -1)
    forward_qmmm_map.resize(static_cast<size_t>(nfragments - 1) * map_size);
    std::fill(forward_qmmm_map.begin(), forward_qmmm_map.end(), -1);
    int nqm_real = nloc; // init for nfragments = 1
    for (int ii = 0; ii < nfragments - 1; ++ii) {

However, the only code path that counts how many atoms in fragments[0] are local runs inside the for (ii < nfragments - 1) loop:

  • for (int ii = 0; ii < nfragments - 1; ++ii) {
    // real
    for (int jj = 0, kk = 0; jj < nqm; ++jj) {
    if (fragments[0][jj] < nloc) {
    forward_qmmm_map[ii * map_size + kk] = fragments[0][jj];
    kk++;
    }
    if (jj == nqm - 1) {
    nqm_real = kk;
    }
    }

With a single QM fragment that covers only a subset of local atoms, nloc_qm is returned as nloc instead of the local count in fragments[0]:

The TensorFlow op trusts that count when copying forward_qm_map, so it can read past the vector that actually contains only the QM fragment atoms:

  • for (int jj = 0; jj < max_nloc_qm + max_nghost_qm; ++jj) {
    if (jj < nloc_qm[ii]) {
    m_forward_qm_map(ii, jj) = forward_qm_maps[ii][jj];
    } else if (jj < max_nloc_qm) {
    m_forward_qm_map(ii, jj) = -1;
    } else if (jj < max_nloc_qm + nghost_qm[ii]) {
    m_forward_qm_map(ii, jj) =
    forward_qm_maps[ii][jj - (max_nloc_qm - nloc_qm[ii])];

Impact

For frames where all non-QM entries are placeholders or where no MM fragment is present, the op can emit incorrect natoms_qm metadata and may read out of bounds while filling forward_qm_map. If single-fragment pairwise DPRc frames are not supported, the current code should reject them explicitly instead of producing inconsistent maps.

Suggested fix

Count local atoms in fragments[0] before the QMMM-fragment loop and use that value for nloc_qm. Separately, if pairwise DPRc requires at least one MM fragment, validate nfragments >= 2 and return a clear TensorFlow InvalidArgument error.

Add coverage for an idxs row with one QM fragment plus -1 placeholders, and verify nloc_qm, forward_qm_map, and padding behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions