Accuracy Tuning Practice

This section introduces the ONNX model accuracy tuning pipeline using the precision problems encountered in actual use as an example. Please read the Model Accuracy Tuning chapter firstly to Understand relevant theoretical knowledge and tool usage.

Typical accuracy issues include:

  1. The all node type int16 quantization accuracy meets the requirements and Accuracy Debug Tool can provide relatively accurate sorting of sensitive nodes;
  2. The all node type int16 quantization accuracy meets the requirements, but setting a large number of sensitive nodes to higher precision cannot effectively improve quantization accuracy;
  3. The all node type int16 quantization accuracy does not meet the standard, under the premise of full BPU quantization of the model, we hope to further improve the quantization accuracy.

Sensitive Node Analysis

Accuracy Debug Tool provides an interface for calculating node quantization sensitivity. It can calculate the impact of each operator's quantization on the output results, set nodes with high quantization loss to higher precision, and complete accuracy tuning. The tuning pipeline is described using the HybridNets model as an example.

Using HMCT default INT8 quantization, percentile is selected as the calibration algorithm, the calibration accuracy does not meet the requirements (the accuracy of det and ll_seg decreases by more than 1%):

                    Model    Float    March  Samples    calibrated_model     Cosine_Similarity  
-------------------------  -------  -------  ---------  ------------------   -------------------
hybridnets-384-640_det     0.77222   nash-e      10000     0.75562(97.85%)               0.98012
hybridnets-384-640_da_seg  0.90467   nash-e      10000     0.89675(99.12%)               0.98012
hybridnets-384-640_ll_seg  0.85376   nash-e      10000     0.81813(95.83%)               0.98012

All Node Type INT16

First, set all_node_type to INT16 and select percentile for the calibration algorithm. At this point, the calibration accuracy meets the requirements, and we can use INT8+INT16 mixed precision to complete the tuning:

quant_config = {"model_config": {"all_node_type": "int16"}}

                    Model    Float    March    Samples    calibrated_model      Cosine_Similarity
-------------------------  -------  -------  ---------  ------------------    -------------------
   hybridnets-384-640_det  0.77222   nash-e      10000     0.76866(99.54%)               0.997147
hybridnets-384-640_da_seg  0.90467   nash-e      10000     0.90405(99.93%)               0.997147
hybridnets-384-640_ll_seg  0.85376   nash-e      10000     0.84732(99.25%)               0.997147

Mixed Precision Debugging

Compile the INT8 calibration model based on the percentile calibration algorithm selected for INT16 model, configure debug_mode: "dump_calibration_data" in the yaml file to save the calibration data, and output the node quantization sensitivity through get_sensitivity_of_nodes:

hmct-debugger get-sensitivity-of-nodes hybridnets-384-640_calibrated_model.onnx calibration_data/ -n node -v True -s ./debug_result
===========================node sensitivity============================
node                                                cosine-similarity  
-----------------------------------------------------------------------
/encoder/_blocks.0/_depthwise_conv/Conv             0.98768            
/encoder/_swish/Mul                                 0.99526            
/encoder/_blocks.2/_depthwise_conv/Conv             0.99852            
/encoder/_blocks.0/Mul                              0.99887            
/encoder/_blocks.0/GlobalAveragePool                0.99889            
/encoder/_blocks.2/_swish/Mul                       0.99957            
/bifpn/bifpn.5/conv3_up/depthwise_conv/conv/Conv    0.99964            
/encoder/_blocks.0/_swish/Mul                       0.99969            
/encoder/_blocks.2/Mul                              0.99979            
/encoder/_blocks.2/GlobalAveragePool                0.9998             
/bifpn/bifpn.2/conv3_up/pointwise_conv/conv/Conv    0.99983            
/encoder/_blocks.2/_swish_1/Mul                     0.99984            
/bifpn/bifpn.5/p4_downsample/Pad                    0.99985            
...er/seg_blocks.4/block/block.0/block/block.0/Pad  0.99985            
/encoder/_blocks.0/_project_conv/Conv               0.99986            
/encoder/_blocks.5/_depthwise_conv/Conv             0.99988            
/bifpn/bifpn.3/conv3_up/depthwise_conv/conv/Conv    0.99989            
/classifier/conv_list.0/depthwise_conv/conv/Conv    0.99989            
..._blocks.4/block/block.0/block/block.0/conv/Conv  0.99989            
/regressor/conv_list.0/depthwise_conv/conv/Conv     0.99989            
/bifpn/bifpn.2/conv3_up/depthwise_conv/conv/Conv    0.99992            
/encoder/_blocks.17/_se_expand/Conv                 0.99992            
/encoder/_blocks.13/Mul                             0.99992            
/encoder/_blocks.1/_depthwise_conv/Conv             0.99992            
/encoder/_blocks.13/GlobalAveragePool               0.99992            
/encoder/_blocks.14/_se_expand/Conv                 0.99992            
/classifier/header/pointwise_conv/conv/Conv         0.99992            
/encoder/_blocks.1/Add                              0.99993            
/encoder/_blocks.3/Mul                              0.99993            
/encoder/_blocks.3/GlobalAveragePool                0.99993            
/encoder/_blocks.1/_swish/Mul                       0.99993            
/encoder/_blocks.15/Mul                             0.99993            
/encoder/_blocks.15/GlobalAveragePool               0.99993            
/bifpn/bifpn.4/conv3_up/depthwise_conv/conv/Conv    0.99993            
/bifpn/bifpn.1/conv3_up/pointwise_conv/conv/Conv    0.99993            
/bifpn/bifpn.4/conv3_up/pointwise_conv/conv/Conv    0.99994            
/encoder/_blocks.8/_project_conv/Conv               0.99994            
/bifpn/bifpn.5/swish_3/Mul                          0.99994            
/bifpn/bifpn.3/conv3_up/pointwise_conv/conv/Conv    0.99994            
/encoder/_blocks.8/GlobalAveragePool                0.99994            
/encoder/_conv_stem/Conv                            0.99994            
/encoder/_blocks.13/_project_conv/Conv              0.99994            
/encoder/_blocks.8/Mul                              0.99994 
/bifpn/bifpn.5/conv3_up/pointwise_conv/conv/Conv    0.99995
...

Sorting by cosine similarity from front to back, gradually set the operator INT16 quantization, and the calibration model accuracy will increase until it meets the requirements:

Serial NumberCosine Similarity Value(<=value will be set to INT16)Accuracy
detda_segll_seg
1None0.75562(97.85%)0.89675(99.12%)0.81813(95.83%)
20.9990.76531(99.11%)0.90274(99.79%)0.83874(98.24%)
30.99980.76545(99.12%)0.90340(99.86%)0.83961(98.34%)
40.99990.76613(99.21%)0.90420(99.95%)0.84216(98.64%)
50.999920.76712(99.34%)0.90356(99.88%)0.84397(98.85%)
60.999930.76781(99.43%)0.90374(99.90%)0.84484(98.95%)
70.999940.76811(99.47%)0.90344(99.86%)0.84528(99.01%)

From the above test table, we can see that if the sensitive nodes with a sensitivity value less than or equal to 0.99994 are set as INT16 nodes, the calibration accuracy meets the requirements:

quant_config = {
    "model_config": {
        "activation": {"calibration_type": "max", "max_percentile": 0.99995},
    },
    "node_config": {
        "/encoder/_blocks.0/_depthwise_conv/Conv": {"qtype": "int16"},
        "/encoder/_swish/Mul": {"qtype": "int16"},
        "/encoder/_blocks.2/_depthwise_conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.0/Mul": {"qtype": "int16"},
        "/encoder/_blocks.0/GlobalAveragePool": {"qtype": "int16"},
        "/encoder/_blocks.2/_swish/Mul": {"qtype": "int16"},
        "/bifpn/bifpn.5/conv3_up/depthwise_conv/conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.0/_swish/Mul": {"qtype": "int16"},
        "/encoder/_blocks.2/Mul": {"qtype": "int16"},
        "/encoder/_blocks.2/GlobalAveragePool": {"qtype": "int16"},
        "/bifpn/bifpn.2/conv3_up/pointwise_conv/conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.2/_swish_1/Mul": {"qtype": "int16"},
        "/bifpn/bifpn.5/p4_downsample/Pad": {"qtype": "int16"},
        "/bifpndecoder/seg_blocks.4/block/block.0/block/block.0/Pad": {"qtype": "int16"},
        "/encoder/_blocks.0/_project_conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.5/_depthwise_conv/Conv": {"qtype": "int16"},
        "/bifpn/bifpn.3/conv3_up/depthwise_conv/conv/Conv": {"qtype": "int16"},
        "/classifier/conv_list.0/depthwise_conv/conv/Conv": {"qtype": "int16"},
        "/bifpndecoder/seg_blocks.4/block/block.0/block/block.0/conv/Conv": {"qtype": "int16"},
        "/regressor/conv_list.0/depthwise_conv/conv/Conv": {"qtype": "int16"},
        "/bifpn/bifpn.2/conv3_up/depthwise_conv/conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.17/_se_expand/Conv": {"qtype": "int16"},
        "/encoder/_blocks.13/Mul": {"qtype": "int16"},
        "/encoder/_blocks.1/_depthwise_conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.13/GlobalAveragePool": {"qtype": "int16"},
        "/encoder/_blocks.14/_se_expand/Conv": {"qtype": "int16"},
        "/classifier/header/pointwise_conv/conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.1/Add": {"qtype": "int16"},
        "/encoder/_blocks.3/Mul": {"qtype": "int16"},
        "/encoder/_blocks.3/GlobalAveragePool": {"qtype": "int16"},
        "/encoder/_blocks.1/_swish/Mul": {"qtype": "int16"},
        "/encoder/_blocks.15/Mul": {"qtype": "int16"},
        "/encoder/_blocks.15/GlobalAveragePool": {"qtype": "int16"},
        "/bifpn/bifpn.4/conv3_up/depthwise_conv/conv/Conv": {"qtype": "int16"},
        "/bifpn/bifpn.1/conv3_up/pointwise_conv/conv/Conv": {"qtype": "int16"},
        "/bifpn/bifpn.4/conv3_up/pointwise_conv/conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.8/_project_conv/Conv": {"qtype": "int16"},
        "/bifpn/bifpn.5/swish_3/Mul": {"qtype": "int16"},
        "/bifpn/bifpn.3/conv3_up/pointwise_conv/conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.8/GlobalAveragePool": {"qtype": "int16"},
        "/encoder/_conv_stem/Conv": {"qtype": "int16"},
        "/encoder/_blocks.13/_project_conv/Conv": {"qtype": "int16"},
        "/encoder/_blocks.8/Mul": {"qtype": "int16"},
    },
}

                    Model    Float    March    Samples    calibrated_model    Cosine_Similarity
-------------------------  -------  -------  ---------  --------------------  -----------------
   hybridnets-384-640_det  0.77222   nash-e      10000     0.76811(99.47%)             0.994576
hybridnets-384-640_da_seg  0.90467   nash-e      10000     0.90344(99.86%)             0.994576
hybridnets-384-640_ll_seg  0.85376   nash-e      10000     0.84528(99.01%)             0.994576

A complete accuracy tuning deployment example is available at: HybriNets Accuracy Tuning Deployment Example.

Sensitive Node Analysis Failure

If using the Accuracy Debug Tool to set sensitive nodes to higher precision fails to effectively improve model accuracy, we can first try specifying output nodes to filter out irrelevant nodes. Additionally, observe the model output error and select other metric to improve the correlation between sensitivity ranking and precision. Furthermore, by analyzing the model structure, typical substructures with a higher risk of quantization loss (such as model outputs, inputs, and structures with specific physical meaning) can be set to higher precision to complete accuracy tuning. This tuning pipeline is described using the YoloP model as an example.

Using HMCT default INT8 quantization, percentile is selected as the calibration algorithm, the calibration accuracy does not meet the requirements (the accuracy of det decreases by more than 1%):

               Model    Float    March      Samples    calibrated_model      Cosine_Similarity
--------------------  -------  -------    ---------   ------------------    -------------------
   yolop-384-640_det  0.76448   nash-e        10000      0.61507(80.46%)              0.999891
yolop-384-640_da_seg  0.89008   nash-e        10000      0.88863(99.84%)              0.999891
yolop-384-640_ll_seg   0.6523   nash-e        10000      0.65357(100.19%)             0.999891

All Node Type INT16

First, set all_node_type to INT16 and select percentile for the calibration algorithm. At this point, the calibration accuracy meets the requirements, and we can use INT8+INT16 mixed precision to complete the tuning:

quant_config = {"model_config": {"all_node_type": "int16"}}   

               Model    Float    March      Samples    calibrated_model      Cosine_Similarity  
--------------------  -------  -------    ---------    ------------------    -----------------
   yolop-384-640_det  0.76448   nash-e        10000      0.75890(99.27%)               0.99999 
yolop-384-640_da_seg  0.89008   nash-e        10000      0.88950(99.93%)               0.99999 
yolop-384-640_ll_seg   0.6523   nash-e        10000      0.64821(99.37%)               0.99999 

