Skip to content

Commit de2bb39

Browse files
[QNN EP] Debug info dump of inputs, outputs and initializers in QNN EP (microsoft#26622)
### Description - Adding comprehensive logging to the QNN EP that displays detailed information about all the graph inputs, outputs and initializers. - Information is dumped into a json file during graph composition only if we are dumping the json qnn graph. ### Motivation and Context - Useful for debugging and understanding if we are hitting peak memory during inference
1 parent d9f379c commit de2bb39

2 files changed

Lines changed: 185 additions & 0 deletions

File tree

onnxruntime/core/providers/qnn/builder/qnn_model.cc

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
184364
Status QnnModel::FinalizeGraphs(const logging::Logger& logger) {
185365
LOGS(logger, VERBOSE) << "FinalizeGraphs started.";
186366

onnxruntime/core/providers/qnn/builder/qnn_model.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ class QnnModel {
115115
Status SetupTensors(std::vector<QnnTensorInfo>& tensors, const std::vector<QnnTensorWrapper>& tensor_wrappers,
116116
bool is_input = true);
117117

118+
void LogTensorDetails(QnnModelWrapper& qnn_model_wrapper,
119+
const std::string& graph_name,
120+
const std::string& json_qnn_graph_path,
121+
const logging::Logger& logger) const;
122+
118123
QnnBackendType GetQnnBackendType() { return qnn_backend_type_; }
119124

120125
size_t GetInputOutputIndex(const std::string& name, const std::unordered_map<std::string, OnnxTensorInfo>& io_info) const {

0 commit comments

Comments
 (0)