o
    ;ήcT                     @   sF  d dl Z d dlZd dlZd dlmZ d dlmZ d dlmZm	Z	 d dl
Zd dlZd dlmZmZmZmZ d dlZddlmZmZmZmZ G dd	 d	eZG d
d de jdZG dd dZG dd deZG dd deZG dd deZG dd deZG dd de jdZG dd deZ ddej!di fdee	e"  fddZ#dS )     N)Enum)Path)OptionalSequence)
ModelProtoTensorProtohelpernumpy_helper   )
apply_plotclone_model_with_shape_infer
load_modelsmooth_distributionc                   @   s   e Zd ZdZdZdZdS )CalibrationMethodr   r
      N)__name__
__module____qualname__MinMaxEntropy
Percentile r   r   I/tmp/pip-target-vg8gfxp4/lib/python/onnxruntime/quantization/calibrate.pyr      s    r   c                   @   s<   e Zd Zedd ZejdefddZdd Z	dd	 Z
d
S )CalibrationDataReaderc                 C   s   t |dr
t|jptS )Nget_next)hasattrcallabler   NotImplemented)clssubclassr   r   r   __subclasshook__   s   z&CalibrationDataReader.__subclasshook__returnc                 C      t )z9generate the input data dict for ONNXinferenceSession runNotImplementedErrorselfr   r   r   r   #   s   zCalibrationDataReader.get_nextc                 C   s   | S Nr   r%   r   r   r   __iter__(   s   zCalibrationDataReader.__iter__c                 C   s   |   }|d u r
t|S r'   )r   StopIteration)r&   resultr   r   r   __next__+   s   zCalibrationDataReader.__next__N)r   r   r   classmethodr    abcabstractmethoddictr   r(   r+   r   r   r   r   r      s    
r   )	metaclassc                   @   sv   e Zd Z				ddeee  fddZdgfdd	Zd
d Zdd Z	dd Z
dd ZdefddZdefddZdS )CalibraterBaseNaugmented_model.onnxFop_types_to_calibratec                 C   s~   t |trtt|d| _nt |trt|d| _nt |tr#|| _ntd|| _|| _|| _	|| _
d| _d| _dg| _dS )a  
        :param model: ONNX model to calibrate. It can be a ModelProto or a model path
        :param op_types_to_calibrate: operator types to calibrate. By default, calibrate all the float32/float16 tensors.
        :param augmented_model_path: save augmented model to this path.
        :param symmetric: make range of tensor symmetric (central point is 0).
        :param use_external_data_format: use external data format to store model which size is >= 2Gb
        Fz5model should be either model path or onnx.ModelProto.NCPUExecutionProvider)
isinstancestrr   r   modelr   
ValueErrorr3   augmented_model_path	symmetricuse_external_data_formataugment_modelinfer_sessionexecution_providers)r&   r7   r3   r9   r:   r;   r   r   r   __init__3   s   


zCalibraterBase.__init__r4   c                 C   s   || _ |   dS )zz
        reset the execution providers to execute the collect_data. It triggers to re-creating inference session.
        N)r>   create_inference_session)r&   r>   r   r   r   set_execution_providersT   s   z&CalibraterBase.set_execution_providersc                 C   s,   t  }t jj|_t j| j|| jd| _dS )z9
        create an OnnxRuntime InferenceSession.
        )sess_options	providersN)	onnxruntimeSessionOptionsGraphOptimizationLevelORT_DISABLE_ALLgraph_optimization_levelInferenceSessionr9   r>   r=   )r&   rB   r   r   r   r@   [   s   
z'CalibraterBase.create_inference_sessionc           	      C   s   dd |j jD }|dd |j jD  |dd |j jD  tdd |j jD }t }ttjtj	g}|j j
D ]6}| jrF|j| jv rqt|j|jD ]"}|| v rp|| }|jdrp|jjj|v rp||vrp|| qNq;||fS )z
        select all quantization_candidates op type nodes' input/output tensors.
        returns:
            tensors (set): set of tensor name.
            value_infos (dict): tensor name to value info.
        c                 S      i | ]}|j |qS r   name).0vir   r   r   
