U
    hb                     @   sN  d dl Z d dlZ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
 d dlZddlmZmZmZmZmZ G dd dZG d	d
 d
Zd$ejeee
e dddZd%e
e dddZd&e
e dddZd'e
e dddZd(eje
ej dddZejdddZd)ejee
ej dddZdd  Z d!d" Z!e"d#krJe!  dS )*    N)deque)IntEnum)Optional   )get_producer_consumer_mapsis_fixed_size_tensoriterate_graph_per_graph_funciterate_graph_per_node_funcoptimize_modelc                   @   s(   e Zd ZdZdd Zdd Zdd ZdS )	_SupportedOpsCheckerz
    Class to process the md file with list of supported ops and caveats for an execution provider.
    e.g. /tools/ci_build/github/android/nnapi_supported_ops.md
         /tools/ci_build/github/apple/coreml_supported_ops.md
    c              	   C   s   || _ i | _t | _t|f}| D ]V}|dr&| d}t	|dkr&|d }|d }|
dd}d|kr&|| j|< q&W 5 Q R X d S )N|      r   z<br/> :)	_filename_opsset	_ops_seenopen	readlines
startswithstripsplitlenreplace)selffilenameflinepieces	domain_opcaveat r#   V/tmp/pip-unpacked-wheel-socb9apf/onnxruntime/tools/mobile_helpers/usability_checker.py__init__   s    

z_SupportedOpsChecker.__init__c                 C   s<   |j r|j nd}|d |j }|| jk}|r8| j| |S )Nai.onnxr   )domainop_typer   r   add)r   noder'   r!   is_supportedr#   r#   r$   is_op_supported3   s    
z$_SupportedOpsChecker.is_op_supportedc                 C   s:   g }t | jD ]&}| j| }|r|| d|  q|S )Nr   )sortedr   r   append)r   caveatsopr"   r#   r#   r$   get_caveats=   s    
z _SupportedOpsChecker.get_caveatsN)__name__
__module____qualname____doc__r%   r,   r1   r#   r#   r#   r$   r      s   
r   c                   @   s>   e Zd ZG dd deZdd Zdd Zeje	ddd	Z
d
S )PartitioningInfoc                   @   s   e Zd ZdZdZdZdS )zPartitioningInfo.TryWithEP)r   )r   r   N)r2   r3   r4   NOMAYBEYESr#   r#   r#   r$   	TryWithEPH   s   r:   c                 C   s<   d| _ d| _d| _d| _d | _g | _t | _d| _d| _	d S )N)
	num_nodesnum_supported_nodesnum_partitionsnum_nodes_in_subgraphssupported_ops_checkersupported_groupsr   unsupported_opsnodes_unsupported_due_to_op&nodes_unsupported_due_to_dynamic_input)r   r#   r#   r$   r%   M   s    zPartitioningInfo.__init__c                 C   sv   | j | j }| j| d }| jdkrL|dkr4tjjS |dkrDtjjS tjjS | jdkrn|dkrftjjS tjjS tjjS )Nd   r   K   2   r   )	r<   r?   r=   r>   r6   r:   r9   r8   r7   )r   r<   Zpct_supportedr#   r#   r$   suitabilityX   s    

zPartitioningInfo.suitability)loggerep_namec              
      s  | j | j }|| j d| j d| d| d | jrJ|| j d | jr|dddd	 | jD  d
 |d| j  | jr|d| j |	 t
