Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions source/op/tf/custom_op.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
#include <iostream>
#include <limits>
#include <string>
#include <utility>
#include <vector>
Expand Down Expand Up @@ -51,6 +52,48 @@ inline Status InvalidArgument(Args&&... args) {
return tensorflow::errors::InvalidArgument(std::forward<Args>(args)...);
#endif
}

/**
* @brief Derive a dense tensor's per-atom width without truncating division.
*
* Several low-level TensorFlow ops flatten atom and feature dimensions into a
* single axis. Validate the flattened width before dividing by `nloc`; raw
* CPU/GPU kernels cannot safely consume a leftover partial atom row.
*
* @param per_atom_width Receives the validated feature width for one atom.
* @param shape Rank-two tensor shape whose second dimension is flattened.
* @param nloc Number of local atoms encoded in the flattened dimension.
* @param tensor_name Human-readable input name used in validation errors.
* @return An OK status, or InvalidArgument when the width is incompatible.
*/
inline Status GetPerAtomWidth(int* per_atom_width,
Comment thread
njzjz-bot marked this conversation as resolved.
const TensorShape& shape,
const int nloc,
const char* tensor_name) {
const int64_t flattened_width = shape.dim_size(1);
if (nloc < 0) {
return InvalidArgument("number of local atoms should be non-negative");
}
if (nloc == 0) {
if (flattened_width != 0) {
return InvalidArgument(tensor_name,
" width should be zero when nloc is zero");
}
*per_atom_width = 0;
return Status();
}
if (flattened_width % nloc != 0) {
return InvalidArgument(tensor_name, " width ", flattened_width,
" should be divisible by nloc ", nloc);
}
const int64_t width = flattened_width / nloc;
if (width > std::numeric_limits<int>::max()) {
return InvalidArgument(tensor_name,
" width per atom exceeds the supported int range");
}
*per_atom_width = static_cast<int>(width);
return Status();
}
} // namespace tf_compat
} // namespace deepmd

Expand Down
22 changes: 18 additions & 4 deletions source/op/tf/prod_force_grad_multi_device.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@ class ProdForceSeAGradOp : public OpKernel {

int nframes = net_deriv_tensor.shape().dim_size(0);
int nloc = natoms(0);
int ndescrpt = nloc > 0 ? net_deriv_tensor.shape().dim_size(1) / nloc : 0;
int nnei = nloc > 0 ? nlist_tensor.shape().dim_size(1) / nloc : 0;
int ndescrpt;
int nnei;
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&ndescrpt, net_deriv_shape, nloc, "net deriv"));
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&nnei, nlist_shape, nloc, "nlist"));

// check the sizes
OP_REQUIRES(
Expand All @@ -97,6 +101,9 @@ class ProdForceSeAGradOp : public OpKernel {
OP_REQUIRES(
context, (nnei == n_a_sel + n_r_sel),
deepmd::tf_compat::InvalidArgument("number of neighbors should match"));
OP_REQUIRES(context, (static_cast<int64_t>(nnei) * 4 == ndescrpt),
deepmd::tf_compat::InvalidArgument(
"descriptor width should be four times neighbor width"));

// Create an output tensor
TensorShape grad_net_shape;
Expand Down Expand Up @@ -194,8 +201,12 @@ class ProdForceSeRGradOp : public OpKernel {

int nframes = net_deriv_tensor.shape().dim_size(0);
int nloc = natoms(0);
int ndescrpt = nloc > 0 ? net_deriv_tensor.shape().dim_size(1) / nloc : 0;
int nnei = nloc > 0 ? nlist_tensor.shape().dim_size(1) / nloc : 0;
int ndescrpt;
int nnei;
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&ndescrpt, net_deriv_shape, nloc, "net deriv"));
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&nnei, nlist_shape, nloc, "nlist"));