<dictcomp>n       z>CalibraterBase.select_tensors_to_calibrate.<locals>.<dictcomp>c                 S   rJ   r   rK   )rM   otr   r   r   rO   o   rP   c                 S   rJ   r   rK   )rM   itr   r   r   rO   p   rP   c                 s       | ]}|j V  qd S r'   rK   )rM   initr   r   r   	<genexpr>q       z=CalibraterBase.select_tensors_to_calibrate.<locals>.<genexpr>tensor_type)graph
value_infoupdateoutputinputsetinitializerr   FLOATFLOAT16noder3   op_type	itertoolschainkeystypeHasFieldrW   	elem_typeadd)	r&   r7   value_infosr^   tensors_to_calibratetensor_type_to_calibratera   tensor_namerN   r   r   r   select_tensors_to_calibrateg   s(   

z*CalibraterBase.select_tensors_to_calibratec                 C      | j S )z.
        return: augmented onnx model
        )r<   r%   r   r   r   get_augment_model   s   z CalibraterBase.get_augment_modelc                 C   r"   )z
        abstract method: augment the input model to prepare for collecting data. It will:
            1. save augmented model to augmented_model_path.
            2. set the self.augment_model
        r#   r%   r   r   r   augment_graph   s   zCalibraterBase.augment_graphdata_readerc                 C   r"   )z
        abstract method: collect the tensors that will be used for range computation. It can be called multiple times.
        r#   r&   rr   r   r   r   collect_data      zCalibraterBase.collect_datac                 C   r"   )zy
        abstract method: compute the [min, max] range for the tensors to calibrate based on the collected data.
        r#   rs   r   r   r   compute_range   ru   zCalibraterBase.compute_range)Nr2   FF)r   r   r   r   r   r6   r?   rA   r@   rn   rp   rq   r   rt   rv   r   r   r   r   r1   2   s    

!r1   c                       sf   e Zd Z						ddeee  f fddZdd	 Zd
d Zde	fddZ
dd Zdd Z  ZS )MinMaxCalibraterNr2   F{Gz?r3   c                    s|   t t| j|||||d g | _d| _t| jjj| _	t
dd | jjjD | _|| _|r9|dk s5|dkr9td|| _dS )a  
        :param model: ONNX model to calibrate. It can be a ModelProto or a model path
        :param op_types_to_calibrate: operator types to calibrate. By default, calibrate all the float32/float16 tensors.
        :param augmented_model_path: save augmented model to this path.
        :param symmetric: make range of tensor symmetric (central point is 0).
        :param use_external_data_format: use external data format to store model which size is >= 2Gb
        :param moving_average: compute the moving average of the minimum and maximum values instead of the global minimum and maximum.
        :param averaging_constant: constant smoothing factor to use when computing the moving average.
        r3   r9   r:   r;   Nc                 s   rS   r'   rK   rM   r[   r   r   r   rU      rV   z,MinMaxCalibrater.__init__.<locals>.<genexpr>r   r
   z;Invalid averaging constant, which should not be < 0 or > 1.)superrw   r?   intermediate_outputscalibrate_tensors_rangelenr7   rX   r[   num_model_outputsr]   model_original_outputsmoving_averager8   averaging_constant)r&   r7   r3   r9   r:   r;   r   r   	__class__r   r   r?      s   

zMinMaxCalibrater.__init__c                    s   t | j |  \}}tt ttj	dgtj
d} jj|  fdd}|D ]}||d ||d q/tj | j| jd  | _dS )	z
        Adds ReduceMin and ReduceMax nodes to all quantization_candidates op type nodes in
        model and ensures their outputs are stored as part of the graph output
        :return: augmented ONNX model
        r
   dtypec                    s~   d}| d | }|d }t jj|| g|g||d}t jjd|g|g|d} jj||g  jjt|t	j
dg d S )Nr
   __Reshape)keepdimsrL   Reshape)inputsoutputsrL   )onnxr   	make_noderX   ra   extendr[   appendmake_tensor_value_infor   r_   )rm   reduce_op_namer   reduce_outputintermediate_outputreduce_nodereshape_noder7   reshape_shape_namer   r   add_reduce_min_max   s    z:MinMaxCalibrater.augment_graph.<locals>.add_reduce_min_max	ReduceMin	ReduceMaxsave_as_external_dataN)r   r7   rn   r6   uuiduuid4r	   