jkr| jr|ddt| j  | j }|rd |dd fdd	|D   | j| d }| jdkr0|| d n| jdkr|dkr`|| d|dd n<|dkr|| d|dd n|| d|dd  nP| jd!kr|dkr|| d"|dd# n || d$| j d%|dd& d'S )(z
        Analyze the partitioning information and log the analysis
        :param logger: Logger to use
        :param ep_name: Execution provider name to use in the log messages
        z partitions with a total of /z nodes can be handled by the z EP.z9 nodes are in subgraphs, which are currently not handled.zPartition sizes: [z, c                 S   s   g | ]}t t|qS r#   )strr   ).0	partitionr#   r#   r$   
<listcomp>   s     z2PartitioningInfo.dump_analysis.<locals>.<listcomp>]z"Unsupported nodes due to operator=z8Unsupported nodes due to input having a dynamic shape=%dzUnsupported ops: ,z     zRCaveats that have not been checked and may result in a node not being supported:   c                    s   g | ]}t j  | qS r#   )oslinesep)rM   r"   indentr#   r$   rO      s     rE   r   z$ cannot run any nodes in this model.r   rF   zD should work well for this model as there is one partition covering z.1fz% of the nodes in the model.rG   z, may work well for this model, however only zD% of nodes will use it. Performance testing is required to validate.z4 will probably not work will for this model as only z.2fz% of nodes will use it.r   zG can be considered for this model as there are two partitions covering z<% of the nodes. Performance testing is required to validate.z1 is not recommended with this model as there are z partitions covering zj% of the nodes in the model. This will most likely result in worse performance than just using the CPU EP.N)r<   r?   infor>   r=   rA   joinrC   rD   getEffectiveLevelloggingDEBUGrB   r-   r@   r1   debug)r   rI   rJ   r<   r/   Zpct_nodes_using_epr#   rU   r$   dump_analysiss   sZ    $


zPartitioningInfo.dump_analysisN)r2   r3   r4   r   r:   r%   rH   rZ   LoggerrL   r]   r#   r#   r#   r$   r6   G   s   r6   F)graphr@   require_fixed_input_sizes
value_infoc                    s  |rst dt| \}}dd | jD fdd dg i }t }t }| jD ]6}	|	|krpt||	 nd}
|
||	< |
dkrX||	 qXtjtjt	gdd	d
}dg}t
| || |d g t d}d}d}t }fdd}|s|r8|s|  |}t }q| }	||	}| p>t fdd|	jD }|oH|}|s|	krf||	 q|s||	jr~|	jnd d|	j  |d7 }n|d7 }|r|d7 }|	 |	krΈ|	 |	|kr||	 D ]}| q|	|kr||	 D ]2}|| }|d8 }|dkr*|| |||< qq|  t| |j t| j}t}t }||_||_||_|d |_||_|_||_||_||_|S )ah  
    Estimate the partitions the graph will be split into for nodes that is_node_supported_fn returns true for.

    The check on whether a node is supported is purely based on the operator type. Additional limitations
    (e.g. NNAPI EP only supports 2D Conv) are not checked, so partitions may not be 100% accurate. The limitations
    for operators in the partitions are printed so the user can manually check.
    :param graph: Graph to process
    :param supported_ops_checker: Checker with info on supported ops.
    :param require_fixed_input_sizes: If True, require that the inputs to a potentially supported node are
                                       fixed size tensors for it to be considered as supported.
                                       If True, onnx.shape_inference.infer_shapes should have been run on the model
                                       to populate the shape information.
    :param value_info: Map of value name to ValueInfoProto. Required if require_fixed_input_sizes is True to lookup
                       the shape of a value.
    :return PartitioningInfo instance with details
    zAvalue_info must be provided if require_fixed_input_sizes is True.c                 S   s   g | ]
}|j qS r#   )namerM   ir#   r#   r$   rO      s     z&check_partitioning.<locals>.<listcomp>c                    s$   | krt |  S |  kr dS dS )NTF)r   )value)initializersra   r#   r$   _is_fixed_shape_value   s
    z1check_partitioning.<locals>._is_fixed_shape_valueNr   Z	cur_graphoriginal_graphcountc                 S   s"   | |kr|d  t | j7  < d S )Nr   )r   r*   rh   r#   r#   r$   _count_subgraph_nodes  s    z1check_partitioning.<locals>._count_subgraph_nodes)ri   rj   c                     s8   r4  p } | r$       d S N)r.   copyclear)Zkeep_partition)on_group_closed_fnsupported_groupsupported_group_borderrA   r#   r$   close_group  s    z'check_partitioning.<locals>.close_groupc                 3   s   | ]} |V  qd S rl   r#   rc   )rg   r#   r$   	<genexpr>(  s     z%check_partitioning.<locals>.<genexpr>r&   r   r   )
ValueErrorr   Zinitializerr   r*   r   r.   onnx
GraphProtointr   r   popleftr,   allinputr)   r'   r(   remover	   r6   r<   r=   r>   r?   r@   rA   rB   rC   rD   )r_   r@   r`   ra   Znode_to_producersZnode_to_consumersZ	in_degreeZnodes_to_processZ nodes_to_process_with_next_groupr*   Znode_input_edge_countrk   Znodes_in_subgraphsr=   Znum_unsupported_nodes_due_to_opZ*num_unsupported_nodes_due_to_dynamic_inputrB   rr   r,   Zis_input_shape_supportedZis_node_supportedZconsumerZconsumer_node_in_degreer<   r>   rW   r#   )rg   rf   ro   rp   rq   rA   ra   r$   check_partitioning   s    



 


$








