o
    ;ήcfb                  	   @   s<  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	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%de	jdededefddZd&defddZd&defddZd&defddZd&de	jdejfddZdejfddZd%dejdedejfddZd d! Zd"d# Ze d$kre  dS dS )'    N)deque)IntEnum   )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|d9}| D ]+}|dr?| d}t	|dkr?|d }|d }|
dd}d|v r?|| j|< qW d    d S 1 sKw   Y  d S )	Nr|      r   z<br/> :)	_filename_opsset	_ops_seenopen	readlines
startswithstripsplitlenreplace)selffilenameflinepieces	domain_opcaveat r#   Y/tmp/pip-target-vg8gfxp4/lib/python/onnxruntime/tools/mobile_helpers/usability_checker.py__init__   s    

"z_SupportedOpsChecker.__init__c                 C   s<   |j r|j nd}|d |j }|| jv }|r| j| |S )Nai.onnxr   )domainop_typer   r   add)r   noder'   r!   is_supportedr#   r#   r$   is_op_supported2   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dejde	f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$   	TryWithEPG   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%   L   s   
zPartitioningInfo.__init__c                 C   sv   | j | j }| j| d }| jdkr&|dkrtjjS |dkr"tjjS tjjS | jdkr7|dkr3t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<   pct_supportedr#   r#   r$   suitabilityW   s   

zPartitioningInfo.suitabilityloggerep_namec              
      s  | j | j }|| j d| j d| d| d | jr%|| j d | jrM|dddd	 | jD  d
 |d| j  | jrM|d| j |	 t
jkr| jre|ddt| j  | j }|rd |dd fdd	|D   | j| d }| jdkr|| d d'S | jdkr|dkr|| d|dd d'S |dkr|| d|dd d'S || d|dd  d'S | jd!kr|dkr|| d"|dd# d'S || 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)rN   r"   indentr#   r$   rP      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   rJ   rK   r<   r/   pct_nodes_using_epr#   rV   r$   dump_analysiss   sn   $


zPartitioningInfo.dump_analysisN)r2   r3   r4   r   r:   r%   rI   r[   LoggerrM   r_   r#   r#   r#   r$   r6   F   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 ]}	|	|v r8t||	 nd}
|
||	< |
dkrG||	 q,dtjd	tjd
t	gfdd}dg}t
| || |d g t d}d}d}t }fdd}|s}|r|s|  |}t }qx| }	||	}| pt fdd|	jD }|o|}|s|	v r||	 qx|s||	jr|	jnd d|	j  |d7 }n|d7 }|r|d7 }|	 |	v r|	 |	|v r||	 D ]}| q|	|v r||	 D ]}|| }|d8 }|dkr|| |||< q|s}|s}|  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rN   ir#   r#   r$   rP      s    z&check_partitioning.<locals>.<listcomp>c                    s$   | v r
t |  S |  v rdS dS )NTF)r   )value)initializersrc   r#   r$   _is_fixed_shape_value   s
   z1check_partitioning.<locals>._is_fixed_shape_valueNr   	cur_graphoriginal_graphcountc                 S   s&   | |kr|d  t | j7  < d S d S )Nr   )r   r*   )rj   rk   rl   r#   r#   r$   _count_subgraph_nodes  s   z1check_partitioning.<locals>._count_subgraph_nodes)rk   rl   c                     s<   r  p } | r       d S d S N)r.   copyclear)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 rn   r#   re   )ri   r#   r$   	<genexpr>(  s    z%check_partitioning.<locals>.<genexpr>r&   r   r   )
ValueErrorr   initializerr   r*   r   r.   onnx
GraphProtointr   r   popleftr,   allinputr)   r'   r(   remover   r6   r<   r=   r>   r?   r@   rA   rB   rC   rD   )ra   r@   rb   rc   node_to_producersnode_to_consumers	in_degreenodes_to_process nodes_to_process_with_next_groupr*   node_input_edge_countrm   nodes_in_subgraphsr=   num_unsupported_nodes_due_to_op*num_unsupported_nodes_due_to_dynamic_inputrB   ru   r,   is_input_shape_supportedis_node_supportedconsumerconsumer_node_in_degreer<   r>   rX   r#   )ri   rh   rr   rs   rt   rA   rc   r$   check_partitioning   s   