from_arraynparrayint64rX   r^   r   r   saver9   r;   r<   )r&   tensorsr   reshape_shaper   tensorr   r   r   rq      s   


zMinMaxCalibrater.augment_graphc                 C   
   g | _ d S r'   r|   r%   r   r   r   clear_collected_data      
z%MinMaxCalibrater.clear_collected_datarr   c                 C   sR   	 |  }|sn| j| jd | qt| jdkrtd|   |   d S )NTr   No data is collected.)	r   r|   r   r=   runr~   r8   rv   r   )r&   rr   r   r   r   r   rt      s   zMinMaxCalibrater.collect_datac                 C   s   |s|S |  D ]F\}}| jr2|d | j|| d |d    }|d | j|| d |d    }nt|d || d }t|d || d }||f||< q|S )Nr   r
   )itemsr   r   minmax)r&   	old_range	new_rangekeyvalue	min_value	max_valuer   r   r   merge_range  s   "$zMinMaxCalibrater.merge_rangec                    s  t jdkr
jS fddtt jd D fddjD }i |D ]}| D ]\}}|g | q.q(jd   fddtdt  dD }tfdd	D }g }tdt  dD ]r}d}	d}
j	rt
j| |  dd
}t
j| |d   dd
}nt| |  }t| |d   }t|tks|jdkrt|}	t|tks|jdkrt|}
jrtt|	t|
}|t| |g qi|t|	|
g qitt||}jrj|_jS |_jS )z
        Compute the min-max range of tensor
        :return: dictionary mapping: {added node names: (ReduceMin, ReduceMax) pairs }
        r   c                       g | ]
} j  | jqS r   r=   get_outputsrL   rM   ir%   r   r   
<listcomp>      z2MinMaxCalibrater.compute_range.<locals>.<listcomp>c                       g | ]	}t t |qS r   r/   ziprM   r   output_namesr   r   r         Nc                    s   g | ]} |  d d qS )r   r   )
rpartitionr   )added_output_namesr   r   r   #  s    r   c                 3   s&    | ]}|j vr| | fV  qd S r'   )r   r   )merged_output_dictr&   r   r   rU   '  s    


z1MinMaxCalibrater.compute_range.<locals>.<genexpr>)axisr
   )r~   r|   r}   ranger   
setdefaultr   r   r/   r   r   meanr   r   rf   intsizefloatr:   abstupler   r   )r&   output_dicts_listdkvcalibrate_tensor_namesmerged_added_output_dictpairsr   r   r   min_value_arraymax_value_arraymax_absolute_valuenew_calibrate_tensors_ranger   )r   r   r   r&   r   rv     sR    

zMinMaxCalibrater.compute_range)Nr2   FFFrx   )r   r   r   r   r   r6   r?   rq   r   r   rt   r   rv   __classcell__r   r   r   r   rw      s    
#.rw   c                       sb   e Zd Z								ddeee  f fd	d
Zdd Zdd Zde	fddZ
dd Z  ZS )HistogramCalibraterNr2   F
percentile      -X@r3   c
           
         sx   t t| j|||||d g | _d| _t| jjj| _	t
dd | jjjD | _d| _|| _|| _|| _|	| _d| _dS )a  
        :param model: ONNX model to calibrate. It can be a ModelProto or a model path
        :param op_types_to_calibrate: operator types to calibrate. By default, calibrate all the float32/float16 tensors.
        :param augmented_model_path: save augmented model to this path.
        :param use_external_data_format: use external data format to store model which size is >= 2Gb
        :param method: A string. One of ['entropy', 'percentile'].
        :param symmetric: make range of tensor symmetric (central point is 0).
        :param num_bins: number of bins to create a new histogram for collecting tensor values.
        :param num_quantized_bins: number of quantized bins. Default 128.
        :param percentile: A float number between [0, 100]. Default 99.99.
        ry   Nc                 s   rS   r'   rK   rz   r   r   r   rU   k  rV   z/HistogramCalibrater.__init__.<locals>.<genexpr>)r{   r   r?   r|   r}   r~   r7   rX   r[   r   r]   r   	collectormethodnum_binsnum_quantized_binsr   rk   )
r&   r7   r3   r9   r;   r   r:   r   r   r   r   r   r   r?   J  s"   