r|   )ra   c                 C   s    t |}t| j||d k	|}|S rl   )r   r|   r_   )modelZsupported_ops_configra   Zsupported_opspartition_infor#   r#   r$   _check_ep_partitioningh  s    r   c                 C   sP   t tj}|d }| r"|}n"|jd }|d d d d d }t| ||S )Nznnapi_supported_ops.md   toolsci_buildgithubZandroidpathlibPath__file__parentexistsparentsr   r}   ra   Z
script_dirZlocal_configZconfig_pathZort_rootr#   r#   r$   check_nnapi_partitionsn  s    
r   c                 C   sP   t tj}|d }| r"|}n"|jd }|d d d d d }t| ||S )Nzcoreml_supported_ops.mdr   r   r   r   Zappler   r   r#   r#   r$   check_coreml_partitions|  s    
r   )r_   rI   c           	      C   sR  d}d}g }| j D ]J}t|sT|| |rJ|ddt|   |d7 }q|d7 }qg }| jD ]J}t|s|| |r|ddt|   |d7 }qh|d7 }qh| jst	| j
dkst	|t	| j ks|d | jD ] }t|r|d7 }q|d7 }q|rJ|d| d|  |rJ|r@|d	 n
|d
 ||fS )a  
    Check the shapes of graph inputs, values and graph outputs to determine if they have static or dynamic sizes.
    NNAPI and CoreML do not support dynamically sized values.
    :param graph: Graph to check. If shape inferencing has been run the checks on values will be meaningful.
    :param logger: Optional logger for diagnostic information.
    :return: Tuple of List of inputs with dynamic shapes, Number of dynamic values found
    r   z"Input is not a fixed size tensor: r   r   z#Output is not a fixed size tensor: ziUnable to check shapes within model. ONNX shape inferencing should be run on the model prior to checking.zNum values with fixed shape=z . Num values with dynamic shape=zModel has dynamic inputs and outputs. Consider re-exporting model with fixed sizes if NNAPI or CoreML can be used with this model.ao  Model has dynamically sized inputs but fixed sized outputs.
                       If the sizes become fixed early in the model (e.g. pre-processing of a dynamic input size
                       results in a fixed input size for the majority of the model) performance with NNAPI and CoreML,
                       if applicable, should not be significantly impacted.)rz   r   r.   rW   rX   rL   r   outputra   r   r*   warning)	r_   rI   Znum_fixed_valuesnum_dynamic_valuesdynamic_inputsrd   Zdynamic_outputsovir#   r#   r$   check_shapes  sN    







&



r   )rI   c                    s   t | }t j|i jjD ]}||j< q"jjD ]}||j< q:jjD ]}||j< qRt	j\ } fdd}|dt
}|dt}|tjjks|tjjkrƈ tjkrƈd d |tjjkp|tjjkS )Nc                    s    d|   |} tjkr4||  | } d|  d|j  |tjj	kr r d |} tjkr d ||  | } d|  d|j  |tjj
krЈ d |j|jkr|}|S )N	Checking zModel should perform well with z as is: zHChecking if model will perform better if the dynamic shapes are fixed...zHPartition information if the model was updated to make the shapes fixed:z) if modified to have fixed input shapes: zPShapes can be altered using python -m onnxruntime.tools.make_dynamic_shape_fixed)rW   rY   rZ   r[   r]   rH   rb   r6   r:   r9   r7   re   )rJ   Zchecker_funcr~   rH   Z partition_info_with_fixed_shapesZfixed_shape_suitabilityr   rI   Zmodel_with_shape_infoZvalue_to_shaper#   r$   check_ep  s*    



zchecker.<locals>.check_epZNNAPIZCoreMLzKRe-run with log level of DEBUG for more details on the NNAPI/CoreML issues.z---------------)ru   loadZshape_inferenceZinfer_shapesr_   rz   rb   r   ra   r   r   r   r6   r:   r9   rY   rZ   r[   rW   r7   )
model_pathrI   r}   vr   r   Znnapi_suitabilityZcoreml_suitabilityr#   r   r$   checker  s,    
 





r   )r   skip_optimizerI   c              	   C   s|   |st d}|t j |d|  d t >}|sXt|| j	 }t
| | |} tt| jdd|}W 5 Q R X |S )a  
    Analyze the provided model to determine if it's likely to work well with the NNAPI or CoreML Execution Providers
    :param model_path: Model to analyze.
    :param skip_optimize: Skip optimizing to BASIC level before checking. When exporting to ORT format we will do this
                          optimization..
    :param logger: Logger for output
    :return: True if either the NNAPI or CoreML Execution Providers may work well with this model.
    Zusability_checkerr   z for usability with ORT Mobile.T)strict)rZ   	getLoggersetLevelINFOrW   tempfileTemporaryDirectoryr   r   rb   r
   r   rL   resolve)r   r   rI   tmpZtmp_pathZtry_epsr#   r#   r$   analyze_model  s    	


 r   c                  C   sZ   t jtjtdd} | jdddddgddd	 | jd
ddd | jdtjdd | 	 S )Nz3Analyze an ONNX model for usage with the ORT mobile)descriptionz--log_levelr\   rW   r   errorzLogging level)choicesdefaulthelpz--skip_optimize
store_truezDon't optimize the model to BASIC level prior to analyzing. Optimization will occur when exporting the model to ORT format, so in general should not be skipped unless you have a specific reason to do so.)actionr   r   zProvide path to ONNX model)typer   )
argparseArgumentParserrS   pathbasenamer   add_argumentr   r   
parse_args)parserr#   r#   r$   r   (  s"    
  
  r   c                  C   s   t  } td}| jdkr(|tj n<| jdkr@|tj n$| jdkrX|tj n|tj | j	
 }t|| j| d S )Nr   r\   rW   r   )r   rZ   r   Z	log_levelr   r[   r   WARNINGERRORr   r   r   r   )argsrI   r   r#   r#   r$   run_analyze_model<  s    




r   __main__)FN)N)N)N)N)FN)#r   rZ   rS   r   r   collectionsr   enumr   typingr   ru   Zonnx_model_utilsr   r   r   r	   r
   r   r6   rv   booldictr|   r   r   r   r^   r   r   r   r   r   r   r2   r#   r#   r#   r$   <module>   s<   	/x   -I;
