Skip to content

[Code scan] Honor tensor-provided neighbor lists in NVNMD env-mat ops #5652

Description

@njzjz

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

Problem

The NVNMD env-mat TensorFlow ops accept tensor-provided neighbor lists (nei_mode == 4) but then ignore the neighbor-list data and rebuild neighbors from coordinates.

Both NVNMD env-mat paths classify mesh.size() > 16 as a tensor-stored neighbor list:

  • int nei_mode = 0;
    bool b_nlist_map = false;
    if (mesh_tensor.shape().dim_size(0) == 16) {
    // lammps neighbor list
    nei_mode = 3;
    } else if (mesh_tensor.shape().dim_size(0) == 6) {
    // manual copied pbc
    assert(nloc == nall);
    nei_mode = 1;
    b_nlist_map = true;
    } else if (mesh_tensor.shape().dim_size(0) == 0) {
    // no pbc
    assert(nloc == nall);
    nei_mode = -1;
    } else if (mesh_tensor.shape().dim_size(0) > 16) {
    // pass neighbor list inside the tensor
    nei_mode = 4;
    } else if (mesh_tensor.shape().dim_size(0) == 7 ||
  • int nei_mode = 0;
    bool b_nlist_map = false;
    if (mesh_tensor.shape().dim_size(0) == 16) {
    // lammps neighbor list
    nei_mode = 3;
    } else if (mesh_tensor.shape().dim_size(0) == 6 ||
    mesh_tensor.shape().dim_size(0) == 7) {
    // manual copied pbc
    nei_mode = 1;
    b_nlist_map = true;
    } else if (mesh_tensor.shape().dim_size(0) == 0 ||
    mesh_tensor.shape().dim_size(0) == 1) {
    // no pbc
    nei_mode = -1;
    } else if (mesh_tensor.shape().dim_size(0) > 16) {
    // pass neighbor list inside the tensor
    nei_mode = 4;
    } else {

However, the shared NVNMD _prepare_coord_nlist_cpu() treats every mode except 3 as "build nlist by myself":

  • template <typename FPTYPE>
    static void _prepare_coord_nlist_cpu(OpKernelContext* context,
    FPTYPE const** coord,
    std::vector<FPTYPE>& coord_cpy,
    int const** type,
    std::vector<int>& type_cpy,
    std::vector<int>& idx_mapping,
    deepmd::InputNlist& inlist,
    std::vector<int>& ilist,
    std::vector<int>& numneigh,
    std::vector<int*>& firstneigh,
    std::vector<std::vector<int>>& jlist,
    int& new_nall,
    int& mem_cpy,
    int& mem_nnei,
    int& max_nbor_size,
    const FPTYPE* box,
    const int* mesh_tensor_data,
    const int& nloc,
    const int& nei_mode,
    const float& rcut_r,
    const int& max_cpy_trial,
    const int& max_nnei_trial) {
    inlist.inum = nloc;
    if (nei_mode != 3) {
    // build nlist by myself
    // normalize and copy coord
    if (nei_mode == 1) {
    int copy_ok = _norm_copy_coord_cpu(coord_cpy, type_cpy, idx_mapping,
    new_nall, mem_cpy, *coord, box, *type,
    nloc, max_cpy_trial, rcut_r);
    OP_REQUIRES(context, copy_ok,
    errors::Aborted("cannot allocate mem for copied coords"));
    *coord = &coord_cpy[0];
    *type = &type_cpy[0];
    }
    // build nlist
    int build_ok = _build_nlist_cpu(ilist, numneigh, firstneigh, jlist,
    max_nbor_size, mem_nnei, *coord, nloc,
    new_nall, max_nnei_trial, rcut_r);
    OP_REQUIRES(context, build_ok,
    errors::Aborted("cannot allocate mem for nlist"));
    inlist.ilist = &ilist[0];
    inlist.numneigh = &numneigh[0];
    inlist.firstneigh = &firstneigh[0];
    } else {

The non-NVNMD implementation has a separate nei_mode == 4 branch that copies ilist, numneigh, and jlist from the mesh tensor:

  • inlist.inum = nloc;
    if (nei_mode != 3 && nei_mode != 4) {
    // build nlist by myself
    // normalize and copy coord
    if (nei_mode == 1) {
    int copy_ok = _norm_copy_coord_cpu(coord_cpy, type_cpy, idx_mapping,
    new_nall, mem_cpy, *coord, box, *type,
    nloc, max_cpy_trial, rcut_r);
    OP_REQUIRES(context, copy_ok,
    errors::Aborted("cannot allocate mem for copied coords"));
    *coord = &coord_cpy[0];
    *type = &type_cpy[0];
    }
    // build nlist
    int build_ok = _build_nlist_cpu(ilist, numneigh, firstneigh, jlist,
    max_nbor_size, mem_nnei, *coord, nloc,
    new_nall, max_nnei_trial, rcut_r);
    OP_REQUIRES(context, build_ok,
    errors::Aborted("cannot allocate mem for nlist"));
    inlist.ilist = &ilist[0];
    inlist.numneigh = &numneigh[0];
    inlist.firstneigh = &firstneigh[0];
    } else if (nei_mode == 4) {
    std::memcpy(&ilist[0], 16 + mesh_tensor_data, sizeof(int) * nloc);
    std::memcpy(&numneigh[0], 16 + nloc + mesh_tensor_data, sizeof(int) * nloc);
    for (int ii = 0, kk = 0; ii < nloc; ++ii) {
    jlist[ii].resize(numneigh[ii]);
    std::memcpy(&jlist[ii][0], 16 + 2 * nloc + kk + mesh_tensor_data,
    sizeof(int) * numneigh[ii]);
    firstneigh[ii] = &jlist[ii][0];
    kk += numneigh[ii];
    }
    inlist.ilist = &ilist[0];
    inlist.numneigh = &numneigh[0];
    inlist.firstneigh = &firstneigh[0];
    } else {

Impact

Calls to ProdEnvMatANvnmdQuantize or ProdEnvMatAMixNvnmdQuantize with a tensor-stored neighbor list can silently compute descriptors using a rebuilt distance neighbor list instead of the caller-provided list. That is especially risky for tests or integrations that intentionally provide a prefiltered or otherwise non-distance-equivalent neighbor list.

Suggested fix

Mirror the non-NVNMD nei_mode == 4 branch in the NVNMD helper, including the ilist, numneigh, jlist, and firstneigh setup.

Add CPU coverage for both NVNMD env-mat ops where the tensor-provided neighbor list differs from the distance-built neighbor list, and verify the op follows the tensor list.

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