zHistogramCalibrater.__init__c                 C   s`   t | j}| |\| _}| jD ]}|| jvr |jj||  qtj	|| j
| jd || _dS )z
        make all quantization_candidates op type nodes as part of the graph output.
        :return: augmented ONNX model
        r   N)r   r7   rn   rk   r   rX   r[   r   r   r   r9   r;   r<   )r&   r7   rj   r   r   r   r   rq   s  s   



z!HistogramCalibrater.augment_graphc                 C   r   r'   r   r%   r   r   r   r     r   z(HistogramCalibrater.clear_collected_datarr   c                    s   	 |  }|snjjd| qtjdkrtdfddttjd D fddjD }i  |D ]}| D ]\}} 	|g | qCq=t
 fdd	 D }jsptjjjjjd
_j|   dS )zy
        Entropy Calibrator collects operators' tensors as well as generates tensor histogram for each operator.
        TNr   r   c                    r   r   r   r   r%   r   r   r     r   z4HistogramCalibrater.collect_data.<locals>.<listcomp>c                    r   r   r   r   r   r   r   r     r   c                 3   s&    | ]}|j v r| | fV  qd S r'   )rk   r   )merged_dictr&   r   r   rU     s   $ z3HistogramCalibrater.collect_data.<locals>.<genexpr>)r   r:   r   r   r   )r   r|   r   r=   r   r~   r8   r   r   r   r/   r   HistogramCollectorr   r:   r   r   r   collectr   )r&   rr   r   r   r   r   r   clean_merged_dictr   )r   r   r&   r   rt     s8    
z HistogramCalibrater.collect_datac                 C   s   | j std| j  S )z
        Compute the min-max range of tensor
        :return: dictionary mapping: {tensor name: (min value, max value)}
        z9No collector created and can't generate calibration data.)r   r8   compute_collection_resultr%   r   r   r   rv     s   
z!HistogramCalibrater.compute_range)Nr2   Fr   Fr   r   r   )r   r   r   r   r   r6   r?   rq   r   r   rt   rv   r   r   r   r   r   r   I  s     
)%r   c                       s:   e Zd Z							d	deee  f fddZ  ZS )
EntropyCalibraterNr2   Fentropyr   r3   c	           	   
      $   t t| j||||||||d dS )a  
        :param model: ONNX model to calibrate. It can be a ModelProto or a model path
        :param op_types_to_calibrate: operator types to calibrate. By default, calibrate all the float32/float16 tensors.
        :param augmented_model_path: save augmented model to this path.
        :param use_external_data_format: use external data format to store model which size is >= 2Gb
        :param method: A string. One of ['entropy', 'percentile'].
        :param symmetric: make range of tensor symmetric (central point is 0).
        :param num_bins: number of bins to create a new histogram for collecting tensor values.
        :param num_quantized_bins: number of quantized bins. Default 128.
        )r   r:   r   r   N)r{   r   r?   )	r&   r7   r3   r9   r;   r   r:   r   r   r   r   r   r?        

zEntropyCalibrater.__init__)Nr2   Fr   Fr   r   r   r   r   r   r   r6   r?   r   r   r   r   r   r         
r   c                       s:   e Zd Z							d
deee  f fdd	Z  ZS )PercentileCalibraterNr2   Fr   r   r   r3   c	           	   
      r   )a  
        :param model: ONNX model to calibrate. It can be a ModelProto or a model path
        :param op_types_to_calibrate: operator types to calibrate. By default, calibrate all the float32/float16 tensors.
        :param augmented_model_path: save augmented model to this path.
        :param use_external_data_format: use external data format to store model which size is >= 2Gb
        :param method: A string. One of ['entropy', 'percentile'].
        :param symmetric: make range of tensor symmetric (central point is 0).
        :param num_quantized_bins: number of quantized bins. Default 128.
        :param percentile: A float number between [0, 100]. Default 99.99.
        )r   r:   r   r   N)r{   r   r?   )	r&   r7   r3   r9   r;   r   r:   r   r   r   r   r   r?     r   zPercentileCalibrater.__init__)Nr2   Fr   Fr   r   r   r   r   r   r   r     r   r   c                   @   s,   e Zd ZdZejdd Zejdd ZdS )CalibrationDataCollectorzL
    Base class for collecting data for calibration-based quantization.
    c                 C   r"   )z
        Generate informative data based on given data.
            name_to_arr : dict
                tensor name to NDArray data
        r#   r&   name_to_arrr   r   r   r     s   z CalibrationDataCollector.collectc                 C   r"   )z?
        Get the optimal result among collection data.
        r#   r%   r   r   r   r     s   z2CalibrationDataCollector.compute_collection_resultN)r   r   r   __doc__r-   r.   r   r   r   r   r   r   r     s    
r   c                   @   s`   e Zd ZdZdd Zdd Zdd Zdd	 Zd
d Zdd Z	dd Z
dd Zdd Zdd ZdS )r   a`  
    Collecting histogram for each tensor. Percentile and Entropy method are supported.

    ref: https://github.com//apache/incubator-mxnet/blob/master/python/mxnet/contrib/quantization.py
    ref: https://docs.nvidia.com/deeplearning/tensorrt/pytorch-quantization-toolkit/docs/_modules/
                 pytorch_quantization/calib/histogram.html
    c                 C   s(   i | _ || _|| _|| _|| _|| _d S r'   )histogram_dictr   r:   r   r   r   )r&   r   r:   r   r   r   r   r   r   r?     s   
zHistogramCollector.__init__c                 C   ro   r'   )r   r%   r   r   r   get_histogram_dict$  s   z%HistogramCollector.get_histogram_dictc                 C   sH   t d | jdkr| |S | jdkr | jr| |S | |S td)Nz/Collecting tensor data and making histogram ...r   r   3Only 'entropy' or 'percentile' method are supported)printr   collect_valuer:   collect_absolute_valuer8   r   r   r   r   r   '  s   




zHistogramCollector.collectc                 C   sL  |  D ]\}}t|}| }|jdkr!t|}t|}nd}d}t|}|| jvrCtj	|| j
d\}}||||f| j|< q| j| }|d }	|d }
|d }|d }t|}||d kr|d |d  }t|d | || |}t||f}tj	||d\}}|dt|  |7  < ||t|	|t|
|f| j|< qdS )z5
        Collect histogram on absolute value
        r   )binsr      r
   N)r   r   asarrayflattenr   r   r   absoluter   	histogramr   arangehstackr~   )r&   r   r   data_arrr   r   hist
hist_edgesold_histogramold_minold_maxold_histold_hist_edges	temp_amaxwidthnew_bin_edgesr   r   r   r   6  s4   






 z)HistogramCollector.collect_absolute_valuec           
      C   s   |  D ]Y\}}t|}| }|jdkr!t|}t|}nd}d}tt|t|}|| jv rE| j| }| 	|||||| j|< qtj
|| j| |fd\}}	||	|||f| j|< qdS )z1
        Collect histogram on real value
        r   r   N)r   r   r   r  r   r   r   r   r   merge_histogramr  r   )
r&   r   r   r  r   r   	thresholdr	  r  r  r   r   r   r   [  s,   





z HistogramCollector.collect_valuec                 C   s  |\}}}}	}
||
kr)t j|t||
 |
fd\}}|| |t||t|	||
fS |
dkrAt j|t|| |fd\}}||7 }n9t|}d|
 | }t||
 | d }|d|  }|| |
 }t j||| |fd\}}||||   |7  < ||t||t|	||fS )Nr  r   r   r
   )r   r  r~   r   r   r   )r&   r	  r  new_minnew_maxnew_thresholdr  r  r
  r  old_thresholdnew_histr   r  r  old_num_bins
old_stridehalf_increased_binsnew_num_binsr   r   r   r  {  s2   
z"HistogramCollector.merge_histogramc                 C   sX   | j r
t| j dkrtdtd| j | jdkr|  S | jdkr(|  S td)Nr   z=Histogram has not been collected. Please run collect() first.z@Finding optimal threshold for each tensor using {} algorithm ...r   r   r   )r   r~   r8   r   formatr   compute_entropycompute_percentiler%   r   r   r   r     s   

z,HistogramCollector.compute_collection_resultc                 C   sr  | j dk s
| j dkrtd| j}| j }i }tdt| td| j tdd| | | D ]\}}|d }|d }| }t	
|| }	| jrgt	|	|d }
t||
  t||
 f||< n"d| d	 }t	|	d
| }
t	|	|}t|| t||
 f||< |d }|d }|| d |k r||| d f||< || d |kr|| d |f||< 	 q5|S )Nr   d   z<Invalid percentile. Must be in range 0 <= percentile <= 100.Number of tensors : {}zNumber of histogram bins : {}zPercentile : ({},{})g      Y@r
   g      i@g      ?r   r   )r   r8   r   r   r  r~   r   r   sumr   cumsumr:   searchsortedr   r   )r&   r   r   thresholds_dictr   r  r  r  totalcdf	idx_rightpercent_to_cut_one_sideidx_leftr   r   r   r   r   r    s@   



z%HistogramCollector.compute_percentilec                 C   sn   | j }| j}i }tdt| td| j td| j | D ]\}}| ||}|||< 	 q%|S )Nr!  zWNumber of histogram bins : {} (The number may increase depends on the data it collects)zNumber of quantized bins : {})	r   r   r   r  r~   r   r   get_entropy_thresholdr   )r&   r   r   r%  r   r  optimal_thresholdr   r   r   r    s   z"HistogramCollector.compute_entropyc                 C   s  ddl }ddlm} |d }|d }|j}|d }|d }	t||	 d }
dd t|
jD }t|	|d dD ]}|| }|| d |krL|| d n|}t|| t|| f|||	 < |||| }|  }t	|d| }t	||d }|d  |7  < |d  |7  < |dk
tj}tj|tjd	}|j| }t|D ]}|| }|| }t	||| ||< q|d  t	||| d 7  < tj|jtjd	}t|D ]$}|| }|| }t	||| }|dkrt|| t| |||< qt|}t|}t|tjr||||
||	 < q8td
|
||	 < q8t|
}|| }|d }|d }|d |k r;||d f}|d |krH|d |f}|S )aF  Given a dataset, find the optimal threshold for quantizing it.
        The reference distribution is `q`, and the candidate distribution is `p`.
        `q` is a truncated version of the original distribution.
        Ref: http://on-demand.gputechconf.com/gtc/2017/presentation/s7310-8-bit-inference-with-tensorrt.pdf
        r   N)r   r
   r   c                 S   s   g | ]}d qS ))r   r   r   r   r   r   r   r     s    z<HistogramCollector.get_entropy_threshold.<locals>.<listcomp>r   r   infr   )copyscipy.statsr   r   r   zerosr   r   deepcopyr"  astyper   r   r5   ndarrayargmin)r&   r  r   r.  r   r  r  r   zero_bin_indexnum_half_quantized_binkl_divergence
thresholdsr   start_index	end_indexsliced_distributionpleft_outliers_countright_outliers_countnonzerosquantized_binsnum_merged_binsindexstartendqnormmin_kl_divergence_idxr,  r   r   r   r   r   r+    sf    


 
z(HistogramCollector.get_entropy_thresholdN)r   r   r   r   r?   r   r   r   r   r  r   r  r  r+  r   r   r   r   r     s    % !-r   r2   Fr3   c              	   C   sB  d }|t jkr1d|vrdn|d }d|vrdn|d }d|vr!dn|d }	t| ||||||	d}n]|t jkr`d|vr<dn|d }
d	|vrFdn|d	 }d|vrPdn|d }t| |||||
|d
}n.|t jkrd|vrkdn|d }
d|vrudn|d }d|vrdn|d }t| |||||
|d}|r|  |  |S t	d
|)Nr:   Fr   r   rx   )r;   r:   r   r   r   r   r   )r;   r:   r   r   r   r   r   T)r;   r:   r   r   z!Unsupported calibration method {})r   r   rw   r   r   r   r   rq   r@   r8   r  )r7   r3   r9   calibrate_methodr;   extra_options
calibratorr:   r   r   r   r   r   r   r   r   create_calibratorL  sZ   	

	
	
rK  )$r-   rc   r   enumr   pathlibr   typingr   r   numpyr   r   r   r   r   r	   rD   quant_utilsr   r   r   r   r   ABCMetar   r1   rw   r   r   r   r   r   r   r6   rK  r   r   r   r   <module>   s>   m +p""  =