Mixed Precision Debugging

Compile the INT8 calibration model based on the percentile calibration algorithm selected for INT16 model, configure debug_mode: "dump_calibration_data" in the yaml file to save the calibration data, and output the node quantization sensitivity through get_sensitivity_of_nodes:

hmct-debugger get-sensitivity-of-nodes yolop-384-640_calibrated_model.onnx calibration_data/ -n node -v True -s ./debug_result
=======================node sensitivity========================
node                                        cosine-similarity  
---------------------------------------------------------------
Mul_943                                     0.99736            
Mul_647                                     0.99894            
Mul_795                                     0.99909            
Conv_50                                     0.99976            
Div_49                                      0.99983            
Conv_92                                     0.99989            
Div_58                                      0.9999             
Conv_1119                                   0.99995            
Conv_88                                     0.99996            
Conv_41                                     0.99996            
Conv_59                                     0.99998            
Slice_4                                     0.99998            
Slice_9                                     0.99998            
Slice_14                                    0.99998            
Slice_19                                    0.99998            
Slice_24                                    0.99998            
Slice_29                                    0.99998            
Slice_34                                    0.99998            
Slice_39                                    0.99998            
Concat_40                                   0.99998            
Div_67                                      0.99998            
MaxPool_297                                 0.99999            
MaxPool_298                                 0.99999            
MaxPool_299                                 0.99999            
Concat_300                                  0.99999            
Concat_1003                                 0.99999            
Conv_177                                    0.99999            
Div_296                                     0.99999            
ScatterND_705                               0.99999            
Slice_645                                   0.99999            
Reshape_706                                 0.99999            
Conv_110                                    0.99999            
Conv_87                                     0.99999            
Concat_89                                   0.99999            
LeakyRelu_91                                0.99999            
Mul_584                                     0.99999            
ScatterND_640                               0.99999            
Concat_1105                                 0.99999            
LeakyRelu_1107                              0.99999            
Conv_1004                                   0.99999            
Add_582                                     0.99999            
Conv_119                                    0.99999            
Resize_1014                                 0.99999            
Conv_1015                                   0.99999            
Conv_1043                                   0.99999            
Conv_266                                    0.99999            
Conv_199                                    0.99999            
Div_100                                     0.99999            
...

Sorting by cosine similarity from front to back, gradually set the operator INT16 quantization. However, even with a large number of sensitive nodes set to INT16, the accuracy still failed to meet the requirements:

Serial NumberCosine Similarity Value(<=value will be set to INT16)Accuracy
detda_segll_seg
1None0.61507(80.46%)0.88863(99.84%)0.65357(100.19%)
20.99990.60956(79.74%)0.88911(99.89%)0.65925(101.07%)
30.999960.60978(79.76%)0.88933(99.92%)0.66112(101.35%)
40.999980.60956(79.73%)0.88931(99.91%)0.66125(101.37%)
50.999990.66426(86.89%)0.88958(99.94%)0.66065(101.28%)

Observing the accuracy results of the INT8 calibrated model, only det branch does not reach 99%. When calculating nodes sensitivity through get_sensitivity_of_nodes interface, we can use the -o option to specify det output, so that a better sensitivity ranking can be obtained:

hmct-debugger get-sensitivity-of-nodes yolop-384-640_calibrated_model.onnx calibration_data/ -n node -o Concat_1003 -v True -s det_debug_result/
=======================node sensitivity========================
node                                        cosine-similarity  
---------------------------------------------------------------
Mul_943                                     0.99736            
Mul_647                                     0.99894            
Mul_795                                     0.99909            
Conv_50                                     0.99997            
Div_58                                      0.99997            
Div_49                                      0.99999            
Concat_1003                                 0.99999            
ScatterND_705                               0.99999            
Slice_645                                   0.99999            
Reshape_706                                 0.99999            
Mul_584                                     0.99999            
ScatterND_640                               0.99999            
Add_582                                     0.99999            
Conv_41                                     0.99999            
Slice_4                                     0.99999            
Slice_9                                     0.99999            
Slice_14                                    0.99999            
Slice_19                                    0.99999            
Slice_24                                    0.99999            
Slice_29                                    0.99999            
Slice_34                                    0.99999            
Slice_39                                    0.99999            
Concat_40                                   0.99999            
Conv_88                                     0.99999            
Conv_59                                     0.99999            
...

