99#include < array>
1010#include < cstdint>
1111#include < cstdio>
12+ #include < cstring>
1213#include < iostream>
1314#include < numeric>
1415#include < ostream>
1516#include < stdexcept>
17+ #include < string>
1618#include < vector>
1719
1820#include " common.h"
@@ -48,6 +50,265 @@ inline void find_function(TF_Function*& found_func,
4850 found_func = NULL ;
4951}
5052
53+ inline bool operation_attr_bool_true (TF_Operation* oper,
54+ const char * attr_name) {
55+ TF_Status* attr_status = TF_NewStatus ();
56+ unsigned char value = 0 ;
57+ TF_OperationGetAttrBool (oper, attr_name, &value, attr_status);
58+ const bool result = TF_GetCode (attr_status) == TF_OK && value != 0 ;
59+ TF_DeleteStatus (attr_status);
60+ return result;
61+ }
62+
63+ inline bool read_proto_varint (const char *& ptr,
64+ const char * end,
65+ uint64_t & value) {
66+ value = 0 ;
67+ int shift = 0 ;
68+ while (ptr < end && shift <= 63 ) {
69+ const uint64_t byte = static_cast <unsigned char >(*ptr++);
70+ value |= (byte & 0x7f ) << shift;
71+ if ((byte & 0x80 ) == 0 ) {
72+ return true ;
73+ }
74+ shift += 7 ;
75+ }
76+ return false ;
77+ }
78+
79+ inline bool read_proto_bytes (const char *& ptr,
80+ const char * end,
81+ const char *& payload,
82+ size_t & payload_size) {
83+ uint64_t size = 0 ;
84+ if (!read_proto_varint (ptr, end, size) ||
85+ size > static_cast <uint64_t >(end - ptr)) {
86+ return false ;
87+ }
88+ payload = ptr;
89+ payload_size = static_cast <size_t >(size);
90+ ptr += payload_size;
91+ return true ;
92+ }
93+
94+ inline bool skip_proto_field (const int wire_type,
95+ const char *& ptr,
96+ const char * end) {
97+ uint64_t ignored = 0 ;
98+ const char * payload = NULL ;
99+ size_t payload_size = 0 ;
100+ switch (wire_type) {
101+ case 0 :
102+ return read_proto_varint (ptr, end, ignored);
103+ case 1 :
104+ if (end - ptr < 8 ) {
105+ return false ;
106+ }
107+ ptr += 8 ;
108+ return true ;
109+ case 2 :
110+ return read_proto_bytes (ptr, end, payload, payload_size);
111+ case 5 :
112+ if (end - ptr < 4 ) {
113+ return false ;
114+ }
115+ ptr += 4 ;
116+ return true ;
117+ default :
118+ return false ;
119+ }
120+ }
121+
122+ inline bool proto_bytes_equal (const char * payload,
123+ const size_t payload_size,
124+ const std::string& expected) {
125+ return payload_size == expected.size () &&
126+ std::memcmp (payload, expected.data (), payload_size) == 0 ;
127+ }
128+
129+ inline bool attr_value_bool_true (const char * data, const size_t size) {
130+ const char * ptr = data;
131+ const char * end = data + size;
132+ while (ptr < end) {
133+ uint64_t tag = 0 ;
134+ if (!read_proto_varint (ptr, end, tag)) {
135+ return false ;
136+ }
137+ const int field_number = static_cast <int >(tag >> 3 );
138+ const int wire_type = static_cast <int >(tag & 0x7 );
139+ // AttrValue.b = 5, encoded as a varint bool.
140+ if (field_number == 5 && wire_type == 0 ) {
141+ uint64_t value = 0 ;
142+ return read_proto_varint (ptr, end, value) && value != 0 ;
143+ }
144+ if (!skip_proto_field (wire_type, ptr, end)) {
145+ return false ;
146+ }
147+ }
148+ return false ;
149+ }
150+
151+ inline bool function_attr_bool_true (TF_Function* func, const char * attr_name) {
152+ TF_Status* attr_status = TF_NewStatus ();
153+ TF_Buffer* attr_value = TF_NewBuffer ();
154+ TF_FunctionGetAttrValueProto (func, attr_name, attr_value, attr_status);
155+ const bool result =
156+ TF_GetCode (attr_status) == TF_OK && attr_value->data != NULL &&
157+ attr_value_bool_true (static_cast <const char *>(attr_value->data ),
158+ attr_value->length );
159+ TF_DeleteBuffer (attr_value);
160+ TF_DeleteStatus (attr_status);
161+ return result;
162+ }
163+
164+ inline bool attr_entry_is_xla_must_compile_true (const char * data,
165+ const size_t size) {
166+ const char * ptr = data;
167+ const char * end = data + size;
168+ bool key_matches = false ;
169+ bool value_is_true = false ;
170+ while (ptr < end) {
171+ uint64_t tag = 0 ;
172+ if (!read_proto_varint (ptr, end, tag)) {
173+ return false ;
174+ }
175+ const int field_number = static_cast <int >(tag >> 3 );
176+ const int wire_type = static_cast <int >(tag & 0x7 );
177+ if (wire_type == 2 && (field_number == 1 || field_number == 2 )) {
178+ const char * payload = NULL ;
179+ size_t payload_size = 0 ;
180+ if (!read_proto_bytes (ptr, end, payload, payload_size)) {
181+ return false ;
182+ }
183+ if (field_number == 1 ) {
184+ key_matches =
185+ proto_bytes_equal (payload, payload_size, " _XlaMustCompile" );
186+ } else if (field_number == 2 ) {
187+ value_is_true = attr_value_bool_true (payload, payload_size);
188+ }
189+ } else if (!skip_proto_field (wire_type, ptr, end)) {
190+ return false ;
191+ }
192+ }
193+ return key_matches && value_is_true;
194+ }
195+
196+ inline bool node_def_uses_xla (const char * data, const size_t size) {
197+ const char * ptr = data;
198+ const char * end = data + size;
199+ while (ptr < end) {
200+ uint64_t tag = 0 ;
201+ if (!read_proto_varint (ptr, end, tag)) {
202+ return false ;
203+ }
204+ const int field_number = static_cast <int >(tag >> 3 );
205+ const int wire_type = static_cast <int >(tag & 0x7 );
206+ if (wire_type == 2 && (field_number == 2 || field_number == 5 )) {
207+ const char * payload = NULL ;
208+ size_t payload_size = 0 ;
209+ if (!read_proto_bytes (ptr, end, payload, payload_size)) {
210+ return false ;
211+ }
212+ // NodeDef.op = 2. This identifies jax2tf native serialization.
213+ if (field_number == 2 &&
214+ proto_bytes_equal (payload, payload_size, " XlaCallModule" )) {
215+ return true ;
216+ }
217+ // NodeDef.attr = 5. This catches PartitionedCall nodes marked by
218+ // tf.function(jit_compile=True).
219+ if (field_number == 5 &&
220+ attr_entry_is_xla_must_compile_true (payload, payload_size)) {
221+ return true ;
222+ }
223+ } else if (!skip_proto_field (wire_type, ptr, end)) {
224+ return false ;
225+ }
226+ }
227+ return false ;
228+ }
229+
230+ inline bool function_def_uses_xla (const char * data, const size_t size) {
231+ // TensorFlow's C API exposes TF_Function bodies only as serialized
232+ // FunctionDef protos. Use a minimal wire-format reader over the stable
233+ // FunctionDef/NodeDef/AttrValue field numbers instead of a raw byte
234+ // substring, and avoid depending on TensorFlow C++ protobuf headers.
235+ const char * ptr = data;
236+ const char * end = data + size;
237+ while (ptr < end) {
238+ uint64_t tag = 0 ;
239+ if (!read_proto_varint (ptr, end, tag)) {
240+ return false ;
241+ }
242+ const int field_number = static_cast <int >(tag >> 3 );
243+ const int wire_type = static_cast <int >(tag & 0x7 );
244+ if (wire_type == 2 && (field_number == 3 || field_number == 5 )) {
245+ const char * payload = NULL ;
246+ size_t payload_size = 0 ;
247+ if (!read_proto_bytes (ptr, end, payload, payload_size)) {
248+ return false ;
249+ }
250+ // FunctionDef.node_def = 3.
251+ if (field_number == 3 && node_def_uses_xla (payload, payload_size)) {
252+ return true ;
253+ }
254+ // FunctionDef.attr = 5. This catches concrete functions marked by
255+ // tf.function(jit_compile=True).
256+ if (field_number == 5 &&
257+ attr_entry_is_xla_must_compile_true (payload, payload_size)) {
258+ return true ;
259+ }
260+ } else if (!skip_proto_field (wire_type, ptr, end)) {
261+ return false ;
262+ }
263+ }
264+ return false ;
265+ }
266+
267+ inline bool function_uses_xla (TF_Function* func, TF_Status* status) {
268+ if (function_attr_bool_true (func, " _XlaMustCompile" )) {
269+ return true ;
270+ }
271+ TF_Buffer* func_def = TF_NewBuffer ();
272+ TF_FunctionToFunctionDef (func, func_def, status);
273+ if (TF_GetCode (status) != TF_OK ) {
274+ std::string msg = TF_Message (status);
275+ TF_DeleteBuffer (func_def);
276+ throw deepmd::deepmd_exception (" TensorFlow C API Error: " + msg);
277+ }
278+ const bool uses_xla =
279+ func_def->data != NULL &&
280+ function_def_uses_xla (static_cast <const char *>(func_def->data ),
281+ func_def->length );
282+ TF_DeleteBuffer (func_def);
283+ return uses_xla;
284+ }
285+
286+ inline bool graph_uses_xla_compilation (TF_Graph* graph) {
287+ size_t pos = 0 ;
288+ while (TF_Operation* oper = TF_GraphNextOperation (graph, &pos)) {
289+ const char * op_type = TF_OperationOpType (oper);
290+ if ((op_type != NULL && std::strcmp (op_type, " XlaCallModule" ) == 0 ) ||
291+ operation_attr_bool_true (oper, " _XlaMustCompile" )) {
292+ return true ;
293+ }
294+ }
295+ return false ;
296+ }
297+
298+ inline bool uses_xla_compilation (TF_Graph* graph,
299+ const std::vector<TF_Function*>& funcs,
300+ TF_Status* status) {
301+ if (graph_uses_xla_compilation (graph)) {
302+ return true ;
303+ }
304+ for (TF_Function* func : funcs) {
305+ if (function_uses_xla (func, status)) {
306+ return true ;
307+ }
308+ }
309+ return false ;
310+ }
311+
51312inline TF_DataType get_data_tensor_type (const std::vector<double >& data) {
52313 return TF_DOUBLE ;
53314}
@@ -278,6 +539,7 @@ void deepmd::DeepPotJAX::init(const std::string& model,
278539 TF_Function** funcs = func_vector.data ();
279540 TF_GraphGetFunctions (graph, funcs, nfuncs, status);
280541 check_status (status);
542+ uses_xla_compilation_ = uses_xla_compilation (graph, func_vector, status);
281543
282544 ctx_opts = TFE_NewContextOptions ();
283545 TFE_ContextOptionsSetConfig (ctx_opts, config.data (), config.size (), status);
@@ -547,16 +809,21 @@ void deepmd::DeepPotJAX::compute(std::vector<ENERGYTYPE>& ener,
547809 std::vector<double > fparam_double (fparam.begin (), fparam.end ());
548810 std::vector<double > aparam_double (aparam.begin (), aparam.end ());
549811
550- if (padding_for_nloc != nloc_real) {
551- padding_to_nall = nall_real * PADDING_FACTOR ;
552- padding_for_nloc = nloc_real;
553- }
554- while (padding_to_nall < nall_real) {
555- padding_to_nall *= PADDING_FACTOR ;
812+ int nall_model = nall_real;
813+ if (uses_xla_compilation_) {
814+ if (padding_for_nloc != nloc_real) {
815+ padding_to_nall = nall_real * PADDING_FACTOR ;
816+ padding_for_nloc = nloc_real;
817+ }
818+ while (padding_to_nall < nall_real) {
819+ padding_to_nall *= PADDING_FACTOR ;
820+ }
821+ nall_model = padding_to_nall;
556822 }
557- // do padding
558- coord_double.resize (nframes * padding_to_nall * 3 , 0.0 );
559- atype.resize (nframes * padding_to_nall, -1 );
823+ // Padding is only useful for XLA-compiled functions; eager TF graphs can use
824+ // the exact atom count without shape recompilation churn.
825+ coord_double.resize (static_cast <size_t >(nframes) * nall_model * 3 , 0.0 );
826+ atype.resize (static_cast <size_t >(nframes) * nall_model, -1 );
560827
561828 TFE_Op* op;
562829 if (atomic) {
@@ -569,11 +836,11 @@ void deepmd::DeepPotJAX::compute(std::vector<ENERGYTYPE>& ener,
569836 std::vector<TFE_TensorHandle*> input_list (6 );
570837 std::vector<TF_Tensor*> data_tensor (6 );
571838 // coord
572- std::vector<int64_t > coord_shape = {nframes, padding_to_nall , 3 };
839+ std::vector<int64_t > coord_shape = {nframes, nall_model , 3 };
573840 input_list[0 ] =
574841 add_input (op, coord_double, coord_shape, data_tensor[0 ], status);
575842 // atype
576- std::vector<int64_t > atype_shape = {nframes, padding_to_nall };
843+ std::vector<int64_t > atype_shape = {nframes, nall_model };
577844 input_list[1 ] = add_input (op, atype, atype_shape, data_tensor[1 ], status);
578845 // nlist
579846 if (ago == 0 ) {
@@ -600,8 +867,8 @@ void deepmd::DeepPotJAX::compute(std::vector<ENERGYTYPE>& ener,
600867 }
601868 input_list[2 ] = add_input (op, nlist, nlist_shape, data_tensor[2 ], status);
602869 // mapping; for now, set it to -1, assume it is not used
603- std::vector<int64_t > mapping_shape = {nframes, padding_to_nall };
604- std::vector<int64_t > mapping (nframes * padding_to_nall , -1 );
870+ std::vector<int64_t > mapping_shape = {nframes, nall_model };
871+ std::vector<int64_t > mapping (static_cast < size_t >( nframes) * nall_model , -1 );
605872 // pass mapping if it is given in the neighbor list
606873 if (lmp_list.mapping ) {
607874 // assume nframes is 1
0 commit comments