"






3

r   c                 C   s    t |}t| j||d u|}|S rn   )r
   r   ra   )modelsupported_ops_configrc   supported_opspartition_infor#   r#   r$   _check_ep_partitioningh  s   r   c                 C   P   t tj}|d }| r|}n|jd }|d d d d d }t| ||S )Nznnapi_supported_ops.md   toolsci_buildgithubandroidpathlibPath__file__parentexistsparentsr   r   rc   
script_dirlocal_configconfig_pathort_rootr#   r#   r$   check_nnapi_partitionsn     
r   c                 C   r   )Nzcoreml_supported_ops.mdr   r   r   r   appler   r   r#   r#   r$   check_coreml_partitions|  r   r   rJ   c           	      C   sP  d}d}g }| j D ]%}t|s*|| |r%|ddt|   |d7 }q	|d7 }q	g }| jD ]%}t|sU|| |rP|ddt|   |d7 }q4|d7 }q4| jsrt	| j
dksrt	|t	| j ksr|d | jD ]}t|r|d7 }qu|d7 }qu|r|d| d|  |r|r|d	 ||fS |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.)r~   r   r.   rX   rY   rM   r   outputrc   r   r*   warning)	ra   rJ   num_fixed_valuesnum_dynamic_valuesdynamic_inputsrf   dynamic_outputsovir#   r#   r$   check_shapes  sP   







&


r   c                    s   t | }t j|i jjD ]}||j< qjjD ]}||j< qjjD ]}||j< q)t	j\ } fdd}|dt
}|dt}|tjjksW|tjjkrc tjkrcd d |tjjkps|tjjkS )Nc                    s    d|   |} tjkr||  | } d|  d|j  |tjj	krp rp d |} tjkrM d ||  | } d|  d|j  |tjj
krh d |j|jkrp|}|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)rX   rZ   r[   r\   r_   rI   rd   r6   r:   r9   r7   rg   )rK   checker_funcr   rI    partition_info_with_fixed_shapesfixed_shape_suitabilityr   rJ   model_with_shape_infovalue_to_shaper#   r$   check_ep  s.   



zchecker.<locals>.check_epNNAPICoreMLzKRe-run with log level of DEBUG for more details on the NNAPI/CoreML issues.z---------------)ry   loadshape_inferenceinfer_shapesra   r~   rd   r   rc   r   r   r   r6   r:   r9   rZ   r[   r\   rX   r7   )
model_pathrJ   r   vr   r   nnapi_suitabilitycoreml_suitabilityr#   r   r$   checker  s,   

 




r   r   skip_optimizec                 C   s   |st d}|t j |d|  d t %}|s,t|| j	 }t
| | |} tt| jdd|}W d   |S 1 sBw   Y  |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.
    usability_checkerr   z for usability with ORT Mobile.T)strictN)r[   	getLoggersetLevelINFOrX   tempfileTemporaryDirectoryr   r   rd   r	   r   rM   resolve)r   r   rJ   tmptmp_pathtry_epsr#   r#   r$   analyze_model  s   	



r   c                  C   sV   t jtjtdd} | jdg d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_level)r]   rX   r   errorrX   z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ArgumentParserrT   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r,|tj n|tj | j	
 }t|| j| d S )Nr   r]   rX   r   )r   r[   r   	log_levelr   r\   r   WARNINGERRORr   r   r   r   )argsrJ   r   r#   r#   r$   run_analyze_model=  s   




r   __main__)FNrn   )!r   r[   rT   r   r   collectionsr   enumr   ry   onnx_model_utilsr   r   r   r   r	   r
   r6   rz   booldictr   r   r   r   r`   r   r   r   r   r   r   r2   r#   r#   r#   r$   <module>   sD   	/y
 -I<
