@@ -161,6 +161,8 @@ Status QnnModel::ComposeGraph(const GraphViewer& graph_viewer,
161161 const bool build_json_graph = !json_qnn_graph_path.empty ();
162162 ORT_RETURN_IF_NOT (qnn_model_wrapper.ComposeQnnGraph (build_json_graph), " Failed to compose Qnn graph." );
163163
164+ LogTensorDetails (qnn_model_wrapper, graph_name, json_qnn_graph_path, logger);
165+
164166 if (build_json_graph) {
165167 const nlohmann::json& json_graph = qnn_model_wrapper.GetQnnJSONGraph ();
166168 std::ofstream ofs (json_qnn_graph_path);
@@ -181,6 +183,184 @@ Status QnnModel::ComposeGraph(const GraphViewer& graph_viewer,
181183 return Status::OK ();
182184}
183185
186+ void QnnModel::LogTensorDetails (QnnModelWrapper& qnn_model_wrapper,
187+ const std::string& graph_name,
188+ const std::string& json_qnn_graph_path,
189+ const logging::Logger& logger) const {
190+ // Only generate tensor details if we have a path to write to
191+ if (json_qnn_graph_path.empty ()) {
192+ return ;
193+ }
194+
195+ // Helper lambda to convert Qnn_DataType_t to string
196+ #define QNN_DATATYPE_CASE (type ) \
197+ case type: \
198+ return #type
199+
200+ auto QnnDataTypeToString = [](Qnn_DataType_t data_type) -> std::string_view {
201+ switch (data_type) {
202+ QNN_DATATYPE_CASE (QNN_DATATYPE_INT_8 );
203+ QNN_DATATYPE_CASE (QNN_DATATYPE_INT_16 );
204+ QNN_DATATYPE_CASE (QNN_DATATYPE_INT_32 );
205+ QNN_DATATYPE_CASE (QNN_DATATYPE_INT_64 );
206+ QNN_DATATYPE_CASE (QNN_DATATYPE_UINT_8 );
207+ QNN_DATATYPE_CASE (QNN_DATATYPE_UINT_16 );
208+ QNN_DATATYPE_CASE (QNN_DATATYPE_UINT_32 );
209+ QNN_DATATYPE_CASE (QNN_DATATYPE_UINT_64 );
210+ QNN_DATATYPE_CASE (QNN_DATATYPE_FLOAT_16 );
211+ QNN_DATATYPE_CASE (QNN_DATATYPE_FLOAT_32 );
212+ QNN_DATATYPE_CASE (QNN_DATATYPE_SFIXED_POINT_8 );
213+ QNN_DATATYPE_CASE (QNN_DATATYPE_SFIXED_POINT_16 );
214+ QNN_DATATYPE_CASE (QNN_DATATYPE_SFIXED_POINT_32 );
215+ QNN_DATATYPE_CASE (QNN_DATATYPE_UFIXED_POINT_8 );
216+ QNN_DATATYPE_CASE (QNN_DATATYPE_UFIXED_POINT_16 );
217+ QNN_DATATYPE_CASE (QNN_DATATYPE_UFIXED_POINT_32 );
218+ QNN_DATATYPE_CASE (QNN_DATATYPE_BOOL_8 );
219+ QNN_DATATYPE_CASE (QNN_DATATYPE_SFIXED_POINT_4 );
220+ QNN_DATATYPE_CASE (QNN_DATATYPE_UFIXED_POINT_4 );
221+ default :
222+ return " QNN_DATATYPE_UNDEFINED" ;
223+ }
224+ };
225+
226+ #undef QNN_DATATYPE_CASE
227+
228+ // Build JSON log structure
229+ nlohmann::json tensor_log;
230+ tensor_log[" graph_name" ] = graph_name;
231+ tensor_log[" inputs" ] = nlohmann::json::array ();
232+ tensor_log[" initializers" ] = nlohmann::json::array ();
233+
234+ size_t total_input_size = 0 ;
235+ size_t num_inputs = 0 ;
236+ size_t total_initializer_size = 0 ;
237+ size_t num_initializers = 0 ;
238+
239+ // Collect input tensor information
240+ const auto & model_graph_viewer = qnn_model_wrapper.GetGraphViewer ();
241+ for (const auto & input : model_graph_viewer.GetInputs ()) {
242+ const std::string& input_name = input->Name ();
243+
244+ // Skip if it's an initializer
245+ if (qnn_model_wrapper.IsConstantInput (input_name)) {
246+ continue ;
247+ }
248+
249+ // Check if this tensor exists in the QNN model
250+ if (qnn_model_wrapper.IsQnnTensorWrapperExist (input_name)) {
251+ const auto & tensor_wrapper = qnn_model_wrapper.GetQnnTensorWrapper (input_name);
252+ const auto & qnn_tensor = tensor_wrapper.GetQnnTensor ();
253+
254+ Qnn_DataType_t data_type = tensor_wrapper.GetTensorDataType ();
255+ const auto & dims = tensor_wrapper.GetTensorDims ();
256+ size_t size_bytes = utils::GetQnnTensorDataSizeInBytes (dims, data_type);
257+ uint32_t num_elements = CalcQnnTensorNumElems (qnn_tensor);
258+
259+ nlohmann::json input_info;
260+ input_info[" name" ] = input_name;
261+ input_info[" datatype" ] = QnnDataTypeToString (data_type);
262+ input_info[" num_elements" ] = num_elements;
263+ input_info[" size_bytes" ] = size_bytes;
264+
265+ tensor_log[" inputs" ].push_back (input_info);
266+ total_input_size += size_bytes;
267+ num_inputs++;
268+ }
269+ }
270+
271+ // Build a map of initializer names to the operators that use them
272+ std::unordered_map<std::string, std::vector<std::string>> initializer_to_ops;
273+ const std::vector<NodeIndex>& sorted_node_indices = model_graph_viewer.GetNodesInTopologicalOrder ();
274+
275+ for (NodeIndex node_index : sorted_node_indices) {
276+ const Node* node = model_graph_viewer.GetNode (node_index);
277+ if (node == nullptr ) {
278+ continue ;
279+ }
280+
281+ const std::string& op_type = node->OpType ();
282+ const std::string& node_name = node->Name ();
283+
284+ // Check each input of the node
285+ const auto & input_defs = node->InputDefs ();
286+ for (const auto * input_def : input_defs) {
287+ if (input_def == nullptr ) {
288+ continue ;
289+ }
290+
291+ const std::string& input_name = input_def->Name ();
292+
293+ // Check if this input is an initializer
294+ if (qnn_model_wrapper.IsConstantInput (input_name)) {
295+ // Add this operator to the list of operators using this initializer
296+ std::string op_info = op_type + " (" + node_name + " )" ;
297+ initializer_to_ops[input_name].push_back (op_info);
298+ }
299+ }
300+ }
301+
302+ // Collect initializer tensor information with operator usage
303+ const auto & initializers = qnn_model_wrapper.GetInitializerTensors ();
304+ for (const auto & initializer_pair : initializers) {
305+ const std::string& initializer_name = initializer_pair.first ;
306+
307+ // Check if this tensor exists in the QNN model
308+ if (qnn_model_wrapper.IsQnnTensorWrapperExist (initializer_name)) {
309+ const auto & tensor_wrapper = qnn_model_wrapper.GetQnnTensorWrapper (initializer_name);
310+ const auto & qnn_tensor = tensor_wrapper.GetQnnTensor ();
311+
312+ Qnn_DataType_t data_type = tensor_wrapper.GetTensorDataType ();
313+ const auto & dims = tensor_wrapper.GetTensorDims ();
314+ size_t size_bytes = utils::GetQnnTensorDataSizeInBytes (dims, data_type);
315+ uint32_t num_elements = CalcQnnTensorNumElems (qnn_tensor);
316+
317+ nlohmann::json init_info;
318+ init_info[" name" ] = initializer_name;
319+ init_info[" datatype" ] = QnnDataTypeToString (data_type);
320+ init_info[" num_elements" ] = num_elements;
321+ init_info[" size_bytes" ] = size_bytes;
322+
323+ // Add operator information if available
324+ auto it = initializer_to_ops.find (initializer_name);
325+ if (it != initializer_to_ops.end () && !it->second .empty ()) {
326+ init_info[" used_by_operators" ] = it->second ;
327+ } else {
328+ init_info[" used_by_operators" ] = nlohmann::json::array ();
329+ }
330+
331+ tensor_log[" initializers" ].push_back (init_info);
332+ total_initializer_size += size_bytes;
333+ num_initializers++;
334+ }
335+ }
336+
337+ // Add summary statistics
338+ tensor_log[" summary" ][" num_inputs" ] = num_inputs;
339+ tensor_log[" summary" ][" total_input_size_bytes" ] = total_input_size;
340+ tensor_log[" summary" ][" num_initializers" ] = num_initializers;
341+ tensor_log[" summary" ][" total_initializer_size_bytes" ] = total_initializer_size;
342+ tensor_log[" summary" ][" total_graph_size_bytes" ] = total_input_size + total_initializer_size;
343+ tensor_log[" summary" ][" total_graph_size_mb" ] = (total_input_size + total_initializer_size) / 1024.0 / 1024.0 ;
344+
345+ // Write JSON log to file
346+ std::string tensor_log_path = json_qnn_graph_path;
347+ size_t ext_pos = tensor_log_path.find_last_of (' .' );
348+ if (ext_pos != std::string::npos) {
349+ tensor_log_path = tensor_log_path.substr (0 , ext_pos) + " _tensor_log.json" ;
350+ } else {
351+ tensor_log_path += " _tensor_log.json" ;
352+ }
353+
354+ std::ofstream tensor_log_file (tensor_log_path);
355+ if (tensor_log_file.is_open ()) {
356+ tensor_log_file << tensor_log.dump (2 ); // Pretty print with 2-space indentation
357+ tensor_log_file.close ();
358+ LOGS (logger, INFO ) << " Tensor log saved to: " << tensor_log_path;
359+ } else {
360+ LOGS (logger, WARNING ) << " Could not open tensor log file: " << tensor_log_path;
361+ }
362+ }
363+
184364Status QnnModel::FinalizeGraphs (const logging::Logger& logger) {
185365 LOGS (logger, VERBOSE ) << " FinalizeGraphs started." ;
186366
0 commit comments