Sorting by cosine similarity from front to back, setting the operator to INT16 quantization, and focusing only on the det output can filter out useless nodes, but the final accuracy still does not meet the requirements:

Serial NumberCosine Similarity Value(<=value will be set to INT16)Accuracy
detda_segll_seg
1None0.61507(80.46%)0.88863(99.84%)0.65357(100.19%)
20.99990.60868(79.62%)0.88836(99.81%)0.65300(100.11%)
30.999970.60961(79.74%)0.88902(99.88%)0.65664(100.66%)
40.999990.66461(86.94%)0.88932(99.91%)0.65876(100.99%)

Observe the output similarity of the INT8 calibrated model. The L1 and L2 distances of the det branch deviate greatly from the floating point. Try replacing cosine similarity with other metrics:

hmct-info yolop-384-640_calibrated_model.onnx -c ./calibration_data/images/00.npy

INFO:root:The quantized model output:
=================================================================================
Output          Cosine Similarity  L1 Distance  L2 Distance  Chebyshev Distance
---------------------------------------------------------------------------------
det_out         0.995352           7.633481     289.637665   552.817566
drive_area_seg  0.998973           0.004005     0.001132     0.592610
lane_line_seg   0.999933           0.000417     0.000069     0.564768

When calculating nodes sensitivity through the get_sensitivity_of_nodes interface, we can specify mse as the metric to improve the discrimination between different nodes:

hmct-debugger get-sensitivity-of-nodes yolop-384-640_calibrated_model.onnx calibration_data/ -n node -o Concat_1003 -m mse -v True -s det_mse_debug_result/
===================node sensitivity====================
node                                        mse        
-------------------------------------------------------
Mul_943                                     164.82712  
Mul_647                                     65.88637   
Mul_795                                     56.86866   
Conv_50                                     2.04226    
Div_58                                      1.88065    
Concat_1003                                 0.87797    
Div_49                                      0.84962    
ScatterND_705                               0.67858    
Slice_645                                   0.67379    
Reshape_706                                 0.67379    
ScatterND_640                               0.55187    
Mul_584                                     0.54884    
Add_582                                     0.52263    
Conv_41                                     0.4714     
Slice_4                                     0.38413    
Slice_9                                     0.38413    
Slice_14                                    0.38413    
Slice_19                                    0.38413    
Slice_24                                    0.38413    
Slice_29                                    0.38413    
Slice_34                                    0.38413    
Slice_39                                    0.38413    
Concat_40                                   0.38413    
Conv_88                                     0.35534    
Conv_59                                     0.33164    
Conv_92                                     0.30398    
Div_67                                      0.16826    
ScatterND_853                               0.16711    
Slice_793                                   0.1627     
Reshape_854                                 0.1627     
ScatterND_788                               0.13494    
Mul_732                                     0.132      
Add_730                                     0.12381    
ScatterND_1001                              0.07478    
Conv_550                                    0.07226    
Conv_518                                    0.06468    
Conv_546                                    0.06344    
Conv_310                                    0.05974    
Conv_68                                     0.05735    
Conv_338                                    0.05684    
Add_86                                      0.04319    
ScatterND_936                               0.0428     
Concat_89                                   0.04201    
LeakyRelu_91                                0.04201    
Concat_517                                  0.04193    
Conv_87                                     0.04148    
Slice_941                                   0.04148    
Reshape_1002                                0.04148    
Conv_448                                    0.04034    
MaxPool_297                                 0.03739    
MaxPool_298                                 0.03739    
MaxPool_299                                 0.03739    
Concat_300                                  0.03739    
Mul_880                                     0.03334    
Div_100                                     0.03328    
Conv_855                                    0.03146    
Concat_547                                  0.03094    
LeakyRelu_549                               0.03094    
Add_878                                     0.03016    
Div_558                                     0.02966  
...

Sort by mse similarity from front to back, set the operator to higher precision, and finally achieve the required accuracy after adding a large number of INT16 nodes:

Serial NumberMSE Value(>=value will be set to INT16)Accuracy
detda_segll_seg
1None0.61507(80.46%)0.88863(99.84%)0.65357(100.19%)
20.50.66471(86.95%)0.88902(99.88%)0.65664(100.66%)
30.20.66447(86.92%)0.88933(99.92%)0.66112(101.35%)
40.10.72969(95.45%)0.88931(99.91%)0.66125(101.37%)
50.050.73393(96.00%)0.88934(99.92%)0.66121(101.37%)
60.040.73321(95.91%)0.88931(99.91%)0.66125(101.37%)
70.030.75707(99.03%)0.88944(99.93%)0.66137(101.39%)

