Skip to content

[Code scan] Materialize JAX default fparam in the C++ API #5658

Description

@njzjz

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

Problem

The JAX C++ API exposes has_default_fparam(), but it does not load or materialize the default frame parameter values before constructing C API tensors.

The JAX export wrapper preserves both the flag and the default values:

  • if hasattr(self.model, "has_default_fparam"):
    # No attrs before v3.1.2
    self._has_default_fparam = self.model.has_default_fparam().numpy().item()
    else:
    self._has_default_fparam = False
    if hasattr(self.model, "get_default_fparam"):
    self.default_fparam = self.model.get_default_fparam().numpy().tolist()
    else:
    self.default_fparam = None
  • def has_default_fparam(self) -> bool:
    """Check whether the model has default frame parameters."""
    return self._has_default_fparam
    def get_default_fparam(self) -> list[float] | None:
    """Get the default frame parameters."""
    return self.default_fparam

DeepPotJAX reads only the boolean:

The header stores no default fparam vector, only the flag:

During inference, the direct path copies the caller-provided fparam vector and always shapes it as {nframes, dfparam}:

  • std::vector<double> coord_double(coord.begin(), coord.end());
    std::vector<double> box_double(box.begin(), box.end());
    std::vector<double> fparam_double(fparam.begin(), fparam.end());
    std::vector<double> aparam_double(aparam.begin(), aparam.end());
    TFE_Op* op;
    if (atomic) {
    op = get_func_op(ctx, "call_with_atomic_virial", func_vector, device,
    status);
    } else {
    op = get_func_op(ctx, "call_without_atomic_virial", func_vector, device,
    status);
    }
    std::vector<TFE_TensorHandle*> input_list(5);
    std::vector<TF_Tensor*> data_tensor(5);
    // coord
    std::vector<int64_t> coord_shape = {nframes, nloc_real, 3};
    input_list[0] =
    add_input(op, coord_double, coord_shape, data_tensor[0], status);
    // atype
    std::vector<int64_t> atype_shape = {nframes, nloc_real};
    input_list[1] = add_input(op, atype, atype_shape, data_tensor[1], status);
    // box
    int box_size = box_double.size() > 0 ? 3 : 0;
    std::vector<int64_t> box_shape = {nframes, box_size, box_size};
    input_list[2] = add_input(op, box_double, box_shape, data_tensor[2], status);
    // fparam
    std::vector<int64_t> fparam_shape = {nframes, dfparam};
    input_list[3] =
    add_input(op, fparam_double, fparam_shape, data_tensor[3], status);

The neighbor-list path has the same behavior:

create_tensor() allocates bytes from data.size(), not from the requested TensorFlow shape:

  • template <typename T>
    inline TF_Tensor* create_tensor(const std::vector<T>& data,
    const std::vector<int64_t>& shape) {
    TF_Tensor* tensor =
    TF_AllocateTensor(get_data_tensor_type(data), shape.data(), shape.size(),
    data.size() * sizeof(T));
    memcpy(TF_TensorData(tensor), data.data(), TF_TensorByteSize(tensor));
    return tensor;

Therefore, when the model has a default fparam and the caller omits fparam, the C++ API reports that a default exists but still tries to build a non-empty {nframes, dfparam} tensor from an empty vector.

Impact

JAX models with default frame parameters can fail or send an invalid empty-buffer tensor through the C++ API unless callers explicitly provide fparam, even though the API advertises default fparam support.

Suggested fix

Load get_default_fparam during JAX model initialization, store it in DeepPotJAX, and materialize/tile it when the caller passes an empty fparam and has_default_fparam_ is true. Also validate create_tensor() inputs so the byte count matches the requested shape before allocating the TensorFlow tensor.

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