mega-qnn-tools 0.1.3
The mega-qnn-tools is a toolkit based on QNN integration, primarily aimed at providing algorithm developers with one-click model deployment tools.
Requires Python: ==3.8

# Mega-qnn-tools
## 0. 版本记录
|version|date|内容|
|---|---|---|
|v0.1.0|2023-2-6|初始提交
|v0.1.1|2023-5-08|更新错误|
|v0.1.2|2024-5-14|新增一键qnn-in-one.py等脚本|
|v0.1.3|2024-5-23|新增7条cli命令

## 1. 简介
mega-qnn-tools是基于QNN集成的工具包，主要目的是为算法开发人员提供一键式的模型部署工具。
该包的功能包括环境设置、转换数据格式、生成原始数据列表、一键式模型转换、生成假样本以及运行onnxruntime推理和执行指定优化Pass。具体来说，它提供了以下命令行工具：

- mega-qnn-env-set：环境设置;
- mega-trans-data：转换校准数据的格式，onnx2qnx，或者qnx2onnx; **QNN要求校准数据格式为NHWC**
- mega-raw-list-generator：生成原始数据列表。
- mega-qnn-in-one：一键式模型转换。
- mega-generate-fake-samples：生成假数据集，如果没有真实校准数据集，可以使用此功能生成，用于调试。
- mega-run-ort：运行onnxruntime推理，用于调试
- mega-pass-run：运行优化Pass，用于调试。

此包仅支持Python 3.8版本。

## 2. 安装
### 2.0 安装mega-qnn-tools
```bash
pip install mega-qnn-tools
```

**安装完这个包之后，以下命令均在终端执行。**
### 2.1 环境设置
```bash
mega-qnn-env-set
```
0.1.1版本：支持qnx环境，android环境还没有在开发进行验证

### 2.2 转换数据格式
QNN要求校准数据格式为NHWC。此命令用于处理数据转换任务，需要用户提供输入数据目录、新数据目录和输入形状列表，同时允许用户自定义转换顺序和最大样本数量。
```bash
mega-trans-data --data_dir /path/to/input/data --data_dir_new /path/to/output/data --shape_list "imgs:1,18,360,640,trans:1,6,3" --trans_order onnx2qnn --max_samples_count 1000
```
`--data_dir`和`--data_dir_new`参数分别指定了输入数据目录和新数据目录的路径。`--shape_list`参数指定了输入形状列表，使用逗号分隔。`--trans_order`参数指定了转换顺序，可以是'onnx2qnn'或'qnn2onnx'。`--max_samples_count`参数指定了最大样本数量。


### 2.3 生成原始数据列表
用于生成原始数据列表，需要用户提供模型路径和数据目录，同时允许用户自定义校准数量和测试样本数量
```bash
mega-raw-list-generator -model_path /path/to/your/model --data_dir /path/to/your/data --max_calib_count 10000 --test_count 20
```
`--model_path`和`--data_dir`参数分别指定了模型文件的路径和数据目录的路径。如果你想要改变最大校准数量和测试样本数量的默认值，可以添加  `--max_calib_count`和`--test_count`参数  

### 2.4 生成假数据集[如果没有真实校准数据集]
如果没有真实校准数据集，可以使用此功能生成假数据集，**用于调试**
```bash
mega-generate-fake-samples --data_dir ./data --model_path ./model/my_model --max_samples_count 1000 --force --range -1.0 1.0
```
`--data_dir`，用于指定存储生成样本数据的目录，`--model_path`表示模型的路径，`--max_samples_count` 用于指定要生成的最大样本数量，表示希望生成的样本数据的数量上限。`--force`：这是一个可选参数。如果提供这个参数，当目标数据目录已经存在时，脚本会强制删除现有目录并重新创建，而不是提示用户目录已存在。这在你想要覆盖旧数据时使用非常有用。`--range`这是一个可选参数，接受两个浮点数作为值，用于指定生成样本值的范围。默认情况下，范围是-2.0到2.0。你可以根据需要调整这个范围。

### 2.5 运行onnxruntime推理
对模型进行onnxruntime推理，**用于调试**
```bash
mega-run-ort --input_model path/to/your/model.onnx --raw_list path/to/your/raw_list.txt --output_dir path/to/your/output_dir --is_io_nhwc --print_part_output
```
` --input_model`为ONNX模型文件的实际路径，`--raw_list`为原始列表文件的实际路径，`--output_dir`为你希望存储输出结果的目录的实际路径，`--is_io_nhwc`表示输入输出是否使用NHWC格式，`--print_part_output`表示打印部分输出

### 2.6 运行优化过程
运行优化Pass，**用于调试**
```bash
mega-pass-run --input_model path/to/input_model.onnx --raw_list path/to/raw_list.txt --output_model path/to/output_model.onnx --model_output_dir path/to/output_dir --pass_list pass1,pass2,pass3
```
` --input_model`为ONNX模型文件的实际路径，`--raw_list`为原始列表文件的实际路径，`--output_model`是经过passes处理后生成的输出模型的文件路径，`--model_output_dir`是模型输出的目录路径，`--pass_list`是要运行的pass名称列表，用逗号分隔。这些pass名称必须是PassManager.get_pass_names()方法返回的有效pass名称。如何定义Pass，请参考以下示例：

```shell
from custom_passes.PassBase import PassBase
from custom_passes.common import *
from printcolor import *

class MakeAllTensorsOutput(PassBase):
    
    @staticmethod
    def add_all_tensors_as_outputs(graph):
        excludes_op_type = ['Reshape', 
                            'Slice', 
                            'Transpose', 
                            'Squeeze', 
                            'Unsqueeze', 
                            'Flatten', 
                            'Concat', 
                            'Gather', 
                            'GatherND',
                            'Tile',
                            'Expand',
                            'Scatter',
                            'ScatterND']
        # Get tensors in topological order
        ordered_tensors = []
        visited = set()
        for node in graph.toposort().nodes:
            if node.op in excludes_op_type:
                continue
            for tensor in node.outputs:
                if tensor.name not in visited:
                    ordered_tensors.append(tensor)
                    visited.add(tensor.name)
        for tensor in graph.outputs:
            ordered_tensors.append(tensor)

        # Add all tensors as graph outputs
        graph.outputs.clear()
        for tensor in ordered_tensors:
            tensor.name = tensor.name.replace(f'/', f'_').replace(f'.', f'_')
            graph.outputs.append(tensor)

        # Clean up any disconnected nodes
        graph.cleanup().toposort()

        return graph
    def run(self, graph):
        printBlue(f'[pass] {self.__class__.__name__}')
        graph = MakeAllTensorsOutput.add_all_tensors_as_outputs(graph)
        return graph
```

## 3 参考文件
### 3.1 Qnn官方教程
- [QNN SDK Document](https://docs.qualcomm.com/bundle/publicresource/topics/80-63442-50/introduction.html?product=1601111740009302)  
- [Converting and executing model on HTP with QNN](https://drive.google.com/file/d/1U44EKPlDVJ72FVCZ4_aKdJQTtMPZDZPF/view?usp=drive_link)  

### 3.2 相关文档
- [HTP Network Design Guide](https://drive.google.com/file/d/1BASLcJ_I0gLDnHJnAjIEgKKTiLSWCCs4/view?usp=drive_link)  
- [QNN_UDO_Template.pdf](https://drive.google.com/file/d/1Ziy8fScx5UjiMHzHh-5a-vaBCqJh0gha/view?usp=drive_link)  
- [QNN_UDO_Guide.pdf](https://drive.google.com/file/d/10XUucUigmpu2O1p88W37DGhAapNcW3if/view?usp=drive_link)