Subgraph Structure Analysis

Even if we only focus on improving the det output based on mse metric, setting a large number of sensitive nodes for higher precision still cannot effectively improve the accuracy. Furthermore, considering that only the det task currently does not meet the requirements, it is inferred that quantization loss is less likely to occur in nodes in the da_seg and ll_seg branches, as well as in the shared backbone. Focus on the accuracy tuning of det branch, combined with model structure analysis, try to specify the det output position subgraph to use higher precision and test the calibration accuracy:

quant_config = {
    "subgraph_config": {
        "det_head": {
            "inputs": ["Conv_559", "Conv_707", "Conv_855"],
            "outputs": ["Concat_1003"],
            "qtype": "int16",
        },
    }
}

               Model    Float    March    Samples    calibrated_model    Cosine_Similarity
--------------------  -------  -------  ---------  ------------------  -------------------
   yolop-384-640_det  0.76448   nash-e      10000     0.76275(99.77%)              0.99991
yolop-384-640_da_seg  0.89008   nash-e      10000     0.88863(99.84%)              0.99991
yolop-384-640_ll_seg   0.6523   nash-e      10000    0.65357(100.19%)              0.99991
Attention

When the sorting of sensitive nodes is inaccurate, the source of the loss can be preliminarily determined by configuring the subgraph for higher precision. If the latency of the subgraph with higher precision increases significantly, sensitivity analysis can be performed within the subgraph, so that fewer nodes with higher precision will be configured.

A complete accuracy tuning deployment example is available at: YoloP Accuracy Tuning Deployment Example.

Accuracy Tuning Techniques

The accuracy tuning pipeline requires constant modification of node configurations, model compilation and accuracy verification. The entire process is time-consuming and expensive to debug. Based on this, we provide the IR interface to support you to directly modify quantization parameters in calibrated_model.onnx for rapid verification.

from hmct.ir import load_model, save_model
from hmct.common import find_input_calibration, find_output_calibration

model = load_model("calibrated_model.onnx")

# Modify specific activation or weight calibration nodes to use specific qtype
node = model.graph.node_mappings["ReduceMax_1317_HzCalibration"]
print(node.qtype)       # Support qtype reading
node.qtype = "float32"  # Support int8, int16, float16, float32 configured
# Configure all activation or weight calibration nodes to use int16 qtype
calibration_nodes = model.graph.type2nodes["HzCalibration"]
# Configure all activation calibration nodes to use int16 qtype
for node in calibration_nodes:
    if node.tensor_type == "feature":
        node.qtype = "int16"
# Configure all weight calibration nodes to use int16 qtype
for node in calibration_nodes:
    if node.tensor_type == "weight":
        node.qtype = "int16"
# Configure all calibration nodes to use int16 qtype
for node in calibration_nodes:
    node.qtype = "int16"
# Configure a node with int16 input qtype
for node in model.graph.nodes:
    if node.name in ["Conv_0"]:
        for i in range(len(node.inputs)):
            input_calib = find_input_calibration(node, i)
            # It is required to be able to find HzCalibration in the
            # input, and tensor_type is feature.
            if input_calib and input_calib.tensor_type == "feature":
                input_calib.qtype = "int16"
# Configure a node with int16 output qtype
for node in model.graph.nodes:
    if node.name in ["Conv_0"]:
        output_calib = find_output_calibration(node)
        # It is required to be able to find HzCalibration in the
        # input, and tensor_type is feature.
        if output_calib and output_calib.tensor_type == "feature":
            input_calib.qtype = "int16"
# Configure nodes with specific op_type to int16
for node in model.graph.nodes:
    if node.op_type in ["Conv"]:
        for i in range(len(node.inputs)):
            input_calib = find_input_calibration(node, i)
            # It is required to be able to find HzCalibration in the
            # input, and tensor_type is feature.
            if input_calib and input_calib.tensor_type == "feature":
                input_calib.qtype = "int16"
# Modify specific activation or weight calibration nodes with specific thresholds
node = model.graph.node_mappings["ReduceMax_1317_HzCalibration"]
print(node.thresholds)    # Support thresholds reading
node.thresholds = [4.23]  # Support np.array, List[float]

save_model(model, "calibrated_model_modified.onnx")