// check the sizes
OP_REQUIRES(
Expand All @@ -215,6 +226,9 @@ class ProdForceSeRGradOp : public OpKernel {
(int_64(nloc) * ndescrpt * 3 == in_deriv_shape.dim_size(1)),
deepmd::tf_compat::InvalidArgument(
"number of descriptors should match"));
OP_REQUIRES(context, (nnei == ndescrpt),
deepmd::tf_compat::InvalidArgument(
"descriptor width should equal neighbor width"));

// Create an output tensor
TensorShape grad_net_shape;
Expand Down
24 changes: 20 additions & 4 deletions source/op/tf/prod_force_multi_device.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,13 @@ class ProdForceSeAOp : public OpKernel {
int nloc = natoms[0];
int nall = natoms[1];
int nframes = net_deriv_tensor.shape().dim_size(0);
int ndescrpt = nloc > 0 ? net_deriv_tensor.shape().dim_size(1) / nloc : 0;
int nnei = nloc > 0 ? nlist_tensor.shape().dim_size(1) / nloc : 0;
int ndescrpt;
int nnei;
OP_REQUIRES_OK(context,
deepmd::tf_compat::GetPerAtomWidth(
&ndescrpt, net_deriv_tensor.shape(), nloc, "net deriv"));
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&nnei, nlist_tensor.shape(), nloc, "nlist"));
// check the sizes
OP_REQUIRES(
context, (nframes == in_deriv_tensor.shape().dim_size(0)),
Expand All @@ -106,6 +111,9 @@ class ProdForceSeAOp : public OpKernel {
(int_64(nloc) * ndescrpt * 3 == in_deriv_tensor.shape().dim_size(1)),
deepmd::tf_compat::InvalidArgument(
"number of descriptors should match"));
OP_REQUIRES(context, (static_cast<int64_t>(nnei) * 4 == ndescrpt),
deepmd::tf_compat::InvalidArgument(
"descriptor width should be four times neighbor width"));
// Create an output tensor
TensorShape force_shape;
force_shape.AddDim(nframes);
Expand Down Expand Up @@ -199,8 +207,13 @@ class ProdForceSeROp : public OpKernel {
int nloc = natoms[0];
int nall = natoms[1];
int nframes = net_deriv_tensor.shape().dim_size(0);
int ndescrpt = nloc > 0 ? net_deriv_tensor.shape().dim_size(1) / nloc : 0;
int nnei = nloc > 0 ? nlist_tensor.shape().dim_size(1) / nloc : 0;
int ndescrpt;
int nnei;
OP_REQUIRES_OK(context,
deepmd::tf_compat::GetPerAtomWidth(
&ndescrpt, net_deriv_tensor.shape(), nloc, "net deriv"));
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&nnei, nlist_tensor.shape(), nloc, "nlist"));
// check the sizes
OP_REQUIRES(
context, (nframes == in_deriv_tensor.shape().dim_size(0)),
Expand All @@ -213,6 +226,9 @@ class ProdForceSeROp : public OpKernel {
in_deriv_tensor.shape().dim_size(1)),
deepmd::tf_compat::InvalidArgument(
"number of descriptors should match"));
OP_REQUIRES(context, (nnei == ndescrpt),
deepmd::tf_compat::InvalidArgument(
"descriptor width should equal neighbor width"));
// Create an output tensor
TensorShape force_shape;
force_shape.AddDim(nframes);
Expand Down
22 changes: 18 additions & 4 deletions source/op/tf/prod_virial_grad_multi_device.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,12 @@ class ProdVirialSeAGradOp : public OpKernel {

int nframes = net_deriv_tensor.shape().dim_size(0);
int nloc = natoms(0);
int ndescrpt = nloc > 0 ? net_deriv_tensor.shape().dim_size(1) / nloc : 0;
int nnei = nloc > 0 ? nlist_tensor.shape().dim_size(1) / nloc : 0;
int ndescrpt;
int nnei;
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&ndescrpt, net_deriv_shape, nloc, "net deriv"));
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&nnei, nlist_shape, nloc, "nlist"));

// check the sizes
OP_REQUIRES(
Expand Down Expand Up @@ -109,6 +113,9 @@ class ProdVirialSeAGradOp : public OpKernel {
OP_REQUIRES(
context, (nnei == n_a_sel + n_r_sel),
deepmd::tf_compat::InvalidArgument("number of neighbors should match"));
OP_REQUIRES(context, (static_cast<int64_t>(nnei) * 4 == ndescrpt),
deepmd::tf_compat::InvalidArgument(
"descriptor width should be four times neighbor width"));

// Create an output tensor
TensorShape grad_net_shape;
Expand Down Expand Up @@ -223,8 +230,12 @@ class ProdVirialSeRGradOp : public OpKernel {

int nframes = net_deriv_tensor.shape().dim_size(0);
int nloc = natoms(0);
int ndescrpt = nloc > 0 ? net_deriv_tensor.shape().dim_size(1) / nloc : 0;
int nnei = nloc > 0 ? nlist_tensor.shape().dim_size(1) / nloc : 0;
int ndescrpt;
int nnei;
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&ndescrpt, net_deriv_shape, nloc, "net deriv"));
OP_REQUIRES_OK(context, deepmd::tf_compat::GetPerAtomWidth(
&nnei, nlist_shape, nloc, "nlist"));

// check the sizes
OP_REQUIRES(
Expand All @@ -250,6 +261,9 @@ class ProdVirialSeRGradOp : public OpKernel {
OP_REQUIRES(
context, (int_64(nloc) * nnei * 3 == rij_shape.dim_size(1)),
deepmd::tf_compat::InvalidArgument("dim of rij should be nnei * 3"));
OP_REQUIRES(context, (nnei == ndescrpt),
deepmd::tf_compat::InvalidArgument(
"descriptor width should equal neighbor width"));

// Create an output tensor
TensorShape grad_net_shape;
Expand Down
Loading
Loading